mirror of
https://github.com/tahoe-lafs/tahoe-lafs.git
synced 2025-02-22 10:20:59 +00:00
tahoe.cfg: add tub.port=listen:i2p (and/or listen:tor)
This delegates the construction of the server Endpoint object to the i2p/tor Provider, which can use the i2p/tor section of the config file to add options which would be awkward to express as text in an endpoint descriptor string. refs ticket:2889 (but note this merely makes room for a function to be written that can process I2CP options, it does not actually handle such options, so it does not close this ticket yet)
This commit is contained in:
parent
d1fd43aa4f
commit
097abb42fa
@ -150,6 +150,14 @@ set the ``tub.location`` option described below.
|
|||||||
Lists of endpoint descriptor strings like the following ``tcp:12345,tcp6:12345``
|
Lists of endpoint descriptor strings like the following ``tcp:12345,tcp6:12345``
|
||||||
are known to not work because an ``Address already in use.`` error.
|
are known to not work because an ``Address already in use.`` error.
|
||||||
|
|
||||||
|
If any descriptor begins with ``listen:tor``, or ``listen:i2p``, the
|
||||||
|
corresponding tor/i2p Provider object will construct additional endpoints
|
||||||
|
for the Tub to listen on. This allows the ``[tor]`` or ``[i2p]`` sections
|
||||||
|
in ``tahoe.cfg`` to customize the endpoint; e.g. to add I2CP control
|
||||||
|
options. If you use ``listen:i2p``, you should not also have an
|
||||||
|
``i2p:..`` endpoint in ``tub.port``, as that would result in multiple
|
||||||
|
I2P-based listeners.
|
||||||
|
|
||||||
If ``tub.port`` is the string ``disabled``, the node will not listen at
|
If ``tub.port`` is the string ``disabled``, the node will not listen at
|
||||||
all, and thus cannot accept connections from other nodes. If ``[storage]
|
all, and thus cannot accept connections from other nodes. If ``[storage]
|
||||||
enabled = true``, or ``[helper] enabled = true``, or the node is an
|
enabled = true``, or ``[helper] enabled = true``, or the node is an
|
||||||
|
@ -400,7 +400,13 @@ class Node(service.MultiService):
|
|||||||
for port in tubport.split(","):
|
for port in tubport.split(","):
|
||||||
if port in ("0", "tcp:0"):
|
if port in ("0", "tcp:0"):
|
||||||
raise ValueError("tub.port cannot be 0: you must choose")
|
raise ValueError("tub.port cannot be 0: you must choose")
|
||||||
self.tub.listenOn(port)
|
if port == "listen:i2p":
|
||||||
|
port_or_endpoint = self._i2p_provider.get_listener()
|
||||||
|
elif port == "listen:tor":
|
||||||
|
port_or_endpoint = self._tor_provider.get_listener()
|
||||||
|
else:
|
||||||
|
port_or_endpoint = port
|
||||||
|
self.tub.listenOn(port_or_endpoint)
|
||||||
self.tub.setLocation(location)
|
self.tub.setLocation(location)
|
||||||
self._tub_is_listening = True
|
self._tub_is_listening = True
|
||||||
self.log("Tub location set to %s" % (location,))
|
self.log("Tub location set to %s" % (location,))
|
||||||
|
@ -178,7 +178,7 @@ class CreateDest(unittest.TestCase):
|
|||||||
"i2p_dest.privkey"),
|
"i2p_dest.privkey"),
|
||||||
}
|
}
|
||||||
self.assertEqual(tahoe_config_i2p, expected)
|
self.assertEqual(tahoe_config_i2p, expected)
|
||||||
self.assertEqual(i2p_port, "i2p:%s:3457:api=SAM:apiEndpoint=goodport" % privkeyfile)
|
self.assertEqual(i2p_port, "listen:i2p")
|
||||||
self.assertEqual(i2p_location, "i2p:FOOBAR.b32.i2p:3457")
|
self.assertEqual(i2p_location, "i2p:FOOBAR.b32.i2p:3457")
|
||||||
|
|
||||||
_None = object()
|
_None = object()
|
||||||
@ -294,6 +294,28 @@ class Provider(unittest.TestCase):
|
|||||||
self.assertIs(h, handler)
|
self.assertIs(h, handler)
|
||||||
i2p.default.assert_called_with(reactor, keyfile=None)
|
i2p.default.assert_called_with(reactor, keyfile=None)
|
||||||
|
|
||||||
|
class Provider_Listener(unittest.TestCase):
|
||||||
|
def test_listener(self):
|
||||||
|
i2p = mock.Mock()
|
||||||
|
handler = object()
|
||||||
|
i2p.local_i2p = mock.Mock(return_value=handler)
|
||||||
|
reactor = object()
|
||||||
|
|
||||||
|
privkeyfile = os.path.join("private", "i2p_dest.privkey")
|
||||||
|
with mock_i2p(i2p):
|
||||||
|
p = i2p_provider.Provider("basedir",
|
||||||
|
FakeConfig(**{
|
||||||
|
"i2p.configdir": "configdir",
|
||||||
|
"sam.port": "goodport",
|
||||||
|
"dest": "true",
|
||||||
|
"dest.port": "3457",
|
||||||
|
"dest.private_key_file": privkeyfile,
|
||||||
|
}),
|
||||||
|
reactor)
|
||||||
|
endpoint_or_description = p.get_listener()
|
||||||
|
self.assertEqual(endpoint_or_description,
|
||||||
|
"i2p:%s:3457:api=SAM:apiEndpoint=goodport" % privkeyfile)
|
||||||
|
|
||||||
class Provider_CheckI2PConfig(unittest.TestCase):
|
class Provider_CheckI2PConfig(unittest.TestCase):
|
||||||
def test_default(self):
|
def test_default(self):
|
||||||
# default config doesn't start an I2P service, so it should be
|
# default config doesn't start an I2P service, so it should be
|
||||||
|
@ -349,7 +349,7 @@ class FakeTub:
|
|||||||
def setLocation(self, location): pass
|
def setLocation(self, location): pass
|
||||||
def setServiceParent(self, parent): pass
|
def setServiceParent(self, parent): pass
|
||||||
|
|
||||||
class MultiplePorts(unittest.TestCase):
|
class Listeners(unittest.TestCase):
|
||||||
def test_multiple_ports(self):
|
def test_multiple_ports(self):
|
||||||
n = EmptyNode()
|
n = EmptyNode()
|
||||||
n.basedir = self.mktemp()
|
n.basedir = self.mktemp()
|
||||||
@ -381,6 +381,36 @@ class MultiplePorts(unittest.TestCase):
|
|||||||
["tcp:%d:interface=127.0.0.1" % port1,
|
["tcp:%d:interface=127.0.0.1" % port1,
|
||||||
"tcp:%d:interface=127.0.0.1" % port2])
|
"tcp:%d:interface=127.0.0.1" % port2])
|
||||||
|
|
||||||
|
def test_tor_i2p_listeners(self):
|
||||||
|
n = EmptyNode()
|
||||||
|
n.basedir = self.mktemp()
|
||||||
|
n.config_fname = os.path.join(n.basedir, "tahoe.cfg")
|
||||||
|
os.mkdir(n.basedir)
|
||||||
|
os.mkdir(os.path.join(n.basedir, "private"))
|
||||||
|
with open(n.config_fname, "w") as f:
|
||||||
|
f.write(BASE_CONFIG)
|
||||||
|
f.write("tub.port = listen:i2p,listen:tor\n")
|
||||||
|
f.write("tub.location = tcp:example.org:1234\n")
|
||||||
|
# we're doing a lot of calling-into-setup-methods here, it might be
|
||||||
|
# better to just create a real Node instance, I'm not sure.
|
||||||
|
n.read_config()
|
||||||
|
n.check_privacy()
|
||||||
|
n.services = []
|
||||||
|
i2p_ep = object()
|
||||||
|
tor_ep = object()
|
||||||
|
n._i2p_provider = mock.Mock()
|
||||||
|
n._i2p_provider.get_listener = mock.Mock(return_value=i2p_ep)
|
||||||
|
n._tor_provider = mock.Mock()
|
||||||
|
n._tor_provider.get_listener = mock.Mock(return_value=tor_ep)
|
||||||
|
n.init_connections()
|
||||||
|
n.set_tub_options()
|
||||||
|
t = FakeTub()
|
||||||
|
with mock.patch("allmydata.node.Tub", return_value=t):
|
||||||
|
n.create_main_tub()
|
||||||
|
self.assertEqual(n._i2p_provider.get_listener.mock_calls, [mock.call()])
|
||||||
|
self.assertEqual(n._tor_provider.get_listener.mock_calls, [mock.call()])
|
||||||
|
self.assertEqual(t.listening_ports, [i2p_ep, tor_ep])
|
||||||
|
|
||||||
class ClientNotListening(unittest.TestCase):
|
class ClientNotListening(unittest.TestCase):
|
||||||
def test_disabled(self):
|
def test_disabled(self):
|
||||||
basedir = "test_node/test_disabled"
|
basedir = "test_node/test_disabled"
|
||||||
|
@ -393,6 +393,20 @@ class Provider(unittest.TestCase):
|
|||||||
self.assertIs(h, handler)
|
self.assertIs(h, handler)
|
||||||
tor.default_socks.assert_called_with()
|
tor.default_socks.assert_called_with()
|
||||||
|
|
||||||
|
class Provider_Listener(unittest.TestCase):
|
||||||
|
def test_listener(self):
|
||||||
|
tor = mock.Mock()
|
||||||
|
handler = object()
|
||||||
|
tor.socks_endpoint = mock.Mock(return_value=handler)
|
||||||
|
reactor = object()
|
||||||
|
|
||||||
|
with mock_tor(tor):
|
||||||
|
p = tor_provider.Provider("basedir",
|
||||||
|
FakeConfig(**{"onion.local_port": "321"}),
|
||||||
|
reactor)
|
||||||
|
endpoint_or_description = p.get_listener()
|
||||||
|
self.assertEqual(endpoint_or_description, "tcp:321:interface=127.0.0.1")
|
||||||
|
|
||||||
class Provider_CheckOnionConfig(unittest.TestCase):
|
class Provider_CheckOnionConfig(unittest.TestCase):
|
||||||
def test_default(self):
|
def test_default(self):
|
||||||
# default config doesn't start an onion service, so it should be
|
# default config doesn't start an onion service, so it should be
|
||||||
|
@ -88,9 +88,7 @@ def create_config(reactor, cli_config):
|
|||||||
print("allocating .i2p address...", file=stdout)
|
print("allocating .i2p address...", file=stdout)
|
||||||
dest = yield txi2p.generateDestination(reactor, privkeyfile, 'SAM', sam_endpoint)
|
dest = yield txi2p.generateDestination(reactor, privkeyfile, 'SAM', sam_endpoint)
|
||||||
print(".i2p address allocated", file=stdout)
|
print(".i2p address allocated", file=stdout)
|
||||||
escaped_sam_port = sam_port.replace(':', '\:')
|
i2p_port = "listen:i2p" # means "see [i2p]", calls Provider.get_listener()
|
||||||
i2p_port = "i2p:%s:%d:api=SAM:apiEndpoint=%s" % \
|
|
||||||
(privkeyfile, external_port, escaped_sam_port)
|
|
||||||
i2p_location = "i2p:%s:%d" % (dest.host, external_port)
|
i2p_location = "i2p:%s:%d" % (dest.host, external_port)
|
||||||
|
|
||||||
# in addition to the "how to launch/connect-to i2p" keys above, we also
|
# in addition to the "how to launch/connect-to i2p" keys above, we also
|
||||||
@ -137,6 +135,20 @@ class Provider(service.MultiService):
|
|||||||
def _get_i2p_config(self, *args, **kwargs):
|
def _get_i2p_config(self, *args, **kwargs):
|
||||||
return self._node_for_config.get_config("i2p", *args, **kwargs)
|
return self._node_for_config.get_config("i2p", *args, **kwargs)
|
||||||
|
|
||||||
|
def get_listener(self):
|
||||||
|
# this is relative to BASEDIR, and our cwd should be BASEDIR
|
||||||
|
privkeyfile = self._get_i2p_config("dest.private_key_file")
|
||||||
|
external_port = self._get_i2p_config("dest.port")
|
||||||
|
sam_port = self._get_i2p_config("sam.port")
|
||||||
|
escaped_sam_port = sam_port.replace(':', '\:')
|
||||||
|
# for now, this returns a string, which then gets passed to
|
||||||
|
# endpoints.serverFromString . But it can also return an Endpoint
|
||||||
|
# directly, which means we don't need to encode all these options
|
||||||
|
# into a string
|
||||||
|
i2p_port = "i2p:%s:%s:api=SAM:apiEndpoint=%s" % \
|
||||||
|
(privkeyfile, external_port, escaped_sam_port)
|
||||||
|
return i2p_port
|
||||||
|
|
||||||
def get_i2p_handler(self):
|
def get_i2p_handler(self):
|
||||||
enabled = self._get_i2p_config("enabled", True, boolean=True)
|
enabled = self._get_i2p_config("enabled", True, boolean=True)
|
||||||
if not enabled:
|
if not enabled:
|
||||||
|
@ -216,6 +216,11 @@ class Provider(service.MultiService):
|
|||||||
def _get_tor_config(self, *args, **kwargs):
|
def _get_tor_config(self, *args, **kwargs):
|
||||||
return self._node_for_config.get_config("tor", *args, **kwargs)
|
return self._node_for_config.get_config("tor", *args, **kwargs)
|
||||||
|
|
||||||
|
def get_listener(self):
|
||||||
|
local_port = self._get_tor_config("onion.local_port")
|
||||||
|
tor_port = "tcp:%s:interface=127.0.0.1" % local_port
|
||||||
|
return tor_port
|
||||||
|
|
||||||
def get_tor_handler(self):
|
def get_tor_handler(self):
|
||||||
enabled = self._get_tor_config("enabled", True, boolean=True)
|
enabled = self._get_tor_config("enabled", True, boolean=True)
|
||||||
if not enabled:
|
if not enabled:
|
||||||
|
Loading…
x
Reference in New Issue
Block a user