Commit 8e3914d3 authored by Matt Traudt's avatar Matt Traudt Committed by Matt Traudt
Browse files

Perform periodic reachability tests to determine which helpers are useful

parent 764e1499
import random
from ..util.simpleauth import authenticate_to_server
from ..util.sockio import (make_socket, close_socket)
from sbws.globals import time_now
from sbws.lib.circuitbuilder import GapsCircuitBuilder as CB
import sbws.util.stem as stem_utils
from stem.descriptor.router_status_entry import RouterStatusEntryV3
from threading import RLock
import random
import logging
log = logging.getLogger(__name__)
......@@ -11,6 +17,11 @@ class HelperRelay:
self._server_host = conf_section['server_host']
self._server_port = conf_section.getint('server_port')
self._password = conf_section['password']
self._name = conf_section.name.split('.')[1]
@property
def name(self):
return self._name
@property
def fingerprint(self):
......@@ -39,7 +50,15 @@ class HelperRelayList:
self.controller = controller
for helper in helpers:
assert isinstance(helper, HelperRelay)
self.helpers = helpers
self._socks_proxy = (conf['tor']['socks_host'],
conf.getint('tor', 'socks_port'))
self._all_helpers = helpers
self._usable_helpers = set()
self._circuit_builder = CB(args, conf, controller)
self._last_reachability_test = 0
self._reachability_test_every = \
conf.getint('helpers', 'reachability_test_every')
self._reachability_lock = RLock()
@staticmethod
def from_config(args, conf, controller=None):
......@@ -50,6 +69,8 @@ class HelperRelayList:
section = conf['helpers']
helpers = []
for key in section.keys():
if key == 'reachability_test_every':
continue
if not section.getboolean(key):
log.debug('%s is disabled; not loading it', key)
continue
......@@ -59,21 +80,80 @@ class HelperRelayList:
helpers.append(HelperRelay(conf[helper_sec]))
return HelperRelayList(args, conf, helpers, controller=controller), ''
def _should_perform_reachability_test(self):
return self._last_reachability_test + self._reachability_test_every <\
time_now()
def _build_circuit_with_relay(self, relay):
assert isinstance(relay, RouterStatusEntryV3)
cb = self._circuit_builder
# Try three times to get a circuit ending at the target relay
# Note that the CircuitBuilder may try each path multiple times (as of
# the time of this comment's writing, it does)
for _ in range(0, 3):
circ_id = cb.build_circuit([None, relay.fingerprint])
if circ_id is not None:
return circ_id
return None
def _helper_server_is_functioning(self, helper, circ_id):
assert circ_id
cb = self._circuit_builder
sock = make_socket(*self._socks_proxy)
connected = stem_utils.connect_over_circuit(
cb.controller, circ_id, sock, helper.server_host,
helper.server_port)
if not connected:
close_socket(sock)
return False
authed = authenticate_to_server(sock, helper.password)
close_socket(sock)
return authed
def _perform_reachability_test(self):
self._reachability_lock.acquire()
log.debug('Performing reachability tests')
helpers = set()
for helper in self._all_helpers:
fp = helper.fingerprint
relay = stem_utils.fp_or_nick_to_relay(self.controller, fp)
if not relay:
log.warning('Helper %s\'s relay with fp %s does not seem to '
'be in the consensus. Ignoring it.', helper.name,
fp[0:8])
continue
circ_id = self._build_circuit_with_relay(relay)
if not circ_id:
log.warning('Unable to build a circuit with helper %s\'s '
'relay %s in it. Ignoring it.', helper.name,
relay.nickname)
continue
if not self._helper_server_is_functioning(helper, circ_id):
log.warning('Unable to speak with the sbws server for helper '
'%s. Ignoring it.', helper.name)
self._circuit_builder.close_circuit(circ_id)
continue
self._circuit_builder.close_circuit(circ_id)
log.debug('Helper %s is usable. Keeping it.', relay.nickname)
helpers.add(helper)
self._last_reachability_test = time_now()
self._usable_helpers = list(helpers)
log.info('After performing reachability tests, we have %d/%d usable '
'helpers: %s', len(self._usable_helpers),
len(self._all_helpers),
', '.join([h.name for h in self._usable_helpers]))
self._reachability_lock.release()
def next(self, blacklist=[]):
''' Returns the next helper in the list that should be used. Do not
pick a helper that has a relay with a fingerprint in the given
blacklist. Returns None if no valid helper is available. '''
# XXX: Consider alternate designs. This just picks a random
# non-blacklisted helper. What if we could keep track of the health of
# the helpers? What if we picked geographically close helpers (probably
# in a different function called pick_best() or something)? What if we
# picked a helper that hasn't been used recently for this relay?
#
# Ideally we should see if the helper is online. It should be easy to
# do since the list has a copy of the stem controller. Just see if
# there's a descriptor for the given fingerprint.
random.shuffle(self.helpers)
for helper in self.helpers:
with self._reachability_lock:
if self._should_perform_reachability_test():
self._perform_reachability_test()
assert not self._should_perform_reachability_test()
random.shuffle(self._usable_helpers)
for helper in self._usable_helpers:
if helper.fingerprint in blacklist:
continue
return helper
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment