diff --git a/src/wormhole/test/test_tor_manager.py b/src/wormhole/test/test_tor_manager.py index dea5e5d..f14e97c 100644 --- a/src/wormhole/test/test_tor_manager.py +++ b/src/wormhole/test/test_tor_manager.py @@ -4,7 +4,7 @@ from twisted.trial import unittest from twisted.internet import defer from twisted.internet.error import ConnectError -from ..tor_manager import get_tor +from ..tor_manager import get_tor, SocksOnlyTor from ..errors import NoTorError from .._interfaces import ITorManager @@ -62,7 +62,7 @@ class Tor(unittest.TestCase): tor = self.successResultOf(d) self.assertIs(tor, my_tor) self.assert_(ITorManager.providedBy(tor)) - self.assertEqual(stderr.getvalue(), " using Tor\n") + self.assertEqual(stderr.getvalue(), " using Tor via control port\n") def test_connect_fails(self): reactor = object() @@ -74,7 +74,27 @@ class Tor(unittest.TestCase): d = get_tor(reactor, tor_control_port=tcp, stderr=stderr) self.assertNoResult(d) self.assertEqual(connect.mock_calls, [mock.call(reactor, tcp)]) - connect_d.errback(ConnectError()) - self.failureResultOf(d, ConnectError) - self.assertEqual(stderr.getvalue(), - " unable to find control port, bailing\n") + + 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") + +class SocksOnly(unittest.TestCase): + def test_tor(self): + reactor = object() + 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)]) + + diff --git a/src/wormhole/tor_manager.py b/src/wormhole/tor_manager.py index fc3beaf..3aa2a11 100644 --- a/src/wormhole/tor_manager.py +++ b/src/wormhole/tor_manager.py @@ -1,5 +1,6 @@ from __future__ import print_function, unicode_literals import sys +from attr import attrs, attrib from zope.interface.declarations import directlyProvides from twisted.internet.defer import inlineCallbacks, returnValue try: @@ -9,6 +10,18 @@ except ImportError: from . import _interfaces, errors from .timing import DebugTiming +@attrs +class SocksOnlyTor(object): + _reactor = attrib() + + 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, + ) + @inlineCallbacks def get_tor(reactor, launch_tor=False, tor_control_port=None, timing=None, stderr=sys.stderr): @@ -18,14 +31,15 @@ def get_tor(reactor, launch_tor=False, tor_control_port=None, connections (and inbound onion-service listeners, if necessary). 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 + to an existing Tor's control port at the endpoint it specifies. I'll ask that Tor for its SOCKS port. 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. + 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]. If I am unable to make a SOCKS connection, the initial connection to the Rendezvous Server will fail, and the program will terminate. @@ -69,17 +83,17 @@ def get_tor(reactor, launch_tor=False, tor_control_port=None, 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", file=stderr) + print(" using Tor via control port", file=stderr) except Exception: - #socks_desc = "tcp:127.0.0.1:9050" # fallback - #print(" using Tor (SOCKS port %s)" % socks_desc, - # file=stderr) - print(" unable to find control port, bailing", + # 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) - # TODO: something nicer. I think connect() is likely to throw - # a reactor.connectTCP -type error, like ConnectionFailed or - # ConnectionRefused or something - raise + tor = SocksOnlyTor(reactor) directlyProvides(tor, _interfaces.ITorManager) returnValue(tor)