[tor-commits] [ooni-probe/master] * Added documentation.

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


commit fa731c54c4c28764a345523ca3ab632e81fad884
Author: Isis Lovecruft <isis at torproject.org>
Date:   Sun Sep 23 12:58:21 2012 +0000

    * Added documentation.
    * Moved general setup, circuit, and bootstrap, to utils/onion.py.
---
 ooni/plugins/bridget.py |  323 +++++++++++++++++++----------------------------
 ooni/utils/circuit.py   |  109 ----------------
 ooni/utils/onion.py     |  260 ++++++++++++++++++++++++++++++++++++++
 3 files changed, 389 insertions(+), 303 deletions(-)

diff --git a/ooni/plugins/bridget.py b/ooni/plugins/bridget.py
index d82f2e7..c4667e1 100644
--- a/ooni/plugins/bridget.py
+++ b/ooni/plugins/bridget.py
@@ -13,7 +13,6 @@
 # :version: 0.1.0-alpha
 
 from __future__                 import with_statement
-from functools                  import wraps, partial
 from random                     import randint
 from twisted.python             import usage
 from twisted.plugin             import IPlugin
@@ -25,11 +24,12 @@ from ooni.plugoo.tests          import ITest, OONITest
 from ooni.plugoo.assets         import Asset
 
 import os
-import signal
 import sys
 
 
 def timer(secs, e=None):
+    import signal
+    import functools.wraps
     def decorator(func):
         def _timer(signum, frame):
             raise TimeoutError, e
@@ -41,7 +41,7 @@ def timer(secs, e=None):
             finally:
                 signal.alarm(0)
             return res
-        return wraps(func)(wrapper)
+        return functools.wraps(func)(wrapper)
     return decorator
 
 
@@ -297,27 +297,60 @@ class BridgetTest(OONITest):
 
     def experiment(self, args):
         """
-        XXX fill me in
+        We cannot use the Asset model, because that model calls
+        self.experiment() with the current Assets, which would be one relay
+        and one bridge, then it gives the defer.Deferred returned from
+        self.experiment() to self.control(), which means that, for each
+        (bridge, relay) pair, experiment gets called again, which instantiates
+        an additional Tor process that attempts to bind to the same
+        ports. Thus, additionally instantiated Tor processes return with
+        RuntimeErrors, which break the final defer.chainDeferred.callback(),
+        sending it into the errback chain.
+    
+            if bridges:
+                1. configure first bridge line
+                2a. configure data_dir, if it doesn't exist
+                2b. write torrc to a tempfile in data_dir
+                3. start tor                              } if any of these
+                4. remove bridges which are public relays } fail, add current
+                5. SIGHUP for each bridge                 } bridge to unreach-
+                                                          } able bridges.
+            if relays:
+                1a. configure the data_dir, if it doesn't exist
+                1b. write torrc to a tempfile in data_dir
+                2. start tor
+                3. remove any of our relays which are already part of current 
+                   circuits
+                4a. attach CustomCircuit() to self.state
+                4b. RELAY_EXTEND for each relay } if this fails, add
+                                                } current relay to list
+                                                } of unreachable relays
+                5. 
+            if bridges and relays:
+                1. configure first bridge line
+                2a. configure data_dir if it doesn't exist
+                2b. write torrc to a tempfile in data_dir
+                3. start tor
+                4. remove bridges which are public relays
+                5. remove any of our relays which are already part of current
+                   circuits
+                6a. attach CustomCircuit() to self.state
+                6b. for each bridge, build three circuits, with three
+                    relays each
+                6c. RELAY_EXTEND for each relay } if this fails, add
+                                                } current relay to list
+                                                } of unreachable relays
 
         :param args:
             The :class:`BridgetAsset` line currently being used.
         """
         try:
