Skip to content

Commit

Permalink
Merge branch 'master' of github.com:meejah/txtorcon
Browse files Browse the repository at this point in the history
Conflicts:
	txtorcon/endpoints.py
	txtorcon/torconfig.py
  • Loading branch information
meejah committed Nov 18, 2015
2 parents 3ef44f0 + f1f0ab4 commit a923c33
Show file tree
Hide file tree
Showing 4 changed files with 93 additions and 84 deletions.
3 changes: 3 additions & 0 deletions docs/releases.rst
Expand Up @@ -15,6 +15,9 @@ unreleased
commands in TorConfig
* a first stealth-authentication implementation (for "normal" hidden
services, not ephemeral)
* bug-fix from `david415 <https://github.com/david415>`_ to raise
ConnectionRefusedError instead of StopIteration when running out of
SOCKS ports.

v0.14.1
-------
Expand Down
98 changes: 59 additions & 39 deletions test/test_endpoints.py
Expand Up @@ -3,7 +3,7 @@
import tempfile

from mock import patch
from mock import Mock
from mock import Mock, MagicMock

from zope.interface import implements

Expand Down Expand Up @@ -39,9 +39,6 @@
import util


connectionRefusedFailure = Failure(ConnectionRefusedError())


class EndpointTests(unittest.TestCase):

def setUp(self):
Expand Down Expand Up @@ -571,12 +568,12 @@ def __init__(self, *args, **kw):
self.transport = None

self.failure = kw.get('failure', None)
self.acceptPort = kw.get('acceptPort', None)
self.accept_port = kw.get('accept_port', None)

