Skip to content

Commit

Permalink
Merge branch 'pr29'
Browse files Browse the repository at this point in the history
  • Loading branch information
warner committed Sep 1, 2016
2 parents 3fb7ae7 + 01b68e6 commit 1c2329f
Show file tree
Hide file tree
Showing 3 changed files with 95 additions and 73 deletions.
4 changes: 2 additions & 2 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,9 +61,9 @@ def run(self):
"cmdclass": commands,
"install_requires": ["twisted[tls] >= 16.0.0", "pyOpenSSL"],
"extras_require": {
"dev": ["mock", "txsocksx", "txtorcon >= 0.15.0", "txi2p"],
"dev": ["mock", "txsocksx", "txtorcon >= 0.16.1", "txi2p"],
"socks": ["txsocksx"],
"tor": ["txtorcon >= 0.15.0"],
"tor": ["txtorcon >= 0.16.1"],
"i2p": ["txi2p"],
},
}
Expand Down
47 changes: 22 additions & 25 deletions src/foolscap/connections/tor.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import os, re
from twisted.internet.interfaces import IStreamClientEndpoint
from twisted.internet.defer import inlineCallbacks, returnValue, succeed
from twisted.internet.endpoints import clientFromString
import ipaddress
from .. import observer

Expand Down Expand Up @@ -29,9 +30,8 @@ def is_non_public_numeric_address(host):

@implementer(IConnectionHintHandler)
class _Common:
# subclasses must:
# define _connect(reactor)
# set self._socks_hostname, self._socks_portnum
# subclasses must define self._connect(reactor), which fires with the
# socks Endpoint that TorClientEndpoint can use

def __init__(self):
self._connected = False
Expand All @@ -40,7 +40,6 @@ def __init__(self):
def _maybe_connect(self, reactor):
if not self._connected:
self._connected = True
# connect
d = self._connect(reactor)
d.addBoth(self._when_connected.fire)
return self._when_connected.whenFired()
Expand All @@ -55,38 +54,34 @@ def hint_to_endpoint(self, hint, reactor):
host, portnum = mo.group(1), int(mo.group(2))
if is_non_public_numeric_address(host):
raise InvalidHintError("ignoring non-Tor-able ipaddr %s" % host)
yield self._maybe_connect(reactor)
socks_endpoint = yield self._maybe_connect(reactor)
# 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 = txtorcon.TorClientEndpoint(host, portnum,
socks_hostname=self._socks_hostname,
socks_port=self._socks_portnum)
socks_endpoint=socks_endpoint)
returnValue( (ep, host) )


# note: TorClientEndpoint imports 'reactor' itself, doesn't provide override.
# This will be fixed in txtorcon 1.0

class _SocksTor(_Common):
def __init__(self, hostname=None, portnum=None):
def __init__(self, socks_endpoint=None):
_Common.__init__(self)
self._connnected = True # no need to call _connect()
self._socks_hostname = hostname
self._socks_portnum = portnum
# portnum=None means to use defaults: 9050, then 9150
self._socks_endpoint = socks_endpoint
# socks_endpoint=None means to use defaults: TCP to 127.0.0.1 with
# 9050, then 9150
def _connect(self, reactor):
return succeed(None)
return succeed(self._socks_endpoint)

def default_socks():
# TorClientEndpoint knows how to cycle through a built-in set of socks
# ports, but it doesn't know to set the hostname to localhost
return _SocksTor("127.0.0.1")
return _SocksTor()

def socks_port(host, portnum):
assert isinstance(portnum, int)
return _SocksTor(host, portnum)
def socks_endpoint(tor_socks_endpoint):
assert IStreamClientEndpoint.providedBy(tor_socks_endpoint)
return _SocksTor(tor_socks_endpoint)


class _LaunchedTor(_Common):
Expand Down Expand Up @@ -115,16 +110,17 @@ def _connect(self, reactor):

#config.ControlPort = allocate_tcp_port() # defaults to 9052
config.SocksPort = allocate_tcp_port()
self._socks_hostname = "127.0.0.1"
self._socks_portnum = config.SocksPort
socks_desc = "tcp:127.0.0.1:%d" % config.SocksPort
self._socks_desc = socks_desc # stash for tests
socks_endpoint = clientFromString(reactor, socks_desc)

