commit 20df35c49c67b86b6fb0538e2d14a33e1bc4f011 Author: Georg Koppen gk@torproject.org Date: Wed Apr 1 09:35:28 2020 +0000
Bug 33009: Require minimum bandwidth for second hop --- sbws/core/scanner.py | 6 ++++++ sbws/lib/relaylist.py | 35 +++++++++++++++++++++++++++++++++++ tests/unit/lib/test_relaylist.py | 11 +++++++++++ 3 files changed, 52 insertions(+)
diff --git a/sbws/core/scanner.py b/sbws/core/scanner.py index 30abd0a..3210c80 100644 --- a/sbws/core/scanner.py +++ b/sbws/core/scanner.py @@ -211,10 +211,16 @@ def _pick_ideal_second_hop(relay, dest, rl, cont, is_exit): else rl.non_exits if not len(candidates): return None + min_relay_bw = rl.exit_min_bw() if is_exit else rl.non_exit_min_bw() log.debug('Picking a 2nd hop to measure %s from %d choices. is_exit=%s', relay.nickname, len(candidates), is_exit) for min_bw_factor in [2, 1.75, 1.5, 1.25, 1]: min_bw = relay.consensus_bandwidth * min_bw_factor + # We might have a really slow/new relay. Try to measure it properly by + # using only relays with or above our calculated min_relay_bw (see: + # _calculate_min_bw_second_hop() in relaylist.py). + if min_bw < min_relay_bw: + min_bw = min_relay_bw new_candidates = stem_utils.only_relays_with_bandwidth( cont, candidates, min_bw=min_bw) if len(new_candidates) > 0: diff --git a/sbws/lib/relaylist.py b/sbws/lib/relaylist.py index 4297784..1852199 100644 --- a/sbws/lib/relaylist.py +++ b/sbws/lib/relaylist.py @@ -278,6 +278,9 @@ class RelayList: [], MAX_RECENT_PRIORITY_RELAY_COUNT, state, "recent_measurement_attempt" ) + # Start with 0 for the min bw for our second hops + self._exit_min_bw = 0 + self._non_exit_min_bw = 0 self._refresh()
def _need_refresh(self): @@ -431,6 +434,10 @@ class RelayList: int(self._measurements_period / 24 / 60 / 60), self.recent_consensus_count)
+ # Calculate minimum bandwidth value for 2nd hop after we refreshed + # our available relays. + self._calculate_min_bw_second_hop() + @property def recent_consensus_count(self): """Number of times a new consensus was obtained.""" @@ -455,3 +462,31 @@ class RelayList: @property def recent_measurement_attempt_count(self): return len(self._recent_measurement_attempt) + + def _calculate_min_bw_second_hop(self): + """ + Calculates the minimum bandwidth for both exit and non-exit relays + chosen as a second hop by picking the lowest bandwidth value available + from the top 75% of the respective category. + """ + # Sort our sets of candidates according to bw, lowest amount first. + # It's okay to keep things simple for the calculation and go over all + # exits, including badexits. + exit_candidates = sorted(self.exits, + key=lambda r: r.consensus_bandwidth) + non_exit_candidates = sorted(self.non_exits, + key=lambda r: r.consensus_bandwidth) + # We know the bandwidth is sorted from least to most. Dividing the + # length of the available relays by 4 gives us the position of the + # relay with the lowest bandwidth from the top 75%. We do this both + # for our exit and non-exit candidates. + pos = int(len(exit_candidates)/4) + self._exit_min_bw = exit_candidates[pos].consensus_bandwidth + pos = int(len(non_exit_candidates)/4) + self._non_exit_min_bw = non_exit_candidates[pos].consensus_bandwidth + + def exit_min_bw(self): + return self._exit_min_bw + + def non_exit_min_bw(self): + return self._non_exit_min_bw diff --git a/tests/unit/lib/test_relaylist.py b/tests/unit/lib/test_relaylist.py index 88f9db5..31673ba 100644 --- a/tests/unit/lib/test_relaylist.py +++ b/tests/unit/lib/test_relaylist.py @@ -18,6 +18,8 @@ def test_init_relays( Test `init_relays` when creating the RelayList the first time and when a new consensus is received. Test that the number of consesus timesamps and relays is correct. + Additionally, make sure the calculated min bw for the second hop for + exit/non-exit relays is correct, too. """ state = State(conf['paths']['state_fpath']) # There is no need to mock datetime to update the consensus, since the @@ -31,6 +33,9 @@ def test_init_relays( # The actual number of relays in the consensus assert len(relay_list._relays) == 6433 fps = {r.fingerprint for r in relay_list._relays} + # The calculated min bw for the second hop + assert 2100000 == relay_list._exit_min_bw + assert 220000 == relay_list._non_exit_min_bw
# One hour later there is a new consensus relay_list._controller = controller_1h_later @@ -44,6 +49,9 @@ def test_init_relays( fps_1h_later = {r.fingerprint for r in relay_list._relays} added_fps = fps_1h_later.difference(fps) assert 6505 == 6433 + len(added_fps) + # The calculated min bw for the second hop + assert 2120000 == relay_list._exit_min_bw + assert 200000 == relay_list._non_exit_min_bw
# Five days later plus 1 second. # The first consensus timestamp will get removed. @@ -62,6 +70,9 @@ def test_init_relays( # The number of relays will be the number of relays in the cosensus plus # the added ones minus the removed ones. assert 6925 == 6505 + len(added_fps) - len(removed_fps) + # The calculated min bw for the second hop + assert 2790000 == relay_list._exit_min_bw + assert 110000 == relay_list._non_exit_min_bw
def test_increment_recent_measurement_attempt(args, conf, controller):