Merge branch 'new-txtorcon'
This commit is contained in:
commit
48b1f0257b
|
@ -13,8 +13,6 @@ environment:
|
|||
# http://www.appveyor.com/docs/installed-software#python
|
||||
- PYTHON: "C:\\Python27"
|
||||
- PYTHON: "C:\\Python27-x64"
|
||||
- PYTHON: "C:\\Python33"
|
||||
- PYTHON: "C:\\Python33-x64"
|
||||
DISTUTILS_USE_SDK: "1"
|
||||
- PYTHON: "C:\\Python34"
|
||||
- PYTHON: "C:\\Python34-x64"
|
||||
|
|
|
@ -21,4 +21,5 @@ after_success:
|
|||
- codecov
|
||||
matrix:
|
||||
allow_failures:
|
||||
- python: "3.3"
|
||||
- python: "nightly"
|
||||
|
|
|
@ -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.
|
||||
|
||||
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
|
||||
3.6.
|
||||
all supported (and installed) versions of python: 2.7, 3.4, 3.5, and 3.6.
|
||||
|
||||
## 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 compatible with python2.7, 3.3, 3.4, 3.5, and 3.6 . It
|
||||
is probably compatible with py2.6, but the latest Twisted (>=15.5.0) is
|
||||
This library is compatible with python2.7, 3.4, 3.5, and 3.6 . It is
|
||||
probably compatible with py2.6, but the latest Twisted (>=15.5.0) is
|
||||
not.
|
||||
|
||||
|
||||
|
|
19
setup.py
19
setup.py
|
@ -1,24 +1,9 @@
|
|||
import sys
|
||||
from setuptools import setup
|
||||
|
||||
import versioneer
|
||||
|
||||
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",
|
||||
version=versioneer.get_version(),
|
||||
description="Securely transfer data between computers",
|
||||
|
@ -53,8 +38,8 @@ setup(name="magic-wormhole",
|
|||
],
|
||||
extras_require={
|
||||
':sys_platform=="win32"': ["pypiwin32"],
|
||||
"tor": ["txtorcon"],
|
||||
"dev": DEV_REQUIREMENTS, # includes txtorcon on py2, but not py3
|
||||
"tor": ["txtorcon >= 0.19.3"],
|
||||
"dev": ["mock", "tox", "pyflakes", "txtorcon >= 0.19.3"],
|
||||
},
|
||||
test_suite="wormhole.test",
|
||||
cmdclass=commands,
|
||||
|
|
|
@ -3,7 +3,7 @@ import re
|
|||
import six
|
||||
from zope.interface import implementer
|
||||
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 automat import MethodicalMachine
|
||||
from . import _interfaces
|
||||
|
@ -35,7 +35,7 @@ class Boss(object):
|
|||
_versions = attrib(validator=instance_of(dict))
|
||||
_reactor = attrib()
|
||||
_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))
|
||||
m = MethodicalMachine()
|
||||
set_trace = getattr(m, "_setTrace", lambda self, f: None)
|
||||
|
@ -53,7 +53,7 @@ class Boss(object):
|
|||
self._R = Receive(self._side, self._timing)
|
||||
self._RC = RendezvousConnector(self._url, self._appid, self._side,
|
||||
self._reactor, self._journal,
|
||||
self._tor_manager, self._timing)
|
||||
self._tor, self._timing)
|
||||
self._L = Lister(self._timing)
|
||||
self._A = Allocator(self._timing)
|
||||
self._I = Input(self._timing)
|
||||
|
|
|
@ -2,7 +2,7 @@ from __future__ import print_function, absolute_import, unicode_literals
|
|||
import os
|
||||
from six.moves.urllib_parse import urlparse
|
||||
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 twisted.python import log
|
||||
from twisted.internet import defer, endpoints
|
||||
|
@ -65,7 +65,7 @@ class RendezvousConnector(object):
|
|||
_side = attrib(validator=instance_of(type(u"")))
|
||||
_reactor = attrib()
|
||||
_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))
|
||||
|
||||
def __attrs_post_init__(self):
|
||||
|
@ -86,8 +86,9 @@ class RendezvousConnector(object):
|
|||
self._trace(old_state="", input=what, new_state="")
|
||||
|
||||
def _make_endpoint(self, hostname, port):
|
||||
if self._tor_manager:
|
||||
return self._tor_manager.get_endpoint_for(hostname, port)
|
||||
if self._tor:
|
||||
# TODO: when we enable TLS, maybe add tls=True here
|
||||
return self._tor.stream_via(hostname, port)
|
||||
return endpoints.HostnameEndpoint(self._reactor, hostname, port)
|
||||
|
||||
def wire(self, boss, nameplate, mailbox, allocator, lister, terminator):
|
||||
|
|
|
@ -7,7 +7,7 @@ from twisted.internet.defer import inlineCallbacks, returnValue
|
|||
from twisted.python import log
|
||||
from wormhole import create, input_with_completion, __version__
|
||||
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,
|
||||
estimate_free_space)
|
||||
from .welcome import handle_welcome
|
||||
|
@ -45,7 +45,7 @@ class Receiver:
|
|||
assert isinstance(args.relay_url, type(u""))
|
||||
self.args = args
|
||||
self._reactor = reactor
|
||||
self._tor_manager = None
|
||||
self._tor = None
|
||||
self._transit_receiver = None
|
||||
|
||||
def _msg(self, *args, **kwargs):
|
||||
|
@ -55,29 +55,26 @@ class Receiver:
|
|||
def go(self):
|
||||
if self.args.tor:
|
||||
with self.args.timing.add("import", which="tor_manager"):
|
||||
from ..tor_manager import TorManager
|
||||
self._tor_manager = TorManager(self._reactor,
|
||||
self.args.launch_tor,
|
||||
self.args.tor_control_port,
|
||||
timing=self.args.timing)
|
||||
if not self._tor_manager.tor_available():
|
||||
raise NoTorError()
|
||||
from ..tor_manager import get_tor
|
||||
# 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
|
||||
# with the user handing off the wormhole code
|
||||
yield self._tor_manager.start()
|
||||
self._tor = yield get_tor(self._reactor,
|
||||
self.args.launch_tor,
|
||||
self.args.tor_control_port,
|
||||
timing=self.args.timing)
|
||||
|
||||
w = create(self.args.appid or APPID, self.args.relay_url,
|
||||
self._reactor,
|
||||
tor_manager=self._tor_manager,
|
||||
tor=self._tor,
|
||||
timing=self.args.timing)
|
||||
self._w = w # so tests can wait on events too
|
||||
|
||||
# I wanted to do this instead:
|
||||
#
|
||||
# try:
|
||||
# yield self._go(w, tor_manager)
|
||||
# yield self._go(w, tor)
|
||||
# finally:
|
||||
# yield w.close()
|
||||
#
|
||||
|
@ -230,7 +227,7 @@ class Receiver:
|
|||
def _build_transit(self, w, sender_transit):
|
||||
tr = TransitReceiver(self.args.transit_helper,
|
||||
no_listen=(not self.args.listen),
|
||||
tor_manager=self._tor_manager,
|
||||
tor=self._tor,
|
||||
reactor=self._reactor,
|
||||
timing=self.args.timing)
|
||||
self._transit_receiver = tr
|
||||
|
|
|
@ -6,8 +6,7 @@ from twisted.python import log
|
|||
from twisted.protocols import basic
|
||||
from twisted.internet import reactor
|
||||
from twisted.internet.defer import inlineCallbacks, returnValue
|
||||
from ..errors import (TransferError, WormholeClosedError, NoTorError,
|
||||
UnsendableFileError)
|
||||
from ..errors import (TransferError, WormholeClosedError, UnsendableFileError)
|
||||
from wormhole import create, __version__
|
||||
from ..transit import TransitSender
|
||||
from ..util import dict_to_bytes, bytes_to_dict, bytes_to_hexstr
|
||||
|
@ -31,7 +30,7 @@ class Sender:
|
|||
def __init__(self, args, reactor):
|
||||
self._args = args
|
||||
self._reactor = reactor
|
||||
self._tor_manager = None
|
||||
self._tor = None
|
||||
self._timing = args.timing
|
||||
self._fd_to_send = None
|
||||
self._transit_sender = None
|
||||
|
@ -41,22 +40,19 @@ class Sender:
|
|||
assert isinstance(self._args.relay_url, type(u""))
|
||||
if self._args.tor:
|
||||
with self._timing.add("import", which="tor_manager"):
|
||||
from ..tor_manager import TorManager
|
||||
self._tor_manager = TorManager(reactor,
|
||||
self._args.launch_tor,
|
||||
self._args.tor_control_port,
|
||||
timing=self._timing)
|
||||
if not self._tor_manager.tor_available():
|
||||
raise NoTorError()
|
||||
from ..tor_manager import get_tor
|
||||
# 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
|
||||
# with the user handing off the wormhole code
|
||||
yield self._tor_manager.start()
|
||||
self._tor = yield get_tor(reactor,
|
||||
self._args.launch_tor,
|
||||
self._args.tor_control_port,
|
||||
timing=self._timing)
|
||||
|
||||
w = create(self._args.appid or APPID, self._args.relay_url,
|
||||
self._reactor,
|
||||
tor_manager=self._tor_manager,
|
||||
tor=self._tor,
|
||||
timing=self._timing)
|
||||
d = self._go(w)
|
||||
|
||||
|
@ -151,7 +147,7 @@ class Sender:
|
|||
if self._fd_to_send:
|
||||
ts = TransitSender(args.transit_helper,
|
||||
no_listen=(not args.listen),
|
||||
tor_manager=self._tor_manager,
|
||||
tor=self._tor,
|
||||
reactor=self._reactor,
|
||||
timing=self._timing)
|
||||
self._transit_sender = ts
|
||||
|
|
|
@ -4,9 +4,10 @@ from textwrap import fill, dedent
|
|||
from humanize import naturalsize
|
||||
import mock
|
||||
import click.testing
|
||||
from zope.interface import implementer
|
||||
from twisted.trial import unittest
|
||||
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.defer import gatherResults, inlineCallbacks, returnValue
|
||||
from .. import __version__
|
||||
|
@ -14,6 +15,7 @@ from .common import ServerBase, config
|
|||
from ..cli import cmd_send, cmd_receive, welcome, cli
|
||||
from ..errors import (TransferError, WrongPasswordError, WelcomeError,
|
||||
UnsendableFileError)
|
||||
from .._interfaces import ITorManager
|
||||
from wormhole.server.cmd_server import MyPlugin
|
||||
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(rc, 0)
|
||||
|
||||
class FakeTorManager:
|
||||
@implementer(ITorManager)
|
||||
class FakeTor:
|
||||
# use normal endpoints, but record the fact that we were asked
|
||||
def __init__(self):
|
||||
self.endpoints = []
|
||||
def tor_available(self):
|
||||
return True
|
||||
def start(self):
|
||||
return defer.succeed(None)
|
||||
def get_endpoint_for(self, host, port):
|
||||
def stream_via(self, host, port):
|
||||
self.endpoints.append((host, port))
|
||||
return endpoints.HostnameEndpoint(reactor, host, port)
|
||||
|
||||
|
@ -456,16 +455,16 @@ class PregeneratedCode(ServerBase, ScriptsBase, unittest.TestCase):
|
|||
if fake_tor:
|
||||
send_cfg.tor = True
|
||||
send_cfg.transit_helper = self.transit
|
||||
tx_tm = FakeTorManager()
|
||||
with mock.patch("wormhole.tor_manager.TorManager",
|
||||
tx_tm = FakeTor()
|
||||
with mock.patch("wormhole.tor_manager.get_tor",
|
||||
return_value=tx_tm,
|
||||
) as mtx_tm:
|
||||
send_d = cmd_send.send(send_cfg)
|
||||
|
||||
recv_cfg.tor = True
|
||||
recv_cfg.transit_helper = self.transit
|
||||
rx_tm = FakeTorManager()
|
||||
with mock.patch("wormhole.tor_manager.TorManager",
|
||||
rx_tm = FakeTor()
|
||||
with mock.patch("wormhole.tor_manager.get_tor",
|
||||
return_value=rx_tm,
|
||||
) as mrx_tm:
|
||||
receive_d = cmd_receive.receive(recv_cfg)
|
||||
|
|
|
@ -3,335 +3,98 @@ import mock, io
|
|||
from twisted.trial import unittest
|
||||
from twisted.internet import defer
|
||||
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):
|
||||
def test_create(self):
|
||||
tm = TorManager(None)
|
||||
del tm
|
||||
def test_no_txtorcon(self):
|
||||
with mock.patch("wormhole.tor_manager.txtorcon", None):
|
||||
self.failureResultOf(get_tor(None), NoTorError)
|
||||
|
||||
def test_bad_args(self):
|
||||
e = self.assertRaises(TypeError,
|
||||
TorManager, None, launch_tor="not boolean")
|
||||
self.assertEqual(str(e), "launch_tor= must be boolean")
|
||||
e = self.assertRaises(TypeError,
|
||||
TorManager, None, tor_control_port=1234)
|
||||
self.assertEqual(str(e), "tor_control_port= must be str or None")
|
||||
e = self.assertRaises(ValueError,
|
||||
TorManager, None, launch_tor=True,
|
||||
tor_control_port="tcp:127.0.0.1:1234")
|
||||
self.assertEqual(str(e),
|
||||
f = self.failureResultOf(get_tor(None, launch_tor="not boolean"),
|
||||
TypeError)
|
||||
self.assertEqual(str(f.value), "launch_tor= must be boolean")
|
||||
|
||||
f = self.failureResultOf(get_tor(None, tor_control_port=1234),
|
||||
TypeError)
|
||||
self.assertEqual(str(f.value), "tor_control_port= must be str or None")
|
||||
f = self.failureResultOf(get_tor(None, launch_tor=True,
|
||||
tor_control_port="tcp:127.0.0.1:1234"),
|
||||
ValueError)
|
||||
self.assertEqual(str(f.value),
|
||||
"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):
|
||||
reactor = object()
|
||||
my_tor = X() # object() didn't like providedBy()
|
||||
launch_d = defer.Deferred()
|
||||
stderr = io.StringIO()
|
||||
tc = mock.Mock()
|
||||
mock_TorConfig = mock.patch("wormhole.tor_manager.TorConfig",
|
||||
return_value=tc)
|
||||
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)
|
||||
tp = mock.Mock()
|
||||
lt_d.callback(tp)
|
||||
res = self.successResultOf(d)
|
||||
self.assertEqual(res, None)
|
||||
self.assertIs(tm._tor_protocol, tp)
|
||||
self.assertIs(tm._tor_config, tc)
|
||||
self.assertEqual(mtc.mock_calls, [mock.call()])
|
||||
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")])
|
||||
with mock.patch("wormhole.tor_manager.txtorcon.launch",
|
||||
side_effect=launch_d) as launch:
|
||||
d = get_tor(reactor, launch_tor=True, stderr=stderr)
|
||||
self.assertNoResult(d)
|
||||
self.assertEqual(launch.mock_calls, [mock.call(reactor)])
|
||||
launch_d.callback(my_tor)
|
||||
tor = self.successResultOf(d)
|
||||
self.assertIs(tor, my_tor)
|
||||
self.assert_(ITorManager.providedBy(tor))
|
||||
self.assertEqual(stderr.getvalue(),
|
||||
" launching a new Tor process, this may take a while..\n")
|
||||
|
||||
def _do_test_try_control_port(self, socks_ports, exp_socks_desc,
|
||||
btc_exception=None, tcfp_exception=None):
|
||||
def test_connect(self):
|
||||
reactor = object()
|
||||
my_tor = X() # object() didn't like providedBy()
|
||||
tcp = "port"
|
||||
connect_d = defer.Deferred()
|
||||
stderr = io.StringIO()
|
||||
ep = object()
|
||||
mock_clientFromString = mock.patch("wormhole.tor_manager.clientFromString",
|
||||
return_value=ep)
|
||||
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)
|
||||
with mock.patch("wormhole.tor_manager.txtorcon.connect",
|
||||
side_effect=connect_d) as connect:
|
||||
d = get_tor(reactor, tor_control_port=tcp, stderr=stderr)
|
||||
self.assertNoResult(d)
|
||||
self.assertEqual(connect.mock_calls, [mock.call(reactor, tcp)])
|
||||
connect_d.callback(my_tor)
|
||||
tor = self.successResultOf(d)
|
||||
self.assertIs(tor, my_tor)
|
||||
self.assert_(ITorManager.providedBy(tor))
|
||||
self.assertEqual(stderr.getvalue(), " using Tor via control port\n")
|
||||
|
||||
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.assertEqual(cfs.mock_calls,
|
||||
[mock.call(reactor, control_port)])
|
||||
self.assertEqual(btc.mock_calls,
|
||||
[mock.call(ep, build_state=False)])
|
||||
self.assertEqual(torconfig.from_protocol.mock_calls, [])
|
||||
|
||||
btc_d.callback(tproto)
|
||||
# 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):
|
||||
def test_connect_fails(self):
|
||||
reactor = object()
|
||||
tcp = "port"
|
||||
connect_d = defer.Deferred()
|
||||
stderr = io.StringIO()
|
||||
ep = object()
|
||||
mock_clientFromString = mock.patch("wormhole.tor_manager.clientFromString",
|
||||
return_value=ep)
|
||||
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)
|
||||
with mock.patch("wormhole.tor_manager.txtorcon.connect",
|
||||
side_effect=connect_d) as connect:
|
||||
d = get_tor(reactor, tor_control_port=tcp, stderr=stderr)
|
||||
self.assertNoResult(d)
|
||||
self.assertEqual(connect.mock_calls, [mock.call(reactor, tcp)])
|
||||
|
||||
control_port = object()
|
||||
connect_d.errback(ConnectError())
|
||||
tor = self.successResultOf(d)
|
||||
self.assertIsInstance(tor, SocksOnlyTor)
|
||||
self.assert_(ITorManager.providedBy(tor))
|
||||
self.assertEqual(tor._reactor, reactor)
|
||||
self.assertEqual(stderr.getvalue(),
|
||||
" unable to find Tor control port, using SOCKS\n")
|
||||
|
||||
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)
|
||||
|
||||
if btc_exc:
|
||||
btc_d.errback(btc_exc)
|
||||
else:
|
||||
btc_d.callback(tproto)
|
||||
assert tcfp_exc
|
||||
tcfp_d.errback(tcfp_exc)
|
||||
|
||||
res = self.successResultOf(d)
|
||||
self.assertEqual(res, (None, None, None))
|
||||
|
||||
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):
|
||||
class SocksOnly(unittest.TestCase):
|
||||
def test_tor(self):
|
||||
reactor = object()
|
||||
stderr = io.StringIO()
|
||||
tm = TorManager(reactor, stderr=stderr)
|
||||
tm._tor_socks_endpoint = tse = object()
|
||||
exp_ep = object()
|
||||
with mock.patch("wormhole.tor_manager.TorClientEndpoint",
|
||||
return_value=exp_ep) as tce:
|
||||
ep = tm.get_endpoint_for("example.com", 1234)
|
||||
self.assertIs(ep, exp_ep)
|
||||
self.assertEqual(tce.mock_calls,
|
||||
[mock.call(b"example.com", 1234,
|
||||
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, [])
|
||||
sot = SocksOnlyTor(reactor)
|
||||
fake_ep = object()
|
||||
with mock.patch("wormhole.tor_manager.txtorcon.TorClientEndpoint",
|
||||
return_value=fake_ep) as tce:
|
||||
ep = sot.stream_via("host", "port")
|
||||
self.assertIs(ep, fake_ep)
|
||||
self.assertEqual(tce.mock_calls, [mock.call("host", "port",
|
||||
socks_endpoint=None,
|
||||
tls=False,
|
||||
reactor=reactor)])
|
||||
|
||||
|
||||
|
|
|
@ -149,14 +149,14 @@ class Hints(unittest.TestCase):
|
|||
self.assertIsInstance(efho(transit.DirectTCPV1Hint("host", 1234, 0.0)),
|
||||
endpoints.HostnameEndpoint)
|
||||
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)
|
||||
c._tor_manager = mock.Mock()
|
||||
c._tor = mock.Mock()
|
||||
def tor_ep(hostname, port):
|
||||
if hostname == "non-public":
|
||||
return None
|
||||
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)),
|
||||
("tor_ep", "host", 1234))
|
||||
self.assertEqual(efho(transit.TorTCPV1Hint("host2.onion", 1234, 0.0)),
|
||||
|
@ -1470,7 +1470,7 @@ class Transit(unittest.TestCase):
|
|||
@inlineCallbacks
|
||||
def test_success_direct_tor(self):
|
||||
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")
|
||||
hints = yield s.get_connection_hints() # start the listener
|
||||
del hints
|
||||
|
@ -1491,7 +1491,7 @@ class Transit(unittest.TestCase):
|
|||
@inlineCallbacks
|
||||
def test_success_direct_tor_relay(self):
|
||||
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")
|
||||
hints = yield s.get_connection_hints() # start the listener
|
||||
del hints
|
||||
|
|
|
@ -1,171 +1,99 @@
|
|||
from __future__ import print_function, unicode_literals
|
||||
import sys, re
|
||||
import six
|
||||
from zope.interface import implementer
|
||||
import sys
|
||||
from attr import attrs, attrib
|
||||
from zope.interface.declarations import directlyProvides
|
||||
from twisted.internet.defer import inlineCallbacks, returnValue
|
||||
from twisted.internet.error import ConnectError
|
||||
from twisted.internet.endpoints import clientFromString
|
||||
try:
|
||||
from txtorcon import (TorConfig, launch_tor, build_tor_connection,
|
||||
DEFAULT_VALUE, TorClientEndpoint)
|
||||
import txtorcon
|
||||
except ImportError:
|
||||
TorConfig = None
|
||||
launch_tor = None
|
||||
build_tor_connection = None
|
||||
TorClientEndpoint = None
|
||||
DEFAULT_VALUE = "DEFAULT_VALUE"
|
||||
import ipaddress
|
||||
from . import _interfaces
|
||||
txtorcon = None
|
||||
from . import _interfaces, errors
|
||||
from .timing import DebugTiming
|
||||
from .transit import allocate_tcp_port
|
||||
|
||||
@attrs
|
||||
class SocksOnlyTor(object):
|
||||
_reactor = attrib()
|
||||
|
||||
@implementer(_interfaces.ITorManager)
|
||||
class TorManager:
|
||||
def __init__(self, reactor, launch_tor=False, tor_control_port=None,
|
||||
timing=None, stderr=sys.stderr):
|
||||
"""
|
||||
If launch_tor=True, I will try to launch a new Tor process, ask it
|
||||
for its SOCKS and control ports, and use those for outbound
|
||||
connections (and inbound onion-service listeners, if necessary).
|
||||
def stream_via(self, host, port, tls=False):
|
||||
return txtorcon.TorClientEndpoint(
|
||||
host, port,
|
||||
socks_endpoint=None, # tries localhost:9050 and 9150
|
||||
tls=tls,
|
||||
reactor=self._reactor,
|
||||
)
|
||||
|
||||
Otherwise if tor_control_port is provided, I will attempt to connect
|
||||
to an existing Tor's control port at the endpoint it specifies. I'll
|
||||
ask that Tor for its SOCKS port.
|
||||
@inlineCallbacks
|
||||
def get_tor(reactor, launch_tor=False, tor_control_port=None,
|
||||
timing=None, stderr=sys.stderr):
|
||||
"""
|
||||
If launch_tor=True, I will try to launch a new Tor process, ask it
|
||||
for its SOCKS and control ports, and use those for outbound
|
||||
connections (and inbound onion-service listeners, if necessary).
|
||||
|
||||
With no arguments, I will try to connect to an existing Tor's 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
|
||||
ask that Tor for its SOCKS port. If none are successful, I'll attempt
|
||||
to do SOCKS to tcp:127.0.0.1:9050.
|
||||
Otherwise if tor_control_port is provided, I will attempt to connect
|
||||
to an existing Tor's control port at the endpoint it specifies. I'll
|
||||
ask that Tor for its SOCKS port.
|
||||
|
||||
If I am unable to make a SOCKS connection, the initial connection to
|
||||
the Rendezvous Server will fail, and the program will terminate.
|
||||
With no arguments, I will try to connect to an existing Tor's 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
|
||||
ask that Tor for its SOCKS port. If none are successful, I'll
|
||||
attempt to do SOCKS to the usual places: [tcp:127.0.0.1:9050,
|
||||
tcp:127.0.0.1:9150].
|
||||
|
||||
Control-port connections can only succeed if I can authenticate (by
|
||||
reading a cookie file named by the Tor process), so the current user
|
||||
must have permission to read that file (either they started Tor, e.g.
|
||||
TorBrowser, or they are in a unix group that's been given access,
|
||||
e.g. debian-tor).
|
||||
"""
|
||||
# rationale: launching a new Tor takes a long time, so only do it if
|
||||
# the user specifically asks for it with --launch-tor. Using an
|
||||
# existing Tor should be much faster, but still requires general
|
||||
# permission via --tor.
|
||||
If I am unable to make a SOCKS connection, the initial connection to
|
||||
the Rendezvous Server will fail, and the program will terminate.
|
||||
|
||||
self._reactor = reactor
|
||||
if not isinstance(launch_tor, bool): # note: False is int
|
||||
raise TypeError("launch_tor= must be boolean")
|
||||
if not isinstance(tor_control_port, (type(""), type(None))):
|
||||
raise TypeError("tor_control_port= must be str or None")
|
||||
if launch_tor and tor_control_port is not None:
|
||||
raise ValueError("cannot combine --launch-tor and --tor-control-port=")
|
||||
self._launch_tor = launch_tor
|
||||
self._tor_control_port = tor_control_port
|
||||
self._timing = timing or DebugTiming()
|
||||
self._stderr = stderr
|
||||
Control-port connections can only succeed if I can authenticate (by
|
||||
reading a cookie file named by the Tor process), so the current user
|
||||
must have permission to read that file (either they started Tor, e.g.
|
||||
TorBrowser, or they are in a unix group that's been given access,
|
||||
e.g. debian-tor).
|
||||
"""
|
||||
# rationale: launching a new Tor takes a long time, so only do it if
|
||||
# the user specifically asks for it with --launch-tor. Using an
|
||||
# existing Tor should be much faster, but still requires general
|
||||
# permission via --tor.
|
||||
|
||||
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)
|
||||
if not txtorcon:
|
||||
raise errors.NoTorError()
|
||||
|
||||
@inlineCallbacks
|
||||
def start(self):
|
||||
# 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
|
||||
# authentication cookie). If we're only acting as a client, we don't
|
||||
# need the control port.
|
||||
if not isinstance(launch_tor, bool): # note: False is int
|
||||
raise TypeError("launch_tor= must be boolean")
|
||||
if not isinstance(tor_control_port, (type(""), type(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:
|
||||
raise ValueError("cannot combine --launch-tor and --tor-control-port=")
|
||||
timing = timing or DebugTiming()
|
||||
|
||||
if self._launch_tor:
|
||||
print(" launching a new Tor process, this may take a while..",
|
||||
file=self._stderr)
|
||||
with self._timing.add("launch tor"):
|
||||
(tproto, tconfig, socks_desc) = yield self._do_launch_tor()
|
||||
else:
|
||||
control_ports = ["unix:/var/run/tor/control", # debian tor package
|
||||
"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)
|
||||
# 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
|
||||
# authentication cookie). If we're only acting as a client, we don't
|
||||
# need the control port.
|
||||
|
||||
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:
|
||||
tproto = yield build_tor_connection(ep, build_state=False)
|
||||
# now wait for bootstrap
|
||||
tconfig = yield TorConfig.from_protocol(tproto)
|
||||
except (ValueError, ConnectError):
|
||||
returnValue(NOPE)
|
||||
socks_ports = list(tconfig.SocksPort)
|
||||
socks_port = socks_ports[0] # TODO: when might there be multiple?
|
||||
# I've seen "9050", and "unix:/var/run/tor/socks WorldWritable"
|
||||
pieces = socks_port.split()
|
||||
p = pieces[0]
|
||||
if p == DEFAULT_VALUE:
|
||||
socks_desc = "tcp:127.0.0.1:9050"
|
||||
elif re.search('^\d+$', p):
|
||||
socks_desc = "tcp:127.0.0.1:%s" % p
|
||||
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
|
||||
if launch_tor:
|
||||
print(" launching a new Tor process, this may take a while..",
|
||||
file=stderr)
|
||||
with timing.add("launch tor"):
|
||||
tor = yield txtorcon.launch(reactor,
|
||||
#data_directory=,
|
||||
#tor_binary=,
|
||||
)
|
||||
else:
|
||||
with timing.add("find tor"):
|
||||
try:
|
||||
# If tor_control_port is None (the default), txtorcon
|
||||
# will look through a list of usual places. If it is set,
|
||||
# it will look only in the place we tell it to.
|
||||
tor = yield txtorcon.connect(reactor, tor_control_port)
|
||||
print(" using Tor via control port", file=stderr)
|
||||
except Exception:
|
||||
# TODO: make this more specific. I think connect() is
|
||||
# likely to throw a reactor.connectTCP -type error, like
|
||||
# ConnectionFailed or ConnectionRefused or something
|
||||
print(" unable to find Tor control port, using SOCKS",
|
||||
file=stderr)
|
||||
tor = SocksOnlyTor(reactor)
|
||||
directlyProvides(tor, _interfaces.ITorManager)
|
||||
returnValue(tor)
|
||||
|
|
|
@ -589,7 +589,7 @@ class Common:
|
|||
RELAY_DELAY = 2.0
|
||||
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):
|
||||
self._side = bytes_to_hexstr(os.urandom(8)) # unicode
|
||||
if transit_relay:
|
||||
|
@ -603,7 +603,7 @@ class Common:
|
|||
self._transit_relays = []
|
||||
self._their_direct_hints = [] # hintobjs
|
||||
self._our_relay_hints = set(self._transit_relays)
|
||||
self._tor_manager = tor_manager
|
||||
self._tor = tor
|
||||
self._transit_key = None
|
||||
self._no_listen = no_listen
|
||||
self._waiting_for_transit_key = []
|
||||
|
@ -614,7 +614,7 @@ class Common:
|
|||
self._timing.add("transit")
|
||||
|
||||
def _build_listener(self):
|
||||
if self._no_listen or self._tor_manager:
|
||||
if self._no_listen or self._tor:
|
||||
return ([], None)
|
||||
portnum = allocate_tcp_port()
|
||||
addresses = ipaddrs.find_addresses()
|
||||
|
@ -820,7 +820,7 @@ class Common:
|
|||
if not ep:
|
||||
continue
|
||||
description = "->%s" % describe_hint_obj(hint_obj)
|
||||
if self._tor_manager:
|
||||
if self._tor:
|
||||
description = "tor" + description
|
||||
d = self._start_connector(ep, description)
|
||||
contenders.append(d)
|
||||
|
@ -847,7 +847,7 @@ class Common:
|
|||
if not ep:
|
||||
continue
|
||||
description = "->relay:%s" % describe_hint_obj(hint_obj)
|
||||
if self._tor_manager:
|
||||
if self._tor:
|
||||
description = "tor" + description
|
||||
d = task.deferLater(self._reactor, relay_delay,
|
||||
self._start_connector, ep, description,
|
||||
|
@ -887,12 +887,14 @@ class Common:
|
|||
return d
|
||||
|
||||
def _endpoint_from_hint_obj(self, hint):
|
||||
if self._tor_manager:
|
||||
if self._tor:
|
||||
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
|
||||
return self._tor_manager.get_endpoint_for(hint.hostname,
|
||||
hint.port)
|
||||
try:
|
||||
return self._tor.stream_via(hint.hostname, hint.port)
|
||||
except ValueError:
|
||||
return None
|
||||
return None
|
||||
if isinstance(hint, DirectTCPV1Hint):
|
||||
return endpoints.HostnameEndpoint(self._reactor,
|
||||
|
|
|
@ -279,7 +279,7 @@ class _DeferredWormhole(object):
|
|||
|
||||
def create(appid, relay_url, reactor, # use keyword args for everything else
|
||||
versions={},
|
||||
delegate=None, journal=None, tor_manager=None,
|
||||
delegate=None, journal=None, tor=None,
|
||||
timing=None,
|
||||
stderr=sys.stderr):
|
||||
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["app_versions"] = versions # app-specific capabilities
|
||||
b = Boss(w, side, relay_url, appid, wormhole_versions,
|
||||
reactor, journal, tor_manager, timing)
|
||||
reactor, journal, tor, timing)
|
||||
w._set_boss(b)
|
||||
b.start()
|
||||
return w
|
||||
|
||||
## def from_serialized(serialized, reactor, delegate,
|
||||
## journal=None, tor_manager=None,
|
||||
## journal=None, tor=None,
|
||||
## timing=None, stderr=sys.stderr):
|
||||
## assert serialized["serialized_wormhole_version"] == 1
|
||||
## timing = timing or DebugTiming()
|
||||
|
|
|
@ -2,8 +2,7 @@ import json
|
|||
from twisted.internet.defer import inlineCallbacks, returnValue
|
||||
|
||||
from . import wormhole
|
||||
from .tor_manager import TorManager
|
||||
from .errors import NoTorError
|
||||
from .tor_manager import get_tor
|
||||
|
||||
@inlineCallbacks
|
||||
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)
|
||||
:type on_code: single-argument callable
|
||||
"""
|
||||
tm = None
|
||||
tor = None
|
||||
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
|
||||
# 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
|
||||
# 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:
|
||||
wh.allocate_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)
|
||||
:type on_code: single-argument callable
|
||||
"""
|
||||
tm = None
|
||||
tor = None
|
||||
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
|
||||
# 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
|
||||
# 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:
|
||||
wh.allocate_code()
|
||||
code = yield wh.get_code()
|
||||
|
|
Loading…
Reference in New Issue
Block a user