Merge branch 'new-txtorcon'

This commit is contained in:
Brian Warner 2017-05-25 01:36:49 -07:00
commit 48b1f0257b
16 changed files with 233 additions and 571 deletions

View File

@ -13,8 +13,6 @@ environment:
# http://www.appveyor.com/docs/installed-software#python # http://www.appveyor.com/docs/installed-software#python
- PYTHON: "C:\\Python27" - PYTHON: "C:\\Python27"
- PYTHON: "C:\\Python27-x64" - PYTHON: "C:\\Python27-x64"
- PYTHON: "C:\\Python33"
- PYTHON: "C:\\Python33-x64"
DISTUTILS_USE_SDK: "1" DISTUTILS_USE_SDK: "1"
- PYTHON: "C:\\Python34" - PYTHON: "C:\\Python34"
- PYTHON: "C:\\Python34-x64" - PYTHON: "C:\\Python34-x64"

View File

@ -21,4 +21,5 @@ after_success:
- codecov - codecov
matrix: matrix:
allow_failures: allow_failures:
- python: "3.3"
- python: "nightly" - python: "nightly"

View File

@ -66,8 +66,7 @@ a bug in pynacl which gets confused when the libsodium runtime is installed
(e.g. `libsodium13`) but not the development package. (e.g. `libsodium13`) but not the development package.
Developers can clone the source tree and run `tox` to run the unit tests on Developers can clone the source tree and run `tox` to run the unit tests on
all supported (and installed) versions of python: 2.7, 3.3, 3.4, 3.5, and all supported (and installed) versions of python: 2.7, 3.4, 3.5, and 3.6.
3.6.
## Motivation ## Motivation
@ -234,8 +233,8 @@ If this happens, run `pip install -e .[dev]` again.
This library is released under the MIT license, see LICENSE for details. This library is released under the MIT license, see LICENSE for details.
This library is compatible with python2.7, 3.3, 3.4, 3.5, and 3.6 . It This library is compatible with python2.7, 3.4, 3.5, and 3.6 . It is
is probably compatible with py2.6, but the latest Twisted (>=15.5.0) is probably compatible with py2.6, but the latest Twisted (>=15.5.0) is
not. not.

View File

