[tor-commits] [ooni-probe/master] Implementing bridge failover in ooniprobe (#540)

art at torproject.org art at torproject.org
Sun Jul 10 20:22:57 UTC 2016


commit 970cb5191f77b1da0d656e6441d77d42d53afb95
Author: Arturo Filastò <arturo at filasto.net>
Date:   Wed Jun 29 16:38:09 2016 +0200

    Implementing bridge failover in ooniprobe (#540)
    
    * Implementing bridge failover in ooniprobe
    
    We support failing over to obfs4 and meek when vanilla tor does not work.
    
    * This implements #538
    
    * Reset the DataDirectory when we the data_dir is not set
    
    Otherwise txtorcon will delete the datadirectory after it shutsdown and not
    re-create it.
---
 data/ooniprobe.conf.sample  |   4 +
 ooni/__init__.py            |   2 -
 ooni/constants.py           |  76 ++++++++++++++++++
 ooni/director.py            |  58 ++-----------
 ooni/oonicli.py             |   5 +-
 ooni/report/cli.py          |   4 +-
 ooni/report/tool.py         |   4 +-
 ooni/tests/test_director.py |   4 +-
 ooni/tests/test_onion.py    |  45 ++++++++++-
 ooni/utils/onion.py         | 192 +++++++++++++++++++++++++++++++++++++++++++-
 10 files changed, 326 insertions(+), 68 deletions(-)

diff --git a/data/ooniprobe.conf.sample b/data/ooniprobe.conf.sample
index fec1026..be35364 100644
--- a/data/ooniprobe.conf.sample
+++ b/data/ooniprobe.conf.sample
@@ -58,6 +58,10 @@ tor:
     # This should be set to something to avoid having Tor download each time
     # the descriptors and consensus data.
     #data_dir: ~/.tor/
+    #
+    # This is the timeout after which we consider to to not have
+    # bootstrapped properly.
+    timeout: 200
     torrc:
         #HTTPProxy: host:port
         #HTTPProxyAuthenticator: user:password
diff --git a/ooni/__init__.py b/ooni/__init__.py
index 5280b1e..6f6e78f 100644
--- a/ooni/__init__.py
+++ b/ooni/__init__.py
@@ -5,8 +5,6 @@ __version__ = "1.5.2.dev1"
 # This is the version number of resources to be downloaded
 # when a release is made it should be aligned to __version__
 __resources_version__ = "1.5.1"
-canonical_bouncer = 'httpo://nkvphnp3p6agi5qq.onion'
-
 
 __all__ = [
     'common',
diff --git a/ooni/constants.py b/ooni/constants.py
new file mode 100644
index 0000000..882edb4
--- /dev/null
+++ b/ooni/constants.py
@@ -0,0 +1,76 @@
+CANONICAL_BOUNCER_ONION = 'httpo://nkvphnp3p6agi5qq.onion'
+
+MEEK_BRIDGES = [
+    ("meek 0.0.2.0:2 B9E7141C594AF25699E0079C1F0146F409495296 "
+     "url=https://d2zfqthxsdq309.cloudfront.net/ front=a0.awsstatic.com"),
+    ("meek 0.0.2.0:3 A2C13B7DFCAB1CBF3A884B6EB99A98067AB6EF44 "
+     "url=https://az786092.vo.msecnd.net/ front=ajax.aspnetcdn.com")
+]
+
+# These are bridges taken from TBB
+OBFS4_BRIDGES = [
+    ("obfs4 154.35.22.10:41835 8FB9F4319E89E5C6223052AA525A192AFBC85D55 "
+     "cert=GGGS1TX4R81m3r0HBl79wKy1OtPPNR2CZUIrHjkRg65Vc2VR8fOyo64f9kmT1UAFG7j0HQ iat-mode=0"),
+
+    ("obfs4 198.245.60.50:443 752CF7825B3B9EA6A98C83AC41F7099D67007EA5 "
+     "cert=xpmQtKUqQ/6v5X7ijgYE/f03+l2/EuQ1dexjyUhh16wQlu"
+     "/cpXUGalmhDIlhuiQPNEKmKw iat-mode=0"),
+
+    ("obfs4 192.99.11.54:443 7B126FAB960E5AC6A629C729434FF84FB5074EC2 "
+     "cert=VW5f8+IBUWpPFxF+rsiVy2wXkyTQG7vEd"
+     "+rHeN2jV5LIDNu8wMNEOqZXPwHdwMVEBdqXEw iat-mode=0"),
+
+    ("obfs4 109.105.109.165:10527 8DFCD8FB3285E855F5A55EDDA35696C743ABFC4E "
+     "cert=Bvg/itxeL4TWKLP6N1MaQzSOC6tcRIBv6q57DYAZc3b2AzuM"
+     "+/TfB7mqTFEfXILCjEwzVA iat-mode=0"),
+
+    ("obfs4 83.212.101.3:41213 A09D536DD1752D542E1FBB3C9CE4449D51298239 "
+     "cert=lPRQ/MXdD1t5SRZ9MquYQNT9m5DV757jtdXdlePmRCudUU9CFUOX1Tm7"
+     "/meFSyPOsud7Cw iat-mode=0"),
+
+    ("obfs4 104.131.108.182:56880 EF577C30B9F788B0E1801CF7E433B3B77792B77A "
+     "cert=0SFhfDQrKjUJP8Qq6wrwSICEPf3Vl"
+     "/nJRsYxWbg3QRoSqhl2EB78MPS2lQxbXY4EW1wwXA iat-mode=0"),
+
+    ("obfs4 109.105.109.147:13764 BBB28DF0F201E706BE564EFE690FE9577DD8386D "
+     "cert=KfMQN/tNMFdda61hMgpiMI7pbwU1T+wxjTulYnfw"
+     "+4sgvG0zSH7N7fwT10BI8MUdAD7iJA iat-mode=0"),
+
+    ("obfs4 154.35.22.11:49868 A832D176ECD5C7C6B58825AE22FC4C90FA249637 "
+     "cert=YPbQqXPiqTUBfjGFLpm9JYEFTBvnzEJDKJxXG5Sxzrr"
+     "/v2qrhGU4Jls9lHjLAhqpXaEfZw iat-mode=0"),
+
+    ("obfs4 154.35.22.12:80 00DC6C4FA49A65BD1472993CF6730D54F11E0DBB "
+      "cert=N86E9hKXXXVz6G7w2z8wFfhIDztDAzZ"
+     "/3poxVePHEYjbKDWzjkRDccFMAnhK75fc65pYSg iat-mode=0"),
+
+    ("obfs4 154.35.22.13:443 FE7840FE1E21FE0A0639ED176EDA00A3ECA1E34D "
+      "cert=fKnzxr+m+jWXXQGCaXe4f2gGoPXMzbL+bTBbXMYXuK0tMotd"
+     "+nXyS33y2mONZWU29l81CA iat-mode=0"),
+
+    ("obfs4 154.35.22.10:80 8FB9F4319E89E5C6223052AA525A192AFBC85D55 "
+      "cert=GGGS1TX4R81m3r0HBl79wKy1OtPPNR2CZUIrHjkRg65Vc2VR8fOyo64f9kmT1UAFG7j0HQ iat-mode=0"),
+
+    ("obfs4 154.35.22.10:443 8FB9F4319E89E5C6223052AA525A192AFBC85D55 "
+      "cert=GGGS1TX4R81m3r0HBl79wKy1OtPPNR2CZUIrHjkRg65Vc2VR8fOyo64f9kmT1UAFG7j0HQ iat-mode=0"),
+
+    ("obfs4 154.35.22.11:443 A832D176ECD5C7C6B58825AE22FC4C90FA249637 "
+     "cert=YPbQqXPiqTUBfjGFLpm9JYEFTBvnzEJDKJxXG5Sxzrr"
+     "/v2qrhGU4Jls9lHjLAhqpXaEfZw iat-mode=0"),
+
+    ("obfs4 154.35.22.11:80 A832D176ECD5C7C6B58825AE22FC4C90FA249637 "
+     "cert=YPbQqXPiqTUBfjGFLpm9JYEFTBvnzEJDKJxXG5Sxzrr"
+     "/v2qrhGU4Jls9lHjLAhqpXaEfZw iat-mode=0"),
+
+    ("obfs4 154.35.22.9:60873 C73ADBAC8ADFDBF0FC0F3F4E8091C0107D093716 "
+     "cert=gEGKc5WN/bSjFa6UkG9hOcft1tuK"
+     "+cV8hbZ0H6cqXiMPLqSbCh2Q3PHe5OOr6oMVORhoJA iat-mode=0"),
+
+    ("obfs4 154.35.22.9:80 C73ADBAC8ADFDBF0FC0F3F4E8091C0107D093716 "
+     "cert=gEGKc5WN/bSjFa6UkG9hOcft1tuK"
+     "+cV8hbZ0H6cqXiMPLqSbCh2Q3PHe5OOr6oMVORhoJA iat-mode=0"),
+
+    ("obfs4 154.35.22.9:443 C73ADBAC8ADFDBF0FC0F3F4E8091C0107D093716 "
+     "cert=gEGKc5WN/bSjFa6UkG9hOcft1tuK"
+     "+cV8hbZ0H6cqXiMPLqSbCh2Q3PHe5OOr6oMVORhoJA iat-mode=0")
+]
diff --git a/ooni/director.py b/ooni/director.py
index e6f864e..02b6743 100644
--- a/ooni/director.py
+++ b/ooni/director.py
@@ -7,13 +7,11 @@ from ooni.utils import log, generate_filename
 from ooni.utils.net import randomFreePort
 from ooni.nettest import NetTest, getNetTestInformation
 from ooni.settings import config
-from ooni import errors
 from ooni.nettest import test_class_name_to_name
 
-from txtorcon import TorConfig, TorState, launch_tor, build_tor_connection
+from ooni.utils.onion import start_tor, connect_to_control_port
 
-from twisted.internet import defer, reactor
-from twisted.internet.endpoints import TCP4ClientEndpoint
+from twisted.internet import defer
 
 
 class Director(object):
@@ -133,8 +131,7 @@ class Director(object):
             if config.advanced.start_tor and config.tor_state is None:
                 yield self.startTor()
             elif config.tor.control_port and config.tor_state is None:
-                log.msg("Connecting to Tor Control Port...")
-                yield self.getTorState()
+                yield connect_to_control_port()
 
         if config.global_options['no-geoip']:
             aux = [False]
@@ -299,11 +296,6 @@ class Director(object):
         config.scapyFactory.registerProtocol(sniffer)
         log.msg("Starting packet capture to: %s" % filename_pcap)
 
-    @defer.inlineCallbacks
-    def getTorState(self):
-        connection = TCP4ClientEndpoint(reactor, '127.0.0.1',
-                                        config.tor.control_port)
-        config.tor_state = yield build_tor_connection(connection)
 
     def startTor(self):
         """ Starts Tor
@@ -312,37 +304,7 @@ class Director(object):
         """
         log.msg("Starting Tor...")
 
-        @defer.inlineCallbacks
-        def state_complete(state):
-            config.tor_state = state
-            log.msg("Successfully bootstrapped Tor")
-            log.debug("We now have the following circuits: ")
-            for circuit in state.circuits.values():
-                log.debug(" * %s" % circuit)
-
-            socks_port = yield state.protocol.get_conf("SocksPort")
-            control_port = yield state.protocol.get_conf("ControlPort")
-
-            config.tor.socks_port = int(socks_port.values()[0])
-            config.tor.control_port = int(control_port.values()[0])
-
-        def setup_failed(failure):
-            log.exception(failure)
-            raise errors.UnableToStartTor
-
-        def setup_complete(proto):
-            """
-            Called when we read from stdout that Tor has reached 100%.
-            """
-            log.debug("Building a TorState")
-            config.tor.protocol = proto
-            state = TorState(proto.tor_protocol)
-            state.post_bootstrap.addCallback(state_complete)
-            state.post_bootstrap.addErrback(setup_failed)
-            return state.post_bootstrap
-
-        def updates(prog, tag, summary):
-            log.msg("%d%%: %s" % (prog, summary))
+        from txtorcon import TorConfig
 
         tor_config = TorConfig()
         if config.tor.control_port is None:
@@ -388,14 +350,4 @@ class Director(object):
         tor_config.save()
         log.debug("Setting control port as %s" % tor_config.ControlPort)
         log.debug("Setting SOCKS port as %s" % tor_config.SocksPort)
-
-        if config.advanced.tor_binary:
-            d = launch_tor(tor_config, reactor,
-                           tor_binary=config.advanced.tor_binary,
-                           progress_updates=updates)
-        else:
-            d = launch_tor(tor_config, reactor,
-                           progress_updates=updates)
-        d.addCallback(setup_complete)
-        d.addErrback(setup_failed)
-        return d
+        return start_tor(tor_config)
diff --git a/ooni/oonicli.py b/ooni/oonicli.py
index 56b331d..74bf9ac 100644
--- a/ooni/oonicli.py
+++ b/ooni/oonicli.py
@@ -10,7 +10,8 @@ import urlparse
 from twisted.python import usage
 from twisted.internet import defer
 
-from ooni import errors, __version__, canonical_bouncer
+from ooni import errors, __version__
+from ooni.constants import CANONICAL_BOUNCER_ONION
 from ooni.settings import config
 from ooni.utils import log
 from backend_client import CollectorClient
@@ -44,7 +45,7 @@ class Options(usage.Options):
         ["collector", "c", None, "Specify the address of the collector for "
                                  "test results. In most cases a user will "
                                  "prefer to specify a bouncer over this."],
-        ["bouncer", "b", canonical_bouncer, "Specify the bouncer used to "
+        ["bouncer", "b", CANONICAL_BOUNCER_ONION, "Specify the bouncer used to "
                                             "obtain the address of the "
                                             "collector and test helpers."],
         ["logfile", "l", None, "Write to this logs to this filename."],
diff --git a/ooni/report/cli.py b/ooni/report/cli.py
index 12ea83c..485ab52 100644
--- a/ooni/report/cli.py
+++ b/ooni/report/cli.py
@@ -3,7 +3,7 @@ from __future__ import print_function
 import os
 import sys
 
-from ooni import canonical_bouncer
+from ooni.constants import CANONICAL_BOUNCER_ONION
 from ooni.report import __version__
 from ooni.report import tool
 from ooni.settings import config
@@ -73,7 +73,7 @@ def run(args=sys.argv[1:]):
     config.read_config_file()
 
     if options['default-collector']:
-        options['bouncer'] = canonical_bouncer
+        options['bouncer'] = CANONICAL_BOUNCER_ONION
 
     if options['command'] == "upload" and options['report_file']:
         tor_check()
diff --git a/ooni/report/tool.py b/ooni/report/tool.py
index faa407f..f8af132 100644
--- a/ooni/report/tool.py
+++ b/ooni/report/tool.py
@@ -4,7 +4,7 @@ import sys
 
 from twisted.internet import defer
 
-from ooni import canonical_bouncer
+from ooni.constants import CANONICAL_BOUNCER_ONION
 from ooni.reporter import OONIBReporter, OONIBReportLog
 
 from ooni.utils import log
@@ -62,7 +62,7 @@ def upload(report_file, collector=None, bouncer=None):
             log.msg("Could not find %s in reporting.yaml. Looking up "
                     "collector with canonical bouncer." % report_file)
             collector_client = yield lookup_collector_client(report.header,
-                                                             canonical_bouncer)
+                                                             CANONICAL_BOUNCER_ONION)
 
     oonib_reporter = OONIBReporter(report.header, collector_client)
     log.msg("Creating report for %s with %s" % (report_file,
diff --git a/ooni/tests/test_director.py b/ooni/tests/test_director.py
index d2c4c82..0cc740a 100644
--- a/ooni/tests/test_director.py
+++ b/ooni/tests/test_director.py
@@ -68,8 +68,8 @@ class TestDirector(ConfigTestCase):
         assert 'http_header_field_manipulation' in nettests
         assert 'traceroute' in nettests
 
-    @patch('ooni.director.TorState', mock_TorState)
-    @patch('ooni.director.launch_tor', mock_launch_tor)
+    @patch('ooni.utils.onion.TorState', mock_TorState)
+    @patch('ooni.utils.onion.launch_tor', mock_launch_tor)
     def test_start_tor(self):
         @defer.inlineCallbacks
         def director_start_tor():
diff --git a/ooni/tests/test_onion.py b/ooni/tests/test_onion.py
index 944f8f5..732614f 100644
--- a/ooni/tests/test_onion.py
+++ b/ooni/tests/test_onion.py
@@ -1,6 +1,9 @@
+from twisted.internet import defer
 from twisted.trial import unittest
+
 from ooni.utils import onion
 from mock import Mock, patch
+from txtorcon.interface import ITorControlProtocol
 
 sample_transport_lines = {
     'fte': 'fte exec /fakebin --managed',
@@ -10,14 +13,28 @@ sample_transport_lines = {
     'obfs4': 'obfs4 exec /fakebin --enableLogging=true --logLevel=INFO' }
 
 
+class MockTorState(object):
+    def __init__(self):
+        self.protocol = Mock()
+        self.protocol.get_state = lambda x: 8080
+        self.protocol.post_bootstrap = defer.succeed(self)
+
+class MockSuccessTorProtocol(object):
+    def __init__(self):
+        self.tor_protocol = Mock(ITorControlProtocol)
+        self.tor_protocol.post_bootstrap = defer.succeed(MockTorState())
+
 class TestOnion(unittest.TestCase):
     def test_tor_details(self):
         assert isinstance(onion.tor_details, dict)
         assert onion.tor_details['version']
         assert onion.tor_details['binary']
+
     def test_transport_dicts(self):
-        self.assertEqual( set(onion.transport_bin_name.keys()),
-                          set(onion._transport_line_templates.keys()) )
+
+        self.assertEqual(set(onion.transport_bin_name.keys()),
+                         set(onion._transport_line_templates.keys()))
+
     def test_bridge_line(self):
         self.assertRaises(onion.UnrecognizedTransport,
             onion.bridge_line, 'rot13', '/log.txt')
@@ -64,3 +81,27 @@ class TestOnion(unittest.TestCase):
 
         self.assertEqual(onion.is_onion_address(
             'http://thirteenchars123.com'), False)
+
+    def test_launcher_fail_once(self):
+        from ooni.utils.onion import TorLauncherWithRetries
+        from txtorcon import TorConfig
+        tor_config = TorConfig()
+        tor_launcher = TorLauncherWithRetries(tor_config)
+
+        self.failures = 0
+        def _launch_tor_fail_once():
+            self.failures += 1
+            if self.failures <= 1:
+                return defer.fail(Exception("Failed once"))
+            return defer.succeed(MockSuccessTorProtocol())
+
+        def _mock_setup_complete(protocol):
+            self.assertIsInstance(protocol, MockSuccessTorProtocol)
+            self.assertTrue(
+                tor_launcher.tor_config.ClientTransportPlugin.startswith("obfs4")
+            )
+            tor_launcher.started.callback(None)
+
+        tor_launcher._launch_tor = _launch_tor_fail_once
+        tor_launcher._setup_complete = _mock_setup_complete
+        return tor_launcher.launch()
diff --git a/ooni/utils/onion.py b/ooni/utils/onion.py
index fea75c2..e18a6ee 100644
--- a/ooni/utils/onion.py
+++ b/ooni/utils/onion.py
@@ -1,16 +1,28 @@
+import os
 import re
 import string
+import StringIO
 import subprocess
 from distutils.spawn import find_executable
 from distutils.version import LooseVersion
 
+from twisted.internet import reactor, defer
+from twisted.internet.endpoints import TCP4ClientEndpoint
+
+from txtorcon import TorConfig, TorState, launch_tor, build_tor_connection
 from txtorcon.util import find_tor_binary as tx_find_tor_binary
 
+from ooni import constants
+from ooni import errors
+from ooni.utils import log
 from ooni.settings import config
 
 ONION_ADDRESS_REGEXP = re.compile("^((httpo|http|https)://)?"
                                   "[a-z0-9]{16}\.onion")
 
+TBB_PT_PATHS = ("/Applications/TorBrowser.app/Contents/MacOS/Tor"
+                "/PluggableTransports/",)
+
 class TorVersion(LooseVersion):
     pass
 
@@ -64,14 +76,22 @@ def transport_name(address):
     transport_name_chars = string.ascii_letters + string.digits
     if all(c in transport_name_chars for c in transport_name):
         return transport_name
-    else:
-        return None
+    return None
 
 
 def is_onion_address(address):
     return ONION_ADDRESS_REGEXP.match(address) != None
 
 
+def find_pt_executable(name):
+    bin_loc = find_executable(name)
+    if bin_loc:
+        return bin_loc
+    for path in TBB_PT_PATHS:
+        bin_loc = os.path.join(path, name)
+        if os.path.isfile(bin_loc):
+            return bin_loc
+    return None
 
 tor_details = {
     'binary': find_tor_binary(),
@@ -107,7 +127,9 @@ _transport_line_templates = {
         _pyobfsproxy_line('obfs3', bin_loc, log_file),
 
     'obfs4': lambda bin_loc, log_file: \
-        "obfs4 exec %s --enableLogging=true --logLevel=INFO" % bin_loc }
+        "obfs4 exec %s --enableLogging=true --logLevel=INFO" % bin_loc,
+
+}
 
 class UnrecognizedTransport(Exception):
     pass
@@ -139,3 +161,167 @@ def bridge_line(transport, log_file):
         raise OutdatedTor
 
     return _transport_line_templates[transport](bin_loc, log_file)
+
+pt_config = {
+    'meek': [
+        {
+            'executable': 'obfs4proxy',
+            'minimum_version': '0.0.6',
+            'version_parse': lambda x: x.split('-')[1],
+            'client_transport_line': 'meek exec {bin_loc}'
+        },
+        {
+            'executable': 'meek-client',
+            'minimum_version': None,
+            'client_transport_line': 'meek exec {bin_loc}'
+        }
+    ],
+
+    'obfs4': [
+        {
+            'executable': 'obfs4proxy',
+            'minimum_version': None,
+            'client_transport_line': 'obfs4 exec {bin_loc}'
+        }
+    ]
+
+}
+
+def get_client_transport(transport):
+    """
+
+    :param transport:
+    :return: client_transport_line
+    """
+
+    try:
+        pts = pt_config[transport]
+    except KeyError:
+        raise UnrecognizedTransport
+
+    for pt in pts:
+        bin_loc = find_pt_executable(pt['executable'])
+        if bin_loc is None:
+            continue
+        if pt['minimum_version'] is not None:
+            pt_version = executable_version(bin_loc, pt['version_parse'])
+            if (pt_version is None or
+                        pt_version < LooseVersion(pt['minimum_version'])):
+                continue
+        return pt['client_transport_line'].format(bin_loc=bin_loc)
+
+    raise UninstalledTransport
+
+
+class TorLauncherWithRetries(object):
+    def __init__(self, tor_config, timeout=config.tor.timeout):
+        self.retry_with = ["obfs4", "meek"]
+        self.started = defer.Deferred()
+        self.tor_output = StringIO.StringIO()
+        self.tor_config = tor_config
+        if timeout is None:
+            # XXX we will want to move setting the default inside of the
+            # config object.
+            timeout = 200
+        self.timeout = timeout
+
+    def _reset_tor_config(self):
+        """
+        This is used to reset the Tor configuration to before launch_tor
+        modified it. This is in particular used to force the regeneration of the
+        DataDirectory.
+        """
+        new_tor_config = TorConfig()
+        for key in self.tor_config:
+            if config.tor.data_dir is None and key == "DataDirectory":
+                continue
+            setattr(new_tor_config, key, getattr(self.tor_config, key))
+        self.tor_config = new_tor_config
+
+    def _progress_updates(self, prog, tag, summary):
+        log.msg("%d%%: %s" % (prog, summary))
+
+    @defer.inlineCallbacks
+    def _state_complete(self, state):
+        config.tor_state = state
+        log.msg("Successfully bootstrapped Tor")
+        log.debug("We now have the following circuits: ")
+        for circuit in state.circuits.values():
+            log.debug(" * %s" % circuit)
+
+        socks_port = yield state.protocol.get_conf("SocksPort")
+        control_port = yield state.protocol.get_conf("ControlPort")
+
+        config.tor.socks_port = int(socks_port.values()[0])
+        config.tor.control_port = int(control_port.values()[0])
+        self.started.callback(state)
+
+    def _setup_failed(self, failure):
+        self.tor_output.seek(0)
+        map(log.debug, self.tor_output.readlines())
+        self.tor_output.seek(0)
+
+        if len(self.retry_with) == 0:
+            self.started.errback(errors.UnableToStartTor())
+            return
+
+        while len(self.retry_with) > 0:
+            self._reset_tor_config()
+            self.tor_config.UseBridges = 1
+            transport = self.retry_with.pop(0)
+            log.msg("Failed to start Tor. Retrying with {0}".format(transport))
+
+            try:
+                bridge_lines = getattr(constants,
+                                       '{0}_BRIDGES'.format(transport).upper())
+            except AttributeError:
+                continue
+
+            try:
+                self.tor_config.ClientTransportPlugin = get_client_transport(transport)
+            except UninstalledTransport:
+                log.err("Pluggable transport {0} is not installed".format(
+                    transport))
+                continue
+            except UnrecognizedTransport:
+                log.err("Unrecognized transport type")
+                continue
+
+            self.tor_config.Bridge = bridge_lines
+            self.launch()
+            break
+
+    def _setup_complete(self, proto):
+        """
+        Called when we read from stdout that Tor has reached 100%.
+        """
+        log.debug("Building a TorState")
+        config.tor.protocol = proto
+        state = TorState(proto.tor_protocol)
+        state.post_bootstrap.addCallbacks(self._state_complete,
+                                          self._setup_failed)
+
+    def _launch_tor(self):
+        return launch_tor(self.tor_config, reactor,
+                          tor_binary=config.advanced.tor_binary,
+                          progress_updates=self._progress_updates,
+                          stdout=self.tor_output,
+                          timeout=self.timeout,
+                          stderr=self.tor_output)
+
+    def launch(self):
+        self._launched = self._launch_tor()
+        self._launched.addCallbacks(self._setup_complete, self._setup_failed)
+        return self.started
+
+
+def start_tor(tor_config):
+    tor_launcher = TorLauncherWithRetries(tor_config)
+    return tor_launcher.launch()
+
+
+ at defer.inlineCallbacks
+def connect_to_control_port():
+    connection = TCP4ClientEndpoint(reactor, '127.0.0.1',
+                                    config.tor.control_port)
+    config.tor_state = yield build_tor_connection(connection)





More information about the tor-commits mailing list