[tor-commits] [ooni-probe/master] * Refactored bridget completely.

isis at torproject.org isis at torproject.org
Thu Oct 4 14:41:15 UTC 2012


commit 6f52ec0c73671e5b1c417cc1b7a0d63266cd8b9c
Author: Isis Lovecruft <isis at torproject.org>
Date:   Wed Sep 19 02:04:55 2012 +0000

    * Refactored bridget completely.
    * Broke up most of experiment() into tiny functions.
    * Added custom exceptions and error handlers.
    * Added more commandline option type enforcers, with custom handlers.
    * Function to drop any privileges if using pluggable transports.
    * The generated torrc now includes a Bridge line written to the file
      descriptor, if using bridges.
    * The Bridge line properly handles pluggable transports.
    TODO: Still need to fix the CustomCircuit class and the attacher.
---
 ooni/plugins/bridget.py |  652 ++++++++++++++++++++++++++---------------------
 ooni/utils/circuit.py   |   50 ++--
 2 files changed, 380 insertions(+), 322 deletions(-)

diff --git a/ooni/plugins/bridget.py b/ooni/plugins/bridget.py
index f22741b..1ed4957 100644
--- a/ooni/plugins/bridget.py
+++ b/ooni/plugins/bridget.py
@@ -3,77 +3,163 @@
 # 
 #  +-----------+
 #  |  BRIDGET  |
-#  |        +----------------------------------------------+
-#  +--------| Use a slave Tor process to test making a Tor |
-#           | connection to a list of bridges or relays.   |
-#           +----------------------------------------------+
+#  |        +--------------------------------------------+
+#  +--------| Use a Tor process to test making a Tor     |
+#           | connection to a list of bridges or relays. |
+#           +--------------------------------------------+
 #
-# :authors: Arturo Filasto, Isis Lovecruft
+# :authors: Isis Lovecruft, Arturo Filasto
 # :licence: see included LICENSE
 # :version: 0.1.0-alpha
 
-from __future__             import with_statement
-from functools              import wraps
-from os                     import getcwd
-from os.path                import isfile
-from os.path                import join as pj
-from twisted.python         import usage
-from twisted.plugin         import IPlugin
-from twisted.internet       import defer, error, reactor
-from zope.interface         import implements
+from __future__                 import with_statement
+from functools                  import wraps, partial
+from twisted.python             import usage
+from twisted.plugin             import IPlugin
+from twisted.internet           import defer, error, reactor
+from twisted.internet.endpoints import TCP4ClientEndpoint
+from zope.interface             import implements
 
+from ooni.utils                 import log
+from ooni.plugoo.tests          import ITest, OONITest
+from ooni.plugoo.assets         import Asset
+
+import tempfile
+import os
 import random
+import shutil
 import signal
 import sys
 
