[Author Prev][Author Next][Thread Prev][Thread Next][Author Index][Thread Index]
[tor-commits] [ooni-probe/master] Implementing bridge failover in ooniprobe (#540)
commit 970cb5191f77b1da0d656e6441d77d42d53afb95
Author: Arturo Filastò <arturo@xxxxxxxxxxx>
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()
+
+
+@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)
_______________________________________________
tor-commits mailing list
tor-commits@xxxxxxxxxxxxxxxxxxxx
https://lists.torproject.org/cgi-bin/mailman/listinfo/tor-commits