[Pkg-privacy-commits] [txtorcon] 16/49: Add basic Tor client endpoint and parser + utests

Ximin Luo infinity0 at debian.org
Mon Oct 19 13:49:51 UTC 2015


This is an automated email from the git hooks/post-receive script.

infinity0 pushed a commit to branch master
in repository txtorcon.

commit fb2d372b8ac1f8981e1488d07e36d07599857771
Author: David Stainton <dstainton415 at gmail.com>
Date:   Sat Feb 7 04:40:24 2015 +0000

    Add basic Tor client endpoint and parser + utests
---
 test/test_endpoints.py | 117 ++++++++++++++++++++++++++++++++++++++++++++++++
 txtorcon/__init__.py   |   2 +
 txtorcon/endpoints.py  | 119 ++++++++++++++++++++++++++++++++++++++++++++++++-
 3 files changed, 237 insertions(+), 1 deletion(-)

diff --git a/test/test_endpoints.py b/test/test_endpoints.py
index b0e613e..45ced81 100644
--- a/test/test_endpoints.py
+++ b/test/test_endpoints.py
@@ -14,6 +14,7 @@ from twisted.internet.endpoints import TCP4ServerEndpoint
 from twisted.internet.endpoints import serverFromString
 from twisted.internet.endpoints import clientFromString
 from twisted.python.failure import Failure
+from twisted.internet.error import ConnectionRefusedError
 from twisted.internet.interfaces import IReactorCore
 from twisted.internet.interfaces import IProtocolFactory
 from twisted.internet.interfaces import IProtocol
@@ -26,6 +27,7 @@ from txtorcon import ITorControlProtocol
 from txtorcon import TorConfig
 from txtorcon import launch_tor
 from txtorcon import TCPHiddenServiceEndpoint
+from txtorcon import TorClientEndpoint
 from txtorcon import TorNotFound
 from txtorcon import TCPHiddenServiceEndpointParser
 from txtorcon import IProgressProvider
@@ -36,6 +38,9 @@ from txtorcon.endpoints import get_global_tor                       # FIXME
 import util
 
 
+connectionRefusedFailure = Failure(ConnectionRefusedError())
+
+
 class EndpointTests(unittest.TestCase):
 
     def setUp(self):
@@ -523,3 +528,115 @@ class FakeReactorTcp(FakeReactor):
             print "BLAAAAAM", args
         r.connect = blam
         return r