def connect(self, fac):
self.factory = fac
if self.acceptPort:
if self.port != self.acceptPort:
if self.accept_port:
if self.port != self.accept_port:
return defer.fail(self.failure)
else:
if self.failure:
Expand All @@ -595,29 +592,29 @@ def test_client_connection_failed(self):
This test is equivalent to txsocksx's
TestSOCKS4ClientEndpoint.test_clientConnectionFailed
"""
def FailTorSocksEndpointGenerator(*args, **kw):
kw['failure'] = connectionRefusedFailure
def fail_tor_socks_endpoint_generator(*args, **kw):
kw['failure'] = Failure(ConnectionRefusedError())
return FakeTorSocksEndpoint(*args, **kw)
endpoint = TorClientEndpoint('', 0, _proxy_endpoint_generator=FailTorSocksEndpointGenerator)
endpoint = TorClientEndpoint('', 0, _proxy_endpoint_generator=fail_tor_socks_endpoint_generator)
d = endpoint.connect(None)
return self.assertFailure(d, ConnectionRefusedError)

def test_client_connection_failed_user_password(self):
"""
Same as above, but with a username/password.
"""
def FailTorSocksEndpointGenerator(*args, **kw):
kw['failure'] = connectionRefusedFailure
def fail_tor_socks_endpoint_generator(*args, **kw):
kw['failure'] = Failure(ConnectionRefusedError())
return FakeTorSocksEndpoint(*args, **kw)
endpoint = TorClientEndpoint(
'invalid host', 0,
socks_username='billy', socks_password='s333cure',
_proxy_endpoint_generator=FailTorSocksEndpointGenerator)
_proxy_endpoint_generator=fail_tor_socks_endpoint_generator)
d = endpoint.connect(None)
return self.assertFailure(d, ConnectionRefusedError)

def test_default_generator(self):
# just ensuring the default generator doesn't blow updoesn't blow up
# just ensuring the default generator doesn't blow up
default_tcp4_endpoint_generator(None, 'foo.bar', 1234)

def test_no_host(self):
Expand Down Expand Up @@ -647,40 +644,60 @@ def test_default_factory(self):
"""
This test is equivalent to txsocksx's TestSOCKS5ClientEndpoint.test_defaultFactory
"""
def TorSocksEndpointGenerator(*args, **kw):
endpoints = []
def tor_socks_endpoint_generator(*args, **kw):
endpoints.append(FakeTorSocksEndpoint(*args, **kw))
return endpoints[-1]
endpoint = TorClientEndpoint('', 0, _proxy_endpoint_generator=tor_socks_endpoint_generator)
endpoint.connect(Mock)
self.assertEqual(1, len(endpoints))
self.assertEqual(endpoints[0].transport.value(), '\x05\x01\x00')

@patch('txtorcon.endpoints.SOCKS5ClientEndpoint')
@defer.inlineCallbacks
def test_success(self, socks5_factory):
ep = MagicMock()
gold_proto = object()
ep.connect = MagicMock(return_value=gold_proto)
socks5_factory.return_value = ep

def tor_socks_endpoint_generator(*args, **kw):
return FakeTorSocksEndpoint(*args, **kw)
endpoint = TorClientEndpoint('', 0, _proxy_endpoint_generator=TorSocksEndpointGenerator)
endpoint.connect(None)
self.assertEqual(endpoint.tor_socks_endpoint.transport.value(), '\x05\x01\x00')

endpoint = TorClientEndpoint('', 0, _proxy_endpoint_generator=tor_socks_endpoint_generator)
other_proto = yield endpoint.connect(MagicMock())
self.assertEqual(other_proto, gold_proto)

def test_good_port_retry(self):
"""
This tests that our Tor client endpoint retry logic works correctly.
We create a proxy endpoint that fires a connectionRefusedFailure
We create a proxy endpoint that fires a ConnectionRefusedError
unless the connecting port matches. We attempt to connect with the
proxy endpoint for each port that the Tor client endpoint will try.
"""
success_ports = TorClientEndpoint.socks_ports_to_try
endpoints = []
for port in success_ports:
def TorSocksEndpointGenerator(*args, **kw):
kw['acceptPort'] = port
kw['failure'] = connectionRefusedFailure
return FakeTorSocksEndpoint(*args, **kw)
endpoint = TorClientEndpoint('', 0, _proxy_endpoint_generator=TorSocksEndpointGenerator)
def tor_socks_endpoint_generator(*args, **kw):
kw['accept_port'] = port
kw['failure'] = Failure(ConnectionRefusedError())
endpoints.append(FakeTorSocksEndpoint(*args, **kw))
return endpoints[-1]
endpoint = TorClientEndpoint('', 0, _proxy_endpoint_generator=tor_socks_endpoint_generator)
endpoint.connect(None)
self.assertEqual(endpoint.tor_socks_endpoint.transport.value(), '\x05\x01\x00')
self.assertEqual(endpoints[-1].transport.value(), '\x05\x01\x00')

def test_bad_port_retry(self):
"""
This tests failure to connect to the ports on the "try" list.
"""
fail_ports = [1984, 666]
for port in fail_ports:
def TorSocksEndpointGenerator(*args, **kw):
kw['acceptPort'] = port
kw['failure'] = connectionRefusedFailure
def tor_socks_endpoint_generator(*args, **kw):
kw['accept_port'] = port
kw['failure'] = Failure(ConnectionRefusedError())
return FakeTorSocksEndpoint(*args, **kw)
endpoint = TorClientEndpoint('', 0, _proxy_endpoint_generator=TorSocksEndpointGenerator)
endpoint = TorClientEndpoint('', 0, _proxy_endpoint_generator=tor_socks_endpoint_generator)
d = endpoint.connect(None)
return self.assertFailure(d, ConnectionRefusedError)

Expand All @@ -689,24 +706,27 @@ def test_good_no_guess_socks_port(self):
This tests that if a SOCKS port is specified, we *only* attempt to
connect to that SOCKS port.
"""
def TorSocksEndpointGenerator(*args, **kw):
kw['acceptPort'] = 6669
kw['failure'] = connectionRefusedFailure
return FakeTorSocksEndpoint(*args, **kw)
endpoint = TorClientEndpoint('', 0, _proxy_endpoint_generator=TorSocksEndpointGenerator, socks_port=6669)
endpoints = []
def tor_socks_endpoint_generator(*args, **kw):
kw['accept_port'] = 6669
kw['failure'] = Failure(ConnectionRefusedError())
endpoints.append(FakeTorSocksEndpoint(*args, **kw))
return endpoints[-1]
endpoint = TorClientEndpoint('', 0, _proxy_endpoint_generator=tor_socks_endpoint_generator, socks_port=6669)
endpoint.connect(None)
self.assertEqual(endpoint.tor_socks_endpoint.transport.value(), '\x05\x01\x00')
self.assertEqual(1, len(endpoints))
self.assertEqual(endpoints[-1].transport.value(), '\x05\x01\x00')

