mirror of
https://gitlab.torproject.org/tpo/core/tor.git
synced 2024-11-24 04:13:28 +01:00
Allow 3 fallbacks per operator
This is safe now we are choosing 200 fallbacks. Closes ticket 20912.
This commit is contained in:
parent
ee3e8fc3e9
commit
35da99a712
@ -21,6 +21,8 @@
|
||||
Part of ticket 18828.
|
||||
- Display the fingerprint when downloading consensuses from fallbacks.
|
||||
Closes ticket 20908.
|
||||
- Allow 3 fallbacks per operator. (This is safe now we are choosing 200
|
||||
fallbacks.) Closes ticket 20912.
|
||||
o Minor bugfix (fallback directories):
|
||||
- Stop failing when OUTPUT_COMMENTS is True in updateFallbackDirs.py.
|
||||
Closes ticket 20877; bugfix on commit 9998343 in tor-0.2.8.3-alpha.
|
||||
|
@ -173,6 +173,15 @@ MAX_FALLBACK_COUNT = None if OUTPUT_CANDIDATES else 200
|
||||
# Emit a C #error if the number of fallbacks is more than 10% below MAX
|
||||
MIN_FALLBACK_COUNT = 0 if OUTPUT_CANDIDATES else MAX_FALLBACK_COUNT*0.9
|
||||
|
||||
# The maximum number of fallbacks on the same address, contact, or family
|
||||
# With 200 fallbacks, this means each operator can see 1% of client bootstraps
|
||||
# (The directory authorities used to see ~12% of client bootstraps each.)
|
||||
MAX_FALLBACKS_PER_IP = 1
|
||||
MAX_FALLBACKS_PER_IPV4 = MAX_FALLBACKS_PER_IP
|
||||
MAX_FALLBACKS_PER_IPV6 = MAX_FALLBACKS_PER_IP
|
||||
MAX_FALLBACKS_PER_CONTACT = 3
|
||||
MAX_FALLBACKS_PER_FAMILY = 3
|
||||
|
||||
## Fallback Bandwidth Requirements
|
||||
|
||||
# Any fallback with the Exit flag has its bandwidth multipled by this fraction
|
||||
@ -1561,49 +1570,85 @@ class CandidateList(dict):
|
||||
else:
|
||||
return None
|
||||
|
||||
# does exclusion_list contain attribute?
|
||||
# return a new bag suitable for storing attributes
|
||||
@staticmethod
|
||||
def attribute_new():
|
||||
return dict()
|
||||
|
||||
# get the count of attribute in attribute_bag
|
||||
# if attribute is None or the empty string, return 0
|
||||
@staticmethod
|
||||
def attribute_count(attribute, attribute_bag):
|
||||
if attribute is None or attribute == '':
|
||||
return 0
|
||||
if attribute not in attribute_bag:
|
||||
return 0
|
||||
return attribute_bag[attribute]
|
||||
|
||||
# does attribute_bag contain more than max_count instances of attribute?
|
||||
# if so, return False
|
||||
# if not, return True
|
||||
# if attribute is None or the empty string, always return True
|
||||
# if attribute is None or the empty string, or max_count is invalid,
|
||||
# always return True
|
||||
@staticmethod
|
||||
def allow(attribute, exclusion_list):
|
||||
if attribute is None or attribute == '':
|
||||
def attribute_allow(attribute, attribute_bag, max_count=1):
|
||||
if attribute is None or attribute == '' or max_count <= 0:
|
||||
return True
|
||||
elif attribute in exclusion_list:
|
||||
elif CandidateList.attribute_count(attribute, attribute_bag) >= max_count:
|
||||
return False
|
||||
else:
|
||||
return True
|
||||
|
||||
# make sure there is only one fallback per IPv4 address, and per IPv6 address
|
||||
# add attribute to attribute_bag, incrementing the count if it is already
|
||||
# present
|
||||
# if attribute is None or the empty string, or count is invalid,
|
||||
# do nothing
|
||||
@staticmethod
|
||||
def attribute_add(attribute, attribute_bag, count=1):
|
||||
if attribute is None or attribute == '' or count <= 0:
|
||||
pass
|
||||
attribute_bag.setdefault(attribute, 0)
|
||||
attribute_bag[attribute] += count
|
||||
|
||||
# make sure there are only MAX_FALLBACKS_PER_IP fallbacks per IPv4 address,
|
||||
# and per IPv6 address
|
||||
# there is only one IPv4 address on each fallback: the IPv4 DirPort address
|
||||
# (we choose the IPv4 ORPort which is on the same IPv4 as the DirPort)
|
||||
# there is at most one IPv6 address on each fallback: the IPv6 ORPort address
|
||||
# we try to match the IPv4 ORPort, but will use any IPv6 address if needed
|
||||
# (clients assume the IPv6 DirPort is the same as the IPv4 DirPort, but
|
||||
# typically only use the IPv6 ORPort)
|
||||
# (clients only use the IPv6 ORPort)
|
||||
# if there is no IPv6 address, only the IPv4 address is checked
|
||||
# return the number of candidates we excluded
|
||||
def limit_fallbacks_same_ip(self):
|
||||
ip_limit_fallbacks = []
|
||||
ip_list = []
|
||||
ip_list = CandidateList.attribute_new()
|
||||
for f in self.fallbacks:
|
||||
if (CandidateList.allow(f.dirip, ip_list)
|
||||
and CandidateList.allow(f.ipv6addr, ip_list)):
|
||||
if (CandidateList.attribute_allow(f.dirip, ip_list,
|
||||
MAX_FALLBACKS_PER_IPV4)
|
||||
and CandidateList.attribute_allow(f.ipv6addr, ip_list,
|
||||
MAX_FALLBACKS_PER_IPV6)):
|
||||
ip_limit_fallbacks.append(f)
|
||||
ip_list.append(f.dirip)
|
||||
CandidateList.attribute_add(f.dirip, ip_list)
|
||||
if f.has_ipv6():
|
||||
ip_list.append(f.ipv6addr)
|
||||
elif not CandidateList.allow(f.dirip, ip_list):
|
||||
logging.info('Eliminated %s: already have fallback on IPv4 %s'%(
|
||||
f._fpr, f.dirip))
|
||||
elif f.has_ipv6() and not CandidateList.allow(f.ipv6addr, ip_list):
|
||||
logging.info('Eliminated %s: already have fallback on IPv6 %s'%(
|
||||
f._fpr, f.ipv6addr))
|
||||
CandidateList.attribute_add(f.ipv6addr, ip_list)
|
||||
elif not CandidateList.attribute_allow(f.dirip, ip_list,
|
||||
MAX_FALLBACKS_PER_IPV4):
|
||||
logging.info('Eliminated %s: already have %d fallback(s) on IPv4 %s'
|
||||
%(f._fpr, CandidateList.attribute_count(f.dirip, ip_list),
|
||||
f.dirip))
|
||||
elif (f.has_ipv6() and
|
||||
not CandidateList.attribute_allow(f.ipv6addr, ip_list,
|
||||
MAX_FALLBACKS_PER_IPV6)):
|
||||
logging.info('Eliminated %s: already have %d fallback(s) on IPv6 %s'
|
||||
%(f._fpr, CandidateList.attribute_count(f.ipv6addr,
|
||||
ip_list),
|
||||
f.ipv6addr))
|
||||
original_count = len(self.fallbacks)
|
||||
self.fallbacks = ip_limit_fallbacks
|
||||
return original_count - len(self.fallbacks)
|
||||
|
||||
# make sure there is only one fallback per ContactInfo
|
||||
# make sure there are only MAX_FALLBACKS_PER_CONTACT fallbacks for each
|
||||
# ContactInfo
|
||||
# if there is no ContactInfo, allow the fallback
|
||||
# this check can be gamed by providing no ContactInfo, or by setting the
|
||||
# ContactInfo to match another fallback
|
||||
@ -1611,37 +1656,45 @@ class CandidateList(dict):
|
||||
# go down at similar times, its usefulness outweighs the risk
|
||||
def limit_fallbacks_same_contact(self):
|
||||
contact_limit_fallbacks = []
|
||||
contact_list = []
|
||||
contact_list = CandidateList.attribute_new()
|
||||
for f in self.fallbacks:
|
||||
if CandidateList.allow(f._data['contact'], contact_list):
|
||||
if CandidateList.attribute_allow(f._data['contact'], contact_list,
|
||||
MAX_FALLBACKS_PER_CONTACT):
|
||||
contact_limit_fallbacks.append(f)
|
||||
contact_list.append(f._data['contact'])
|
||||
CandidateList.attribute_add(f._data['contact'], contact_list)
|
||||
else:
|
||||
logging.info(('Eliminated %s: already have fallback on ' +
|
||||
'ContactInfo %s')%(f._fpr, f._data['contact']))
|
||||
logging.info(
|
||||
'Eliminated %s: already have %d fallback(s) on ContactInfo %s'
|
||||
%(f._fpr, CandidateList.attribute_count(f._data['contact'],
|
||||
contact_list),
|
||||
f._data['contact']))
|
||||
original_count = len(self.fallbacks)
|
||||
self.fallbacks = contact_limit_fallbacks
|
||||
return original_count - len(self.fallbacks)
|
||||
|
||||
# make sure there is only one fallback per effective family
|
||||
# make sure there are only MAX_FALLBACKS_PER_FAMILY fallbacks per effective
|
||||
# family
|
||||
# if there is no family, allow the fallback
|
||||
# this check can't be gamed, because we use effective family, which ensures
|
||||
# mutual family declarations
|
||||
# we use effective family, which ensures mutual family declarations
|
||||
# but the check can be gamed by not declaring a family at all
|
||||
# if any indirect families exist, the result depends on the order in which
|
||||
# fallbacks are sorted in the list
|
||||
def limit_fallbacks_same_family(self):
|
||||
family_limit_fallbacks = []
|
||||
fingerprint_list = []
|
||||
fingerprint_list = CandidateList.attribute_new()
|
||||
for f in self.fallbacks:
|
||||
if CandidateList.allow(f._fpr, fingerprint_list):
|
||||
if CandidateList.attribute_allow(f._fpr, fingerprint_list,
|
||||
MAX_FALLBACKS_PER_FAMILY):
|
||||
family_limit_fallbacks.append(f)
|
||||
fingerprint_list.append(f._fpr)
|
||||
fingerprint_list.extend(f._data['effective_family'])
|
||||
CandidateList.attribute_add(f._fpr, fingerprint_list)
|
||||
for family_fingerprint in f._data['effective_family']:
|
||||
CandidateList.attribute_add(family_fingerprint, fingerprint_list)
|
||||
else:
|
||||
# technically, we already have a fallback with this fallback in its
|
||||
# effective family
|
||||
logging.info('Eliminated %s: already have fallback in effective ' +
|
||||
'family'%(f._fpr))
|
||||
# we already have a fallback with this fallback in its effective
|
||||
# family
|
||||
logging.info(
|
||||
'Eliminated %s: already have %d fallback(s) in effective family'
|
||||
%(f._fpr, CandidateList.attribute_count(f._fpr, fingerprint_list)))
|
||||
original_count = len(self.fallbacks)
|
||||
self.fallbacks = family_limit_fallbacks
|
||||
return original_count - len(self.fallbacks)
|
||||
|
Loading…
Reference in New Issue
Block a user