+
+
+class FakeTorSocksEndpoint(object):
+    def __init__(self, *args, **kw):
+        self.host     = args[1]
+        self.port     = args[2]
+        self.transport = None
+
+        if kw.has_key('failure'):
+            self.failure = kw['failure']
+        else:
+            self.failure = None
+        if kw.has_key('acceptPort'):
+            self.acceptPort = kw['acceptPort']
+        else:
+            self.acceptPort = None
+
+    def connect(self, fac):
+        self.factory = fac
+        if self.acceptPort:
+            if self.port != self.acceptPort:
+                return defer.fail(self.failure)
+        else:
+            if self.failure:
+                return defer.fail(self.failure)
+        self.proto = fac.buildProtocol(None)
+        transport = proto_helpers.StringTransport()
+        self.proto.makeConnection(transport)
+        self.transport = transport
+        return defer.succeed(self.proto)
+
+
+class TestTorClientEndpoint(unittest.TestCase):
+
+    def test_clientConnectionFailed(self):
+        """
+        This test is equivalent to txsocksx's TestSOCKS4ClientEndpoint.test_clientConnectionFailed
+        """
+        def FailTorSocksEndpointGenerator(*args, **kw):
+            kw['failure'] = connectionRefusedFailure
+            return FakeTorSocksEndpoint(*args, **kw)
+        endpoint = TorClientEndpoint('', 0, proxyEndpointGenerator=FailTorSocksEndpointGenerator)
+        d = endpoint.connect(None)
+        return self.assertFailure(d, ConnectionRefusedError)
+
+    def test_defaultFactory(self):
+        """
+        This test is equivalent to txsocksx's TestSOCKS5ClientEndpoint.test_defaultFactory
+        """
+        def TorSocksEndpointGenerator(*args, **kw):
+            return FakeTorSocksEndpoint(*args, **kw)
+        endpoint = TorClientEndpoint('', 0, proxyEndpointGenerator=TorSocksEndpointGenerator)
+        endpoint.connect(None)
+        self.assertEqual(endpoint.torSocksEndpoint.transport.value(), '\x05\x01\x00')
+
+    def test_goodPortRetry(self):
+        """
+        This tests that our Tor client endpoint retry logic works correctly.
+        We create a proxy endpoint that fires a connectionRefusedFailure
+        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
+        for port in success_ports:
+            def TorSocksEndpointGenerator(*args, **kw):
+                kw['acceptPort'] = port
+                kw['failure']    = connectionRefusedFailure
+                return FakeTorSocksEndpoint(*args, **kw)
+            endpoint = TorClientEndpoint('', 0, proxyEndpointGenerator=TorSocksEndpointGenerator)
+            endpoint.connect(None)
+            self.assertEqual(endpoint.torSocksEndpoint.transport.value(), '\x05\x01\x00')
+
+    def test_badPortRetry(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
+                return FakeTorSocksEndpoint(*args, **kw)
+            endpoint = TorClientEndpoint('', 0, proxyEndpointGenerator=TorSocksEndpointGenerator)
+            d = endpoint.connect(None)
+            return self.assertFailure(d, ConnectionRefusedError)
+
+    def test_goodNoGuessSocksPort(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, proxyEndpointGenerator=TorSocksEndpointGenerator, socksPort=6669)
+        endpoint.connect(None)
+        self.assertEqual(endpoint.torSocksEndpoint.transport.value(), '\x05\x01\x00')
+
+    def test_badNoGuessSocksPort(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
+            return FakeTorSocksEndpoint(*args, **kw)
+        endpoint = TorClientEndpoint('', 0, proxyEndpointGenerator=TorSocksEndpointGenerator, socksPort=6669)
+        d = endpoint.connect(None)
+        self.assertFailure(d, ConnectionRefusedError)
diff --git a/txtorcon/__init__.py b/txtorcon/__init__.py
index 8ed0c16..f3616f0 100644
--- a/txtorcon/__init__.py
+++ b/txtorcon/__init__.py
@@ -29,6 +29,8 @@ from txtorcon.endpoints import TorOnionAddress
 from txtorcon.endpoints import TorOnionListeningPort
 from txtorcon.endpoints import TCPHiddenServiceEndpoint
 from txtorcon.endpoints import TCPHiddenServiceEndpointParser
+from txtorcon.endpoints import TorClientEndpoint
+from txtorcon.endpoints import TorClientEndpointStringParser
 from txtorcon.endpoints import IHiddenService
 from txtorcon.endpoints import IProgressProvider
 from txtorcon.endpoints import get_global_tor
diff --git a/txtorcon/endpoints.py b/txtorcon/endpoints.py
index 8fa6239..2545961 100644
--- a/txtorcon/endpoints.py
+++ b/txtorcon/endpoints.py
@@ -6,20 +6,26 @@ import functools
 
 from txtorcon.util import available_tcp_port
 
-from twisted.internet import defer
+from twisted.internet import defer, reactor
 from twisted.python import log
 from twisted.internet.interfaces import IStreamServerEndpointStringParser
+from twisted.internet.interfaces import IStreamClientEndpointStringParser
 from twisted.internet.interfaces import IStreamServerEndpoint
+from twisted.internet.interfaces import IStreamClientEndpoint
 from twisted.internet.interfaces import IListeningPort
 from twisted.internet.interfaces import IAddress
 from twisted.internet.endpoints import serverFromString
 from twisted.internet.endpoints import clientFromString
+from twisted.internet.endpoints import TCP4ClientEndpoint
+from twisted.internet import error
 from twisted.plugin import IPlugin
 from twisted.python.util import FancyEqMixin
 
 from zope.interface import implementer
 from zope.interface import Interface, Attribute
 
+from txsocksx.client import SOCKS5ClientEndpoint
+
 from torconfig import TorConfig, launch_tor, HiddenService
 from torstate import build_tor_connection
 
@@ -574,3 +580,114 @@ class TCPHiddenServiceEndpointParser(object):
                                                    hidden_service_dir=hsd,
                                                    local_port=localPort,
                                                    control_port=controlPort)
+
+def DefaultTCP4EndpointGenerator(*args, **kw):
+    """
+    Default generator used to create client-side TCP4ClientEndpoint instances.
+    We do this to make the unit tests work...
+    """
+    return TCP4ClientEndpoint(*args, **kw)
+
+ at implementer(IStreamClientEndpoint)
+class TorClientEndpoint(object):
+    """I am an endpoint class who attempts to establish a SOCKS5 connection
+    with the system tor process. Either the user must pass a SOCKS port into my
+    constructor OR I will attempt to guess the Tor SOCKS port by iterating over a list of ports
+    that tor is likely to be listening on.
+
+    :param host: The hostname to connect to.
+    This of course can be a Tor Hidden Service onion address.
+
+    :param port: The tcp port or Tor Hidden Service port.
+
+    :param proxyEndpointGenerator: This is used for unit tests.
+
+    :param socksPort: This optional argument lets the user specify which Tor SOCKS port should be used.
+    """
+    socks_ports_to_try = [9050, 9150]
+
+    def __init__(self, host, port, proxyEndpointGenerator=DefaultTCP4EndpointGenerator, socksHostname=None, socksPort=None, socksUsername=None, socksPassword=None):
+        if host is None or port is None:
+            raise ValueError('host and port must be specified')
+
+        self.host = host
+        self.port = port
+        self.proxyEndpointGenerator = proxyEndpointGenerator
+        self.socksHostname = socksHostname
+        self.socksPort = socksPort
+        self.socksUsername = socksUsername
+        self.socksPassword = socksPassword
+
+        if self.socksPort is None:
+            self.socksPortIter = iter(self.socks_ports_to_try)
+            self.socksGuessingEnabled = True
+        else:
+            self.socksGuessingEnabled = False
+
+    def connect(self, protocolfactory):
+        self.protocolfactory = protocolfactory
+
+        if self.socksGuessingEnabled:
+            self.socksPort = self.socksPortIter.next()
+
+        d = self._try_connect()
+        return d
+
+    def _try_connect(self):
+        self.torSocksEndpoint = self.proxyEndpointGenerator(reactor, self.socksHostname, self.socksPort)
+
+        if self.socksUsername is None or self.socksPassword is None:
+            socks5ClientEndpoint = SOCKS5ClientEndpoint(self.host, self.port, self.torSocksEndpoint)
+        else:
+            socks5ClientEndpoint = SOCKS5ClientEndpoint(self.host, self.port, self.torSocksEndpoint,
+                                                        methods={ 'login': (self.socksUsername, self.socksPassword) })
+
+
+        d = socks5ClientEndpoint.connect(self.protocolfactory)
+        if self.socksGuessingEnabled:
+            d.addErrback(self._retry_socks_port)
+        return d
+
+    def _retry_socks_port(self, failure):
+        failure.trap(error.ConnectError)
+        try:
+            self.socksPort = self.socksPortIter.next()
+        except StopIteration:
+            return failure
+        d = self._try_connect()
+        d.addErrback(self._retry_socks_port)
+        return d
+
+
+ at implementer(IPlugin, IStreamClientEndpointStringParser)
+class TorClientEndpointStringParser(object):
+    """
+    This provides a twisted IPlugin and
+    IStreamClientEndpointsStringParser so you can call
+    :api:`twisted.internet.endpoints.clientFromString
+    <clientFromString>` with a string argument like:
+
+    ``tor:host=timaq4ygg2iegci7.onion:port=80:socksPort=9050``
+
+    ...or simply:
+
+    ``tor:host=timaq4ygg2iegci7.onion:port=80``
+
+    If ``socksPort`` is specified, it means only use that port to attempt to
+    proxy through Tor. If unspecified then try some likely socksPorts
+    such as [9050, 9150].
+    """
+    prefix = "tor"
+
+    def _parseClient(self, host=None, port=None, socksHostname=None, socksPort=None, socksUsername=None, socksPassword=None):
+        if port is not None:
+            port = int(port)
+        if socksHostname is None:
+            socksHostname = '127.0.0.1'
+        if socksPort is not None:
+            socksPort = int(socksPort)
+
+        return TorClientEndpoint(host, port, socksHostname=socksHostname, socksPort=socksPort, socksUsername=socksUsername, socksPassword=socksPassword)
+
+    def parseStreamClient(self, *args, **kwargs):
+        return self._parseClient(*args, **kwargs)

-- 
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/pkg-privacy/packages/txtorcon.git



More information about the Pkg-privacy-commits mailing list