[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