[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