-            from tempfile import mkstemp, mkdtemp
-            from shutil   import rmtree
-
-            from twisted.internet.endpoints import TCP4ClientEndpoint
-
-            from ooni.utils        import circuit
-            from ooni.lib.txtorcon import TorProcessProtocol
-            from ooni.lib.txtorcon import TorProtocolFactory
+            from ooni.utils.onion  import start_tor, CustomCircuit
             from ooni.lib.txtorcon import TorConfig, TorState
-
         except ImportError:
             raise TxtorconImportError
-
-        except TxtorconImportError:
-            ## XXX is this something we should add to the reactor?
+        except TxtorconImportError, tie:
+            log.err(tie)
             sys.exit()
 
         def bootstrap(ctrl):
@@ -329,18 +362,6 @@ class BridgetTest(OONITest):
             conf.post_bootstrap.addCallback(setup_done).addErrback(setup_fail)
             log.msg("Tor process connected, bootstrapping ...")
 
-        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:
-                    rmtree(temp, ignore_errors=True)
-
-        #@timer(self.circuit_timeout)
         @defer.inlineCallbacks
         def reconfigure_bridge(state, bridge, use_pt=False, pt_type=None):
             """
@@ -352,10 +373,11 @@ class BridgetTest(OONITest):
             log.msg("Current Bridge: %s" % bridge)
             try:
                 if use_pt is False:
-                    reset_tor = yield state.protocol.set_conf('Bridge', bridge)
-                elif use_pt and pt_type is not None:
                     reset_tor = yield state.protocol.set_conf('Bridge', 
-                                                          pt_type +' '+ bridge)
+                                                              bridge)
+                elif use_pt and pt_type is not None:
+                    reset_tor = yield state.protocol.set_conf(
+                        'Bridge', pt_type +' '+ bridge)
                 else:
                     raise PTNotFoundException
 
@@ -368,16 +390,17 @@ class BridgetTest(OONITest):
                 if controller_response == 'OK':
                     defer.returnValue(state.callback)
                 else:
-                    log.msg("TorControlProtocol responded with error:\n%s\n"
-                            % controller_response)
+                    log.msg("TorControlProtocol responded with error:\n%s",
+                            controller_response)
                     defer.returnValue(state.callback)
                 
             except Exception, e:
-                log.msg("Reconfiguring torrc with Bridge line %s failed:\n%s\n" 
-                        % (bridge, e))
+                log.msg("Reconfiguring torrc with Bridge line %s failed:\n%s",
+                        bridge, e)
 
         def reconfigure_fail(state, bridge, bad):
-            log.msg("Reconfiguring TorConfig with parameters %s failed" % state)
+            log.msg("Reconfiguring TorConfig with parameters %s failed", 
+                    state)
             bad.append(bridge)
 
         @defer.inlineCallbacks
@@ -392,104 +415,34 @@ class BridgetTest(OONITest):
             def __remove_line__(node, bridges=bridges):
                 for line in bridges:
                     if line.startswith(node):
-                        log.msg("Removing %s because it is a public relay" % node)
-                        bridges.remove(line)
+                        try:
+                            log.msg("Removing %s because it is a public relay",
+                                    node)
+                            bridges.remove(line)
+                        except ValueError, ve:
+                            log.err(ve)
 
             if len(both) > 0:
                 try:
-                    updated = yield map(lambda node: __remove_line__(node), both)
+                    updated = yield map(lambda node: 
+                                        __remove_line__(node), both)
                     if not updated:
                         ## XXX do these need to be state.callback?
                         defer.returnValue(state)
                     else:
                         defer.returnValue(state)
                 except Exception, e:
-                    log.msg("Unable to remove public relays from bridge list:\n%s"
-                            % both)
+                    log.msg("Removing public relays from bridge list failed:\n%s",
+                            both)
                     log.err(e)
-
-        def request_circuit_build(state, deferred, path=None):
-            if path is None:
-                if relays_remaining() > 0:
-                    first, middle,last = (state.relays.pop()
-                                          for i in range(3))
-                else:
-                    first = random.choice(state.entry_guards.values())
-                    middle, last = (random.choice(state.routers.values())
-                                    for i in range(2))
-                path = [first, middle, last]
-            else:
-                assert type(path) is list, "Circuit path must be a list of routers!"
-                assert len(path) >= 3, "Circuits must be at least three hops!"
-
-            log.msg("Requesting a circuit: %s"
-                    % '->'.join(map(lambda node: node, path)))
-
-            class AppendWaiting:
-                def __init__(self, attacher, deferred):
-                    self.attacher = attacher
-                    self.d        = deferred
-                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 ..." % circ.id)
-                    self.attacher.waiting_circuits.append((circ.id, self.d))
-
-            return self.state.build_circuit(path).addCallback(
-                AppendWaiting(self, deferred)).addErrback(
-                log.err)
+                except ValueError, ve:
+                    log.err(ve)
 
         def attacher_extend_circuit(attacher, deferred, router):
-            
-
-        def setup_fail(proto, bridge_list, relay_list):
-            log.err("Setup Failed: %s" % proto)
-            log.err("We were given bridge list:\n%s\nAnd our relay list:\n%s\n"
-                    % (bridge_list, relay_list))
-            report.update({'setup_fail': 'FAILED', 
-                           'proto': proto, 
-                           'bridge_list': bridge_list, 
-                           'relay_list': relay_list})
-            reactor.stop()
-
-        def setup_done(proto, bridge_list, relay_list):
-            log.msg("Setup Complete: %s" % proto)
-            state = TorState(proto.tor_protocol)
-            state.post_bootstrap.addCallback(state_complete).addErrback(setup_fail)
-            if bridge_list is not None:
-                state.post_bootstrap.addCallback(remove_public_relays, bridge_list)
-            if relay_list is not None:
-                raise NotImplemented
-            #report.update({'success': args})
-
-        def start_tor(reactor, update, torrc, to_delete, control_port, tor_binary, 
-                      data_directory, bridge_list=None, relay_list=None):
-            """
-            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.
-            """
-            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, bridge_list, relay_list
+            ## XXX todo write me
+            ## state.attacher.extend_circuit
+            raise NotImplemented
+            #attacher.extend_circuit
 
         def state_complete(state, bridge_list=None, relay_list=None):
             """Called when we've got a TorState."""