def test_bad_no_guess_socks_port(self):
"""
This tests that are connection fails if we try to connect to an unavailable
specified SOCKS port... even if there is a valid SOCKS port listening on
the socks_ports_to_try list.
"""
def TorSocksEndpointGenerator(*args, **kw):
kw['acceptPort'] = 9050
kw['failure'] = connectionRefusedFailure
def tor_socks_endpoint_generator(*args, **kw):
kw['accept_port'] = 9050
kw['failure'] = Failure(ConnectionRefusedError())
return FakeTorSocksEndpoint(*args, **kw)
endpoint = TorClientEndpoint('', 0, _proxy_endpoint_generator=TorSocksEndpointGenerator, socks_port=6669)
endpoint = TorClientEndpoint('', 0, _proxy_endpoint_generator=tor_socks_endpoint_generator, socks_port=6669)
d = endpoint.connect(None)
self.assertFailure(d, ConnectionRefusedError)
70 changes: 27 additions & 43 deletions txtorcon/endpoints.py
Expand Up @@ -23,10 +23,11 @@
from twisted.internet.endpoints import serverFromString
from twisted.internet.endpoints import clientFromString
from twisted.internet.endpoints import TCP4ClientEndpoint
from twisted.internet.error import ConnectionRefusedError
from twisted.internet import error
from twisted.plugin import IPlugin
from twisted.python.util import FancyEqMixin
from twisted.internet.error import ConnectionRefusedError
from twisted.python.failure import Failure

from zope.interface import implementer
from zope.interface import Interface, Attribute
Expand Down Expand Up @@ -669,62 +670,45 @@ def __init__(self, host, port,
self.port = int(port)
self._proxy_endpoint_generator = _proxy_endpoint_generator
self.socks_hostname = socks_hostname
self.socks_port = int(socks_port) if socks_port else None
self.socks_port = int(socks_port) if socks_port is not None else None
self.socks_username = socks_username
self.socks_password = socks_password

if self.socks_port is None:
self._socks_port_iter = iter(self.socks_ports_to_try)
self._socks_guessing_enabled = True
else:
self._socks_port_iter = [socks_port]
self._socks_guessing_enabled = False

@defer.inlineCallbacks
def connect(self, protocolfactory):
self.protocolfactory = protocolfactory

if self._socks_guessing_enabled:
self.socks_port = self._socks_port_iter.next()
last_error = None
for socks_port in self._socks_port_iter:
self.socks_port = socks_port
tor_ep = self._proxy_endpoint_generator(
reactor,
self.socks_hostname,
self.socks_port,
)

d = self._try_connect()
return d
args = (self.host, self.port, tor_ep)
kwargs = dict()
if self.socks_username is not None and self.socks_password is not None:
kwargs['methods'] = dict(
login=(self.socks_username, self.socks_password),
)

def _try_connect(self):
self.tor_socks_endpoint = self._proxy_endpoint_generator(
reactor,
self.socks_hostname,
self.socks_port
)
socks_ep = SOCKS5ClientEndpoint(*args, **kwargs)

if self.socks_username is None or self.socks_password is None:
ep = SOCKS5ClientEndpoint(
self.host,
self.port,
self.tor_socks_endpoint
)
else:
ep = SOCKS5ClientEndpoint(
self.host,
self.port,
self.tor_socks_endpoint,
methods=dict(login=(self.socks_username, self.socks_password))
)

d = ep.connect(self.protocolfactory)
if self._socks_guessing_enabled:
d.addErrback(self._retry_socks_port)
return d
try:
proto = yield socks_ep.connect(protocolfactory)
defer.returnValue(proto)

def _retry_socks_port(self, failure):
failure.trap(error.ConnectError)
try:
self.socks_port = self._socks_port_iter.next()
except StopIteration:
return defer.fail(
ConnectionRefusedError('tor socks port retry failed')
)
d = self._try_connect()
d.addErrback(self._retry_socks_port)
return d
except error.ConnectError as e0:
last_error = e0
if last_error is not None:
raise last_error


@implementer(IPlugin, IStreamClientEndpointStringParser)
Expand Down
6 changes: 4 additions & 2 deletions txtorcon/torconfig.py
Expand Up @@ -846,7 +846,8 @@ def __init__(self, ports, key_blob_or_type='NEW:BEST', auth=[], ver=2):
# FIXME nicer than assert, plz
assert ' ' not in self._key_blob
assert isinstance(ports, types.ListType)
if not key_blob_or_type.startswith('NEW:') and len(key_blob_or_type) != (812 + 8):
if not key_blob_or_type.startswith('NEW:') \
and (len(key_blob_or_type) > 825 or len(key_blob_or_type) < 820):
raise RuntimeError('Wrong size key-blob')

@defer.inlineCallbacks
Expand All @@ -861,7 +862,8 @@ def add_to_tor(self, protocol):
ans = yield protocol.queue_command(cmd)
ans = find_keywords(ans.split('\n'))
self.hostname = ans['ServiceID'] + '.onion'
self.private_key = ans['PrivateKey']
if self._key_blob == 'NEW:BEST':
self.private_key = ans['PrivateKey']

log.msg('Created hidden-service at', self.hostname)

Expand Down

0 comments on commit a923c33

Please sign in to comment.