Merge branch 'new-txtorcon'
This commit is contained in:
commit
48b1f0257b
|
@ -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"
|
||||||
|
|
|
@ -21,4 +21,5 @@ after_success:
|
||||||
- codecov
|
- codecov
|
||||||
matrix:
|
matrix:
|
||||||
allow_failures:
|
allow_failures:
|
||||||
|
- python: "3.3"
|
||||||
- python: "nightly"
|
- 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.
|
(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.
|
||||||
|
|
||||||
|
|
||||||
|
|
19
setup.py
19
setup.py
|
@ -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,
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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):
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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, [])
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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()
|
||||||
|
|
|
@ -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()
|
||||||
|
|
2
tox.ini
2
tox.ini
|
@ -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
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue
Block a user