-from ooni.utils             import log
-from ooni.plugoo.tests      import ITest, OONITest
-from ooni.plugoo.assets     import Asset
-
-
-def portCheck(number):
-    number = int(number)
-    if number not in range(1024, 65535):
-        raise ValueError("Port out of range")
-
-portCheckAllowed     = "must be between 1024 and 65535."
-sockCheck, ctrlCheck = portCheck, portCheck
-sockCheck.coerceDoc  = "Port to use for Tor's SocksPort, "   + portCheckAllowed
-ctrlCheck.coerceDoc  = "Port to use for Tor's ControlPort, " + portCheckAllowed
-
+def timer(secs, e=None):
+    def decorator(func):
+        def _timer(signum, frame):
+            raise TimeoutError, e
+        def wrapper(*args, **kwargs):
+            signal.signal(signal.SIGALRM, _timer)
+            signal.alarm(secs)
+            try:
+                res = func(*args, **kwargs)
+            finally:
+                signal.alarm(0)
+            return res
+        return wraps(func)(wrapper)
+    return decorator
+
+
+class MissingAssetException(Exception):
+    """Raised when neither there are neither bridges nor relays to test."""
+    def __init__(self):
+        log.msg("Bridget can't run without bridges or relays to test!")
+        return sys.exit()
+
+class PTNoBridgesException(Exception):
+    """Raised when a pluggable transport is specified, but no bridges."""
+    def __init__(self):
+        log.msg("Pluggable transport requires the bridges option")
+        return sys.exit()
+
+class PTNotFoundException(Exception):
+    def __init__(self, transport_type):
+        m  = "Pluggable Transport type %s was unaccounted " % transport_type
+        m += "for, please contact isis(at)torproject(dot)org and it will "
+        m += "get included."
+        log.msg("%s" % m)
+        return sys.exit()
+
+class ValueChecker(object):
+    def port_check(self, number):
+        """Check that given ports are in the allowed range."""
+        number = int(number)
+        if number not in range(1024, 65535):
+            raise ValueError("Port out of range")
+
+    sock_check, ctrl_check = port_check, port_check
+    port_check_allowed     = "must be between 1024 and 65535."
+    sock_check.coerceDoc   = "Port to use for Tor's SocksPort, "   + port_check_allowed
+    ctrl_check.coerceDoc   = "Port to use for Tor's ControlPort, " + port_check_allowed
+
+    def uid_check(pluggable_transport):
+        """Check that we are not root when trying to use pluggable transports."""
+        uid, gid = os.getuid(), os.getgid()
+        if uid == 0 and gid == 0:
+            log.msg("Error: Running bridget as root with --transport=%s not allowed."
+                    % pluggable_transport)
+            log.msg("Dropping privileges to normal user...")
+            os.setgid(1000)
+            os.setuid(1000)
+
+    def dir_check(d):
+        """Check that the given directory exists."""
+        if not os.isdir(d):
+            raise ValueError("%s doesn't exist, or has wrong permissions" % d)
+
+    def file_check(f):
+        if not os.isfile(f):
+            raise ValueError("%s does not exist, or has wrong permissions" % f)
+
+class RandomPortException(Exception):
+    """Raised when using a random port conflicts with configured ports."""
+    def __init__(self):
+        log.msg("Unable to use random and specific ports simultaneously")
+        return sys.exit()
+
+class TimeoutError(Exception):
+    """Raised when a timer runs out."""
+    pass
+
+class TxtorconImportError(ImportError):
+    """Raised when /ooni/lib/txtorcon cannot be imported from."""
+    cwd, tx = os.getcwd(), 'lib/txtorcon/torconfig.py'
+    try:
+        log.msg("Unable to import from ooni.lib.txtorcon")
+        if cwd.endswith('ooni'):
+            check = os.path.join(cwd, tx)
+        else:
+            check = os.path.join(cwd, 'ooni/'+tx)
+        assert isfile(check)
+    except:
+        log.msg("Error: Some OONI libraries are missing!")
+        log.msg("Please go to /ooni/lib/ and do \"make all\"")
 
 class BridgetArgs(usage.Options):