@@ -508,12 +461,14 @@ class BridgetTest(OONITest):
             else:
                 return state, None
 
-        def state_attach(state, relay_list):
+        def state_attach(state, path):
             log.msg("Setting up custom circuit builder...")
             attacher = CustomCircuit(state)
             state.set_attacher(attacher, reactor)
             state.add_circuit_listener(attacher)
+            return state
 
+            ## OLD
             #for circ in state.circuits.values():
             #    for relay in circ.path:
             #        try:
@@ -526,80 +481,47 @@ class BridgetTest(OONITest):
             return d
 
         def state_attach_fail(state):
-            log.msg("Attaching custom circuit builder failed.")
+            log.err("Attaching custom circuit builder failed: %s", state)
 
-        def updates(prog, tag, summary):
-            log.msg("%d%%: %s" % (prog, summary))
-
-        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 = mkdtemp(prefix='bridget-tordata')
-                delete_list.append(data_dir)
-            conf.DataDirectory = data_dir
-
-            (fd, torrc) = mkstemp(dir=data_dir)
-            delete_list.append(torrc)
-            os.write(fd, conf.create_torrc())
-            os.close(fd)
-            return torrc, data_dir, delete_list
-        
 
         log.msg("Bridget: initiating test ... ")
 
-        while self.bridges_remaining() > 0:
+        if self.bridges_remaining() > 0:
+            for self.current_bridge in self.bridges:
+                #self.current_bridge = bridge
+                
+                if not self.config.config.has_key('Bridge'):
 
-            self.current_bridge = self.bridges.pop()
-            #self.current_bridge = args['bridge']
+                    self.config.Bridge = self.current_bridge
 