#print "launching tor"
tpp = yield txtorcon.launch_tor(config, reactor,
tor_binary=self._tor_binary)
#print "launched"
# gives a TorProcessProtocol with .tor_protocol
self._tor_protocol = tpp.tor_protocol
returnValue(True)
returnValue(socks_endpoint)

def launch(data_directory=None, tor_binary=None):
"""Return a handler which launches a new Tor process (once).
Expand Down Expand Up @@ -159,9 +155,10 @@ def _connect(self, reactor):
p = "9050"
try:
portnum = int(p)
self._socks_hostname = "127.0.0.1"
self._socks_portnum = portnum
return
socks_desc = "tcp:127.0.0.1:%d" % portnum
self._socks_desc = socks_desc # stash for tests
socks_endpoint = clientFromString(reactor, socks_desc)
returnValue(socks_endpoint)
except ValueError:
pass
raise ValueError("could not use config.SocksPort: %r" % (ports,))
Expand Down
117 changes: 71 additions & 46 deletions src/foolscap/test/test_connection.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
from zope.interface import implementer
from twisted.trial import unittest
from twisted.internet import endpoints, defer, reactor
from twisted.internet.endpoints import clientFromString
from twisted.internet.defer import inlineCallbacks
from twisted.application import service
import txtorcon
Expand Down Expand Up @@ -223,18 +224,25 @@ class Empty:
class Tor(unittest.TestCase):
@inlineCallbacks
def test_default_socks(self):
with mock.patch("foolscap.connections.tor.txtorcon") as ttc:
ttc.TorClientEndpoint = tce = mock.Mock()
with mock.patch("foolscap.connections.tor.txtorcon.TorClientEndpoint"
) as tce:
tce.return_value = expected_ep = object()
h = tor.default_socks()
res = yield h.hint_to_endpoint("tcp:example.com:1234", reactor)
self.assertEqual(tce.mock_calls,
[mock.call("example.com", 1234,
socks_hostname="127.0.0.1",
socks_port=None)])
ep, host = res
self.assertIdentical(ep, expected_ep)
self.assertEqual(host, "example.com")
socks_endpoint=None)])
ep, host = res
self.assertIdentical(ep, expected_ep)
self.assertEqual(host, "example.com")

@inlineCallbacks
def test_default_socks_real(self):
h = tor.default_socks()
res = yield h.hint_to_endpoint("tcp:example.com:1234", reactor)
ep, host = res
self.assertIsInstance(ep, txtorcon.endpoints.TorClientEndpoint)
self.assertEqual(host, "example.com")

def test_badaddr(self):
isnon = tor.is_non_public_numeric_address
Expand All @@ -261,14 +269,28 @@ def test_default_socks_badaddr(self):
self.assertEqual(str(f), "unrecognized TCP/Tor hint")

@inlineCallbacks
def test_socks_port(self):
h = tor.socks_port("socks_host", 100)
def test_socks_endpoint(self):
tor_socks_endpoint = clientFromString(reactor, "tcp:socks_host:100")
with mock.patch("foolscap.connections.tor.txtorcon.TorClientEndpoint"
) as tce:
tce.return_value = expected_ep = object()
h = tor.socks_endpoint(tor_socks_endpoint)
res = yield h.hint_to_endpoint("tcp:example.com:1234", reactor)
self.assertEqual(tce.mock_calls,
[mock.call("example.com", 1234,
socks_endpoint=tor_socks_endpoint)])
ep, host = res
self.assertIs(ep, expected_ep)
self.assertEqual(host, "example.com")

@inlineCallbacks
def test_socks_endpoint_real(self):
tor_socks_endpoint = clientFromString(reactor, "tcp:socks_host:100")
h = tor.socks_endpoint(tor_socks_endpoint)
res = yield h.hint_to_endpoint("tcp:example.com:1234", reactor)
ep, host = res
self.assertIsInstance(ep, txtorcon.endpoints.TorClientEndpoint)
self.assertEqual(host, "example.com")
self.assertEqual(ep.socks_hostname, "socks_host")
self.assertEqual(ep.socks_port, 100)

@inlineCallbacks
def test_launch(self):
Expand All @@ -277,16 +299,17 @@ def test_launch(self):
h = tor.launch()
fake_reactor = object()
with mock.patch("txtorcon.launch_tor", return_value=tpp) as lt:
# we ignore the return value of hint_to_endpoint()
yield h.hint_to_endpoint("tor:foo.onion:29212", fake_reactor)
res = yield h.hint_to_endpoint("tor:foo.onion:29212", fake_reactor)
self.assertEqual(len(lt.mock_calls), 1)
args,kwargs = lt.mock_calls[0][1:]
self.assertIdentical(args[0], h.config)
self.assertIdentical(args[1], fake_reactor)
self.assertEqual(kwargs, {"tor_binary": None})
self.assertEqual(h._socks_hostname, "127.0.0.1")
# the socks port will be allocated by launch_tor, so it should match
self.assertEqual(h._socks_portnum, h.config.SocksPort)
ep, host = res
self.assertIsInstance(ep, txtorcon.endpoints.TorClientEndpoint)
self.assertEqual(host, "foo.onion")
# launch_tor will allocate a local TCP port for SOCKS
self.assert_(h._socks_desc.startswith("tcp:127.0.0.1:"), h._socks_desc)

@inlineCallbacks
def test_launch_tor_binary(self):
Expand All @@ -295,16 +318,16 @@ def test_launch_tor_binary(self):
h = tor.launch(tor_binary="/bin/tor")
fake_reactor = object()
with mock.patch("txtorcon.launch_tor", return_value=tpp) as lt:
# we ignore the return value of hint_to_endpoint()
yield h.hint_to_endpoint("tor:foo.onion:29212", fake_reactor)
res = yield h.hint_to_endpoint("tor:foo.onion:29212", fake_reactor)
self.assertEqual(len(lt.mock_calls), 1)
args,kwargs = lt.mock_calls[0][1:]
self.assertIdentical(args[0], h.config)
self.assertIdentical(args[1], fake_reactor)
self.assertEqual(kwargs, {"tor_binary": "/bin/tor"})
self.assertEqual(h._socks_hostname, "127.0.0.1")
# the socks port will be allocated by launch_tor, so it should match
self.assertEqual(h._socks_portnum, h.config.SocksPort)
ep, host = res
self.assertIsInstance(ep, txtorcon.endpoints.TorClientEndpoint)
self.assertEqual(host, "foo.onion")
self.assert_(h._socks_desc.startswith("tcp:127.0.0.1:"), h._socks_desc)

@inlineCallbacks
def test_launch_data_directory(self):
Expand All @@ -314,17 +337,17 @@ def test_launch_data_directory(self):
h = tor.launch(data_directory=datadir)
fake_reactor = object()
with mock.patch("txtorcon.launch_tor", return_value=tpp) as lt:
# we ignore the return value of hint_to_endpoint()
yield h.hint_to_endpoint("tor:foo.onion:29212", fake_reactor)
res = yield h.hint_to_endpoint("tor:foo.onion:29212", fake_reactor)
self.assertEqual(len(lt.mock_calls), 1)
args,kwargs = lt.mock_calls[0][1:]
self.assertIdentical(args[0], h.config)
self.assertIdentical(args[1], fake_reactor)
self.assertEqual(kwargs, {"tor_binary": None})
self.assertEqual(h.config.DataDirectory, datadir)
self.assertEqual(h._socks_hostname, "127.0.0.1")
# the socks port will be allocated by launch_tor, so it should match
self.assertEqual(h._socks_portnum, h.config.SocksPort)
ep, host = res
self.assertIsInstance(ep, txtorcon.endpoints.TorClientEndpoint)
self.assertEqual(host, "foo.onion")
self.assert_(h._socks_desc.startswith("tcp:127.0.0.1:"), h._socks_desc)

@inlineCallbacks
def test_launch_data_directory_exists(self):
Expand All @@ -335,17 +358,17 @@ def test_launch_data_directory_exists(self):
h = tor.launch(data_directory=datadir)
fake_reactor = object()
with mock.patch("txtorcon.launch_tor", return_value=tpp) as lt:
# we ignore the return value of hint_to_endpoint()
yield h.hint_to_endpoint("tor:foo.onion:29212", fake_reactor)
res = yield h.hint_to_endpoint("tor:foo.onion:29212", fake_reactor)
self.assertEqual(len(lt.mock_calls), 1)
args,kwargs = lt.mock_calls[0][1:]
self.assertIdentical(args[0], h.config)
self.assertIdentical(args[1], fake_reactor)
self.assertEqual(kwargs, {"tor_binary": None})
self.assertEqual(h.config.DataDirectory, datadir)
self.assertEqual(h._socks_hostname, "127.0.0.1")
# the socks port will be allocated by launch_tor, so it should match
self.assertEqual(h._socks_portnum, h.config.SocksPort)
ep, host = res
self.assertIsInstance(ep, txtorcon.endpoints.TorClientEndpoint)
self.assertEqual(host, "foo.onion")
self.assert_(h._socks_desc.startswith("tcp:127.0.0.1:"), h._socks_desc)

@inlineCallbacks
def test_control_endpoint(self):
Expand All @@ -357,15 +380,16 @@ def test_control_endpoint(self):
# from actually talking to a Tor daemon (which probably doesn't exist
# on this host).
config = Empty()
config.SocksPort = ["9050"]
config.SocksPort = ["1234"]
with mock.patch("txtorcon.build_tor_connection",
return_value=None):
with mock.patch("txtorcon.TorConfig.from_protocol",
return_value=config):
# we ignore the return value of hint_to_endpoint()
yield h.hint_to_endpoint("tor:foo.onion:29212", reactor)
self.assertEqual(h._socks_hostname, "127.0.0.1")
self.assertEqual(h._socks_portnum, 9050)
res = yield h.hint_to_endpoint("tor:foo.onion:29212", reactor)
ep, host = res
self.assertIsInstance(ep, txtorcon.endpoints.TorClientEndpoint)
self.assertEqual(host, "foo.onion")
self.assertEqual(h._socks_desc, "tcp:127.0.0.1:1234")

@inlineCallbacks
def test_control_endpoint_default(self):
Expand All @@ -377,25 +401,27 @@ def test_control_endpoint_default(self):
return_value=None):
with mock.patch("txtorcon.TorConfig.from_protocol",
return_value=config):
# we ignore the return value of hint_to_endpoint()
yield h.hint_to_endpoint("tor:foo.onion:29212", reactor)
self.assertEqual(h._socks_hostname, "127.0.0.1")
self.assertEqual(h._socks_portnum, 9050)
res = yield h.hint_to_endpoint("tor:foo.onion:29212", reactor)
ep, host = res
self.assertIsInstance(ep, txtorcon.endpoints.TorClientEndpoint)
self.assertEqual(host, "foo.onion")
self.assertEqual(h._socks_desc, "tcp:127.0.0.1:9050")

@inlineCallbacks
def test_control_endpoint_non_numeric(self):
control_ep = endpoints.HostnameEndpoint(reactor, "localhost", 9051)
h = tor.control_endpoint(control_ep)
config = Empty()
config.SocksPort = ["unix:var/run/tor/socks WorldWritable", "9050"]
config.SocksPort = ["unix:var/run/tor/socks WorldWritable", "1234"]
with mock.patch("txtorcon.build_tor_connection",
return_value=None):
with mock.patch("txtorcon.TorConfig.from_protocol",
return_value=config):
# we ignore the return value of hint_to_endpoint()
yield h.hint_to_endpoint("tor:foo.onion:29212", reactor)
self.assertEqual(h._socks_hostname, "127.0.0.1")
self.assertEqual(h._socks_portnum, 9050)
res = yield h.hint_to_endpoint("tor:foo.onion:29212", reactor)
ep, host = res
self.assertIsInstance(ep, txtorcon.endpoints.TorClientEndpoint)
self.assertEqual(host, "foo.onion")
self.assertEqual(h._socks_desc, "tcp:127.0.0.1:1234")

@inlineCallbacks
def test_control_endpoint_no_port(self):
Expand All @@ -407,7 +433,6 @@ def test_control_endpoint_no_port(self):
return_value=None):
with mock.patch("txtorcon.TorConfig.from_protocol",
return_value=config):
# we ignore the return value of hint_to_endpoint()
d = h.hint_to_endpoint("tor:foo.onion:29212", reactor)
f = yield self.assertFailure(d, ValueError)
self.assertIn("could not use config.SocksPort", str(f))
Expand Down

0 comments on commit 1c2329f

Please sign in to comment.