@ -1,24 +1,9 @@
import sys
from setuptools import setup from setuptools import setup
import versioneer import versioneer
commands = versioneer.get_cmdclass() commands = versioneer.get_cmdclass()
DEV_REQUIREMENTS = [
"mock",
"tox",
"pyflakes",
]
if sys.version_info[0] < 3:
# txtorcon is not yet compatible with py3, so we include "txtorcon" in
# DEV_REQUIREMENTS under py2 but not under py3. The test suite will skip
# the tor tests when txtorcon is not importable. This results in
# different wheels when built under py2 vs py3 (with different
# extras_require[dev] dependencies), but I think this is ok, since nobody
# should be installing with [dev] from a wheel.
DEV_REQUIREMENTS.append("txtorcon")
setup(name="magic-wormhole", setup(name="magic-wormhole",
version=versioneer.get_version(), version=versioneer.get_version(),
description="Securely transfer data between computers", description="Securely transfer data between computers",
@ -53,8 +38,8 @@ setup(name="magic-wormhole",
], ],
extras_require={ extras_require={
':sys_platform=="win32"': ["pypiwin32"], ':sys_platform=="win32"': ["pypiwin32"],
"tor": ["txtorcon"], "tor": ["txtorcon >= 0.19.3"],
"dev": DEV_REQUIREMENTS, # includes txtorcon on py2, but not py3 "dev": ["mock", "tox", "pyflakes", "txtorcon >= 0.19.3"],
}, },
test_suite="wormhole.test", test_suite="wormhole.test",
cmdclass=commands, cmdclass=commands,

View File

@ -3,7 +3,7 @@ import re
import six import six
from zope.interface import implementer from zope.interface import implementer
from attr import attrs, attrib from attr import attrs, attrib
from attr.validators import provides, instance_of from attr.validators import provides, instance_of, optional
from twisted.python import log from twisted.python import log
from automat import MethodicalMachine from automat import MethodicalMachine
from . import _interfaces from . import _interfaces
@ -35,7 +35,7 @@ class Boss(object):
_versions = attrib(validator=instance_of(dict)) _versions = attrib(validator=instance_of(dict))
_reactor = attrib() _reactor = attrib()
_journal = attrib(validator=provides(_interfaces.IJournal)) _journal = attrib(validator=provides(_interfaces.IJournal))
_tor_manager = attrib() # TODO: ITorManager or None _tor = attrib(validator=optional(provides(_interfaces.ITorManager)))
_timing = attrib(validator=provides(_interfaces.ITiming)) _timing = attrib(validator=provides(_interfaces.ITiming))
m = MethodicalMachine() m = MethodicalMachine()
set_trace = getattr(m, "_setTrace", lambda self, f: None) set_trace = getattr(m, "_setTrace", lambda self, f: None)
@ -53,7 +53,7 @@ class Boss(object):
self._R = Receive(self._side, self._timing) self._R = Receive(self._side, self._timing)
self._RC = RendezvousConnector(self._url, self._appid, self._side, self._RC = RendezvousConnector(self._url, self._appid, self._side,
self._reactor, self._journal, self._reactor, self._journal,
self._tor_manager, self._timing) self._tor, self._timing)
self._L = Lister(self._timing) self._L = Lister(self._timing)
self._A = Allocator(self._timing) self._A = Allocator(self._timing)
self._I = Input(self._timing) self._I = Input(self._timing)

View File

@ -2,7 +2,7 @@ from __future__ import print_function, absolute_import, unicode_literals
import os import os
from six.moves.urllib_parse import urlparse from six.moves.urllib_parse import urlparse
from attr import attrs, attrib from attr import attrs, attrib
from attr.validators import provides, instance_of from attr.validators import provides, instance_of, optional
from zope.interface import implementer from zope.interface import implementer
from twisted.python import log from twisted.python import log
from twisted.internet import defer, endpoints from twisted.internet import defer, endpoints
@ -65,7 +65,7 @@ class RendezvousConnector(object):
_side = attrib(validator=instance_of(type(u""))) _side = attrib(validator=instance_of(type(u"")))
_reactor = attrib() _reactor = attrib()
_journal = attrib(validator=provides(_interfaces.IJournal)) _journal = attrib(validator=provides(_interfaces.IJournal))
_tor_manager = attrib() # TODO: ITorManager or None _tor = attrib(validator=optional(provides(_interfaces.ITorManager)))
_timing = attrib(validator=provides(_interfaces.ITiming)) _timing = attrib(validator=provides(_interfaces.ITiming))
def __attrs_post_init__(self): def __attrs_post_init__(self):
@ -86,8 +86,9 @@ class RendezvousConnector(object):
self._trace(old_state="", input=what, new_state="") self._trace(old_state="", input=what, new_state="")
def _make_endpoint(self, hostname, port): def _make_endpoint(self, hostname, port):
if self._tor_manager: if self._tor:
return self._tor_manager.get_endpoint_for(hostname, port) # TODO: when we enable TLS, maybe add tls=True here
return self._tor.stream_via(hostname, port)
return endpoints.HostnameEndpoint(self._reactor, hostname, port) return endpoints.HostnameEndpoint(self._reactor, hostname, port)
def wire(self, boss, nameplate, mailbox, allocator, lister, terminator): def wire(self, boss, nameplate, mailbox, allocator, lister, terminator):

View File

@ -7,7 +7,7 @@ from twisted.internet.defer import inlineCallbacks, returnValue
from twisted.python import log from twisted.python import log
from wormhole import create, input_with_completion, __version__ from wormhole import create, input_with_completion, __version__
from ..transit import TransitReceiver from ..transit import TransitReceiver
from ..errors import TransferError, WormholeClosedError, NoTorError from ..errors import TransferError, WormholeClosedError
from ..util import (dict_to_bytes, bytes_to_dict, bytes_to_hexstr, from ..util import (dict_to_bytes, bytes_to_dict, bytes_to_hexstr,
estimate_free_space) estimate_free_space)
from .welcome import handle_welcome from .welcome import handle_welcome
@ -45,7 +45,7 @@ class Receiver:
assert isinstance(args.relay_url, type(u"")) assert isinstance(args.relay_url, type(u""))
self.args = args self.args = args
self._reactor = reactor self._reactor = reactor
self._tor_manager = None self._tor = None
self._transit_receiver = None self._transit_receiver = None
def _msg(self, *args, **kwargs): def _msg(self, *args, **kwargs):
@ -55,29 +55,26 @@ class Receiver:
def go(self): def go(self):
if self.args.tor: if self.args.tor:
with self.args.timing.add("import", which="tor_manager"): with self.args.timing.add("import", which="tor_manager"):
from ..tor_manager import TorManager from ..tor_manager import get_tor
self._tor_manager = TorManager(self._reactor, # For now, block everything until Tor has started. Soon: launch
# tor in parallel with everything else, make sure the Tor object
# can lazy-provide an endpoint, and overlap the startup process
# with the user handing off the wormhole code
self._tor = yield get_tor(self._reactor,
self.args.launch_tor, self.args.launch_tor,
self.args.tor_control_port, self.args.tor_control_port,
timing=self.args.timing) timing=self.args.timing)
if not self._tor_manager.tor_available():
raise NoTorError()
# For now, block everything until Tor has started. Soon: launch
# tor in parallel with everything else, make sure the TorManager
# can lazy-provide an endpoint, and overlap the startup process
# with the user handing off the wormhole code
yield self._tor_manager.start()
w = create(self.args.appid or APPID, self.args.relay_url, w = create(self.args.appid or APPID, self.args.relay_url,
self._reactor, self._reactor,
tor_manager=self._tor_manager, tor=self._tor,
timing=self.args.timing) timing=self.args.timing)
self._w = w # so tests can wait on events too self._w = w # so tests can wait on events too
# I wanted to do this instead: # I wanted to do this instead:
# #
# try: # try:
# yield self._go(w, tor_manager) # yield self._go(w, tor)
# finally: # finally:
# yield w.close() # yield w.close()
# #
@ -230,7 +227,7 @@ class Receiver:
def _build_transit(self, w, sender_transit): def _build_transit(self, w, sender_transit):
tr = TransitReceiver(self.args.transit_helper, tr = TransitReceiver(self.args.transit_helper,
no_listen=(not self.args.listen), no_listen=(not self.args.listen),
tor_manager=self._tor_manager, tor=self._tor,
reactor=self._reactor, reactor=self._reactor,
timing=self.args.timing) timing=self.args.timing)
self._transit_receiver = tr self._transit_receiver = tr

View File

@ -6,8 +6,7 @@ from twisted.python import log
from twisted.protocols import basic from twisted.protocols import basic
from twisted.internet import reactor from twisted.internet import reactor
from twisted.internet.defer import inlineCallbacks, returnValue from twisted.internet.defer import inlineCallbacks, returnValue
from ..errors import (TransferError, WormholeClosedError, NoTorError, from ..errors import (TransferError, WormholeClosedError, UnsendableFileError)
UnsendableFileError)
from wormhole import create, __version__ from wormhole import create, __version__
from ..transit import TransitSender from ..transit import TransitSender
from ..util import dict_to_bytes, bytes_to_dict, bytes_to_hexstr from ..util import dict_to_bytes, bytes_to_dict, bytes_to_hexstr
@ -31,7 +30,7 @@ class Sender:
def __init__(self, args, reactor): def __init__(self, args, reactor):
self._args = args self._args = args
self._reactor = reactor self._reactor = reactor
self._tor_manager = None self._tor = None
self._timing = args.timing self._timing = args.timing
self._fd_to_send = None self._fd_to_send = None
self._transit_sender = None self._transit_sender = None
@ -41,22 +40,19 @@ class Sender:
assert isinstance(self._args.relay_url, type(u"")) assert isinstance(self._args.relay_url, type(u""))
if self._args.tor: if self._args.tor:
with self._timing.add("import", which="tor_manager"): with self._timing.add("import", which="tor_manager"):
from ..tor_manager import TorManager from ..tor_manager import get_tor
self._tor_manager = TorManager(reactor, # For now, block everything until Tor has started. Soon: launch
# tor in parallel with everything else, make sure the Tor object
# can lazy-provide an endpoint, and overlap the startup process
# with the user handing off the wormhole code
self._tor = yield get_tor(reactor,
self._args.launch_tor, self._args.launch_tor,
self._args.tor_control_port, self._args.tor_control_port,
timing=self._timing) timing=self._timing)
if not self._tor_manager.tor_available():
raise NoTorError()
# For now, block everything until Tor has started. Soon: launch
# tor in parallel with everything else, make sure the TorManager
# can lazy-provide an endpoint, and overlap the startup process
# with the user handing off the wormhole code
yield self._tor_manager.start()
w = create(self._args.appid or APPID, self._args.relay_url, w = create(self._args.appid or APPID, self._args.relay_url,
self._reactor, self._reactor,
tor_manager=self._tor_manager, tor=self._tor,
timing=self._timing) timing=self._timing)
d = self._go(w) d = self._go(w)
@ -151,7 +147,7 @@ class Sender:
if self._fd_to_send: if self._fd_to_send:
ts = TransitSender(args.transit_helper, ts = TransitSender(args.transit_helper,
no_listen=(not args.listen), no_listen=(not args.listen),
tor_manager=self._tor_manager, tor=self._tor,
reactor=self._reactor, reactor=self._reactor,
timing=self._timing) timing=self._timing)
self._transit_sender = ts self._transit_sender = ts

View File

@ -4,9 +4,10 @@ from textwrap import fill, dedent
from humanize import naturalsize from humanize import naturalsize
import mock import mock
import click.testing import click.testing
from zope.interface import implementer
from twisted.trial import unittest from twisted.trial import unittest
from twisted.python import procutils, log from twisted.python import procutils, log
from twisted.internet import defer, endpoints, reactor from twisted.internet import endpoints, reactor
from twisted.internet.utils import getProcessOutputAndValue from twisted.internet.utils import getProcessOutputAndValue
from twisted.internet.defer import gatherResults, inlineCallbacks, returnValue from twisted.internet.defer import gatherResults, inlineCallbacks, returnValue
from .. import __version__ from .. import __version__
@ -14,6 +15,7 @@ from .common import ServerBase, config
from ..cli import cmd_send, cmd_receive, welcome, cli from ..cli import cmd_send, cmd_receive, welcome, cli
from ..errors import (TransferError, WrongPasswordError, WelcomeError, from ..errors import (TransferError, WrongPasswordError, WelcomeError,
UnsendableFileError) UnsendableFileError)
from .._interfaces import ITorManager
from wormhole.server.cmd_server import MyPlugin from wormhole.server.cmd_server import MyPlugin
from wormhole.server.cli import server from wormhole.server.cli import server
@ -297,15 +299,12 @@ class ScriptVersion(ServerBase, ScriptsBase, unittest.TestCase):
self.failUnlessEqual(ver.strip(), "magic-wormhole {}".format(__version__)) self.failUnlessEqual(ver.strip(), "magic-wormhole {}".format(__version__))
self.failUnlessEqual(rc, 0) self.failUnlessEqual(rc, 0)
class FakeTorManager: @implementer(ITorManager)
class FakeTor:
# use normal endpoints, but record the fact that we were asked # use normal endpoints, but record the fact that we were asked
def __init__(self): def __init__(self):
self.endpoints = [] self.endpoints = []
def tor_available(self): def stream_via(self, host, port):
return True
def start(self):
return defer.succeed(None)
def get_endpoint_for(self, host, port):
self.endpoints.append((host, port)) self.endpoints.append((host, port))
return endpoints.HostnameEndpoint(reactor, host, port) return endpoints.HostnameEndpoint(reactor, host, port)
@ -456,16 +455,16 @@ class PregeneratedCode(ServerBase, ScriptsBase, unittest.TestCase):
if fake_tor: if fake_tor:
send_cfg.tor = True send_cfg.tor = True
send_cfg.transit_helper = self.transit send_cfg.transit_helper = self.transit
tx_tm = FakeTorManager() tx_tm = FakeTor()
with mock.patch("wormhole.tor_manager.TorManager", with mock.patch("wormhole.tor_manager.get_tor",
return_value=tx_tm, return_value=tx_tm,
) as mtx_tm: ) as mtx_tm:
send_d = cmd_send.send(send_cfg) send_d = cmd_send.send(send_cfg)
recv_cfg.tor = True recv_cfg.tor = True
recv_cfg.transit_helper = self.transit recv_cfg.transit_helper = self.transit
rx_tm = FakeTorManager() rx_tm = FakeTor()
with mock.patch("wormhole.tor_manager.TorManager", with mock.patch("wormhole.tor_manager.get_tor",
return_value=rx_tm, return_value=rx_tm,
) as mrx_tm: ) as mrx_tm:
receive_d = cmd_receive.receive(recv_cfg) receive_d = cmd_receive.receive(recv_cfg)

View File

@ -3,335 +3,98 @@ import mock, io
from twisted.trial import unittest from twisted.trial import unittest
from twisted.internet import defer from twisted.internet import defer
from twisted.internet.error import ConnectError from twisted.internet.error import ConnectError
from six import next
from ..tor_manager import TorManager, DEFAULT_VALUE from ..tor_manager import get_tor, SocksOnlyTor
from ..errors import NoTorError
from .._interfaces import ITorManager
class X():
pass
class Tor(unittest.TestCase): class Tor(unittest.TestCase):
def test_create(self): def test_no_txtorcon(self):
tm = TorManager(None) with mock.patch("wormhole.tor_manager.txtorcon", None):
del tm self.failureResultOf(get_tor(None), NoTorError)
def test_bad_args(self): def test_bad_args(self):
e = self.assertRaises(TypeError, f = self.failureResultOf(get_tor(None, launch_tor="not boolean"),
TorManager, None, launch_tor="not boolean") TypeError)
self.assertEqual(str(e), "launch_tor= must be boolean") self.assertEqual(str(f.value), "launch_tor= must be boolean")
e = self.assertRaises(TypeError,
TorManager, None, tor_control_port=1234) f = self.failureResultOf(get_tor(None, tor_control_port=1234),
self.assertEqual(str(e), "tor_control_port= must be str or None") TypeError)
e = self.assertRaises(ValueError, self.assertEqual(str(f.value), "tor_control_port= must be str or None")
TorManager, None, launch_tor=True, f = self.failureResultOf(get_tor(None, launch_tor=True,
tor_control_port="tcp:127.0.0.1:1234") tor_control_port="tcp:127.0.0.1:1234"),
self.assertEqual(str(e), ValueError)
self.assertEqual(str(f.value),
"cannot combine --launch-tor and --tor-control-port=") "cannot combine --launch-tor and --tor-control-port=")
def test_start_launch_tor(self):
reactor = object()
stderr = io.StringIO()
tm = TorManager(reactor, launch_tor=True, stderr=stderr)
dlt_d = defer.Deferred()
tm._do_launch_tor = mock.Mock(return_value=dlt_d)
tm._try_control_port = mock.Mock()
d = tm.start()
self.assertNoResult(d)
tsep = object()
with mock.patch("wormhole.tor_manager.clientFromString",
return_value=tsep) as cfs:
dlt_d.callback(("tproto", "tconfig", "socks_desc"))
res = self.successResultOf(d)
self.assertEqual(res, None)
self.assertEqual(tm._tor_protocol, "tproto")
self.assertEqual(tm._tor_config, "tconfig")
self.assertEqual(tm._tor_socks_endpoint, tsep)
self.assertEqual(tm._do_launch_tor.mock_calls, [mock.call()])
self.assertEqual(tm._try_control_port.mock_calls, [])
self.assertEqual(cfs.mock_calls, [mock.call(reactor, "socks_desc")])
def test_start_control_port_default_failure(self):
reactor = object()
stderr = io.StringIO()
tm = TorManager(reactor, stderr=stderr)
tm._do_launch_tor = mock.Mock()
tcp_ds = [defer.Deferred() for i in range(5)]
tcp_ds_iter = iter(tcp_ds)
attempted_control_ports = []
def next_d(control_port):
attempted_control_ports.append(control_port)
return next(tcp_ds_iter)
tm._try_control_port = mock.Mock(side_effect=next_d)
d = tm.start()
tsep = object()
with mock.patch("wormhole.tor_manager.clientFromString",
return_value=tsep) as cfs:
self.assertNoResult(d)
self.assertEqual(attempted_control_ports,
["unix:/var/run/tor/control"])
self.assertEqual(tm._try_control_port.mock_calls,
[mock.call("unix:/var/run/tor/control")])
tcp_ds[0].callback((None, None, None))
self.assertNoResult(d)
self.assertEqual(attempted_control_ports,
["unix:/var/run/tor/control",
"tcp:127.0.0.1:9051",
])
self.assertEqual(tm._try_control_port.mock_calls,
[mock.call("unix:/var/run/tor/control"),
mock.call("tcp:127.0.0.1:9051"),
])
tcp_ds[1].callback((None, None, None))
self.assertNoResult(d)
self.assertEqual(attempted_control_ports,
["unix:/var/run/tor/control",
"tcp:127.0.0.1:9051",
"tcp:127.0.0.1:9151",
])
self.assertEqual(tm._try_control_port.mock_calls,
[mock.call("unix:/var/run/tor/control"),
mock.call("tcp:127.0.0.1:9051"),
mock.call("tcp:127.0.0.1:9151"),
])
tcp_ds[2].callback((None, None, None))
res = self.successResultOf(d)
self.assertEqual(res, None)
self.assertEqual(tm._tor_protocol, None)
self.assertEqual(tm._tor_config, None)
self.assertEqual(tm._tor_socks_endpoint, tsep)
self.assertEqual(tm._do_launch_tor.mock_calls, [])
self.assertEqual(cfs.mock_calls,
[mock.call(reactor, "tcp:127.0.0.1:9050")])
def test_start_control_port_default(self):
reactor = object()
stderr = io.StringIO()
tm = TorManager(reactor, stderr=stderr)
tm._do_launch_tor = mock.Mock()
tcp_d = defer.Deferred()
# let it succeed on the first try
tm._try_control_port = mock.Mock(return_value=tcp_d)
d = tm.start()
self.assertNoResult(d)
tsep = object()
with mock.patch("wormhole.tor_manager.clientFromString",
return_value=tsep) as cfs:
tcp_d.callback(("tproto", "tconfig", "socks_desc"))
res = self.successResultOf(d)
self.assertEqual(res, None)
self.assertEqual(tm._tor_protocol, "tproto")
self.assertEqual(tm._tor_config, "tconfig")
self.assertEqual(tm._tor_socks_endpoint, tsep)
self.assertEqual(tm._do_launch_tor.mock_calls, [])
self.assertEqual(tm._try_control_port.mock_calls,
[mock.call("unix:/var/run/tor/control")])
self.assertEqual(cfs.mock_calls, [mock.call(reactor, "socks_desc")])
def test_start_control_port_non_default_failure(self):
reactor = object()
my_port = "my_port"
stderr = io.StringIO()
tm = TorManager(reactor, tor_control_port=my_port, stderr=stderr)
tm._do_launch_tor = mock.Mock()
tcp_ds = [defer.Deferred() for i in range(5)]
tcp_ds_iter = iter(tcp_ds)
attempted_control_ports = []
def next_d(control_port):
attempted_control_ports.append(control_port)
return next(tcp_ds_iter)
tm._try_control_port = mock.Mock(side_effect=next_d)
d = tm.start()
tsep = object()
with mock.patch("wormhole.tor_manager.clientFromString",
return_value=tsep) as cfs:
self.assertNoResult(d)
self.assertEqual(attempted_control_ports, [my_port])
self.assertEqual(tm._try_control_port.mock_calls,
[mock.call(my_port)])
tcp_ds[0].callback((None, None, None))
res = self.successResultOf(d)
self.assertEqual(res, None)
self.assertEqual(tm._tor_protocol, None)
self.assertEqual(tm._tor_config, None)
self.assertEqual(tm._tor_socks_endpoint, tsep)
self.assertEqual(tm._do_launch_tor.mock_calls, [])
self.assertEqual(cfs.mock_calls,
[mock.call(reactor, "tcp:127.0.0.1:9050")])
def test_start_control_port_non_default(self):
reactor = object()
my_port = "my_port"
stderr = io.StringIO()
tm = TorManager(reactor, tor_control_port=my_port, stderr=stderr)
tm._do_launch_tor = mock.Mock()
tcp_d = defer.Deferred()
tm._try_control_port = mock.Mock(return_value=tcp_d)
d = tm.start()
self.assertNoResult(d)
tsep = object()
with mock.patch("wormhole.tor_manager.clientFromString",
return_value=tsep) as cfs:
tcp_d.callback(("tproto", "tconfig", "socks_desc"))
res = self.successResultOf(d)
self.assertEqual(res, None)
self.assertEqual(tm._tor_protocol, "tproto")
self.assertEqual(tm._tor_config, "tconfig")
self.assertEqual(tm._tor_socks_endpoint, tsep)
self.assertEqual(tm._do_launch_tor.mock_calls, [])
self.assertEqual(tm._try_control_port.mock_calls,
[mock.call(my_port)])
self.assertEqual(cfs.mock_calls, [mock.call(reactor, "socks_desc")])
def test_launch(self): def test_launch(self):
reactor = object() reactor = object()
my_tor = X() # object() didn't like providedBy()
launch_d = defer.Deferred()
stderr = io.StringIO() stderr = io.StringIO()
tc = mock.Mock() with mock.patch("wormhole.tor_manager.txtorcon.launch",
mock_TorConfig = mock.patch("wormhole.tor_manager.TorConfig", side_effect=launch_d) as launch:
return_value=tc) d = get_tor(reactor, launch_tor=True, stderr=stderr)
lt_d = defer.Deferred()
mock_launch_tor = mock.patch("wormhole.tor_manager.launch_tor",
return_value=lt_d)
mock_allocate_tcp_port = mock.patch("wormhole.tor_manager.allocate_tcp_port",
return_value=12345)
mock_clientFromString = mock.patch("wormhole.tor_manager.clientFromString")
with mock_TorConfig as mtc:
with mock_launch_tor as mlt:
with mock_allocate_tcp_port as matp:
with mock_clientFromString as mcfs:
tm = TorManager(reactor, launch_tor=True, stderr=stderr)
d = tm.start()
self.assertNoResult(d) self.assertNoResult(d)
tp = mock.Mock() self.assertEqual(launch.mock_calls, [mock.call(reactor)])
lt_d.callback(tp) launch_d.callback(my_tor)
res = self.successResultOf(d) tor = self.successResultOf(d)
self.assertEqual(res, None) self.assertIs(tor, my_tor)
self.assertIs(tm._tor_protocol, tp) self.assert_(ITorManager.providedBy(tor))
self.assertIs(tm._tor_config, tc) self.assertEqual(stderr.getvalue(),
self.assertEqual(mtc.mock_calls, [mock.call()]) " launching a new Tor process, this may take a while..\n")
self.assertEqual(mlt.mock_calls, [mock.call(tc, reactor)])
self.assertEqual(matp.mock_calls, [mock.call()])
self.assertEqual(mcfs.mock_calls,
[mock.call(reactor, "tcp:127.0.0.1:12345")])
def _do_test_try_control_port(self, socks_ports, exp_socks_desc, def test_connect(self):
btc_exception=None, tcfp_exception=None):
reactor = object() reactor = object()
my_tor = X() # object() didn't like providedBy()
tcp = "port"
connect_d = defer.Deferred()
stderr = io.StringIO() stderr = io.StringIO()
ep = object() with mock.patch("wormhole.tor_manager.txtorcon.connect",
mock_clientFromString = mock.patch("wormhole.tor_manager.clientFromString", side_effect=connect_d) as connect:
return_value=ep) d = get_tor(reactor, tor_control_port=tcp, stderr=stderr)
tproto = mock.Mock()
btc_d = defer.Deferred()
mock_build_tor_connection = mock.patch("wormhole.tor_manager.build_tor_connection", return_value=btc_d)
torconfig = mock.Mock()
tc = mock.Mock()
tc.SocksPort = iter(socks_ports)
tc_d = defer.Deferred()
torconfig.from_protocol = mock.Mock(return_value=tc_d)
mock_torconfig = mock.patch("wormhole.tor_manager.TorConfig", torconfig)
control_port = object()
with mock_clientFromString as cfs:
with mock_build_tor_connection as btc:
with mock_torconfig:
tm = TorManager(reactor, stderr=stderr)
d = tm._try_control_port(control_port)
# waiting in 'tproto = yield build_tor_connection(..)'
self.assertNoResult(d) self.assertNoResult(d)
self.assertEqual(cfs.mock_calls, self.assertEqual(connect.mock_calls, [mock.call(reactor, tcp)])
[mock.call(reactor, control_port)]) connect_d.callback(my_tor)
self.assertEqual(btc.mock_calls, tor = self.successResultOf(d)
[mock.call(ep, build_state=False)]) self.assertIs(tor, my_tor)
self.assertEqual(torconfig.from_protocol.mock_calls, []) self.assert_(ITorManager.providedBy(tor))
self.assertEqual(stderr.getvalue(), " using Tor via control port\n")
btc_d.callback(tproto) def test_connect_fails(self):
# waiting in 'tconfig = yield TorConfig.from_protocol(..)'
self.assertNoResult(d)
self.assertEqual(torconfig.from_protocol.mock_calls,
[mock.call(tproto)])
tc_d.callback(tc)
res = self.successResultOf(d)
self.assertEqual(res, (tproto, tc, exp_socks_desc))
def test_try_control_port(self):
self._do_test_try_control_port(["1234 ignorestuff",
"unix:/foo WorldWritable"],
"tcp:127.0.0.1:1234")
self._do_test_try_control_port(["unix:/foo WorldWritable",
"1234 ignorestuff"],
"unix:/foo")
self._do_test_try_control_port([DEFAULT_VALUE,
"1234"],
"tcp:127.0.0.1:9050")
def _do_test_try_control_port_exception(self, btc_exc=None, tcfp_exc=None):
reactor = object() reactor = object()
tcp = "port"
connect_d = defer.Deferred()
stderr = io.StringIO() stderr = io.StringIO()
ep = object() with mock.patch("wormhole.tor_manager.txtorcon.connect",
mock_clientFromString = mock.patch("wormhole.tor_manager.clientFromString", side_effect=connect_d) as connect:
return_value=ep) d = get_tor(reactor, tor_control_port=tcp, stderr=stderr)
tproto = mock.Mock()
btc_d = defer.Deferred()
mock_build_tor_connection = mock.patch("wormhole.tor_manager.build_tor_connection", return_value=btc_d)
torconfig = mock.Mock()
tcfp_d = defer.Deferred()
torconfig.from_protocol = mock.Mock(return_value=tcfp_d)
mock_torconfig = mock.patch("wormhole.tor_manager.TorConfig", torconfig)
control_port = object()
with mock_clientFromString:
with mock_build_tor_connection:
with mock_torconfig:
tm = TorManager(reactor, stderr=stderr)
d = tm._try_control_port(control_port)
# waiting in 'tproto = yield build_tor_connection(..)'
self.assertNoResult(d) self.assertNoResult(d)
self.assertEqual(connect.mock_calls, [mock.call(reactor, tcp)])
if btc_exc: connect_d.errback(ConnectError())
btc_d.errback(btc_exc) tor = self.successResultOf(d)
else: self.assertIsInstance(tor, SocksOnlyTor)
btc_d.callback(tproto) self.assert_(ITorManager.providedBy(tor))
assert tcfp_exc self.assertEqual(tor._reactor, reactor)
tcfp_d.errback(tcfp_exc) self.assertEqual(stderr.getvalue(),
" unable to find Tor control port, using SOCKS\n")
res = self.successResultOf(d) class SocksOnly(unittest.TestCase):
self.assertEqual(res, (None, None, None)) def test_tor(self):
def test_try_control_port_error(self):
self._do_test_try_control_port_exception(btc_exc=ValueError())
self._do_test_try_control_port_exception(btc_exc=ConnectError())
self._do_test_try_control_port_exception(tcfp_exc=ValueError())
self._do_test_try_control_port_exception(tcfp_exc=ConnectError())
def test_badaddr(self):
tm = TorManager(None)
isnon = tm.is_non_public_numeric_address
self.assertTrue(isnon("10.0.0.1"))
self.assertTrue(isnon("127.0.0.1"))
self.assertTrue(isnon("192.168.78.254"))
self.assertTrue(isnon("::1"))
self.assertFalse(isnon("8.8.8.8"))
self.assertFalse(isnon("example.org"))
def test_endpoint(self):
reactor = object() reactor = object()
stderr = io.StringIO() sot = SocksOnlyTor(reactor)
tm = TorManager(reactor, stderr=stderr) fake_ep = object()
tm._tor_socks_endpoint = tse = object() with mock.patch("wormhole.tor_manager.txtorcon.TorClientEndpoint",
exp_ep = object() return_value=fake_ep) as tce:
with mock.patch("wormhole.tor_manager.TorClientEndpoint", ep = sot.stream_via("host", "port")
return_value=exp_ep) as tce: self.assertIs(ep, fake_ep)
ep = tm.get_endpoint_for("example.com", 1234) self.assertEqual(tce.mock_calls, [mock.call("host", "port",
self.assertIs(ep, exp_ep) socks_endpoint=None,
self.assertEqual(tce.mock_calls, tls=False,
[mock.call(b"example.com", 1234, reactor=reactor)])
socks_endpoint=tse)])
with mock.patch("wormhole.tor_manager.TorClientEndpoint",
return_value=exp_ep) as tce:
ep = tm.get_endpoint_for("127.0.0.1", 1234)
self.assertEqual(ep, None)
self.assertEqual(tce.mock_calls, [])

View File

@ -149,14 +149,14 @@ class Hints(unittest.TestCase):
self.assertIsInstance(efho(transit.DirectTCPV1Hint("host", 1234, 0.0)), self.assertIsInstance(efho(transit.DirectTCPV1Hint("host", 1234, 0.0)),
endpoints.HostnameEndpoint) endpoints.HostnameEndpoint)
self.assertEqual(efho("unknown:stuff:yowza:pivlor"), None) self.assertEqual(efho("unknown:stuff:yowza:pivlor"), None)
# c._tor_manager is currently None # c._tor is currently None
self.assertEqual(efho(transit.TorTCPV1Hint("host", "port", 0)), None) self.assertEqual(efho(transit.TorTCPV1Hint("host", "port", 0)), None)
c._tor_manager = mock.Mock() c._tor = mock.Mock()
def tor_ep(hostname, port): def tor_ep(hostname, port):
if hostname == "non-public": if hostname == "non-public":
return None return None
return ("tor_ep", hostname, port) return ("tor_ep", hostname, port)
c._tor_manager.get_endpoint_for = mock.Mock(side_effect=tor_ep) c._tor.stream_via = mock.Mock(side_effect=tor_ep)
self.assertEqual(efho(transit.DirectTCPV1Hint("host", 1234, 0.0)), self.assertEqual(efho(transit.DirectTCPV1Hint("host", 1234, 0.0)),
("tor_ep", "host", 1234)) ("tor_ep", "host", 1234))
self.assertEqual(efho(transit.TorTCPV1Hint("host2.onion", 1234, 0.0)), self.assertEqual(efho(transit.TorTCPV1Hint("host2.onion", 1234, 0.0)),
@ -1470,7 +1470,7 @@ class Transit(unittest.TestCase):
@inlineCallbacks @inlineCallbacks
def test_success_direct_tor(self): def test_success_direct_tor(self):
clock = task.Clock() clock = task.Clock()
s = transit.TransitSender("", tor_manager=mock.Mock(), reactor=clock) s = transit.TransitSender("", tor=mock.Mock(), reactor=clock)
s.set_transit_key(b"key") s.set_transit_key(b"key")
hints = yield s.get_connection_hints() # start the listener hints = yield s.get_connection_hints() # start the listener
del hints del hints
@ -1491,7 +1491,7 @@ class Transit(unittest.TestCase):
@inlineCallbacks @inlineCallbacks
def test_success_direct_tor_relay(self): def test_success_direct_tor_relay(self):
clock = task.Clock() clock = task.Clock()
s = transit.TransitSender("", tor_manager=mock.Mock(), reactor=clock) s = transit.TransitSender("", tor=mock.Mock(), reactor=clock)
s.set_transit_key(b"key") s.set_transit_key(b"key")
hints = yield s.get_connection_hints() # start the listener hints = yield s.get_connection_hints() # start the listener
del hints del hints

View File

@ -1,28 +1,29 @@
from __future__ import print_function, unicode_literals from __future__ import print_function, unicode_literals
import sys, re import sys
import six from attr import attrs, attrib
from zope.interface import implementer from zope.interface.declarations import directlyProvides
from twisted.internet.defer import inlineCallbacks, returnValue from twisted.internet.defer import inlineCallbacks, returnValue
from twisted.internet.error import ConnectError
from twisted.internet.endpoints import clientFromString
try: try:
from txtorcon import (TorConfig, launch_tor, build_tor_connection, import txtorcon
DEFAULT_VALUE, TorClientEndpoint)
except ImportError: except ImportError:
TorConfig = None txtorcon = None
launch_tor = None from . import _interfaces, errors
build_tor_connection = None
TorClientEndpoint = None
DEFAULT_VALUE = "DEFAULT_VALUE"
import ipaddress
from . import _interfaces
from .timing import DebugTiming from .timing import DebugTiming
from .transit import allocate_tcp_port
@attrs
class SocksOnlyTor(object):
_reactor = attrib()
@implementer(_interfaces.ITorManager) def stream_via(self, host, port, tls=False):
class TorManager: return txtorcon.TorClientEndpoint(
def __init__(self, reactor, launch_tor=False, tor_control_port=None, host, port,
socks_endpoint=None, # tries localhost:9050 and 9150
tls=tls,
reactor=self._reactor,
)
@inlineCallbacks
def get_tor(reactor, launch_tor=False, tor_control_port=None,
timing=None, stderr=sys.stderr): timing=None, stderr=sys.stderr):
""" """
If launch_tor=True, I will try to launch a new Tor process, ask it If launch_tor=True, I will try to launch a new Tor process, ask it
@ -36,8 +37,9 @@ class TorManager:
With no arguments, I will try to connect to an existing Tor's control With no arguments, I will try to connect to an existing Tor's control
port at the usual places: [unix:/var/run/tor/control, port at the usual places: [unix:/var/run/tor/control,
tcp:127.0.0.1:9051, tcp:127.0.0.1:9151]. If any are successful, I'll tcp:127.0.0.1:9051, tcp:127.0.0.1:9151]. If any are successful, I'll
ask that Tor for its SOCKS port. If none are successful, I'll attempt ask that Tor for its SOCKS port. If none are successful, I'll
to do SOCKS to tcp:127.0.0.1:9050. attempt to do SOCKS to the usual places: [tcp:127.0.0.1:9050,
tcp:127.0.0.1:9150].
If I am unable to make a SOCKS connection, the initial connection to If I am unable to make a SOCKS connection, the initial connection to
the Rendezvous Server will fail, and the program will terminate. the Rendezvous Server will fail, and the program will terminate.
@ -53,119 +55,45 @@ class TorManager:
# existing Tor should be much faster, but still requires general # existing Tor should be much faster, but still requires general
# permission via --tor. # permission via --tor.
self._reactor = reactor if not txtorcon:
raise errors.NoTorError()
if not isinstance(launch_tor, bool): # note: False is int if not isinstance(launch_tor, bool): # note: False is int
raise TypeError("launch_tor= must be boolean") raise TypeError("launch_tor= must be boolean")
if not isinstance(tor_control_port, (type(""), type(None))): if not isinstance(tor_control_port, (type(""), type(None))):
raise TypeError("tor_control_port= must be str or None") raise TypeError("tor_control_port= must be str or None")
assert tor_control_port != ""
if launch_tor and tor_control_port is not None: if launch_tor and tor_control_port is not None:
raise ValueError("cannot combine --launch-tor and --tor-control-port=") raise ValueError("cannot combine --launch-tor and --tor-control-port=")
self._launch_tor = launch_tor timing = timing or DebugTiming()
self._tor_control_port = tor_control_port
self._timing = timing or DebugTiming()
self._stderr = stderr
def tor_available(self):
# unit tests mock out everything we get from txtorcon, so we can test
# this class under py3 even if txtorcon isn't installed. But the real
# commands need to know if they have Tor or not.
return bool(TorConfig)
@inlineCallbacks
def start(self):
# Connect to an existing Tor, or create a new one. If we need to # Connect to an existing Tor, or create a new one. If we need to
# launch an onion service, then we need a working control port (and # launch an onion service, then we need a working control port (and
# authentication cookie). If we're only acting as a client, we don't # authentication cookie). If we're only acting as a client, we don't
# need the control port. # need the control port.
if self._launch_tor: if launch_tor:
print(" launching a new Tor process, this may take a while..", print(" launching a new Tor process, this may take a while..",
file=self._stderr) file=stderr)
with self._timing.add("launch tor"): with timing.add("launch tor"):
(tproto, tconfig, socks_desc) = yield self._do_launch_tor() tor = yield txtorcon.launch(reactor,
#data_directory=,
#tor_binary=,
)
else: else:
control_ports = ["unix:/var/run/tor/control", # debian tor package with timing.add("find tor"):
"tcp:127.0.0.1:9051", # standard Tor
"tcp:127.0.0.1:9151", # TorBrowser
]
if self._tor_control_port:
control_ports = [self._tor_control_port]
with self._timing.add("find tor"):
for control_port in control_ports:
(tproto, tconfig,
socks_desc) = yield self._try_control_port(control_port)
if tproto:
print(" using Tor (control port %s) (SOCKS port %s)"
% (control_port, socks_desc),
file=self._stderr)
break
else:
tproto = None
tconfig = None
socks_desc = "tcp:127.0.0.1:9050" # fallback
print(" using Tor (SOCKS port %s)" % socks_desc,
file=self._stderr)
self._tor_protocol = tproto
self._tor_config = tconfig
self._tor_socks_endpoint = clientFromString(self._reactor, socks_desc)
@inlineCallbacks
def _do_launch_tor(self):
tconfig = TorConfig()
#tconfig.ControlPort = allocate_tcp_port() # defaults to 9052
tconfig.SocksPort = allocate_tcp_port()
socks_desc = "tcp:127.0.0.1:%d" % tconfig.SocksPort
# this could take tor_binary=
tproto = yield launch_tor(tconfig, self._reactor)
returnValue((tproto, tconfig, socks_desc))
@inlineCallbacks
def _try_control_port(self, control_port):
NOPE = (None, None, None)
ep = clientFromString(self._reactor, control_port)
try: try:
tproto = yield build_tor_connection(ep, build_state=False) # If tor_control_port is None (the default), txtorcon
# now wait for bootstrap # will look through a list of usual places. If it is set,
tconfig = yield TorConfig.from_protocol(tproto) # it will look only in the place we tell it to.
except (ValueError, ConnectError): tor = yield txtorcon.connect(reactor, tor_control_port)
returnValue(NOPE) print(" using Tor via control port", file=stderr)
socks_ports = list(tconfig.SocksPort) except Exception:
socks_port = socks_ports[0] # TODO: when might there be multiple? # TODO: make this more specific. I think connect() is
# I've seen "9050", and "unix:/var/run/tor/socks WorldWritable" # likely to throw a reactor.connectTCP -type error, like
pieces = socks_port.split() # ConnectionFailed or ConnectionRefused or something
p = pieces[0] print(" unable to find Tor control port, using SOCKS",
if p == DEFAULT_VALUE: file=stderr)
socks_desc = "tcp:127.0.0.1:9050" tor = SocksOnlyTor(reactor)
elif re.search('^\d+$', p): directlyProvides(tor, _interfaces.ITorManager)
socks_desc = "tcp:127.0.0.1:%s" % p returnValue(tor)
else:
socks_desc = p
returnValue((tproto, tconfig, socks_desc))
def is_non_public_numeric_address(self, host):
# for numeric hostnames, skip RFC1918 addresses, since no Tor exit
# node will be able to reach those. Likewise ignore IPv6 addresses.
try:
a = ipaddress.ip_address(host)
except ValueError:
return False # non-numeric, let Tor try it
if a.version != 4:
return True # IPv6 gets ignored
if (a.is_loopback or a.is_multicast or a.is_private or a.is_reserved
or a.is_unspecified):
return True # too weird, don't connect
return False
def get_endpoint_for(self, host, port):
assert isinstance(port, six.integer_types)
if self.is_non_public_numeric_address(host):
return None
# txsocksx doesn't like unicode: it concatenates some binary protocol
# bytes with the hostname when talking to the SOCKS server, so the
# py2 automatic unicode promotion blows up
host = host.encode("ascii")
ep = TorClientEndpoint(host, port,
socks_endpoint=self._tor_socks_endpoint)
return ep

View File

@ -589,7 +589,7 @@ class Common:
RELAY_DELAY = 2.0 RELAY_DELAY = 2.0
TRANSIT_KEY_LENGTH = SecretBox.KEY_SIZE TRANSIT_KEY_LENGTH = SecretBox.KEY_SIZE
def __init__(self, transit_relay, no_listen=False, tor_manager=None, def __init__(self, transit_relay, no_listen=False, tor=None,
reactor=reactor, timing=None): reactor=reactor, timing=None):
self._side = bytes_to_hexstr(os.urandom(8)) # unicode self._side = bytes_to_hexstr(os.urandom(8)) # unicode
if transit_relay: if transit_relay:
@ -603,7 +603,7 @@ class Common:
self._transit_relays = [] self._transit_relays = []
self._their_direct_hints = [] # hintobjs self._their_direct_hints = [] # hintobjs
self._our_relay_hints = set(self._transit_relays) self._our_relay_hints = set(self._transit_relays)
self._tor_manager = tor_manager self._tor = tor
self._transit_key = None self._transit_key = None
self._no_listen = no_listen self._no_listen = no_listen
self._waiting_for_transit_key = [] self._waiting_for_transit_key = []
@ -614,7 +614,7 @@ class Common:
self._timing.add("transit") self._timing.add("transit")
def _build_listener(self): def _build_listener(self):
if self._no_listen or self._tor_manager: if self._no_listen or self._tor:
return ([], None) return ([], None)
portnum = allocate_tcp_port() portnum = allocate_tcp_port()
addresses = ipaddrs.find_addresses() addresses = ipaddrs.find_addresses()
@ -820,7 +820,7 @@ class Common:
if not ep: if not ep:
continue continue
description = "->%s" % describe_hint_obj(hint_obj) description = "->%s" % describe_hint_obj(hint_obj)
if self._tor_manager: if self._tor:
description = "tor" + description description = "tor" + description
d = self._start_connector(ep, description) d = self._start_connector(ep, description)
contenders.append(d) contenders.append(d)
@ -847,7 +847,7 @@ class Common:
if not ep: if not ep:
continue continue
description = "->relay:%s" % describe_hint_obj(hint_obj) description = "->relay:%s" % describe_hint_obj(hint_obj)
if self._tor_manager: if self._tor:
description = "tor" + description description = "tor" + description
d = task.deferLater(self._reactor, relay_delay, d = task.deferLater(self._reactor, relay_delay,
self._start_connector, ep, description, self._start_connector, ep, description,
@ -887,12 +887,14 @@ class Common:
return d return d
def _endpoint_from_hint_obj(self, hint): def _endpoint_from_hint_obj(self, hint):
if self._tor_manager: if self._tor:
if isinstance(hint, (DirectTCPV1Hint, TorTCPV1Hint)): if isinstance(hint, (DirectTCPV1Hint, TorTCPV1Hint)):
# our TorManager will return None for non-public IPv4 # this Tor object will throw ValueError for non-public IPv4
# addresses and any IPv6 address # addresses and any IPv6 address
return self._tor_manager.get_endpoint_for(hint.hostname, try:
hint.port) return self._tor.stream_via(hint.hostname, hint.port)
except ValueError:
return None
return None return None
if isinstance(hint, DirectTCPV1Hint): if isinstance(hint, DirectTCPV1Hint):
return endpoints.HostnameEndpoint(self._reactor, return endpoints.HostnameEndpoint(self._reactor,

View File

@ -279,7 +279,7 @@ class _DeferredWormhole(object):
def create(appid, relay_url, reactor, # use keyword args for everything else def create(appid, relay_url, reactor, # use keyword args for everything else
versions={}, versions={},
delegate=None, journal=None, tor_manager=None, delegate=None, journal=None, tor=None,
timing=None, timing=None,
stderr=sys.stderr): stderr=sys.stderr):
timing = timing or DebugTiming() timing = timing or DebugTiming()
@ -292,13 +292,13 @@ def create(appid, relay_url, reactor, # use keyword args for everything else
wormhole_versions = {} # will be used to indicate Wormhole capabilities wormhole_versions = {} # will be used to indicate Wormhole capabilities
wormhole_versions["app_versions"] = versions # app-specific capabilities wormhole_versions["app_versions"] = versions # app-specific capabilities
b = Boss(w, side, relay_url, appid, wormhole_versions, b = Boss(w, side, relay_url, appid, wormhole_versions,
reactor, journal, tor_manager, timing) reactor, journal, tor, timing)
w._set_boss(b) w._set_boss(b)
b.start() b.start()
return w return w
## def from_serialized(serialized, reactor, delegate, ## def from_serialized(serialized, reactor, delegate,
## journal=None, tor_manager=None, ## journal=None, tor=None,
## timing=None, stderr=sys.stderr): ## timing=None, stderr=sys.stderr):
## assert serialized["serialized_wormhole_version"] == 1 ## assert serialized["serialized_wormhole_version"] == 1
## timing = timing or DebugTiming() ## timing = timing or DebugTiming()

View File

@ -2,8 +2,7 @@ import json
from twisted.internet.defer import inlineCallbacks, returnValue from twisted.internet.defer import inlineCallbacks, returnValue
from . import wormhole from . import wormhole
from .tor_manager import TorManager from .tor_manager import get_tor
from .errors import NoTorError
@inlineCallbacks @inlineCallbacks
def receive(reactor, appid, relay_url, code, def receive(reactor, appid, relay_url, code,
@ -27,18 +26,15 @@ def receive(reactor, appid, relay_url, code,
:param on_code: if not None, this is called when we have a code (even if you passed in one explicitly) :param on_code: if not None, this is called when we have a code (even if you passed in one explicitly)
:type on_code: single-argument callable :type on_code: single-argument callable
""" """
tm = None tor = None
if use_tor: if use_tor:
tm = TorManager(reactor, launch_tor, tor_control_port) tor = yield get_tor(reactor, launch_tor, tor_control_port)
# For now, block everything until Tor has started. Soon: launch # For now, block everything until Tor has started. Soon: launch
# tor in parallel with everything else, make sure the TorManager # tor in parallel with everything else, make sure the Tor object
# can lazy-provide an endpoint, and overlap the startup process # can lazy-provide an endpoint, and overlap the startup process
# with the user handing off the wormhole code # with the user handing off the wormhole code
if not tm.tor_available():
raise NoTorError()
yield tm.start()
wh = wormhole.create(appid, relay_url, reactor, tor_manager=tm) wh = wormhole.create(appid, relay_url, reactor, tor=tor)
if code is None: if code is None:
wh.allocate_code() wh.allocate_code()
code = yield wh.get_code() code = yield wh.get_code()
@ -92,17 +88,14 @@ def send(reactor, appid, relay_url, data, code,
:param on_code: if not None, this is called when we have a code (even if you passed in one explicitly) :param on_code: if not None, this is called when we have a code (even if you passed in one explicitly)
:type on_code: single-argument callable :type on_code: single-argument callable
""" """
tm = None tor = None
if use_tor: if use_tor:
tm = TorManager(reactor, launch_tor, tor_control_port) tor = yield get_tor(reactor, launch_tor, tor_control_port)
# For now, block everything until Tor has started. Soon: launch # For now, block everything until Tor has started. Soon: launch
# tor in parallel with everything else, make sure the TorManager # tor in parallel with everything else, make sure the Tor object
# can lazy-provide an endpoint, and overlap the startup process # can lazy-provide an endpoint, and overlap the startup process
# with the user handing off the wormhole code # with the user handing off the wormhole code
if not tm.tor_available(): wh = wormhole.create(appid, relay_url, reactor, tor=tor)
raise NoTorError()
yield tm.start()
wh = wormhole.create(appid, relay_url, reactor, tor_manager=tm)
if code is None: if code is None:
wh.allocate_code() wh.allocate_code()
code = yield wh.get_code() code = yield wh.get_code()

View File

@ -4,7 +4,7 @@
# and then run "tox" from this directory. # and then run "tox" from this directory.
[tox] [tox]
envlist = {py27,py33,py34,py35,py36,pypy} envlist = {py27,py34,py35,py36,pypy}
skip_missing_interpreters = True skip_missing_interpreters = True
minversion = 2.4.0 minversion = 2.4.0