-            try:
-                self.bridges.remove(self.current_bridge)
-            except ValueError, ve:
-                log.err(ve)
-                
-            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, 
-                                  bridge_list=self.bridges)
-                state.addCallback(setup_done)
-                state.addErrback(setup_fail)
-                state.addCallback(remove_public_relays, self.bridges)
+                    state = start_tor(reactor,
+                                      self.config,
+                                      self.control_port, 
+                                      self.tor_binary, 
+                                      self.data_directory)
+                    state.addCallback(remove_public_relays, self.bridges)
+
+                else:
+                    log.msg("We now have %d untested bridges..." 
+                            % self.bridges_remaining())
+                    reconf = defer.Deferred()
+                    reconf.addCallback(reconfigure_bridge, state,
+                                       self.current_bridge, self.use_pt,
+                                       self.pt_type)
+                    reconf.addErrback(reconfigure_fail, state,
+                                      self.current_bridge, self.bridges_down)
+                    state.chainDeferred(reconf)
+                    state.callback()
+                    #all = []
+                    #reconf = reconfigure_bridge(state, self.current_bridge,
+                    #                            self.use_pt, self.pt_type)
+                    #reconf.addCallback(reconfigure_done)
+                    #reconf.addErrback(reconfigure_fail)
+                    #state.DeferredList(all)
+
+            if self.relays_remaining() != 0:
+                state.chainDeferred()
 
-            else:
-                log.msg("We now have %d untested bridges..." 
-                        % self.bridges_remaining())
-                reconf = defer.Deferred()
-                reconf.addCallback(reconfigure_bridge, state,
-                                   self.current_bridge, self.use_pt,
-                                   self.pt_type)
-                reconf.addErrback(reconfigure_fail, state,
-                                  self.current_bridge, self.bridges_down)
-                state.chainDeferred(reconf)
-                #all = []
-                #reconf = reconfigure_bridge(state, self.current_bridge,
-                #                            self.use_pt, self.pt_type)
-                #reconf.addCallback(reconfigure_done)
-                #reconf.addErrback(reconfigure_fail)
-                #state.DeferredList(all)
 
             while self.relays_remaining() >= 3:
                 #path = list(self.relays.pop() for i in range(3))
@@ -609,10 +531,19 @@ class BridgetTest(OONITest):
                     for node in circ.path:
                         if node == self.current_relay:
                             self.relays_up.append(self.current_relay)
-                    try:
-                        ext = attacher_extend_circuit(state.attacher, circ, 
-                                                      self.current_relay)
-                        
+
+                    if len(circ.path) < 3:
+                        try:
+                            parameters = (state.attacher, circ, 
+                                          self.current_relay)
+                            ext = attacher_extend_circuit(parameters)
+                            ext.addCallback(attacher_extend_circuit_done, 
+                                            parameters)
+                        except Exception, e:
+                            log.msg("Extend circuit failed: %s %s" 
+                                    % (e, parameters))
+                    else:
+                        continue
 
             return state
         ## still need to attach attacher to state
@@ -647,6 +578,10 @@ bridget = BridgetTest(None, None, None)
 
 ## ISIS' NOTES
 ## -----------
+## nickm points out that the format operator '%', when used in log.LEVEL(),
+## forces string generation even when LEVEL is not used, increasing overhead;
+## we should do 'log.err("string with stuff %s", stuff)' instead.
+##
 ## TODO:
 ##       o  cleanup documentation
 ##       x  add DataDirectory option
