|
@@ -120,39 +120,31 @@ CONSENSUS_DOWNLOAD_RETRY = True
|
|
|
|
|
|
# The target for these parameters is 20% of the guards in the network
|
|
|
# This is around 200 as of October 2015
|
|
|
-FALLBACK_PROPORTION_OF_GUARDS = None if OUTPUT_CANDIDATES else 0.2
|
|
|
+_FB_POG = 0.2
|
|
|
+FALLBACK_PROPORTION_OF_GUARDS = None if OUTPUT_CANDIDATES else _FB_POG
|
|
|
|
|
|
# Limit the number of fallbacks (eliminating lowest by weight)
|
|
|
MAX_FALLBACK_COUNT = None if OUTPUT_CANDIDATES else 500
|
|
|
# Emit a C #error if the number of fallbacks is below
|
|
|
-MIN_FALLBACK_COUNT = 50
|
|
|
+MIN_FALLBACK_COUNT = 100
|
|
|
|
|
|
## Fallback Weight Settings
|
|
|
|
|
|
-# Any fallback with the Exit flag has its weight multipled by this fraction
|
|
|
+# Any fallback with the Exit flag has its consensus weight multipled by this
|
|
|
EXIT_WEIGHT_FRACTION = 1.0
|
|
|
|
|
|
-# If True, emit a C #error if we can't satisfy various constraints
|
|
|
-# If False, emit a C comment instead
|
|
|
-STRICT_FALLBACK_WEIGHTS = False
|
|
|
-
|
|
|
-# Limit the proportional weight
|
|
|
-# If a single fallback's weight is too high, it will see too many clients
|
|
|
-# We reweight using a lower threshold to provide some leeway for:
|
|
|
-# * elimination of low weight relays
|
|
|
-# * consensus weight changes
|
|
|
-# * fallback directory losses over time
|
|
|
-# A relay weighted at 1 in 10 fallbacks will see about 10% of clients that
|
|
|
-# use the fallback directories. (The 9 directory authorities see a similar
|
|
|
-# proportion of clients.)
|
|
|
-TARGET_MAX_WEIGHT_FRACTION = 1/10.0
|
|
|
-REWEIGHTING_FUDGE_FACTOR = 0.8
|
|
|
-MAX_WEIGHT_FRACTION = TARGET_MAX_WEIGHT_FRACTION * REWEIGHTING_FUDGE_FACTOR
|
|
|
-# If a single fallback's weight is too low, it's pointless adding it.
|
|
|
-# (Final weights may be slightly higher than this, due to low weight relays
|
|
|
-# being excluded.)
|
|
|
-# A relay weighted at 1 in 1000 fallbacks will see about 0.1% of clients.
|
|
|
-MIN_WEIGHT_FRACTION = 0.0 if OUTPUT_CANDIDATES else 1/1000.0
|
|
|
+# If a single fallback's consensus weight is too low, it's pointless adding it
|
|
|
+# We expect fallbacks to handle an extra 30 kilobytes per second of traffic
|
|
|
+# Make sure they support a hundred times that
|
|
|
+MIN_CONSENSUS_WEIGHT = 30.0 * 100.0
|
|
|
+
|
|
|
+# All fallback weights are equal, and set to the value below
|
|
|
+# Authorities are weighted 1.0 by default
|
|
|
+# Clients use these weights to select fallbacks and authorities at random
|
|
|
+# If there are 100 fallbacks and 9 authorities:
|
|
|
+# - each fallback is chosen with probability 10/(1000 + 9) ~= 0.99%
|
|
|
+# - each authority is chosen with probability 1/(1000 + 9) ~= 0.09%
|
|
|
+FALLBACK_OUTPUT_WEIGHT = 10.0
|
|
|
|
|
|
## Other Configuration Parameters
|
|
|
|
|
@@ -465,9 +457,7 @@ class Candidate(object):
|
|
|
logging.debug("Failed to get an ipv6 address for %s."%(self._fpr,))
|
|
|
# Reduce the weight of exits to EXIT_WEIGHT_FRACTION * consensus_weight
|
|
|
if self.is_exit():
|
|
|
- current_weight = self._data['consensus_weight']
|
|
|
- exit_weight = current_weight * EXIT_WEIGHT_FRACTION
|
|
|
- self._data['original_consensus_weight'] = current_weight
|
|
|
+ exit_weight = self._data['consensus_weight'] * EXIT_WEIGHT_FRACTION
|
|
|
self._data['consensus_weight'] = exit_weight
|
|
|
|
|
|
def _stable_sort_or_addresses(self):
|
|
@@ -757,6 +747,12 @@ class Candidate(object):
|
|
|
logging.info('%s not a candidate: guard avg too low (%lf)',
|
|
|
self._fpr, self._guard)
|
|
|
return False
|
|
|
+ if (MIN_CONSENSUS_WEIGHT is not None
|
|
|
+ and self._data['consensus_weight'] < MIN_CONSENSUS_WEIGHT):
|
|
|
+ logging.info('%s not a candidate: consensus weight %.0f too low, must ' +
|
|
|
+ 'be at least %.0f', self._fpr,
|
|
|
+ self._data['consensus_weight'], MIN_CONSENSUS_WEIGHT)
|
|
|
+ return False
|
|
|
return True
|
|
|
|
|
|
def is_in_whitelist(self, relaylist):
|
|
@@ -895,20 +891,6 @@ class Candidate(object):
|
|
|
def is_running(self):
|
|
|
return 'Running' in self._data['flags']
|
|
|
|
|
|
- def fallback_weight_fraction(self, total_weight):
|
|
|
- return float(self._data['consensus_weight']) / total_weight
|
|
|
-
|
|
|
- # return the original consensus weight, if it exists,
|
|
|
- # or, if not, return the consensus weight
|
|
|
- def original_consensus_weight(self):
|
|
|
- if self._data.has_key('original_consensus_weight'):
|
|
|
- return self._data['original_consensus_weight']
|
|
|
- else:
|
|
|
- return self._data['consensus_weight']
|
|
|
-
|
|
|
- def original_fallback_weight_fraction(self, total_weight):
|
|
|
- return float(self.original_consensus_weight()) / total_weight
|
|
|
-
|
|
|
@staticmethod
|
|
|
def fallback_consensus_dl_speed(dirip, dirport, nickname, max_time):
|
|
|
download_failed = False
|
|
@@ -976,17 +958,15 @@ class Candidate(object):
|
|
|
CONSENSUS_DOWNLOAD_SPEED_MAX)
|
|
|
return ((not ipv4_failed) and (not ipv6_failed))
|
|
|
|
|
|
- def fallbackdir_line(self, total_weight, original_total_weight, dl_speed_ok):
|
|
|
+ def fallbackdir_line(self, dl_speed_ok):
|
|
|
# /*
|
|
|
# nickname
|
|
|
# flags
|
|
|
- # weight / total (percentage)
|
|
|
- # [original weight / original total (original percentage)]
|
|
|
# [contact]
|
|
|
# */
|
|
|
# "address:dirport orport=port id=fingerprint"
|
|
|
# "[ipv6=addr:orport]"
|
|
|
- # "weight=num",
|
|
|
+ # "weight=FALLBACK_OUTPUT_WEIGHT",
|
|
|
#
|
|
|
# Multiline C comment
|
|
|
s = '/*'
|
|
@@ -996,19 +976,6 @@ class Candidate(object):
|
|
|
s += 'Flags: '
|
|
|
s += cleanse_c_multiline_comment(' '.join(sorted(self._data['flags'])))
|
|
|
s += '\n'
|
|
|
- weight = self._data['consensus_weight']
|
|
|
- percent_weight = self.fallback_weight_fraction(total_weight)*100
|
|
|
- s += 'Fallback Weight: %d / %d (%.3f%%)'%(weight, total_weight,
|
|
|
- percent_weight)
|
|
|
- s += '\n'
|
|
|
- o_weight = self.original_consensus_weight()
|
|
|
- if o_weight != weight:
|
|
|
- o_percent_weight = self.original_fallback_weight_fraction(
|
|
|
- original_total_weight)*100
|
|
|
- s += 'Consensus Weight: %d / %d (%.3f%%)'%(o_weight,
|
|
|
- original_total_weight,
|
|
|
- o_percent_weight)
|
|
|
- s += '\n'
|
|
|
if self._data['contact'] is not None:
|
|
|
s += cleanse_c_multiline_comment(self._data['contact'])
|
|
|
s += '\n'
|
|
@@ -1030,7 +997,7 @@ class Candidate(object):
|
|
|
s += '" ipv6=%s:%s"'%(
|
|
|
cleanse_c_string(self.ipv6addr), cleanse_c_string(self.ipv6orport))
|
|
|
s += '\n'
|
|
|
- s += '" weight=%d",'%(weight)
|
|
|
+ s += '" weight=%d",'%(FALLBACK_OUTPUT_WEIGHT)
|
|
|
if not dl_speed_ok:
|
|
|
s += '\n'
|
|
|
s += '*/'
|
|
@@ -1205,48 +1172,11 @@ class CandidateList(dict):
|
|
|
|
|
|
# Remove any fallbacks in excess of MAX_FALLBACK_COUNT,
|
|
|
# starting with the lowest-weighted fallbacks
|
|
|
- # total_weight should be recalculated after calling this
|
|
|
+ # this changes total weight
|
|
|
def exclude_excess_fallbacks(self):
|
|
|
if MAX_FALLBACK_COUNT is not None:
|
|
|
self.fallbacks = self.fallbacks[:MAX_FALLBACK_COUNT]
|
|
|
|
|
|
- # Clamp the weight of all fallbacks to MAX_WEIGHT_FRACTION * total_weight
|
|
|
- # fallbacks are kept sorted, but since excessive weights are reduced to
|
|
|
- # the maximum acceptable weight, these relays end up with equal weights
|
|
|
- def clamp_high_weight_fallbacks(self, total_weight):
|
|
|
- if MAX_WEIGHT_FRACTION * len(self.fallbacks) < 1.0:
|
|
|
- error_str = 'Max Fallback Weight %.3f%% is unachievable'%(
|
|
|
- MAX_WEIGHT_FRACTION)
|
|
|
- error_str += ' with Current Fallback Count %d.'%(len(self.fallbacks))
|
|
|
- if STRICT_FALLBACK_WEIGHTS:
|
|
|
- print '#error ' + error_str
|
|
|
- else:
|
|
|
- print '/* ' + error_str + ' */'
|
|
|
- relays_clamped = 0
|
|
|
- max_acceptable_weight = total_weight * MAX_WEIGHT_FRACTION
|
|
|
- for f in self.fallbacks:
|
|
|
- frac_weight = f.fallback_weight_fraction(total_weight)
|
|
|
- if frac_weight > MAX_WEIGHT_FRACTION:
|
|
|
- relays_clamped += 1
|
|
|
- current_weight = f._data['consensus_weight']
|
|
|
- # if we already have an original weight, keep it
|
|
|
- if (not f._data.has_key('original_consensus_weight')
|
|
|
- or f._data['original_consensus_weight'] == current_weight):
|
|
|
- f._data['original_consensus_weight'] = current_weight
|
|
|
- f._data['consensus_weight'] = max_acceptable_weight
|
|
|
- return relays_clamped
|
|
|
-
|
|
|
- # Remove any fallbacks with weights lower than MIN_WEIGHT_FRACTION
|
|
|
- # total_weight should be recalculated after calling this
|
|
|
- def exclude_low_weight_fallbacks(self, total_weight):
|
|
|
- self.fallbacks = filter(
|
|
|
- lambda x:
|
|
|
- x.fallback_weight_fraction(total_weight) >= MIN_WEIGHT_FRACTION,
|
|
|
- self.fallbacks)
|
|
|
-
|
|
|
- def fallback_weight_total(self):
|
|
|
- return sum(f._data['consensus_weight'] for f in self.fallbacks)
|
|
|
-
|
|
|
def fallback_min_weight(self):
|
|
|
if len(self.fallbacks) > 0:
|
|
|
return self.fallbacks[-1]
|
|
@@ -1259,14 +1189,12 @@ class CandidateList(dict):
|
|
|
else:
|
|
|
return None
|
|
|
|
|
|
- def summarise_fallbacks(self, eligible_count, eligible_weight,
|
|
|
- relays_clamped, clamped_weight,
|
|
|
- guard_count, target_count, max_count):
|
|
|
+ def summarise_fallbacks(self, eligible_count, guard_count, target_count,
|
|
|
+ max_count):
|
|
|
# Report:
|
|
|
# the number of fallback directories (with min & max limits);
|
|
|
# #error if below minimum count
|
|
|
- # the total weight, min & max fallback proportions
|
|
|
- # #error if outside max weight proportion
|
|
|
+ # min & max fallback weights
|
|
|
# Multiline C comment with #error if things go bad
|
|
|
s = '/*'
|
|
|
s += '\n'
|
|
@@ -1279,7 +1207,7 @@ class CandidateList(dict):
|
|
|
else:
|
|
|
fallback_proportion = '%d (%d * %f)'%(target_count, guard_count,
|
|
|
FALLBACK_PROPORTION_OF_GUARDS)
|
|
|
- s += 'Final Count: %d (Eligible %d, Usable %d, Target %d%s'%(
|
|
|
+ s += 'Final Count: %d (Eligible %d, Target %d%s'%(
|
|
|
min(max_count, fallback_count),
|
|
|
eligible_count,
|
|
|
fallback_count,
|
|
@@ -1299,51 +1227,19 @@ class CandidateList(dict):
|
|
|
s += '\n'
|
|
|
s += '/*'
|
|
|
s += '\n'
|
|
|
- total_weight = self.fallback_weight_total()
|
|
|
min_fb = self.fallback_min_weight()
|
|
|
min_weight = min_fb._data['consensus_weight']
|
|
|
- min_percent = min_fb.fallback_weight_fraction(total_weight)*100.0
|
|
|
max_fb = self.fallback_max_weight()
|
|
|
max_weight = max_fb._data['consensus_weight']
|
|
|
- max_frac = max_fb.fallback_weight_fraction(total_weight)
|
|
|
- max_percent = max_frac*100.0
|
|
|
- s += 'Final Weight: %d (Eligible %d)'%(total_weight, eligible_weight)
|
|
|
- s += '\n'
|
|
|
- s += 'Max Weight: %d (%.3f%%) (Clamped to %.3f%%)'%(
|
|
|
- max_weight,
|
|
|
- max_percent,
|
|
|
- TARGET_MAX_WEIGHT_FRACTION*100)
|
|
|
+ s += 'Max Weight: %d'%(max_weight)
|
|
|
s += '\n'
|
|
|
- s += 'Min Weight: %d (%.3f%%) (Clamped to %.3f%%)'%(
|
|
|
- min_weight,
|
|
|
- min_percent,
|
|
|
- MIN_WEIGHT_FRACTION*100)
|
|
|
+ s += 'Min Weight: %d'%(min_weight)
|
|
|
s += '\n'
|
|
|
if eligible_count != fallback_count:
|
|
|
- s += 'Excluded: %d (Clamped, Below Target, or Low Weight)'%(
|
|
|
+ s += 'Excluded: %d (Eligible Count Exceeded Target Count)'%(
|
|
|
eligible_count - fallback_count)
|
|
|
s += '\n'
|
|
|
- if relays_clamped > 0:
|
|
|
- s += 'Clamped: %d (%.3f%%) Excess Weight, '%(
|
|
|
- clamped_weight,
|
|
|
- (100.0 * clamped_weight) / total_weight)
|
|
|
- s += '%d High Weight Fallbacks (%.1f%%)'%(
|
|
|
- relays_clamped,
|
|
|
- (100.0 * relays_clamped) / fallback_count)
|
|
|
- s += '\n'
|
|
|
s += '*/'
|
|
|
- if max_frac > TARGET_MAX_WEIGHT_FRACTION:
|
|
|
- s += '\n'
|
|
|
- # We must restrict the maximum fallback weight, so an adversary
|
|
|
- # at or near the fallback doesn't see too many clients
|
|
|
- error_str = 'Max Fallback Weight %.3f%% is too high. '%(max_frac*100)
|
|
|
- error_str += 'Must be at most %.3f%% for client anonymity.'%(
|
|
|
- TARGET_MAX_WEIGHT_FRACTION*100)
|
|
|
- if STRICT_FALLBACK_WEIGHTS:
|
|
|
- s += '#error ' + error_str
|
|
|
- else:
|
|
|
- s += '/* ' + error_str + ' */'
|
|
|
- s += '\n'
|
|
|
if PERFORM_IPV4_DIRPORT_CHECKS or PERFORM_IPV6_DIRPORT_CHECKS:
|
|
|
s += '/* Checked %s%s%s DirPorts served a consensus within %.1fs. */'%(
|
|
|
'IPv4' if PERFORM_IPV4_DIRPORT_CHECKS else '',
|
|
@@ -1386,54 +1282,17 @@ def list_fallbacks():
|
|
|
excluded_count = candidates.apply_filter_lists()
|
|
|
print candidates.summarise_filters(initial_count, excluded_count)
|
|
|
eligible_count = len(candidates.fallbacks)
|
|
|
- eligible_weight = candidates.fallback_weight_total()
|
|
|
|
|
|
# print the raw fallback list
|
|
|
- #total_weight = candidates.fallback_weight_total()
|
|
|
#for x in candidates.fallbacks:
|
|
|
- # print x.fallbackdir_line(total_weight, total_weight)
|
|
|
+ # print x.fallbackdir_line(True)
|
|
|
|
|
|
- # When candidates are excluded, total_weight decreases, and
|
|
|
- # the proportional weight of other candidates increases.
|
|
|
+ # exclude low-weight fallbacks if we have more than we want
|
|
|
candidates.exclude_excess_fallbacks()
|
|
|
- total_weight = candidates.fallback_weight_total()
|
|
|
-
|
|
|
- # When candidates are reweighted, total_weight decreases, and
|
|
|
- # the proportional weight of other candidates increases.
|
|
|
- # Previously low-weight candidates might obtain sufficient proportional
|
|
|
- # weights to be included.
|
|
|
- # Save the weight at which we reweighted fallbacks for the summary.
|
|
|
- pre_clamp_total_weight = total_weight
|
|
|
- relays_clamped = candidates.clamp_high_weight_fallbacks(total_weight)
|
|
|
-
|
|
|
- # When candidates are excluded, total_weight decreases, and
|
|
|
- # the proportional weight of other candidates increases.
|
|
|
- # No new low weight candidates will be created during exclusions.
|
|
|
- # However, high weight candidates may increase over the maximum proportion.
|
|
|
- # This should not be an issue, except in pathological cases.
|
|
|
- candidates.exclude_low_weight_fallbacks(total_weight)
|
|
|
- total_weight = candidates.fallback_weight_total()
|
|
|
-
|
|
|
- # check we haven't exceeded TARGET_MAX_WEIGHT_FRACTION
|
|
|
- # since reweighting preserves the orginal sort order,
|
|
|
- # the maximum weights will be at the head of the list
|
|
|
- if len(candidates.fallbacks) > 0:
|
|
|
- max_weight_fb = candidates.fallback_max_weight()
|
|
|
- max_weight = max_weight_fb.fallback_weight_fraction(total_weight)
|
|
|
- if max_weight > TARGET_MAX_WEIGHT_FRACTION:
|
|
|
- error_str = 'Maximum fallback weight: %.3f%% exceeds target %.3f%%. '%(
|
|
|
- max_weight*100.0,
|
|
|
- TARGET_MAX_WEIGHT_FRACTION*100.0)
|
|
|
- error_str += 'Try decreasing REWEIGHTING_FUDGE_FACTOR.'
|
|
|
- if STRICT_FALLBACK_WEIGHTS:
|
|
|
- print '#error ' + error_str
|
|
|
- else:
|
|
|
- print '/* ' + error_str + ' */'
|
|
|
|
|
|
- print candidates.summarise_fallbacks(eligible_count, eligible_weight,
|
|
|
- relays_clamped,
|
|
|
- pre_clamp_total_weight - total_weight,
|
|
|
- guard_count, target_count, max_count)
|
|
|
+ if len(candidates.fallbacks) > 0:
|
|
|
+ print candidates.summarise_fallbacks(eligible_count, guard_count,
|
|
|
+ target_count, max_count)
|
|
|
else:
|
|
|
print '/* No Fallbacks met criteria */'
|
|
|
|
|
@@ -1442,7 +1301,7 @@ def list_fallbacks():
|
|
|
|
|
|
for x in candidates.fallbacks[:max_count]:
|
|
|
dl_speed_ok = x.fallback_consensus_dl_check()
|
|
|
- print x.fallbackdir_line(total_weight, pre_clamp_total_weight, dl_speed_ok)
|
|
|
+ print x.fallbackdir_line(dl_speed_ok)
|
|
|
#print json.dumps(candidates[x]._data, sort_keys=True, indent=4,
|
|
|
# separators=(',', ': '), default=json_util.default)
|
|
|
|