diff --git a/setup.py b/setup.py index e25677e50..2bef4c078 100644 --- a/setup.py +++ b/setup.py @@ -261,7 +261,7 @@ setup(name="tahoe-lafs", # also set in __init__.py "coverage", "mock", "tox", - "foolscap[tor]", + "foolscap[tor] >= 0.12.3", "txtorcon", # in case pip's resolver doesn't work "foolscap[i2p]", "txi2p", # in case pip's resolver doesn't work @@ -269,7 +269,7 @@ setup(name="tahoe-lafs", # also set in __init__.py "pytest-twisted", ], "tor": [ - "foolscap[tor]", + "foolscap[tor] >= 0.12.3", "txtorcon", # in case pip's resolver doesn't work ], "i2p": [ diff --git a/src/allmydata/_auto_deps.py b/src/allmydata/_auto_deps.py index a1754c6ca..f4c6ecde8 100644 --- a/src/allmydata/_auto_deps.py +++ b/src/allmydata/_auto_deps.py @@ -39,8 +39,8 @@ install_requires = [ # * foolscap 0.8.0 generates 2048-bit RSA-with-SHA-256 signatures, # rather than 1024-bit RSA-with-MD5. This also allows us to work # with a FIPS build of OpenSSL. - # * foolscap >= 0.12.2 provides tcp/tor/i2p connection handlers we need - "foolscap >= 0.12.2", + # * foolscap >= 0.12.3 provides tcp/tor/i2p connection handlers we need + "foolscap >= 0.12.3", # Needed for SFTP. # pycrypto 2.2 doesn't work due to diff --git a/src/allmydata/node.py b/src/allmydata/node.py index 11f0cda6f..eaf62052a 100644 --- a/src/allmydata/node.py +++ b/src/allmydata/node.py @@ -207,24 +207,10 @@ class Node(service.MultiService): datadir = os.path.join(self.basedir, "private", "tor-statedir") return tor.launch(data_directory=datadir, tor_binary=executable) - socksport = self.get_config("tor", "socks.port", None) - if socksport: - # this is nominally and endpoint string, but txtorcon requires - # TCP host and port. So parse it now, and reject non-TCP - # endpoints. - - pieces = socksport.split(":") - if pieces[0] != "tcp" or len(pieces) != 3: - raise ValueError("'tahoe.cfg [tor] socks.port' = " - "is currently limited to 'tcp:HOST:PORT', " - "not '%s'" % (socksport,)) - host = pieces[1] - try: - port = int(pieces[2]) - except ValueError: - raise ValueError("'tahoe.cfg [tor] socks.port' used " - "non-numeric PORT value '%s'" % (pieces[2],)) - return tor.socks_port(host, port) + socks_endpoint_desc = self.get_config("tor", "socks.port", None) + if socks_endpoint_desc: + socks_ep = endpoints.clientFromString(reactor, socks_endpoint_desc) + return tor.socks_endpoint(socks_ep) controlport = self.get_config("tor", "control.port", None) if controlport: diff --git a/src/allmydata/test/test_connections.py b/src/allmydata/test/test_connections.py index 1185bf0d4..80ecb789a 100644 --- a/src/allmydata/test/test_connections.py +++ b/src/allmydata/test/test_connections.py @@ -3,6 +3,7 @@ import mock from io import BytesIO from twisted.trial import unittest from twisted.internet import reactor, endpoints +from twisted.internet.interfaces import IStreamClientEndpoint from ConfigParser import SafeConfigParser from foolscap.connections import tcp from ..node import Node, PrivacyError @@ -71,33 +72,42 @@ class Tor(unittest.TestCase): self.assertEqual(f.mock_calls, [exp]) self.assertIdentical(h, h1) - def test_socksport(self): - n = FakeNode(BASECONFIG+"[tor]\nsocks.port = tcp:127.0.0.1:1234\n") + def test_socksport_unix_endpoint(self): + n = FakeNode(BASECONFIG+"[tor]\nsocks.port = unix:/var/lib/fw-daemon/tor_socks.socket\n") h1 = mock.Mock() - with mock.patch("foolscap.connections.tor.socks_port", + with mock.patch("foolscap.connections.tor.socks_endpoint", return_value=h1) as f: h = n._make_tor_handler() - self.assertEqual(f.mock_calls, [mock.call("127.0.0.1", 1234)]) + self.assertTrue(IStreamClientEndpoint.providedBy(f.mock_calls[0])) self.assertIdentical(h, h1) - def test_socksport_otherhost(self): - n = FakeNode(BASECONFIG+"[tor]\nsocks.port = tcp:otherhost:1234\n") + def test_socksport_endpoint(self): + n = FakeNode(BASECONFIG+"[tor]\nsocks.port = tcp:127.0.0.1:1234\n") h1 = mock.Mock() - with mock.patch("foolscap.connections.tor.socks_port", + with mock.patch("foolscap.connections.tor.socks_endpoint", return_value=h1) as f: h = n._make_tor_handler() - self.assertEqual(f.mock_calls, [mock.call("otherhost", 1234)]) + self.assertTrue(IStreamClientEndpoint.providedBy(f.mock_calls[0])) + self.assertIdentical(h, h1) + + def test_socksport_endpoint_otherhost(self): + n = FakeNode(BASECONFIG+"[tor]\nsocks.port = tcp:otherhost:1234\n") + h1 = mock.Mock() + with mock.patch("foolscap.connections.tor.socks_endpoint", + return_value=h1) as f: + h = n._make_tor_handler() + self.assertTrue(IStreamClientEndpoint.providedBy(f.mock_calls[0])) self.assertIdentical(h, h1) def test_socksport_bad_endpoint(self): - n = FakeNode(BASECONFIG+"[tor]\nsocks.port = unix:unsupported\n") + n = FakeNode(BASECONFIG+"[tor]\nsocks.port = meow:unsupported\n") e = self.assertRaises(ValueError, n._make_tor_handler) - self.assertIn("is currently limited to 'tcp:HOST:PORT'", str(e)) + self.assertIn("Unknown endpoint type: 'meow'", str(e)) def test_socksport_not_integer(self): n = FakeNode(BASECONFIG+"[tor]\nsocks.port = tcp:localhost:kumquat\n") e = self.assertRaises(ValueError, n._make_tor_handler) - self.assertIn("used non-numeric PORT value", str(e)) + self.assertIn("invalid literal for int() with base 10: 'kumquat'", str(e)) def test_controlport(self): n = FakeNode(BASECONFIG+"[tor]\ncontrol.port = tcp:localhost:1234\n")