|
@@ -2,9 +2,12 @@
|
|
|
|
|
|
|
|
|
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
|
|
|
-
|
|
|
-
|
|
|
+
|
|
|
+
|
|
|
|
|
|
|
|
|
|
|
@@ -34,6 +37,21 @@ import logging
|
|
|
|
|
|
|
|
|
logging.basicConfig(level=logging.WARNING)
|
|
|
+logging.root.name = ''
|
|
|
+
|
|
|
+logging.getLogger('stem').setLevel(logging.WARNING)
|
|
|
+
|
|
|
+HAVE_IPADDRESS = False
|
|
|
+try:
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+ import ipaddress
|
|
|
+ HAVE_IPADDRESS = True
|
|
|
+except ImportError:
|
|
|
+
|
|
|
+ logging.warning('Unable to import ipaddress, please install py2-ipaddress')
|
|
|
|
|
|
|
|
|
|
|
@@ -468,6 +486,9 @@ class Candidate(object):
|
|
|
|
|
|
|
|
|
details['advertised_bandwidth'] = 0
|
|
|
+ if (not 'effective_family' in details
|
|
|
+ or details['effective_family'] is None):
|
|
|
+ details['effective_family'] = []
|
|
|
details['last_changed_address_or_port'] = parse_ts(
|
|
|
details['last_changed_address_or_port'])
|
|
|
self._data = details
|
|
@@ -480,7 +501,7 @@ class Candidate(object):
|
|
|
if self.orport is None:
|
|
|
raise Exception("Failed to get an orport for %s."%(self._fpr,))
|
|
|
self._compute_ipv6addr()
|
|
|
- if self.ipv6addr is None:
|
|
|
+ if not self.has_ipv6():
|
|
|
logging.debug("Failed to get an ipv6 address for %s."%(self._fpr,))
|
|
|
|
|
|
def _stable_sort_or_addresses(self):
|
|
@@ -584,14 +605,14 @@ class Candidate(object):
|
|
|
(ipaddr, port) = i.rsplit(':', 1)
|
|
|
if (port == self.orport) and Candidate.is_valid_ipv6_address(ipaddr):
|
|
|
self.ipv6addr = ipaddr
|
|
|
- self.ipv6orport = port
|
|
|
+ self.ipv6orport = int(port)
|
|
|
return
|
|
|
|
|
|
for i in self._data['or_addresses']:
|
|
|
(ipaddr, port) = i.rsplit(':', 1)
|
|
|
if Candidate.is_valid_ipv6_address(ipaddr):
|
|
|
self.ipv6addr = ipaddr
|
|
|
- self.ipv6orport = port
|
|
|
+ self.ipv6orport = int(port)
|
|
|
return
|
|
|
|
|
|
@staticmethod
|
|
@@ -804,9 +825,10 @@ class Candidate(object):
|
|
|
'ORPort (%d) does not match entry ORPort (%d)',
|
|
|
self._fpr, self.orport, int(entry['orport']))
|
|
|
continue
|
|
|
- has_ipv6 = self.ipv6addr is not None and self.ipv6orport is not None
|
|
|
- if (entry.has_key('ipv6') and has_ipv6):
|
|
|
- ipv6 = self.ipv6addr + ':' + self.ipv6orport
|
|
|
+ ipv6 = None
|
|
|
+ if self.has_ipv6():
|
|
|
+ ipv6 = '%s:%d'%(self.ipv6addr, self.ipv6orport)
|
|
|
+ if entry.has_key('ipv6') and self.has_ipv6():
|
|
|
|
|
|
if entry['ipv6'] != ipv6:
|
|
|
logging.info('%s is not in the whitelist: fingerprint matches, ' +
|
|
@@ -815,14 +837,14 @@ class Candidate(object):
|
|
|
continue
|
|
|
|
|
|
|
|
|
- elif entry.has_key('ipv6') and not has_ipv6:
|
|
|
+ elif entry.has_key('ipv6') and not self.has_ipv6():
|
|
|
logging.info('%s is not in the whitelist: fingerprint matches, but ' +
|
|
|
'it has no IPv6, and entry has IPv6 (%s)', self._fpr,
|
|
|
entry['ipv6'])
|
|
|
logging.warning('%s excluded: has it lost its former IPv6 address %s?',
|
|
|
self._fpr, entry['ipv6'])
|
|
|
continue
|
|
|
- elif not entry.has_key('ipv6') and has_ipv6:
|
|
|
+ elif not entry.has_key('ipv6') and self.has_ipv6():
|
|
|
logging.info('%s is not in the whitelist: fingerprint matches, but ' +
|
|
|
'it has IPv6 (%s), and entry has no IPv6', self._fpr,
|
|
|
ipv6)
|
|
@@ -871,9 +893,10 @@ class Candidate(object):
|
|
|
'entry has no DirPort or ORPort', self._fpr,
|
|
|
self.dirip)
|
|
|
return True
|
|
|
- has_ipv6 = self.ipv6addr is not None and self.ipv6orport is not None
|
|
|
- ipv6 = (self.ipv6addr + ':' + self.ipv6orport) if has_ipv6 else None
|
|
|
- if (key == 'ipv6' and has_ipv6):
|
|
|
+ ipv6 = None
|
|
|
+ if self.has_ipv6():
|
|
|
+ ipv6 = '%s:%d'%(self.ipv6addr, self.ipv6orport)
|
|
|
+ if (key == 'ipv6' and self.has_ipv6()):
|
|
|
|
|
|
|
|
|
if value == ipv6:
|
|
@@ -889,18 +912,18 @@ class Candidate(object):
|
|
|
logging.info('%s is in the blacklist: IPv6 (%s) matches, and' +
|
|
|
'entry has no DirPort', self._fpr, ipv6)
|
|
|
return True
|
|
|
- elif (key == 'ipv6' or has_ipv6):
|
|
|
+ elif (key == 'ipv6' or self.has_ipv6()):
|
|
|
|
|
|
if entry.has_key('id') and entry['id'] == self._fpr:
|
|
|
logging.info('%s skipping IPv6 blacklist comparison: relay ' +
|
|
|
'has%s IPv6%s, but entry has%s IPv6%s', self._fpr,
|
|
|
- '' if has_ipv6 else ' no',
|
|
|
- (' (' + ipv6 + ')') if has_ipv6 else '',
|
|
|
+ '' if self.has_ipv6() else ' no',
|
|
|
+ (' (' + ipv6 + ')') if self.has_ipv6() else '',
|
|
|
'' if key == 'ipv6' else ' no',
|
|
|
(' (' + value + ')') if key == 'ipv6' else '')
|
|
|
logging.warning('Has %s %s IPv6 address %s?', self._fpr,
|
|
|
- 'gained an' if has_ipv6 else 'lost its former',
|
|
|
- ipv6 if has_ipv6 else value)
|
|
|
+ 'gained an' if self.has_ipv6() else 'lost its former',
|
|
|
+ ipv6 if self.has_ipv6() else value)
|
|
|
return False
|
|
|
|
|
|
def cw_to_bw_factor(self):
|
|
@@ -936,6 +959,101 @@ class Candidate(object):
|
|
|
def is_running(self):
|
|
|
return 'Running' in self._data['flags']
|
|
|
|
|
|
+
|
|
|
+ def has_ipv6(self):
|
|
|
+ return self.ipv6addr is not None and self.ipv6orport is not None
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+ @staticmethod
|
|
|
+ def strip_ipv6_brackets(ip):
|
|
|
+ if ip is None:
|
|
|
+ return unicode('')
|
|
|
+ if len(ip) < 2:
|
|
|
+ return unicode(ip)
|
|
|
+ if ip[0] == '[' and ip[-1] == ']':
|
|
|
+ return unicode(ip[1:-1])
|
|
|
+ return unicode(ip)
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+ @staticmethod
|
|
|
+ def netblocks_equal(ip_a, ip_b, mask_bits):
|
|
|
+ if ip_a is None or ip_b is None:
|
|
|
+ return False
|
|
|
+ ip_a = Candidate.strip_ipv6_brackets(ip_a)
|
|
|
+ ip_b = Candidate.strip_ipv6_brackets(ip_b)
|
|
|
+ a = ipaddress.ip_address(ip_a)
|
|
|
+ b = ipaddress.ip_address(ip_b)
|
|
|
+ if a.version != b.version:
|
|
|
+ raise Exception('Mismatching IP versions in %s and %s'%(ip_a, ip_b))
|
|
|
+ if mask_bits > a.max_prefixlen:
|
|
|
+ logging.warning('Bad IP mask %d for %s and %s'%(mask_bits, ip_a, ip_b))
|
|
|
+ mask_bits = a.max_prefixlen
|
|
|
+ if mask_bits < 0:
|
|
|
+ logging.warning('Bad IP mask %d for %s and %s'%(mask_bits, ip_a, ip_b))
|
|
|
+ mask_bits = 0
|
|
|
+ a_net = ipaddress.ip_network('%s/%d'%(ip_a, mask_bits), strict=False)
|
|
|
+ return b in a_net
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+ def ipv4_netblocks_equal(self, other, mask_bits):
|
|
|
+ return Candidate.netblocks_equal(self.dirip, other.dirip, mask_bits)
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+ def ipv6_netblocks_equal(self, other, mask_bits):
|
|
|
+ if not self.has_ipv6() or not other.has_ipv6():
|
|
|
+ return False
|
|
|
+ return Candidate.netblocks_equal(self.ipv6addr, other.ipv6addr, mask_bits)
|
|
|
+
|
|
|
+
|
|
|
+ def dirport_equal(self, other):
|
|
|
+ return self.dirport == other.dirport
|
|
|
+
|
|
|
+
|
|
|
+ def ipv4_orport_equal(self, other):
|
|
|
+ return self.orport == other.orport
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+ def ipv6_orport_equal(self, other):
|
|
|
+ if not self.has_ipv6() or not other.has_ipv6():
|
|
|
+ return False
|
|
|
+ return self.ipv6orport == other.ipv6orport
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+ def port_equal(self, other):
|
|
|
+ return (self.dirport_equal(other) or self.ipv4_orport_equal(other)
|
|
|
+ or self.ipv6_orport_equal(other))
|
|
|
+
|
|
|
+
|
|
|
+ def port_list(self):
|
|
|
+ ports = [self.dirport, self.orport]
|
|
|
+ if self.has_ipv6() and not self.ipv6orport in ports:
|
|
|
+ ports.append(self.ipv6orport)
|
|
|
+ return ports
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+ def port_shared(self, other):
|
|
|
+ for p in self.port_list():
|
|
|
+ if p in other.port_list():
|
|
|
+ return True
|
|
|
+ return False
|
|
|
+
|
|
|
|
|
|
@staticmethod
|
|
|
def fallback_consensus_download_speed(dirip, dirport, nickname, max_time):
|
|
@@ -984,7 +1102,7 @@ class Candidate(object):
|
|
|
self.dirport,
|
|
|
self._data['nickname'],
|
|
|
CONSENSUS_DOWNLOAD_SPEED_MAX)
|
|
|
- if self.ipv6addr is not None and PERFORM_IPV6_DIRPORT_CHECKS:
|
|
|
+ if self.has_ipv6() and PERFORM_IPV6_DIRPORT_CHECKS:
|
|
|
|
|
|
ipv6_failed = Candidate.fallback_consensus_download_speed(self.ipv6addr,
|
|
|
self.dirport,
|
|
@@ -1086,9 +1204,8 @@ class Candidate(object):
|
|
|
self.orport,
|
|
|
cleanse_c_string(self._fpr))
|
|
|
s += '\n'
|
|
|
- if self.ipv6addr is not None:
|
|
|
- s += '" ipv6=%s:%s"'%(
|
|
|
- cleanse_c_string(self.ipv6addr), cleanse_c_string(self.ipv6orport))
|
|
|
+ if self.has_ipv6():
|
|
|
+ s += '" ipv6=%s:%d"'%(cleanse_c_string(self.ipv6addr), self.ipv6orport)
|
|
|
s += '\n'
|
|
|
s += '" weight=%d",'%(FALLBACK_OUTPUT_WEIGHT)
|
|
|
if comment_string:
|
|
@@ -1126,7 +1243,7 @@ class CandidateList(dict):
|
|
|
d = fetch('details',
|
|
|
fields=('fingerprint,nickname,contact,last_changed_address_or_port,' +
|
|
|
'consensus_weight,advertised_bandwidth,or_addresses,' +
|
|
|
- 'dir_address,recommended_version,flags'))
|
|
|
+ 'dir_address,recommended_version,flags,effective_family'))
|
|
|
logging.debug('Loading details document done.')
|
|
|
|
|
|
if not 'relays' in d: raise Exception("No relays found in document.")
|
|
@@ -1163,19 +1280,19 @@ class CandidateList(dict):
|
|
|
|
|
|
|
|
|
def sort_fallbacks_by_cw_to_bw_factor(self):
|
|
|
- self.fallbacks.sort(key=lambda f: f.cw_to_bw_factor(), self.fallbacks)
|
|
|
+ self.fallbacks.sort(key=lambda f: f.cw_to_bw_factor())
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def sort_fallbacks_by_measured_bandwidth(self):
|
|
|
self.fallbacks.sort(key=lambda f: f._data['measured_bandwidth'],
|
|
|
- self.fallbacks, reverse=True)
|
|
|
+ reverse=True)
|
|
|
|
|
|
|
|
|
|
|
|
def sort_fallbacks_by_fingerprint(self):
|
|
|
- self.fallbacks.sort(key=lambda f: self[f]._fpr, self.fallbacks)
|
|
|
+ self.fallbacks.sort(key=lambda f: f._fpr)
|
|
|
|
|
|
@staticmethod
|
|
|
def load_relaylist(file_name):
|
|
@@ -1341,6 +1458,91 @@ class CandidateList(dict):
|
|
|
else:
|
|
|
return None
|
|
|
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+ @staticmethod
|
|
|
+ def allow(attribute, exclusion_list):
|
|
|
+ if attribute is None or attribute == '':
|
|
|
+ return True
|
|
|
+ elif attribute in exclusion_list:
|
|
|
+ return False
|
|
|
+ else:
|
|
|
+ return True
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+ def limit_fallbacks_same_ip(self):
|
|
|
+ ip_limit_fallbacks = []
|
|
|
+ ip_list = []
|
|
|
+ for f in self.fallbacks:
|
|
|
+ if (CandidateList.allow(f.dirip, ip_list)
|
|
|
+ and CandidateList.allow(f.ipv6addr, ip_list)):
|
|
|
+ ip_limit_fallbacks.append(f)
|
|
|
+ ip_list.append(f.dirip)
|
|
|
+ if f.has_ipv6():
|
|
|
+ ip_list.append(f.ipv6addr)
|
|
|
+ elif not CandidateList.allow(f.dirip, ip_list):
|
|
|
+ logging.debug('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.debug('Eliminated %s: already have fallback on IPv6 %s'%(
|
|
|
+ f._fpr, f.ipv6addr))
|
|
|
+ original_count = len(self.fallbacks)
|
|
|
+ self.fallbacks = ip_limit_fallbacks
|
|
|
+ return original_count - len(self.fallbacks)
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+ def limit_fallbacks_same_contact(self):
|
|
|
+ contact_limit_fallbacks = []
|
|
|
+ contact_list = []
|
|
|
+ for f in self.fallbacks:
|
|
|
+ if CandidateList.allow(f._data['contact'], contact_list):
|
|
|
+ contact_limit_fallbacks.append(f)
|
|
|
+ contact_list.append(f._data['contact'])
|
|
|
+ else:
|
|
|
+ logging.debug(('Eliminated %s: already have fallback on ' +
|
|
|
+ 'ContactInfo %s')%(f._fpr, f._data['contact']))
|
|
|
+ original_count = len(self.fallbacks)
|
|
|
+ self.fallbacks = contact_limit_fallbacks
|
|
|
+ return original_count - len(self.fallbacks)
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+ def limit_fallbacks_same_family(self):
|
|
|
+ family_limit_fallbacks = []
|
|
|
+ fingerprint_list = []
|
|
|
+ for f in self.fallbacks:
|
|
|
+ if CandidateList.allow(f._fpr, fingerprint_list):
|
|
|
+ family_limit_fallbacks.append(f)
|
|
|
+ fingerprint_list.append(f._fpr)
|
|
|
+ fingerprint_list.extend(f._data['effective_family'])
|
|
|
+ else:
|
|
|
+
|
|
|
+
|
|
|
+ logging.debug('Eliminated %s: already have fallback in effective ' +
|
|
|
+ 'family'%(f._fpr))
|
|
|
+ original_count = len(self.fallbacks)
|
|
|
+ self.fallbacks = family_limit_fallbacks
|
|
|
+ return original_count - len(self.fallbacks)
|
|
|
+
|
|
|
|
|
|
|
|
|
|
|
@@ -1361,6 +1563,7 @@ class CandidateList(dict):
|
|
|
|
|
|
|
|
|
|
|
|
+
|
|
|
def perform_download_consensus_checks(self, max_count):
|
|
|
self.sort_fallbacks_by_measured_bandwidth()
|
|
|
self.try_download_consensus_checks(max_count)
|
|
@@ -1370,12 +1573,245 @@ class CandidateList(dict):
|
|
|
self.try_download_consensus_checks(max_count)
|
|
|
|
|
|
|
|
|
+ original_count = len(self.fallbacks)
|
|
|
self.fallbacks = filter(lambda x: x.get_fallback_download_consensus(),
|
|
|
self.fallbacks)
|
|
|
+
|
|
|
+
|
|
|
+ failed_count = original_count - len(self.fallbacks)
|
|
|
self.fallbacks = self.fallbacks[:max_count]
|
|
|
+ return failed_count
|
|
|
+
|
|
|
+
|
|
|
+ @staticmethod
|
|
|
+ def describe_percentage(a, b):
|
|
|
+ return '%d/%d = %.0f%%'%(a, b, (a*100.0)/b)
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+ def fallbacks_by_ipv4_netblock(self, mask_bits):
|
|
|
+ netblocks = {}
|
|
|
+ for f in self.fallbacks:
|
|
|
+ found_netblock = False
|
|
|
+ for b in netblocks.keys():
|
|
|
+
|
|
|
+ if f.ipv4_netblocks_equal(self[b], mask_bits):
|
|
|
+
|
|
|
+ netblocks[b].append(f)
|
|
|
+ found_netblock = True
|
|
|
+ break
|
|
|
+
|
|
|
+ if not found_netblock:
|
|
|
+ netblocks[f._fpr] = [f]
|
|
|
+ return netblocks
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+ def fallbacks_by_ipv6_netblock(self, mask_bits):
|
|
|
+ netblocks = {}
|
|
|
+ for f in self.fallbacks:
|
|
|
+
|
|
|
+ if not f.has_ipv6():
|
|
|
+ continue
|
|
|
+ found_netblock = False
|
|
|
+ for b in netblocks.keys():
|
|
|
+
|
|
|
+ if f.ipv6_netblocks_equal(self[b], mask_bits):
|
|
|
+
|
|
|
+ netblocks[b].append(f)
|
|
|
+ found_netblock = True
|
|
|
+ break
|
|
|
+
|
|
|
+ if not found_netblock:
|
|
|
+ netblocks[f._fpr] = [f]
|
|
|
+ return netblocks
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+ def describe_fallback_ipv4_netblock_mask(self, mask_bits):
|
|
|
+ fallback_count = len(self.fallbacks)
|
|
|
+ shared_netblock_fallback_count = 0
|
|
|
+ most_frequent_netblock = None
|
|
|
+ netblocks = self.fallbacks_by_ipv4_netblock(mask_bits)
|
|
|
+ for b in netblocks.keys():
|
|
|
+ if len(netblocks[b]) > 1:
|
|
|
+
|
|
|
+ shared_netblock_fallback_count += len(netblocks[b])
|
|
|
+
|
|
|
+ if (most_frequent_netblock is None
|
|
|
+ or len(netblocks[b]) > len(netblocks[most_frequent_netblock])):
|
|
|
+ most_frequent_netblock = b
|
|
|
+ logging.debug('Fallback IPv4 addresses in the same /%d:'%(mask_bits))
|
|
|
+ for f in netblocks[b]:
|
|
|
+ logging.debug('%s - %s', f.dirip, f._fpr)
|
|
|
+ if most_frequent_netblock is not None:
|
|
|
+ logging.warning('There are %s fallbacks in the IPv4 /%d containing %s'%(
|
|
|
+ CandidateList.describe_percentage(
|
|
|
+ len(netblocks[most_frequent_netblock]),
|
|
|
+ fallback_count),
|
|
|
+ mask_bits,
|
|
|
+ self[most_frequent_netblock].dirip))
|
|
|
+ if shared_netblock_fallback_count > 0:
|
|
|
+ logging.warning(('%s of fallbacks are in an IPv4 /%d with other ' +
|
|
|
+ 'fallbacks')%(CandidateList.describe_percentage(
|
|
|
+ shared_netblock_fallback_count,
|
|
|
+ fallback_count),
|
|
|
+ mask_bits))
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+ def describe_fallback_ipv6_netblock_mask(self, mask_bits):
|
|
|
+ fallback_count = len(self.fallbacks_with_ipv6())
|
|
|
+ shared_netblock_fallback_count = 0
|
|
|
+ most_frequent_netblock = None
|
|
|
+ netblocks = self.fallbacks_by_ipv6_netblock(mask_bits)
|
|
|
+ for b in netblocks.keys():
|
|
|
+ if len(netblocks[b]) > 1:
|
|
|
+
|
|
|
+ shared_netblock_fallback_count += len(netblocks[b])
|
|
|
+
|
|
|
+ if (most_frequent_netblock is None
|
|
|
+ or len(netblocks[b]) > len(netblocks[most_frequent_netblock])):
|
|
|
+ most_frequent_netblock = b
|
|
|
+ logging.debug('Fallback IPv6 addresses in the same /%d:'%(mask_bits))
|
|
|
+ for f in netblocks[b]:
|
|
|
+ logging.debug('%s - %s', f.ipv6addr, f._fpr)
|
|
|
+ if most_frequent_netblock is not None:
|
|
|
+ logging.warning('There are %s fallbacks in the IPv6 /%d containing %s'%(
|
|
|
+ CandidateList.describe_percentage(
|
|
|
+ len(netblocks[most_frequent_netblock]),
|
|
|
+ fallback_count),
|
|
|
+ mask_bits,
|
|
|
+ self[most_frequent_netblock].ipv6addr))
|
|
|
+ if shared_netblock_fallback_count > 0:
|
|
|
+ logging.warning(('%s of fallbacks are in an IPv6 /%d with other ' +
|
|
|
+ 'fallbacks')%(CandidateList.describe_percentage(
|
|
|
+ shared_netblock_fallback_count,
|
|
|
+ fallback_count),
|
|
|
+ mask_bits))
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+ def describe_fallback_ipv4_netblocks(self):
|
|
|
+
|
|
|
+
|
|
|
+ self.describe_fallback_ipv4_netblock_mask(16)
|
|
|
+ self.describe_fallback_ipv4_netblock_mask(24)
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+ def describe_fallback_ipv6_netblocks(self):
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+ self.describe_fallback_ipv6_netblock_mask(32)
|
|
|
+ self.describe_fallback_ipv6_netblock_mask(48)
|
|
|
+ self.describe_fallback_ipv6_netblock_mask(64)
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+ def describe_fallback_netblocks(self):
|
|
|
+ self.describe_fallback_ipv4_netblocks()
|
|
|
+ self.describe_fallback_ipv6_netblocks()
|
|
|
+
|
|
|
+
|
|
|
+ def fallbacks_on_ipv4_orport(self, port):
|
|
|
+ return filter(lambda x: x.orport == port, self.fallbacks)
|
|
|
+
|
|
|
+
|
|
|
+ def fallbacks_on_ipv6_orport(self, port):
|
|
|
+ return filter(lambda x: x.ipv6orport == port, self.fallbacks_with_ipv6())
|
|
|
+
|
|
|
+
|
|
|
+ def fallbacks_on_dirport(self, port):
|
|
|
+ return filter(lambda x: x.dirport == port, self.fallbacks)
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+ def describe_fallback_ipv4_orport(self, port):
|
|
|
+ port_count = len(self.fallbacks_on_ipv4_orport(port))
|
|
|
+ fallback_count = len(self.fallbacks)
|
|
|
+ logging.warning('%s of fallbacks are on IPv4 ORPort %d'%(
|
|
|
+ CandidateList.describe_percentage(port_count,
|
|
|
+ fallback_count),
|
|
|
+ port))
|
|
|
+ return port_count
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+ def describe_fallback_ipv6_orport(self, port):
|
|
|
+ port_count = len(self.fallbacks_on_ipv6_orport(port))
|
|
|
+ fallback_count = len(self.fallbacks_with_ipv6())
|
|
|
+ logging.warning('%s of IPv6 fallbacks are on IPv6 ORPort %d'%(
|
|
|
+ CandidateList.describe_percentage(port_count,
|
|
|
+ fallback_count),
|
|
|
+ port))
|
|
|
+ return port_count
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+ def describe_fallback_dirport(self, port):
|
|
|
+ port_count = len(self.fallbacks_on_dirport(port))
|
|
|
+ fallback_count = len(self.fallbacks)
|
|
|
+ logging.warning('%s of fallbacks are on DirPort %d'%(
|
|
|
+ CandidateList.describe_percentage(port_count,
|
|
|
+ fallback_count),
|
|
|
+ port))
|
|
|
+ return port_count
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+ def describe_fallback_ports(self):
|
|
|
+ fallback_count = len(self.fallbacks)
|
|
|
+ ipv4_or_count = fallback_count
|
|
|
+ ipv4_or_count -= self.describe_fallback_ipv4_orport(443)
|
|
|
+ ipv4_or_count -= self.describe_fallback_ipv4_orport(9001)
|
|
|
+ logging.warning('%s of fallbacks are on other IPv4 ORPorts'%(
|
|
|
+ CandidateList.describe_percentage(ipv4_or_count,
|
|
|
+ fallback_count)))
|
|
|
+ ipv6_fallback_count = len(self.fallbacks_with_ipv6())
|
|
|
+ ipv6_or_count = ipv6_fallback_count
|
|
|
+ ipv6_or_count -= self.describe_fallback_ipv6_orport(443)
|
|
|
+ ipv6_or_count -= self.describe_fallback_ipv6_orport(9001)
|
|
|
+ logging.warning('%s of IPv6 fallbacks are on other IPv6 ORPorts'%(
|
|
|
+ CandidateList.describe_percentage(ipv6_or_count,
|
|
|
+ ipv6_fallback_count)))
|
|
|
+ dir_count = fallback_count
|
|
|
+ dir_count -= self.describe_fallback_dirport(80)
|
|
|
+ dir_count -= self.describe_fallback_dirport(9030)
|
|
|
+ logging.warning('%s of fallbacks are on other DirPorts'%(
|
|
|
+ CandidateList.describe_percentage(dir_count,
|
|
|
+ fallback_count)))
|
|
|
+
|
|
|
+
|
|
|
+ def fallbacks_with_exit(self):
|
|
|
+ return filter(lambda x: x.is_exit(), self.fallbacks)
|
|
|
+
|
|
|
+
|
|
|
+ def describe_fallback_exit_flag(self):
|
|
|
+ exit_falback_count = len(self.fallbacks_with_exit())
|
|
|
+ fallback_count = len(self.fallbacks)
|
|
|
+ logging.warning('%s of fallbacks have the Exit flag'%(
|
|
|
+ CandidateList.describe_percentage(exit_falback_count,
|
|
|
+ fallback_count)))
|
|
|
+
|
|
|
+
|
|
|
+ def fallbacks_with_ipv6(self):
|
|
|
+ return filter(lambda x: x.has_ipv6(), self.fallbacks)
|
|
|
+
|
|
|
+
|
|
|
+ def describe_fallback_ip_family(self):
|
|
|
+ ipv6_falback_count = len(self.fallbacks_with_ipv6())
|
|
|
+ fallback_count = len(self.fallbacks)
|
|
|
+ logging.warning('%s of fallbacks are on IPv6'%(
|
|
|
+ CandidateList.describe_percentage(ipv6_falback_count,
|
|
|
+ fallback_count)))
|
|
|
|
|
|
- def summarise_fallbacks(self, eligible_count, guard_count, target_count,
|
|
|
- max_count):
|
|
|
+ def summarise_fallbacks(self, eligible_count, operator_count, failed_count,
|
|
|
+ guard_count, target_count):
|
|
|
|
|
|
|
|
|
|
|
@@ -1399,17 +1835,23 @@ class CandidateList(dict):
|
|
|
if FALLBACK_PROPORTION_OF_GUARDS is None:
|
|
|
fallback_proportion = ''
|
|
|
else:
|
|
|
- fallback_proportion = ', Target %d (%d * %f)'%(target_count, guard_count,
|
|
|
- FALLBACK_PROPORTION_OF_GUARDS)
|
|
|
- s += 'Final Count: %d (Eligible %d%s'%(fallback_count,
|
|
|
- eligible_count,
|
|
|
+ fallback_proportion = ', Target %d (%d * %.2f)'%(target_count,
|
|
|
+ guard_count,
|
|
|
+ FALLBACK_PROPORTION_OF_GUARDS)
|
|
|
+ s += 'Final Count: %d (Eligible %d%s'%(fallback_count, eligible_count,
|
|
|
fallback_proportion)
|
|
|
if MAX_FALLBACK_COUNT is not None:
|
|
|
- s += ', Clamped to %d'%(MAX_FALLBACK_COUNT)
|
|
|
+ s += ', Max %d'%(MAX_FALLBACK_COUNT)
|
|
|
s += ')\n'
|
|
|
if eligible_count != fallback_count:
|
|
|
- s += 'Excluded: %d (Eligible Count Exceeded Target Count)'%(
|
|
|
- eligible_count - fallback_count)
|
|
|
+ removed_count = eligible_count - fallback_count
|
|
|
+ excess_to_target_or_max = (eligible_count - operator_count - failed_count
|
|
|
+ - fallback_count)
|
|
|
+
|
|
|
+
|
|
|
+ s += ('Excluded: %d (Same Operator %d, Failed/Skipped Download %d, ' +
|
|
|
+ 'Excess %d)')%(removed_count, operator_count, failed_count,
|
|
|
+ excess_to_target_or_max)
|
|
|
s += '\n'
|
|
|
min_fb = self.fallback_min()
|
|
|
min_bw = min_fb._data['measured_bandwidth']
|
|
@@ -1473,18 +1915,46 @@ def list_fallbacks():
|
|
|
|
|
|
|
|
|
|
|
|
+
|
|
|
+
|
|
|
+ candidates.sort_fallbacks_by_measured_bandwidth()
|
|
|
+ operator_count = 0
|
|
|
+
|
|
|
+
|
|
|
+ if not OUTPUT_CANDIDATES:
|
|
|
+ operator_count += candidates.limit_fallbacks_same_ip()
|
|
|
+ operator_count += candidates.limit_fallbacks_same_contact()
|
|
|
+ operator_count += candidates.limit_fallbacks_same_family()
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+ failed_count = candidates.perform_download_consensus_checks(max_count)
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+ candidates.describe_fallback_ip_family()
|
|
|
+
|
|
|
+ if HAVE_IPADDRESS:
|
|
|
+ candidates.describe_fallback_netblocks()
|
|
|
+ candidates.describe_fallback_ports()
|
|
|
+ candidates.describe_fallback_exit_flag()
|
|
|
+
|
|
|
+
|
|
|
if len(candidates.fallbacks) > 0:
|
|
|
- print candidates.summarise_fallbacks(eligible_count, guard_count,
|
|
|
- target_count, max_count)
|
|
|
+ print candidates.summarise_fallbacks(eligible_count, operator_count,
|
|
|
+ failed_count, guard_count,
|
|
|
+ target_count)
|
|
|
else:
|
|
|
print '/* No Fallbacks met criteria */'
|
|
|
|
|
|
+
|
|
|
for s in fetch_source_list():
|
|
|
print describe_fetch_source(s)
|
|
|
|
|
|
-
|
|
|
- candidates.perform_download_consensus_checks(max_count)
|
|
|
-
|
|
|
|
|
|
|
|
|
|