+    """Commandline options."""
+    vc = ValueChecker()
+
     optParameters = [
         ['bridges', 'b', None,
          'File listing bridge IP:ORPorts to test'],
         ['relays', 'f', None,
          'File listing relay IPs to test'],
-        ['socks', 's', 9049, None, portCheck],
-        ['control', 'c', 9052, None, portCheck],
+        ['socks', 's', 9049, None, vc.sock_check],
+        ['control', 'c', 9052, None, vc.ctrl_check],
         ['torpath', 'p', None,
          'Path to the Tor binary to use'],
         ['datadir', 'd', None,
          'Tor DataDirectory to use'],
-        ['transport', 't', None, 
+        ['transport', 't', None,
          'Tor ClientTransportPlugin'],
         ['resume', 'r', 0,
          'Resume at this index']]
-    optFlags = [
-        ['random', 'x', 'Use random ControlPort and SocksPort']]
+    optFlags = [['random', 'x', 'Use random ControlPort and SocksPort']]
 
     def postOptions(self):
-        if self['transport'] and not self['bridges']:
-            e = "Pluggable transport requires the bridges option"
-            raise usage.UsageError, e
-        if self['socks'] and self['control']:
+        if not self['bridges'] and not self['relays']:
+            raise MissingAssetException
+        if self['transport']:
+            vc.uid_check(self['transport'])
+            if not self['bridges']:
+                raise PTNoBridgesException
+        if self['socks'] or self['control']:
             if self['random']:
-                e = "Unable to use random and specific ports simultaneously"
-                raise usage.usageError, e
+                raise RandomPortException
+        if self['datadir']:
+            vc.dir_check(self['datadir'])
+        if self['torpath']:
+            vc.file_check(self['torpath'])
 
 class BridgetAsset(Asset):
-    """
-    Class for parsing bridget Assets ignoring commented out lines.
-    """
+    """Class for parsing bridget Assets ignoring commented out lines."""
     def __init__(self, file=None):
         self = Asset.__init__(self, file)
 
@@ -89,17 +175,15 @@ class BridgetTest(OONITest):
 
     :ivar config: 
         An :class:`ooni.lib.txtorcon.TorConfig` instance.
-    :ivar relay_list:
-        A list of all provided relays to test. We have to do this because
-        txtorcon.TorState().entry_guards won't build a custom circuit if the
-        first hop isn't in the torrc's EntryNodes.
-    :ivar bridge_list:
+    :ivar relays:
+        A list of all provided relays to test.
+    :ivar bridges:
         A list of all provided bridges to test.
     :ivar socks_port:
         Integer for Tor's SocksPort.
     :ivar control_port:
         Integer for Tor's ControlPort.
-    :ivar plug_transport:
+    :ivar transport:
         String defining the Tor's ClientTransportPlugin, for testing 
         a bridge's pluggable transport functionality.
     :ivar tor_binary:
@@ -116,99 +200,83 @@ class BridgetTest(OONITest):
     def initialize(self):
         """
         Extra initialization steps. We only want one child Tor process
-        running, so we need to deal with the creation of TorConfig() only
-        once, before the experiment runs.
+        running, so we need to deal with most of the TorConfig() only once,
+        before the experiment runs.
         """
         self.socks_port      = 9049
         self.control_port    = 9052
+        self.circuit_timeout = 90
         self.tor_binary      = '/usr/sbin/tor'
         self.data_directory  = None
-        self.circuit_timeout = 90
+        self.use_pt          = False
+        self.pt_type         = None
+
+        self.bridges, self.bridges_up, self.bridges_down = ([] for i in range(3))
+        self.bridges_remaining  = lambda: len(self.bridges)
+        self.bridges_down_count = lambda: len(self.bridges_down)
+        self.current_bridge     = None
+
+        self.relays, self.relays_up, self.relays_down = ([] for i in range(3))
+        self.relays_remaining   = lambda: len(self.relays)
+        self.relays_down_count  = lambda: len(self.relays_down)
+        self.current_relay      = None
+
+        def make_asset_list(opt, lst):
+            log.msg("Loading information from %s ..." % opt)
+            with open(opt) as opt_file:
+                for line in opt_file.readlines():
+                    if line.startswith('#'):
+                        continue
+                    else:
+                        lst.append(line.replace('\n',''))
 
         if self.local_options:
-            options = self.local_options
-
-            if not options['bridges'] and not options['relays']:
-                self.suicide = True
-                return
-
             try:
                 from ooni.lib.txtorcon import TorConfig
             except ImportError:
-                log.msg ("Bridget: Unable to import from ooni.lib.txtorcon")
-                wd, tx = getcwd(), 'lib/txtorcon/torconfig.py'
-                chk = pj(wd,tx) if wd.endswith('ooni') else pj(wd,'ooni/'+tx)
-                try:
-                    assert isfile(chk)
-                except:
-                    log.msg("Error: Some OONI libraries are missing!")
-                    log.msg("Please go to /ooni/lib/ and do \"make all\"")
+                raise TxtorconImportError
 
-            self.config = TorConfig()
+            options = self.local_options
+            config  = self.config = TorConfig()
 
             if options['bridges']:
                 self.config.UseBridges = 1
-                
+                make_asset_list(options['bridges'], self.bridges)
             if options['relays']:
-                ## Stupid hack for testing only relays:
-                ## Tor doesn't use EntryNodes when UseBridges is enabled, but
-                ## config.state.entry_guards needs to include the first hop to
-                ## build a custom circuit.
+                ## first hop must be in TorState().entry_guards to build circuits
                 self.config.EntryNodes = ','.join(relay_list)
-
+                make_asset_list(options['relays'], self.relays)
             if options['socks']:
                 self.socks_port = options['socks']
-                
             if options['control']:
                 self.control_port = options['control']
-
             if options['random']:
                 log.msg("Using randomized ControlPort and SocksPort ...")
                 self.socks_port   = random.randint(1024, 2**16)
                 self.control_port = random.randint(1024, 2**16)
-
             if options['torpath']:
                 self.tor_binary = options['torpath']
 
-            if options['datadir']:
-                self.config.DataDirectory = options['datadir']
+            if self.local_options['datadir']:
+                self.data_directory = local_options['datadir']
+            else:
+                self.data_directory = None
 
             if options['transport']:
-                ## ClientTransportPlugin transport socks4|socks5 IP:PORT
+                self.use_pt = True
+                log.msg("Using ClientTransportPlugin %s" % options['transport'])
+                [self.pt_type, pt_exec] = options['transport'].split(' ', 1)
+
                 ## ClientTransportPlugin transport exec path-to-binary [options]
-                if not options['bridges']:
-                    e = "You must use the bridge option to test a transport."
-                    raise usage.UsageError("%s" % e)
+                ## XXX we need a better way to deal with all PTs
+                if self.pt_type == "obfs2":
+                    config.ClientTransportPlugin = self.pt_type + " " + pt_exec
                 else:
-                    ## XXX fixme there's got to be a better way to check the exec
-                    ##
-                    ## we could use:
-                    ##    os.setgid( NEW_GID )
-                    ##    os.setuid( NEW_UID )
-                    ## to drop any and all privileges
-                    assert type(options['transport']) is str
-                    [self.pt_type, 
-                     self.pt_exec] = options['transport'].split(' ', 1)
-                    log.msg("Using ClientTransportPlugin %s %s" 
-                            % (self.pt_type, self.pt_exec))
-                    self.pt_use = True
-
-                    ## XXX fixme we need a better way to deal with all PTs
-                    if self.pt_type == "obfs2":
-                        self.ctp = self.pt_type +" "+ self.pt_exec
-                    else:
-                        m  = "Pluggable Transport type %s was " % self.pt_type
-                        m += "unaccounted for, please contact isis (at) "
-                        m += "torproject (dot) org, with info and it'll get "
-                        m += "included."
-                        log.msg("%s" % m)
-                        self.ctp = None
-            else:
-                self.pt_use = False
+                    raise PTNotFoundException
 
-            self.config.SocksPort   = self.socks_port
-            self.config.ControlPort = self.control_port
-            self.config.save()
+            config.SocksPort                 = self.socks_port
+            config.ControlPort               = self.control_port
+            config.CookieAuthentication      = 1
 
     def load_assets(self):
         """
@@ -216,32 +284,14 @@ class BridgetTest(OONITest):
         should be given in the form IP:ORport. We don't want to load these as
         assets, because it's inefficient to start a Tor process for each one.
         """
-        assets           = {}
-        self.bridge_list = []
-        self.relay_list  = []
-
-        ## XXX fix me
-        ## we should probably find a more memory nice way to load addresses,
-        ## in case the files are really large
+        assets = {}
         if self.local_options:
-
-            def make_asset_list(opt, lst):
-                log.msg("Loading information from %s ..." % opt)
-                with open(opt) as opt_file:
-                    for line in opt_file.readlines():
-                        if line.startswith('#'):
-                            continue
-                        else:
-                            lst.append(line.replace('\n',''))
-
             if self.local_options['bridges']:
-                make_asset_list(self.local_options['bridges'], 
-                                self.bridge_list)
-                assets.update({'bridges': self.bridge_list})
+                assets.update({'bridge': 
+                               BridgetAsset(self.local_options['bridges'])})
             if self.local_options['relays']:
-                make_asset_list(self.local_options['relays'],
-                                self.relay_list)
-                assets.update({'relays': self.relay_list})
+                assets.update({'relay': 
+                               BridgetAsset(self.local_options['relays'])})
         return assets
 
     def experiment(self, args):
@@ -249,55 +299,69 @@ class BridgetTest(OONITest):
         XXX fill me in
 
         :param args:
-            The :class:`ooni.plugoo.asset.Asset <Asset>` line currently being
-            used.
-        :meth launch_tor:
-            Returns a Deferred which callbacks with a
-            :class:`ooni.lib.txtorcon.torproto.TorProcessProtocol
-            <TorProcessProtocol>` connected to the fully-bootstrapped Tor;
-            this has a :class:`ooni.lib.txtorcon.torcontol.TorControlProtocol
-            <TorControlProtocol>` instance as .protocol.
+            The :class:`BridgetAsset` line currently being used.
         """
         try:
-            from ooni.lib.txtorcon import CircuitListenerMixin, IStreamAttacher
-            from ooni.lib.txtorcon import TorProtocolFactory, TorConfig, TorState
-            from ooni.lib.txtorcon import DEFAULT_VALUE, launch_tor
+            from ooni.utils         import circuit
+            from ooni.lib.txtorcon import TorProcessProtocol
+            from ooni.lib.txtorcon import TorProtocolFactory
+            from ooni.lib.txtorcon import TorConfig, TorState
         except ImportError:
-            log.msg("Error: Unable to import from ooni.lib.txtorcon")
-            wd, tx = getcwd(), 'lib/txtorcon/torconfig.py'
-            chk = pj(wd,tx) if wd.endswith('ooni') else pj(wd,'ooni/'+tx)
-            try:
-                assert isfile(chk)
-            except:
-                log.msg("Error: Some OONI libraries are missing!")
-                log.msg("       Please go to /ooni/lib/ and do \"make all\"")
-                return sys.exit()
+            raise TxtorconImportError
+        except TxtorconImportError:
+            ## XXX is this something we should add to the reactor?
+            sys.exit()
 
         def bootstrap(ctrl):
             """
             Launch a Tor process with the TorConfig instance returned from
-            initialize().
+            initialize() and write_torrc().
             """
             conf = TorConfig(ctrl)
             conf.post_bootstrap.addCallback(setup_done).addErrback(setup_fail)
             log.msg("Tor process connected, bootstrapping ...")
 
-        def reconf_controller(conf, bridge):
-            ## if bridges and relays, use one bridge then build a circuit 
-            ## from three relays
-            conf.Bridge = ""
-            ## XXX do we need a SIGHUP to restart?                
+        def delete_temp(delete_list):
+            """
+            Given a list of files or directories to delete, delete all and 
+            suppress all errors.
+            """
+            for temp in delete_list:
+                try:
+                    os.unlink(temp)
+                except OSError:
+                    shutil.rmtree(temp, ignore_errors=True)
+
+        @timer(self.circuit_timeout)
+        def reconfigure_bridge(state, bridge, use_pt=False, pt_type=None):
+            """Rewrite the Bridge line in our torrc."""
+            log.msg("Current Bridge: %s" % bridge)                        
+            if use_pt is False:
+                new = state.protocol.set_conf('Bridge', bridge)
+            elif use_pt and pt_type is not None:
+                new = state.protocol.set_conf('Bridge', pt_type +' '+ bridge)
+            else:
+                raise PTNotFoundException
+            return new
 
-            ## XXX see txtorcon.TorControlProtocol.add_event_listener we
-            ## may not need full CustomCircuit class
+        def reconfigure_fail(*param):
+            log.msg("Reconfiguring TorConfig with parameters %s failed" % param)
+            reactor.stop()
 
-            ## if bridges only, try one bridge at a time, but don't build
-            ## circuits, just return
-            ## if relays only, build circuits from relays
+        def remove_relays(state, bridge_list):
+            """
+            Remove bridges from our bridge list which are also listed as
+            public relays.
+            """
+            ips = map(lambda addr: addr.split(':',1)[0], bridge_list)
+            both = set(state.relays.values()).intersection(ips)
+            if len(both) > 0:
+                for bridge in both:
+                    bridge_list.remove(bridge)
+            return state
 
-        def reconf_fail(args):
-            log.msg("Reconfiguring Tor config with args %s failed" % args)
-            reactor.stop()
+        def remove_relays_fail(state):
+            log.msg("Unable to remove public relays from the bridge list.")
 
         def setup_fail(args):
             log.msg("Setup Failed.")
@@ -310,152 +374,148 @@ class BridgetTest(OONITest):
             state.post_bootstrap.addCallback(state_complete).addErrback(setup_fail)
             report.update({'success': args})
 
+        def start_tor(reactor, update, torrc, to_delete, control_port, 
+                      tor_binary, data_directory):
+            """
+            Create a TCP4ClientEndpoint at our control_port, and connect
+            it to our reactor and a spawned Tor process. Compare with 
+            :meth:`txtorcon.launch_tor` for differences.
+            """
+            ## XXX do we need self.reactor?
+            end_point = TCP4ClientEndpoint(reactor, 'localhost', control_port)
+            connection_creator = partial(end_point.connect, TorProtocolFactory())
+            process_protocol = TorProcessProtocol(connection_creator, updates)
+            process_protocol.to_delete = to_delete
+            reactor.addSystemEventTrigger('before', 'shutdown', 
+                                          partial(delete_temp, to_delete))
+            try:
+                transport = reactor.spawnProcess(process_protocol, 
+                                                 tor_binary,
+                                                 args=(tor_binary,'-f',torrc),
+                                                 env={'HOME': data_directory},
+                                                 path=data_directory)
+                transport.closeStdin()
+            except RuntimeError, e:
+                process_protocol.connected_cb.errback(e)
+            finally:
+                return process_protocol.connected_cb
+
+        def state_complete(state):
+            """Called when we've got a TorState."""
+            log.msg("We've completely booted up a Tor version %s at PID %d" 
+                    % (state.protocol.version, state.tor_pid))
+
+            log.msg("This Tor has the following %d Circuits:" 
+                    % len(state.circuits))
+            for circ in state.circuits.values():
+                log.msg("%s" % circ)
+
+            return state
+
+        def state_attach(state, relay_list):
+            log.msg("Setting up custom circuit builder...")
+            attacher = CustomCircuit(state)
+            state.set_attacher(attacher, reactor)
+            state.add_circuit_listener(attacher)
+
+            for circ in state.circuits.values():
+                for relay in circ.path:
+                    try:
+                        relay_list.remove(relay)
+                    except KeyError:
+                        continue
+            ## XXX how do we attach to circuits with bridges?
+            d = defer.Deferred()
+            attacher.request_circuit_build(d)
+            return d
+
         def updates(prog, tag, summary):
             log.msg("%d%%: %s" % (prog, summary))
 
-        if not self.circuit_timeout:
-            self.circuit_timeout = 90
-            
-        class TimeoutError(Exception):
-            pass
-
-        def stupid_timer(secs, e=None):
-            def decorator(func):
-                def _timer(signum, frame):
-                    raise TimeoutError, e
-                def wrapper(*args, **kwargs):
-                    signal.signal(signal.SIGALRM, _timer)
-                    signal.alarm(secs)
-                    try:
-                        res = func(*args, **kwargs)
-                    finally:
-                        signal.alarm(0)
-                    return res
-                return wraps(func)(wrapper)
-            return decorator
+        def write_torrc(conf, data_dir=None):
+            """
+            Create a torrc in our data_directory. If we don't yet have a 
+            data_directory, create a temporary one. Any temporary files or
+            folders are added to delete_list.
+
+            :return: delete_list, data_dir, torrc
+            """
+            delete_list = []
+
+            if data_dir is None:
+                data_dir = tempfile.mkdtemp(prefix='bridget-tordata')
+                delete_list.append(data_dir)
+            conf.DataDirectory = data_dir
+            #conf.__OwningControllerProcess = os.getpid()
+
+            (fd, torrc) = tempfile.mkstemp(dir=data_dir)
+            delete_list.append(torrc)
+            os.write(fd, conf.create_torrc())
+            os.close(fd)
+            return torrc, data_dir, delete_list
         
-        @stupid_timer(self.circuit_timeout)
-        def _launch_tor_and_report_bridge_status(_config, 
-                                                 _reactor, 
-                                                 _updates, 
-                                                 _binary,
-                                                 _callback,
-                                                 _callback_arg,
-                                                 _errback):
-            """The grossest Python function you've ever seen."""
-
-            log.msg("Starting Tor ...")        
-            ## :return: a Deferred which callbacks with a TorProcessProtocol
-            ##          connected to the fully-bootstrapped Tor; this has a 
-            ##          txtorcon.TorControlProtocol instance as .protocol.
-            proc_proto = launch_tor(_config, _reactor, progress_updates=_updates, 
-                                    tor_binary=_binary).addCallback(_callback,
-                                                                    _callback_arg).addErrback(_errback)
-            return proc_proto
-                    ## now build circuits
-
-
-
-        if len(args) == 0:
-            log.msg("Bridget can't run without bridges or relays to test!")
-            log.msg("Exiting ...")
-            return sys.exit()
-        else:
-            log.msg("Bridget: initiating test ... ")
-            self.london_bridge          = []
-            self.working_bridge         = []
 
-            if len(self.bridge_list) >= 1:
-                self.untested_bridge_count  = len(self.bridge_list)
-                self.burnt_bridge_count     = len(self.london_bridge)
-                self.current_bridge         = None
+        log.msg("Bridget: initiating test ... ")
+
+        while self.bridges_remaining() > 0:
+            self.current_bridge = self.bridges.pop()
+            #current_ip = self.current_bridge.split(':', 1)[0]
+            #print "CURRENT BRIDGE IP %s" % current_ip
+
+            if not self.config.config.has_key('Bridge'):
+                self.config.Bridge = self.current_bridge
+                (torrc, self.data_directory, to_delete) = write_torrc(
+                    self.config, self.data_directory)
+        
+                log.msg("Starting Tor ...")        
+                log.msg("Using the following as our torrc:\n%s" 
+                        % self.config.create_torrc())
+                report = {'tor_config': self.config.create_torrc()}
+
+                state = start_tor(reactor, updates, torrc, to_delete,
+                                  self.control_port, self.tor_binary, 
+                                  self.data_directory)
+                state.addCallback(setup_done)
+                state.addErrback(setup_fail)
+                state.addCallback(remove_relays, self.bridges)
+                state.addErrback(remove_relays_fail)
+                state.addCallback(state_attach, self.bridges)
+                ## XXX write state_attach_fail()
+                state.addErrback(state_attach_fail)
 
-            ## while bridges are in the bucket
-            while self.config.UseBridges and self.untested_bridge_count > 0:
+            ## XXX see txtorcon.TorControlProtocol.add_event_listener we
+            ##     may not need full CustomCircuit class
+            ## o if bridges and relays, use one bridge then build a circuit 
+            ##   from three relays
+            ## o if bridges only, try one bridge at a time, but don't build
+            ##   circuits, just return
+            ## o if relays only, build circuits from relays
+            else:
+                log.msg("We now have %d untested bridges..." 
+                        % self.bridges_remaining())
                 try:
-                    ## if the user wanted to use pluggable transports
-                    assert self.pt_use is True
-                except AssertionError as no_use_pt:
-                    ## if the user didn't want to use pluggable transports
-                    log.msg("Configuring Bridge line without pluggable transport")
-                    log.msg("%s" % no_use_pt)
-                    self.pt_good = False
+                    state.addCallback(reconfigure_bridge, self.current_bridge, 
+                                      self.use_pt, self.pt_type)
+                    state.addErrback(reconfigure_fail)
+                except TimeoutError:
+                    log.msg("Adding %s to unreachable bridges..." 
+                            % self.current_bridge)
+                    self.bridges_down.append(self.current_bridge)
                 else:
-                    ## user wanted PT, and we recognized transport
-                    if self.ctp is not None:
-                        try:
-                            assert self.ctp is str
-                        except AssertionError as not_a_string:
-                            log.msg("Error: ClientTransportPlugin string unusable: %s" 
-                                    % not_a_string)
-                            log.msg("       Exiting ...")
-                            sys.exit()
-                        else:
-                            ## configure the transport
-                            self.config.ClientTransportPlugin = self.ctp
-                            self.pt_good = True
-                    ## user wanted PT, but we didn't recognize it
-                    else:
-                        log.msg("Error: Unable to use ClientTransportPlugin %s %s" 
-                                % (self.pt_type, self.pt_exec))
-                        log.msg("       Exiting...")
-                        sys.exit()
-                ## whether or not we're using a pluggable transport, we need
-                ## to set the Bridge line
-                finally:
-                    log.msg("We now have %d bridges in our list..." 
-                            % self.untested_bridge_count)
-                    self.current_bridge = self.bridge_list.pop()
-                    self.untested_bridge_count -= 1
-
-                    log.msg("Current Bridge: %s" % self.current_bridge)
-                    
-                    if self.pt_good:
-                        self.config.Bridge = self.pt_type +" "+ self.current_bridge
-                    else:
-                        self.config.Bridge = self.current_bridge
-                        
-                    log.msg("Using the following as our torrc:\n%s" 
-                            % self.config.create_torrc())
-                    report = {'tor_config': self.config.create_torrc()}
-                    self.config.save()
+                    log.msg("Adding %s to reachable bridges..." 
+                            % self.current_bridge)
+                    self.bridges_up.append(self.current_bridge)
 
-                    try:
-                        _launch_tor_and_report_bridge_status(self.config,
-                                                             reactor,
-                                                             updates,
-                                                             self.tor_binary,
-                                                             bootstrap,
-                                                             self.config,
-                                                             setup_fail)
-                    except TimeoutError:
-                        log.msg("Adding %s to bad bridges..." % self.current_bridge)
-                        self.london_bridge.append(self.current_bridge)
-                    else:
-                        log.msg("Adding %s to good bridges..." % self.current_bridge)
-                        self.working_bridge.append(self.current_bridge)
+        reactor.run()
+        ## now build circuits
 
-            d = defer.Deferred()
-            d.addCallback(log.msg, 'Working Bridges:\n%s\nUnreachable Bridges:\n%s\n'
-                          % (self.working_bridge, self.london_bridge))
-            return d
-
-        def control(self, exp_res):
-            exp_res
 
 ## So that getPlugins() can register the Test:
 bridget = BridgetTest(None, None, None)
 
 ## ISIS' NOTES
 ## -----------
-## self.config.save() only needs to be called if Tor is already running.
-## 
-## to test gid, uid, and euid:
-## with open('/proc/self/state') as uidfile:
-##     print uidfile.read(1000)
-##
 ## TODO:
-##       o  add option for any kwarg=arg self.config setting
 ##       o  cleanup documentation
 ##       x  add DataDirectory option
 ##       o  check if bridges are public relays
diff --git a/ooni/utils/circuit.py b/ooni/utils/circuit.py
index bb1ab11..0c77dd1 100644
--- a/ooni/utils/circuit.py
+++ b/ooni/utils/circuit.py
@@ -13,6 +13,9 @@
 from zope.interface    import implements
 
 from ooni.lib.txtorcon import CircuitListenerMixin, IStreamAttacher
+from ooni.utils        import log
+
+import random
 
 
 class CustomCircuit(CircuitListenerMixin):
@@ -28,7 +31,7 @@ class CustomCircuit(CircuitListenerMixin):
     def waiting_on(self, circuit):
         for (circid, d) in self.waiting_circuits:
             if circuit.id == circid:
-                return true
+                return True
         return False
 
     def circuit_extend(self, circuit, router):
@@ -36,8 +39,7 @@ class CustomCircuit(CircuitListenerMixin):
         if circuit.purpose != 'GENERAL':
             return
         if self.waiting_on(circuit):
-            log.msg("Circuit %d (%s)" 
-                    % (circuit.id, router.id_hex))
+            log.msg("Circuit %d (%s)" % (circuit.id, router.id_hex))
 
     def circuit_built(self, circuit):
         "ICircuitListener"
@@ -47,7 +49,7 @@ class CustomCircuit(CircuitListenerMixin):
         log.msg("Full path of %s: %s" % (circuit.id, circuit.path))
         for (circid, d) in self.waiting_circuits:
             if circid == circuit.id:
-                self.waiting_circuits.remove(circid, d)
+                self.waiting_circuits.remove((circid, d))
                 d.callback(circuit)
 
     def circuit_failed(self, circuit, reason):
@@ -57,7 +59,7 @@ class CustomCircuit(CircuitListenerMixin):
             circid, d = None, None
             for x in self.waiting_circuits:
                 if x[0] == circuit.id:
-                    circid, d, stream_cc = x
+                    circid, d = x
             if d is None:
                 raise Exception("Expected to find circuit.")
 
@@ -71,36 +73,32 @@ class CustomCircuit(CircuitListenerMixin):
             TorInfo.dump(self)
 
     def request_circuit_build(self, deferred):
-        entries = self.state.entry_guards.value()
-        relays  = self.state.routers.values()
-        log.msg("We have these nodes listed as entry guards:") 
-        log.msg("%s" % entries)
-        log.msg("We have these nodes listed as relays:")
-        log.msg("%s" % relays)
-        path = [random.choice(entries),
-                random.choice(relays),
-                random.choice(relays)]
+        if self.state.relays_remaining() > 0:
+            first, middle,last = (self.state.relays.pop()
+                                  for i in range(3))
+        else:
+            first = random.choice(self.state.entry_guards.values())
+            middle, last = (random.choice(self.state.routers.values())
+                            for i in range(2))
+        path = [first, middle, last]
+
         log.msg("Requesting a circuit: %s" 
-                % '-->'.join(map(lambda x: x.location.countrycode, 
-                                 path)))
+                % '->'.join(map(lambda node: node, path)))
 
         class AppendWaiting:
             def __init__(self, attacher, deferred):
                 self.attacher = attacher
                 self.d        = deferred
-
-            def __call__(self, circuit):
+            def __call__(self, circ):
                 """
                 Return from build_circuit is a Circuit, however,
                 we want to wait until it is built before we can
                 issue an attach on it and callback to the Deferred
                 we issue here.
                 """
-                log.msg("Circuit %s is in progress ..." % circuit.id)
-                self.attacher.waiting_circuits.append((circuit.id, 
-                                                       self.d))
-
-        fin = self.state.build_circuit(path)
-        fin.addCallback(AppendWaiting(self, deferred_to_callback))
-        fin.addErrback(log.err)
-        return fin
+                log.msg("Circuit %s is in progress ..." % circ.id)
+                self.attacher.waiting_circuits.append((circ.id, self.d))
+
+        return self.state.build_circuit(path).addCallback(
+            AppendWaiting(self, deferred_to_callback)).addErrback(
+            log.err)





More information about the tor-commits mailing list