[tor-commits] [sbws/master] Measure exits in the exit position; try harder to find a helper

pastly at torproject.org pastly at torproject.org
Thu Jun 14 13:29:51 UTC 2018


commit 2fc8f53329caf2406870007e53110060ad3a73f1
Author: Matt Traudt <sirmatt at ksu.edu>
Date:   Tue Jun 12 21:18:13 2018 -0400

    Measure exits in the exit position; try harder to find a helper
---
 CHANGELOG.md          | 13 ++++++++++
 sbws/core/scanner.py  | 67 ++++++++++++++++++++++++++++++++++++++++-----------
 sbws/lib/relaylist.py | 20 +++++++++++++++
 3 files changed, 86 insertions(+), 14 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 3097ce2..918421a 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -7,6 +7,19 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
 
 ## [Unreleased]
 
+### Changed
+
+- If the relay to measure is an exit, put it in the exit position and choose a
+  non-exit to help. Previously the relay to measure would always be the first
+hop. (GH#181)
+- Try harder to find a relay to help measure the target relay with two changes.
+  Essentially: (1) Instead of only picking from relays that are 1.25 - 2.00
+times faster than it by consensus weight, try (in order) to find a relay that
+is at least 2.00, 1.75, 1.50, 1.25, or 1.00 times as fast. If that fails,
+instead of giving up, (2) pick the fastest relay in the network instead of
+giving up. This compliments the previous change about measuring target exits in
+the exit position.
+
 ### Fixed
 
 - Exception that causes sbws to fall back to one measurement thread. We first
diff --git a/sbws/core/scanner.py b/sbws/core/scanner.py
index 8e9f9c4..6af1b0c 100644
--- a/sbws/core/scanner.py
+++ b/sbws/core/scanner.py
@@ -131,6 +131,40 @@ def measure_bandwidth_to_server(session, conf, dest, content_length):
     return results
 
 
+def _pick_ideal_second_hop(relay, dest, rl, cont, is_exit):
+    '''
+    Sbws builds two hop circuits. Given the **relay** to measure with
+    destination **dest**, pick a second relay that is or is not an exit
+    according to **is_exit**.
+    '''
+    candidates = []
+    candidates.extend(rl.exits if is_exit else rl.non_exits)
+    if not len(candidates):
+        return None
+    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.bandwidth * min_bw_factor
+        new_candidates = stem_utils.only_relays_with_bandwidth(
+            cont, candidates, min_bw=min_bw)
+        if len(new_candidates) > 0:
+            chosen = rng.choice(new_candidates)
+            log.debug(
+                'Found %d candidate 2nd hops with at least %sx the bandwidth '
+                'of %s. Returning %s (bw=%s).',
+                len(new_candidates), min_bw_factor, relay.nickname,
+                chosen.nickname, chosen.bandwidth)
+            return chosen
+    candidates = sorted(candidates, key=lambda r: r.bandwidth, reverse=True)
+    chosen = candidates[0]
+    log.debug(
+        'Didn\'t find any 2nd hops at least as fast as %s (bw=%s). It\'s '
+        'probably really fast. Returning %s (bw=%s), the fastest '
+        'candidate we have.', relay.nickname, relay.bandwidth,
+        chosen.nickname, chosen.bandwidth)
+    return chosen
+
+
 def measure_relay(args, conf, destinations, cb, rl, relay):
     s = requests_utils.make_session(
         cb.controller, conf.getfloat('general', 'http_timeout'))
@@ -140,24 +174,29 @@ def measure_relay(args, conf, destinations, cb, rl, relay):
         log.warning('Unable to get destination to measure %s %s',
                     relay.nickname, relay.fingerprint[0:8])
         return None
-    # Pick an exit
-    exits = rl.exits_can_exit_to(dest.hostname, dest.port)
-    exits = [e for e in exits if e.fingerprint != relay.fingerprint]
-    exits = stem_utils.only_relays_with_bandwidth(
-        cb.controller, exits, min_bw=round(relay.bandwidth*1.25),
-        max_bw=max(round(relay.bandwidth*2.00), 100))
-    if len(exits) < 1:
-        log.warning('No available exits to help measure %s %s', relay.nickname,
-                    relay.fingerprint[0:8])
+    # Pick a relay to help us measure the given relay. If the given relay is an
+    # exit, then pick a non-exit. Otherwise pick an exit.
+    helper = None
+    circ_fps = None
+    if relay.can_exit_to(dest.hostname, dest.port):
+        helper = _pick_ideal_second_hop(
+            relay, dest, rl, cb.controller, is_exit=False)
+        if helper:
+            circ_fps = [helper.fingerprint, relay.fingerprint]
+    else:
+        helper = _pick_ideal_second_hop(
+            relay, dest, rl, cb.controller, is_exit=True)
+        if helper:
+            circ_fps = [relay.fingerprint, helper.fingerprint]
+    if not helper:
         # TODO: Return ResultError of some sort
+        log.warning('Unable to pick a 2nd hop to help measure %s %s',
+                    relay.nickname, relay.fingerprint[0:8])
         return None
-    exit = rng.choice(exits)
+    assert helper
+    assert circ_fps is not None and len(circ_fps) == 2
     # Build the circuit
-    log.debug('We selected exit %s %s (cw=%d) to help measure %s %s (cw=%d)',
-              exit.nickname, exit.fingerprint[0:8], exit.bandwidth,
-              relay.nickname, relay.fingerprint[0:8], relay.bandwidth)
     our_nick = conf['scanner']['nickname']
-    circ_fps = [relay.fingerprint, exit.fingerprint]
     circ_id = cb.build_circuit(circ_fps)
     if not circ_id:
         log.warning('Could not build circuit involving %s', relay.nickname)
diff --git a/sbws/lib/relaylist.py b/sbws/lib/relaylist.py
index 7b3d58a..2cc5fef 100644
--- a/sbws/lib/relaylist.py
+++ b/sbws/lib/relaylist.py
@@ -88,6 +88,22 @@ class Relay:
         # it seems that stem parses it as ed25519_master_key
         return self._from_desc('ed25519_master_key').rstrip('=')
 
+    def can_exit_to(self, host, port):
+        if not self.exit_policy:
+            return False
+        assert isinstance(host, str)
+        assert isinstance(port, int)
+        if not is_valid_ipv4_address(host) and not is_valid_ipv6_address(host):
+            # It certainly isn't perfect trying to guess if an exit can connect
+            # to an ipv4/6 address based on the DNS result we got locally. But
+            # it's the best we can do.
+            #
+            # Also, only use the first ipv4/6 we get even if there is more than
+            # one.
+            host = resolve(host)[0]
+        assert is_valid_ipv4_address(host) or is_valid_ipv6_address(host)
+        return self.exit_policy.can_exit_to(host, port)
+
 
 class RelayList:
     ''' Keeps a list of all relays in the current Tor network and updates it
@@ -116,6 +132,10 @@ class RelayList:
         return self._relays_with_flag(Flag.EXIT)
 
     @property
+    def non_exits(self):
+        return self._relays_without_flag(Flag.EXIT)
+
+    @property
     def guards(self):
         return self._relays_with_flag(Flag.GUARD)
 





More information about the tor-commits mailing list