Commit 1102cbd5 authored by Matt Traudt's avatar Matt Traudt
Browse files

Use stem's new circuit timeout feature in 1.6.0-dev

Not currently properly checking that stem is a new enough version.
It needs to be 'git clone'd and installed manually ...

Added in [0] and small fix in [1]

[0]: https://gitweb.torproject.org/stem.git/commit/stem?id=8a065f42952c9dc9762eaebe4d3fa48210783998
[1]: https://gitweb.torproject.org/stem.git/commit/stem?id=60f034ad8b9c3aa48e7e2ecb0a2e159b6ed5bc71
parent 66cb0be3
Loading
Loading
Loading
Loading
+7 −80
Original line number Diff line number Diff line
from stem import CircuitExtensionFailed, InvalidRequest, ProtocolError
from stem import CircuitExtensionFailed, InvalidRequest, ProtocolError, Timeout
from stem import InvalidArguments
from stem.control import CircStatus, EventType
import random
@@ -48,6 +48,7 @@ class CircuitBuilder:
        self.relay_list = RelayList(args, conf, self.controller)
        self.built_circuits = set()
        self.close_circuits_on_exit = close_circuits_on_exit
        self.circuit_timeout = conf.getint('general', 'circuit_timeout')

    @property
    def relays(self):
@@ -82,93 +83,19 @@ class CircuitBuilder:
            raise PathLengthException()
        c = self.controller
        assert stem_utils.is_controller_okay(c)
        timeout = self.circuit_timeout
        fp_path = '[' + ' -> '.join([p[0:8] for p in path]) + ']'
        log.debug('Building %s', fp_path)
        ignore_events = [CircStatus.LAUNCHED, CircStatus.EXTENDED]
        event_queue = queue.Queue(maxsize=3)

        def event_listener(event):
            event_queue.put(event)

        stem_utils.add_event_listener(c, event_listener, EventType.CIRC)
        for _ in range(0, 3):
            circ_built = False
            # The time we started trying to build the circuit. An offset of
            # this is when we know we should give up
            start_time = time.time()
            try:
                # To work around ORCONNs not timing out for a long time, we
                # need to NOT let stem block on the circuit to be built, but do
                # the blocking ourselves.
                circ_id = c.new_circuit(path, await_build=False)
                while True:
                    try:
                        # If the timeout is hit, this will throw queue.Empty.
                        # Otherwise it will return the event
                        circ = event_queue.get(block=True, timeout=1)
                    except queue.Empty:
                        circ = None
                        # This is the common place to allow ourselves to give
                        # up. We will usually not get an event at roughly the
                        # same time we want to give up
                        if start_time + 10 < time.time():
                            log.warning(
                                'Could not build circ %s %s fast enough. '
                                'Giving up on it.', circ_id, fp_path)
                            break
                        else:
                            # If it isn't time to give up, we need to skip the
                            # remaining part of this loop because we don't have
                            # a **circ** event.
                            continue
                    # We have a **circ** event! Make sure it is for the circuit
                    # we are waiting on
                    if circ.id != circ_id:
                        pass
                    # Make sure it isn't an uninterseting event
                    elif circ.status in ignore_events:
                        pass
                    # This is the good case! We are finally done
                    elif circ.status == CircStatus.BUILT:
                        circ_built = True
                        break
                    # This is a bad case. We are done, but the circuit doesn't
                    # exist
                    elif circ.status == CircStatus.FAILED:
                        break
                    # This is a bad case. We are done, but the circuit doesn't
                    # exist
                    elif circ.status == CircStatus.CLOSED:
                        break
                    # This is a case we weren't expecting
                    else:
                        log.warning(
                            'We weren\'t expecting to get a circ status '
                            'of %s when waiting for circ %s %s to build. '
                            'Ignoring.',
                            circ.status, circ_id, fp_path)
                        pass
                    # If we made it this far, we always want to check if we
                    # should give up and return the build status that we've
                    # determined
                    if start_time + 10 < time.time():
                        log.warning(
                            'We\'ve waited long enough for circ %s %s. %s',
                            circ_id, fp_path, 'It became built at the last '
                            'second.' if circ_built else 'It was never '
                            'built.')
                        break
                circ_id = c.new_circuit(
                    path, await_build=True, timeout=timeout)
            except (InvalidRequest, CircuitExtensionFailed,
                    ProtocolError) as e:
                    ProtocolError, Timeout) as e:
                log.warning(e)
                continue
            if circ_built:
                stem_utils.remove_event_listener(c, event_listener)
                self.built_circuits.add(circ_id)
                return circ_id
            else:
                self.close_circuit(circ_id)
        stem_utils.remove_event_listener(c, event_listener)
                return circ_id
        return None

    def __del__(self):
+8 −9
Original line number Diff line number Diff line
@@ -114,17 +114,16 @@ def _init_controller_socket(socket):

def launch_tor(conf):
    assert isinstance(conf, ConfigParser)
    section = conf['tor']
    os.makedirs(section['datadir'], mode=0o700, exist_ok=True)
    os.makedirs(conf['tor']['datadir'], mode=0o700, exist_ok=True)
    # Bare minimum things, more or less
    torrc = copy.deepcopy(TORRC_STARTING_POINT)
    # Very important and/or common settings that we don't know until runtime
    torrc.update({
        'DataDirectory': section['datadir'],
        'PidFile': os.path.join(section['datadir'], 'tor.pid'),
        'ControlSocket': section['control_socket'],
        'DataDirectory': conf['tor']['datadir'],
        'PidFile': os.path.join(conf['tor']['datadir'], 'tor.pid'),
        'ControlSocket': conf['tor']['control_socket'],
        'Log': [
            'NOTICE file {}'.format(section['log']),
            'NOTICE file {}'.format(conf['tor']['log']),
        ],
        # Things needed to make circuits fail a little faster. We get the
        # circuit_timeout as a string instead of an int on purpose: stem only
@@ -147,7 +146,7 @@ def launch_tor(conf):
    #     extra_lines =
    #         Log debug file /tmp/tor-debug.log
    #         NumCPUs 1
    for line in section['extra_lines'].split('\n'):
    for line in conf['tor']['extra_lines'].split('\n'):
        # Remove leading and trailing whitespace, if any
        line = line.strip()
        # Ignore blank lines
@@ -183,13 +182,13 @@ def launch_tor(conf):
    stem.process.launch_tor_with_config(
        torrc, init_msg_handler=log.debug, take_ownership=True)
    # And return a controller to it
    cont = _init_controller_socket(section['control_socket'])
    cont = _init_controller_socket(conf['tor']['control_socket'])
    assert is_controller_okay(cont)
    # Because we build things by hand and can't set these before Tor bootstraps
    cont.set_conf('__DisablePredictedCircuits', '1')
    cont.set_conf('__LeaveStreamsUnattached', '1')
    log.info('Started and connected to Tor %s via %s', cont.get_version(),
             section['control_socket'])
             conf['tor']['control_socket'])
    return cont