fall backs to SOCKS if we can't reach control port

This commit is contained in:
Brian Warner 2017-05-23 15:01:57 -07:00
parent 46a9c9eeb9
commit 269faf190a
2 changed files with 53 additions and 19 deletions

View File

@ -4,7 +4,7 @@ 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 ..tor_manager import get_tor from ..tor_manager import get_tor, SocksOnlyTor
from ..errors import NoTorError from ..errors import NoTorError
from .._interfaces import ITorManager from .._interfaces import ITorManager
@ -62,7 +62,7 @@ class Tor(unittest.TestCase):
tor = self.successResultOf(d) tor = self.successResultOf(d)
self.assertIs(tor, my_tor) self.assertIs(tor, my_tor)
self.assert_(ITorManager.providedBy(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): def test_connect_fails(self):
reactor = object() reactor = object()
@ -74,7 +74,27 @@ class Tor(unittest.TestCase):
d = get_tor(reactor, tor_control_port=tcp, stderr=stderr) d = get_tor(reactor, tor_control_port=tcp, stderr=stderr)
self.assertNoResult(d) self.assertNoResult(d)
self.assertEqual(connect.mock_calls, [mock.call(reactor, tcp)]) self.assertEqual(connect.mock_calls, [mock.call(reactor, tcp)])
connect_d.errback(ConnectError()) connect_d.errback(ConnectError())
self.failureResultOf(d, ConnectError) tor = self.successResultOf(d)
self.assertIsInstance(tor, SocksOnlyTor)
self.assert_(ITorManager.providedBy(tor))
self.assertEqual(tor._reactor, reactor)
self.assertEqual(stderr.getvalue(), self.assertEqual(stderr.getvalue(),
" unable to find control port, bailing\n") " 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)])

View File

@ -1,5 +1,6 @@
from __future__ import print_function, unicode_literals from __future__ import print_function, unicode_literals
import sys import sys
from attr import attrs, attrib
from zope.interface.declarations import directlyProvides from zope.interface.declarations import directlyProvides
from twisted.internet.defer import inlineCallbacks, returnValue from twisted.internet.defer import inlineCallbacks, returnValue
try: try:
@ -9,6 +10,18 @@ except ImportError:
from . import _interfaces, errors from . import _interfaces, errors
from .timing import DebugTiming 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 @inlineCallbacks
def get_tor(reactor, launch_tor=False, tor_control_port=None, def get_tor(reactor, launch_tor=False, tor_control_port=None,
timing=None, stderr=sys.stderr): timing=None, stderr=sys.stderr):
@ -24,8 +37,9 @@ def get_tor(reactor, launch_tor=False, tor_control_port=None,
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.
@ -69,17 +83,17 @@ def get_tor(reactor, launch_tor=False, tor_control_port=None,
else: else:
with timing.add("find tor"): with timing.add("find tor"):
try: 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) tor = yield txtorcon.connect(reactor, tor_control_port)
print(" using Tor", file=stderr) print(" using Tor via control port", file=stderr)
except Exception: except Exception:
#socks_desc = "tcp:127.0.0.1:9050" # fallback # TODO: make this more specific. I think connect() is
#print(" using Tor (SOCKS port %s)" % socks_desc, # likely to throw a reactor.connectTCP -type error, like
# file=stderr) # ConnectionFailed or ConnectionRefused or something
print(" unable to find control port, bailing", print(" unable to find Tor control port, using SOCKS",
file=stderr) file=stderr)
# TODO: something nicer. I think connect() is likely to throw tor = SocksOnlyTor(reactor)
# a reactor.connectTCP -type error, like ConnectionFailed or
# ConnectionRefused or something
raise
directlyProvides(tor, _interfaces.ITorManager) directlyProvides(tor, _interfaces.ITorManager)
returnValue(tor) returnValue(tor)