diff --git a/test/test_endpoints.py b/test/test_endpoints.py index ab0c68a0..893d97b1 100644 --- a/test/test_endpoints.py +++ b/test/test_endpoints.py @@ -34,7 +34,6 @@ from txtorcon import TorOnionAddress from txtorcon.util import NoOpProtocolFactory from txtorcon.endpoints import get_global_tor # FIXME -from txtorcon.endpoints import default_tcp4_endpoint_generator import util @@ -592,10 +591,11 @@ def test_client_connection_failed(self): This test is equivalent to txsocksx's TestSOCKS4ClientEndpoint.test_clientConnectionFailed """ - def fail_tor_socks_endpoint_generator(*args, **kw): - kw['failure'] = Failure(ConnectionRefusedError()) - return FakeTorSocksEndpoint(*args, **kw) - endpoint = TorClientEndpoint('', 0, _proxy_endpoint_generator=fail_tor_socks_endpoint_generator) + args = "host123" + kw = dict() + kw['failure'] = Failure(ConnectionRefusedError()) + tor_endpoint = FakeTorSocksEndpoint(*args, **kw) + endpoint = TorClientEndpoint('', 0, socks_endpoint=tor_endpoint) d = endpoint.connect(None) return self.assertFailure(d, ConnectionRefusedError) @@ -603,20 +603,17 @@ def test_client_connection_failed_user_password(self): """ Same as above, but with a username/password. """ - def fail_tor_socks_endpoint_generator(*args, **kw): - kw['failure'] = Failure(ConnectionRefusedError()) - return FakeTorSocksEndpoint(*args, **kw) + args = "fakehost" + kw = dict() + kw['failure'] = Failure(ConnectionRefusedError()) + tor_endpoint = FakeTorSocksEndpoint(*args, **kw) endpoint = TorClientEndpoint( 'invalid host', 0, socks_username='billy', socks_password='s333cure', - _proxy_endpoint_generator=fail_tor_socks_endpoint_generator) + socks_endpoint = tor_endpoint) d = endpoint.connect(None) return self.assertFailure(d, ConnectionRefusedError) - def test_default_generator(self): - # just ensuring the default generator doesn't blow up - default_tcp4_endpoint_generator(None, 'foo.bar', 1234) - def test_no_host(self): self.assertRaises( ValueError, @@ -628,7 +625,8 @@ def test_parser_basic(self): self.assertEqual(ep.host, 'timaq4ygg2iegci7.onion') self.assertEqual(ep.port, 80) - self.assertEqual(ep.socks_port, 9050) + # XXX what's "the Twisted way" to get the port out here? + self.assertEqual(ep.socks_endpoint._port, 9050) def test_parser_user_password(self): epstring = 'tor:host=torproject.org:port=443' + \ @@ -644,15 +642,13 @@ def test_default_factory(self): """ This test is equivalent to txsocksx's TestSOCKS5ClientEndpoint.test_defaultFactory """ - 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) + args = "fakehost" + kw = dict() + tor_endpoint = FakeTorSocksEndpoint(*args, **kw) + endpoint = TorClientEndpoint('', 0, socks_endpoint=tor_endpoint) endpoint.connect(Mock) - self.assertEqual(1, len(endpoints)) - self.assertEqual(endpoints[0].transport.value(), '\x05\x01\x00') + self.assertEqual(tor_endpoint.transport.value(), '\x05\x01\x00') @patch('txtorcon.endpoints.SOCKS5ClientEndpoint') @defer.inlineCallbacks @@ -661,11 +657,10 @@ def test_success(self, socks5_factory): 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=tor_socks_endpoint_generator) + args = "fakehost" + kw = dict() + tor_endpoint = FakeTorSocksEndpoint(*args, **kw) + endpoint = TorClientEndpoint('', 0, socks_endpoint = tor_endpoint) other_proto = yield endpoint.connect(MagicMock()) self.assertEqual(other_proto, gold_proto) @@ -677,16 +672,16 @@ def test_good_port_retry(self): 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 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) + tor_endpoint = FakeTorSocksEndpoint( + "fakehost", "127.0.0.1", port, + accept_port=port, + failure=Failure(ConnectionRefusedError()), + ) + + endpoint = TorClientEndpoint('', 0, socks_endpoint=tor_endpoint) endpoint.connect(None) - self.assertEqual(endpoints[-1].transport.value(), '\x05\x01\x00') + self.assertEqual(tor_endpoint.transport.value(), '\x05\x01\x00') def test_bad_port_retry(self): """ @@ -694,41 +689,51 @@ def test_bad_port_retry(self): """ fail_ports = [1984, 666] for port in fail_ports: - 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=tor_socks_endpoint_generator) + ep = FakeTorSocksEndpoint( + '', '', 0, + accept_port=port, + failure=Failure(ConnectionRefusedError()), + ) + endpoint = TorClientEndpoint('', 0, socks_endpoint=ep) d = endpoint.connect(None) return self.assertFailure(d, ConnectionRefusedError) - 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. - """ - 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(1, len(endpoints)) - self.assertEqual(endpoints[-1].transport.value(), '\x05\x01\x00') - - def test_bad_no_guess_socks_port(self): + @patch('txtorcon.endpoints.SOCKS5ClientEndpoint') + def test_default_socks_ports_fails(self, ep_mock): """ - 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. + Ensure we iterate over the default socks ports """ - 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=tor_socks_endpoint_generator, socks_port=6669) + + class FakeSocks5(object): + + def __init__(self, *args, **kw): + pass + + def connect(self, *args, **kw): + raise ConnectionRefusedError() + + ep_mock.side_effect = FakeSocks5 + endpoint = TorClientEndpoint('', 0)#, socks_endpoint=ep) d = endpoint.connect(None) self.assertFailure(d, ConnectionRefusedError) + + @patch('txtorcon.endpoints.SOCKS5ClientEndpoint') + @defer.inlineCallbacks + def test_default_socks_ports_happy(self, ep_mock): + """ + Ensure we iterate over the default socks ports + """ + + proto = object() + class FakeSocks5(object): + + def __init__(self, *args, **kw): + pass + + def connect(self, *args, **kw): + return proto + + ep_mock.side_effect = FakeSocks5 + endpoint = TorClientEndpoint('', 0) + p2 = yield endpoint.connect(None) + self.assertTrue(proto is p2) diff --git a/txtorcon/endpoints.py b/txtorcon/endpoints.py index a901555b..158df60b 100644 --- a/txtorcon/endpoints.py +++ b/txtorcon/endpoints.py @@ -633,14 +633,6 @@ def parseStreamServer(self, reactor, public_port, localPort=None, control_port=controlPort) -def default_tcp4_endpoint_generator(*args, **kw): - """ - Default generator used to create client-side TCP4ClientEndpoint - instances. We do this to make the unit tests work... - """ - return TCP4ClientEndpoint(*args, **kw) - - @implementer(IStreamClientEndpoint) class TorClientEndpoint(object): """ @@ -667,55 +659,63 @@ class TorClientEndpoint(object): socks_ports_to_try = [9050, 9150] def __init__(self, host, port, - socks_hostname=None, socks_port=None, - socks_username=None, socks_password=None, - _proxy_endpoint_generator=default_tcp4_endpoint_generator): + socks_endpoint=None, + socks_username=None, socks_password=None, **kw): if host is None or port is None: raise ValueError('host and port must be specified') self.host = host 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 is not None else None + self.socks_endpoint = socks_endpoint self.socks_username = socks_username self.socks_password = socks_password - if self.socks_port is None: + # backwards-compatibility: you used to specify a TCP SOCKS + # endpoint via socks_host= and socks_port= kwargs + if self.socks_endpoint is None: + try: + self.socks_endpoint = TCP4ClientEndpoint(reactor, kw['socks_host'], kw['socks_port']) + # XXX should deprecation-warn here + except KeyError: + pass + + if self.socks_endpoint 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): 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, + 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), ) - - 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), - ) - + if self.socks_endpoint is not None: + args = (self.host, self.port, self.socks_endpoint) socks_ep = SOCKS5ClientEndpoint(*args, **kwargs) + proto = yield socks_ep.connect(protocolfactory) + defer.returnValue(proto) + else: + for socks_port in self._socks_port_iter: + tor_ep = TCP4ClientEndpoint( + reactor, + "127.0.0.1", + socks_port, + ) + args = (self.host, self.port, tor_ep) + socks_ep = SOCKS5ClientEndpoint(*args, **kwargs) - try: - proto = yield socks_ep.connect(protocolfactory) - defer.returnValue(proto) + try: + proto = yield socks_ep.connect(protocolfactory) + defer.returnValue(proto) - except error.ConnectError as e0: - last_error = e0 - if last_error is not None: - raise last_error + except error.ConnectError as e0: + last_error = e0 + if last_error is not None: + raise last_error @implementer(IPlugin, IStreamClientEndpointStringParserWithReactor) @@ -762,10 +762,14 @@ def _parseClient(self, host=None, port=None, if socksPort is not None: socksPort = int(socksPort) + ep = None + if socksPort is not None: + ep = TCP4ClientEndpoint(reactor, socksHostname, socksPort) return TorClientEndpoint( host, port, - socks_hostname=socksHostname, socks_port=socksPort, - socks_username=socksUsername, socks_password=socksPassword + socks_endpoint=ep, + socks_username=socksUsername, + socks_password=socksPassword, ) def parseStreamClient(self, *args, **kwargs):