diff --git a/ooni/utils/circuit.py b/ooni/utils/circuit.py
deleted file mode 100644
index d725879..0000000
--- a/ooni/utils/circuit.py
+++ /dev/null
@@ -1,109 +0,0 @@
-#
-# circuit.py
-# ----------
-# Utilities for working with Tor circuits.
-#
-# This code is largely taken from attach_streams_by_country.py in the txtorcon
-# documentation, and as such any and all credit should go to meejah. Minor
-# adjustments have been made to use OONI's logging system, and to build custom
-# circuits without actually attaching streams.
-#
-# :author: Meejah, Isis Lovecruft
-# :license: see included LICENSE file
-# :version: 0.1.0-alpha
-#
-
-import random
-
-from ooni.lib.txtorcon import CircuitListenerMixin, IStreamAttacher
-from ooni.lib.txtorcon import TorInfo
-from ooni.utils        import log
-from zope.interface    import implements
-
-
-class CustomCircuit(CircuitListenerMixin):
-    implements(IStreamAttacher)
-
-    def __init__(self, state, relays=None):
-        self.state = state
-        self.waiting_circuits = []
-        self.relays = relays
-
-    def waiting_on(self, circuit):
-        for (circid, d) in self.waiting_circuits:
-            if circuit.id == circid:
-                return True
-        return False
-
-    def circuit_extend(self, circuit, router):
-        "ICircuitListener"
-        if circuit.purpose != 'GENERAL':
-            return
-        if self.waiting_on(circuit):
-            log.msg("Circuit %d (%s)" % (circuit.id, router.id_hex))
-
-    def circuit_built(self, circuit):
-        "ICircuitListener"
-        if circuit.purpose != 'GENERAL':
-            return
-        log.msg("Circuit %s built ..." % circuit.id)
-        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))
-                d.callback(circuit)
-
-    def circuit_failed(self, circuit, reason):
-        if self.waiting_on(circuit):
-            log.msg("Circuit %s failed for reason %s" 
-                    % (circuit.id, reason))
-            circid, d = None, None
-            for c in self.waiting_circuits:
-                if c[0] == circuit.id:
-                    circid, d = c
-            if d is None:
-                raise Exception("Expected to find circuit.")
-
-            self.waiting_circuits.remove((circid, d))
-            log.msg("Trying to build a circuit for %s" % circid)
-            self.request_circuit_build(d)
-
-    def check_circuit_route(self, circuit, router):
-        if router in circuit.path:
-            #router.update() ## XXX can i use without args? no.
-            TorInfo.dump(self)
-
-    def request_circuit_build(self, deferred, path=None):
-        if path is None:
-            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]
-        else:
-            assert type(path) is list, "Circuit path must be a list of routers!"
-            assert len(path) >= 3, "Circuits must be at least three hops!"
-
-        log.msg("Requesting a circuit: %s" 
-                % '->'.join(map(lambda node: node, path)))
-
-        class AppendWaiting:
-            def __init__(self, attacher, deferred):
-                self.attacher = attacher
-                self.d        = deferred
-            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 ..." % circ.id)
-                self.attacher.waiting_circuits.append((circ.id, self.d))
-
-        return self.state.build_circuit(path).addCallback(
-            AppendWaiting(self, deferred)).addErrback(
-            log.err)
diff --git a/ooni/utils/onion.py b/ooni/utils/onion.py
new file mode 100644
index 0000000..76ec909
--- /dev/null
+++ b/ooni/utils/onion.py
@@ -0,0 +1,260 @@
+#
+# onion.py
+# ----------
+# Utilities for working with Tor.
+#
+# This code is largely taken from txtorcon and its documentation, and as such
+# any and all credit should go to Meejah. Minor adjustments have been made to
+# use OONI's logging system, and to build custom circuits without actually
+# attaching streams.
+#
+# :author: Meejah, Isis Lovecruft
+# :license: see included LICENSE file
+# :copyright: copyright (c) 2012 The Tor Project, Inc.
+# :version: 0.1.0-alpha
+#
+
+import random
+
+from ooni.lib.txtorcon import CircuitListenerMixin, IStreamAttacher
+from ooni.utils        import log
+from zope.interface    import implements
+
+
+def __setup_done__(proto):
+    log.msg("Setup Complete: %s" % proto)
+    state = TorState(proto.tor_protocol)
+    state.post_bootstrap.addCallback(state_complete).addErrback(__setup_fail__)
+
+def __setup_fail__(proto):
+    log.err("Setup Failed: %s" % proto)
+    report.update({'setup_fail': proto})
+    reactor.stop()
+
+def __updates__(_progress, _tag, _summary):
+    log.msg("%d%%: %s", _progress, _summary)
+
+def write_torrc(conf, data_dir=None):
+    """
+    Create a torrc in our data_dir. If we don't yet have a data_dir, create a
+    temporary one. Any temporary files or folders are added to delete_list.
+    
+    :return: torrc, data_dir, delete_list
+    """
+    try:
+        from os       import write, close
+        from tempfile import mkstemp, mkdtemp
+    except ImportError, ie:
+        log.err(ie)
+
+    delete_list = []
+    
+    if data_dir is None:
+        data_dir = mkdtemp(prefix='bridget-tordata')
+        delete_list.append(data_dir)
+    conf.DataDirectory = data_dir
+    
+    (fd, torrc) = mkstemp(dir=data_dir)
+    delete_list.append(torrc)
+    write(fd, conf.create_torrc())
+    close(fd)
+    return torrc, data_dir, delete_list
+
+def delete_files_or_dirs(delete_list):
+    """
+    Given a list of files or directories to delete, delete all and suppress
+    all errors.
+
+    :param delete_list:
+        A list of files or directories to delete.
+    """
+    try:
+        from os     import unlink
+        from shutil import rmtree
+    except ImportError, ie:
+        log.err(ie)
+
+    for temp in delete_list:
+        try:
+            unlink(temp)
+        except OSError:
+            rmtree(temp, ignore_errors=True)
+
+def start_tor(reactor, config, control_port, tor_binary, data_dir,
+              report=None, progress=__updates__, process_cb=__setup_done__,
+              process_eb=__setup_fail__):
+    """
+    Use a txtorcon.TorConfig() instance, config, to write a torrc to a
+    tempfile in our DataDirectory, data_dir. If data_dir is None, a temp
+    directory will be created. Finally, 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.
+
+    :param reactor:
+        An instance of class:`twisted.internet.reactor`.
+    :param config:
+        An instance of class:`txtorcon.TorConfig` with all torrc options
+        already configured. ivar:`config.ControlPort`,
+        ivar:`config.SocksPort`, ivar:`config.CookieAuthentication`, should
+        already be set, as well as ivar:`config.UseBridges` and
+        ivar:`config.Bridge` if bridges are to be used.
+        ivar:`txtorcon.DataDirectory` does not need to be set.
+    :param control_port:
+        The port number to use for Tor's ControlPort.
+    :param tor_binary:
+        The full path to the Tor binary to use.
+    :param data_dir:
+        The directory to use as Tor's DataDirectory.
+    :param report:
+        The class:`ooni.plugoo.reports.Report` instance to .update().
+    :param progress:
+        A non-blocking function to handle bootstrapping updates, which takes
+        three parameters: _progress, _tag, and _summary.
+    :param process_cb:
+        The function to callback to after 
+        class:`ooni.lib.txtorcon.TorProcessProtocol` returns with the fully
+        bootstrapped Tor process.
+    :param process_eb:
+        The function to errback to if 
+        class:`ooni.lib.txtorcon.TorProcessProtocol` fails.
+    :return:
+        A class:`ooni.lib.txtorcon.TorProcessProtocol` which callbacks with a
+        class:`txtorcon.TorControlProtocol` as .protocol.
+    """
+    try:
+        from functools                  import partial
+        from twisted.internet.endpoints import TCP4ClientEndpoint
+        from ooni.lib.txtorcon          import TorProtocolFactory
+        from ooni.lib.txtorcon          import TorProcessProtocol
+    except ImportError, ie:
+        log.err(ie)
+
+    ## TODO: add option to specify an already existing torrc, which
+    ##       will require prior parsing to enforce necessary lines
+    (torrc, data_dir, to_delete) = write_torrc(config, data_dir)
+
+    log.msg("Starting Tor ...")
+    log.msg("Using the following as our torrc:\n%s", config.create_torrc())
+    if report is None:
+        report = {'torrc': config.create_torrc()}
+    else:
+        report.update({'torrc': config.create_torrc()})
+
+    end_point = TCP4ClientEndpoint(reactor, 'localhost', control_port)
+    connection_creator = partial(end_point.connect, TorProtocolFactory())
+    process_protocol = TorProcessProtocol(connection_creator, progress)
+    process_protocol.to_delete = to_delete
+
+    reactor.addSystemEventTrigger('before', 'shutdown',
+                                  partial(delete_files_or_dirs, to_delete))
+    try:
+        transport = reactor.spawnProcess(process_protocol,
+                                         tor_binary,
+                                         args=(tor_binary,'-f',torrc),
+                                         env={'HOME': data_dir},
+                                         path=data_dir)
+        transport.closeStdin()
+    except RuntimeError, e:
+        log.err("Starting Tor failed: %s", e)
+        process_protocol.connected_cb.errback(e)
+    except NotImplementedError, e:
+        url = "http://starship.python.net/crew/mhammond/win32/Downloads.html"
+        log.err("Running bridget on Windows requires pywin32: %s", url)
+        process_protocol.connected_cb.errback(e)
+
+    proto = process_protocol.connected_cb     ## new defer.Deferred()
+    proto.addCallback(process_cb)
+    proto.addErrback(process_eb)
+    return proto
+    
+
+class CustomCircuit(CircuitListenerMixin):
+    implements(IStreamAttacher)
+
+    def __init__(self, state, relays=None):
+        self.state = state
+        self.waiting_circuits = []
+        self.relays = relays
+
+    def waiting_on(self, circuit):
+        for (circid, d) in self.waiting_circuits:
+            if circuit.id == circid:
+                return True
+        return False
+
+    def circuit_extend(self, circuit, router):
+        "ICircuitListener"
+        if circuit.purpose != 'GENERAL':
+            return
+        if self.waiting_on(circuit):
+            log.msg("Circuit %d (%s)", circuit.id, router.id_hex)
+
+    def circuit_built(self, circuit):
+        "ICircuitListener"
+        if circuit.purpose != 'GENERAL':
+            return
+        log.msg("Circuit %s built ...", circuit.id)
+        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))
+                d.callback(circuit)
+
+    def circuit_failed(self, circuit, reason):
+        if self.waiting_on(circuit):
+            log.msg("Circuit %s failed for reason %s", circuit.id, reason)
+            circid, d = None, None
+            for c in self.waiting_circuits:
+                if c[0] == circuit.id:
+                    circid, d = c
+            if d is None:
+                raise Exception("Expected to find circuit.")
+
+            self.waiting_circuits.remove((circid, d))
+            log.msg("Trying to build a circuit for %s", circid)
+            self.request_circuit_build(d)
+
+    def check_circuit_route(self, router):
+        """
+        Check if a relay is a hop in one of our already built circuits. 
+
+        """
+        for circ in self.state.circuits.values():
+            if router in circuit.path:
+            #router.update() ## XXX can i use without args? no.
+                TorInfo.dump(self)
+
+    def request_circuit_build(self, deferred, path=None):
+        if path is None:
+            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]
+        else:
+            assert type(path) is list, "Circuit path must be a list of relays!"
+            assert len(path) >= 3, "Circuits must be at least three hops!"
+
+        log.msg("Requesting a circuit: %s" 
+                % '->'.join(map(lambda node: node, path)))
+
+        class AppendWaiting:
+            def __init__(self, attacher, deferred):
+                self.attacher = attacher
+                self.d        = deferred
+            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 ...", circ.id)
+                self.attacher.waiting_circuits.append((circ.id, self.d))
+
+        return self.state.build_circuit(path).addCallback(
+            AppendWaiting(self, deferred)).addErrback(
+            log.err)





More information about the tor-commits mailing list