mirror of
https://github.com/tahoe-lafs/tahoe-lafs.git
synced 2025-04-07 02:46:49 +00:00
Merge 2788-connection-handlers: add Tor/I2P support
This adds client-side support for automatically connecting to Tor and I2P -hosted servers (when the server advertises a "tor:" or "i2p:" -style connection hint, and when the necessary extra libraries are installed, and when a Tor/I2P daemon is running and reachable at the default location). The new `[connections]` section of `tahoe.cfg` can include `tcp: tor` to make all TCP connections use Tor instead. This ought to hide the client's IP address from servers and the Introducer. The new `[tor]` and `[i2p]` sections of `tahoe.cfg` will control how Tor/I2P connections are made (whether to spin up a new Tor daemon, what port to use to connect to an existing one, etc). `configuration.rst` explains the possible syntax. Note that only the default I2P connection method is implemented in Foolscap-0.12.2, but not the alternatives, so many options that are legal syntax for `tahoe.cfg` will not work yet. It also enables the `connections` section of `private/servers.yaml` to override the connection-handler mapping. refs ticket:517 closes ticket:2788
This commit is contained in:
commit
095120112d
@ -6,15 +6,16 @@ Configuring a Tahoe-LAFS node
|
||||
|
||||
1. `Node Types`_
|
||||
2. `Overall Node Configuration`_
|
||||
3. `Client Configuration`_
|
||||
4. `Storage Server Configuration`_
|
||||
5. `Frontend Configuration`_
|
||||
6. `Running A Helper`_
|
||||
7. `Running An Introducer`_
|
||||
8. `Other Files in BASEDIR`_
|
||||
9. `Static Server Definitions`_
|
||||
10. `Other files`_
|
||||
11. `Example`_
|
||||
3. `Connection Management`_
|
||||
4. `Client Configuration`_
|
||||
5. `Storage Server Configuration`_
|
||||
6. `Frontend Configuration`_
|
||||
7. `Running A Helper`_
|
||||
8. `Running An Introducer`_
|
||||
9. `Other Files in BASEDIR`_
|
||||
10. `Static Server Definitions`_
|
||||
11. `Other files`_
|
||||
12. `Example`_
|
||||
|
||||
A Tahoe-LAFS node is configured by writing to files in its base directory.
|
||||
These files are read by the node when it starts, so each time you change
|
||||
@ -317,6 +318,190 @@ set the ``tub.location`` option described below.
|
||||
string will be interpreted relative to the node's base directory.
|
||||
|
||||
|
||||
Connection Management
|
||||
=====================
|
||||
|
||||
Three sections (``[tor]``, ``[i2p]``, and ``[connections]``) control how the
|
||||
Tahoe node makes outbound connections. Tor and I2P are configured here. This
|
||||
also controls when Tor and I2P are used: for all TCP connections (to hide
|
||||
your IP address), or only when necessary (just for servers which declare that
|
||||
they need Tor, because they use ``.onion`` addresses).
|
||||
|
||||
``[connections]``
|
||||
-----------------
|
||||
|
||||
This section controls *when* Tor and I2P are used. The ``[tor]`` and
|
||||
``[i2p]`` sections (described later) control *how* Tor/I2P connections are
|
||||
managed.
|
||||
|
||||
All Tahoe nodes need to make a connection to the Introducer; the ``[node]
|
||||
introducer.furl`` setting (described below) indicates where the Introducer
|
||||
lives. Tahoe client nodes must also make connections to storage servers:
|
||||
these targets are specified in announcements that come from the Introducer.
|
||||
Both are expressed as FURLs (a Foolscap URL), which include a list of
|
||||
"connection hints". Each connection hint describes one (of perhaps many)
|
||||
network endpoints where the service might live.
|
||||
|
||||
Connection hints include a type, and look like:
|
||||
|
||||
* ``tcp:tahoe.example.org:12345``
|
||||
* ``tor:u33m4y7klhz3b.onion:1000``
|
||||
* ``i2p:c2ng2pbrmxmlwpijn``
|
||||
|
||||
``tor`` hints are always handled by the ``tor`` handler (configured in the
|
||||
``[tor]`` section, described below). Likewise, ``i2p`` hints are always
|
||||
routed to the ``i2p`` handler. But either will be ignored if Tahoe was not
|
||||
installed with the necessary Tor/I2P support libraries, or if the Tor/I2P
|
||||
daemon is unreachable.
|
||||
|
||||
The ``[connections]`` section lets you control how ``tcp`` hints are handled.
|
||||
By default, they use the normal TCP handler, which just makes direct
|
||||
connections (revealing your node's IP address to both the target server and
|
||||
the intermediate network). The node behaves this way if the ``[connections]``
|
||||
section is missing entirely, or if it looks like this::
|
||||
|
||||
[connections]
|
||||
tcp = tcp
|
||||
|
||||
To hide the Tahoe node's IP address from the servers that it uses, set the
|
||||
``[connections]`` section to use Tor for TCP hints::
|
||||
|
||||
[connections]
|
||||
tcp = tor
|
||||
|
||||
(Note that I2P does not support connections to normal TCP ports, so
|
||||
``[connections] tcp = i2p`` is invalid)
|
||||
|
||||
In the future, Tahoe services may be changed to live on HTTP/HTTPS URLs
|
||||
instead of Foolscap. In that case, connections will be made using whatever
|
||||
handler is configured for ``tcp`` hints. So the same ``tcp = tor``
|
||||
configuration will work.
|
||||
|
||||
``[tor]``
|
||||
---------
|
||||
|
||||
This controls how Tor connections are made. The defaults (all empty) mean
|
||||
that, when Tor is needed, the node will try to connect to a Tor daemon's
|
||||
SOCKS proxy on localhost port 9050 or 9150. Port 9050 is the default Tor
|
||||
SOCKS port, so it should be available under any system Tor instance (e.g. the
|
||||
one launched at boot time when the standard Debian ``tor`` package is
|
||||
installed). Port 9150 is the SOCKS port for the Tor Browser Bundle, so it
|
||||
will be available any time the TBB is running.
|
||||
|
||||
You can set ``launch = True`` to cause the Tahoe node to launch a new Tor
|
||||
daemon when it starts up (and kill it at shutdown), if you don't have a
|
||||
system-wide instance available. Note that it takes 30-60 seconds for Tor to
|
||||
get running, so using a long-running Tor process may enable a faster startup.
|
||||
If your Tor executable doesn't live on ``$PATH``, use ``tor.executable=`` to
|
||||
specify it.
|
||||
|
||||
``[tor]``
|
||||
|
||||
``enable = (boolean, optional, defaults to True)``
|
||||
|
||||
If False, this will disable the use of Tor entirely. The default of True
|
||||
means the node will use Tor, if necessary, and if possible.
|
||||
|
||||
``socks.port = (string, optional, endpoint specification string, defaults to empty)``
|
||||
|
||||
This tells the node that Tor connections should be routed to a SOCKS
|
||||
proxy listening on the given endpoint. The default (of an empty value)
|
||||
will cause the node to first try localhost port 9050, then if that fails,
|
||||
try localhost port 9150. These are the default listening ports of the
|
||||
standard Tor daemon, and the Tor Browser Bundle, respectively.
|
||||
|
||||
While this nominally accepts an arbitrary endpoint string, internal
|
||||
limitations prevent it from accepting anything but ``tcp:HOST:PORT``
|
||||
(unfortunately, unix-domain sockets are not yet supported). See ticket
|
||||
#2813 for details. Also note that using a HOST of anything other than
|
||||
localhost is discouraged, because you would be revealing your IP address
|
||||
to external (and possibly hostile) machines.
|
||||
|
||||
``control.port = (string, optional, endpoint specification string)``
|
||||
|
||||
This tells the node to connect to a pre-existing Tor daemon on the given
|
||||
control port (which is typically ``unix://var/run/tor/control`` or
|
||||
``tcp:localhost:9051``). The node will then ask Tor what SOCKS port it is
|
||||
using, and route Tor connections to that.
|
||||
|
||||
``launch = (bool, optional, defaults to False)``
|
||||
|
||||
If True, the node will spawn a new (private) copy of Tor at startup, and
|
||||
will kill it at shutdown. The new Tor will be given a persistent state
|
||||
directory under ``NODEDIR/private/``, where Tor's microdescriptors will
|
||||
be cached, to speed up subsequent startup.
|
||||
|
||||
``tor.executable = (string, optional, defaults to empty)``
|
||||
|
||||
This controls which Tor executable is used when ``launch = True``. If
|
||||
empty, the first executable program named ``tor`` found on ``$PATH`` will
|
||||
be used.
|
||||
|
||||
There are 5 valid combinations of these configuration settings:
|
||||
|
||||
* 1: ``(empty)``: use SOCKS on port 9050/9150
|
||||
* 2: ``launch = true``: launch a new Tor
|
||||
* 3: ``socks.port = tcp:HOST:PORT``: use an existing Tor on the given SOCKS port
|
||||
* 4: ``control.port = ENDPOINT``: use an existing Tor at the given control port
|
||||
* 5: ``enable = false``: no Tor at all
|
||||
|
||||
1 is the default, and should work for any Linux host with the system Tor
|
||||
package installed. 2 should work on any box with Tor installed into $PATH,
|
||||
but will take an extra 30-60 seconds at startup. 3 and 4 can be used for
|
||||
specialized installations, where Tor is already running, but not listening on
|
||||
the default port. 5 should be used in environments where Tor is installed,
|
||||
but should not be used (perhaps due to a site-wide policy).
|
||||
|
||||
Note that Tor support depends upon some additional Python libraries. To
|
||||
install Tahoe with Tor support, use ``pip install tahoe-lafs[tor]``.
|
||||
|
||||
``[i2p]``
|
||||
---------
|
||||
|
||||
This controls how I2P connections are made. Like with Tor, the all-empty
|
||||
defaults will cause I2P connections to be routed to a pre-existing I2P daemon
|
||||
on port 7656. This is the default SAM port for the ``i2p`` daemon.
|
||||
|
||||
|
||||
``[i2p]``
|
||||
|
||||
``enable = (boolean, optional, defaults to True)``
|
||||
|
||||
If False, this will disable the use of I2P entirely. The default of True
|
||||
means the node will use I2P, if necessary, and if possible.
|
||||
|
||||
``sam.port = (string, optional, endpoint descriptor, defaults to empty)``
|
||||
|
||||
This tells the node that I2P connections should be made via the SAM
|
||||
protocol on the given port. The default (of an empty value) will cause
|
||||
the node to try localhost port 7656. This is the default listening port
|
||||
of the standard I2P daemon.
|
||||
|
||||
``launch = (bool, optional, defaults to False)``
|
||||
|
||||
If True, the node will spawn a new (private) copy of I2P at startup, and
|
||||
will kill it at shutdown. The new I2P will be given a persistent state
|
||||
directory under ``NODEDIR/private/``, where I2P's microdescriptors will
|
||||
be cached, to speed up subsequent startup. The daemon will allocate its
|
||||
own SAM port, which will be queried from the config directory.
|
||||
|
||||
``i2p.configdir = (string, optional, directory)``
|
||||
|
||||
This tells the node to parse an I2P config file in the given directory,
|
||||
and use the SAM port it finds there. If ``launch = True``, the new I2P
|
||||
daemon will be told to use the given directory (which can be
|
||||
pre-populated with a suitable config file). If ``launch = False``, we
|
||||
assume there is a pre-running I2P daemon running from this directory, and
|
||||
can again parse the config file for the SAM port.
|
||||
|
||||
``i2p.executable = (string, optional, defaults to empty)``
|
||||
|
||||
This controls which I2P executable is used when ``launch = True``. If
|
||||
empty, the first executable program named ``i2p`` found on ``$PATH`` will
|
||||
be used.
|
||||
|
||||
|
||||
|
||||
Client Configuration
|
||||
====================
|
||||
|
||||
@ -569,6 +754,8 @@ Other Files in BASEDIR
|
||||
|
||||
Some configuration is not kept in ``tahoe.cfg``, for the following reasons:
|
||||
|
||||
* it doesn't fit into the INI format of ``tahoe.cfg`` (e.g.
|
||||
``private/servers.yaml``)
|
||||
* it is generated by the node at startup, e.g. encryption keys. The node
|
||||
never writes to ``tahoe.cfg``.
|
||||
* it is generated by user action, e.g. the "``tahoe create-alias``" command.
|
||||
@ -740,6 +927,38 @@ Or, if you're feeling really lazy::
|
||||
|
||||
.. _YAML: http://yaml.org/
|
||||
|
||||
Overriding Connection-Handlers for Static Servers
|
||||
-------------------------------------------------
|
||||
|
||||
A ``connections`` entry will override the default connection-handler mapping
|
||||
(as established by ``tahoe.cfg [connections]``). This can be used to build a
|
||||
"Tor-mostly client": one which is restricted to use Tor for all connections,
|
||||
except for a few private servers to which normal TCP connections will be
|
||||
made. To override the published announcement (and thus avoid connecting twice
|
||||
to the same server), the server ID must exactly match.
|
||||
|
||||
``tahoe.cfg``::
|
||||
|
||||
[connections]
|
||||
# this forces the use of Tor for all "tcp" hints
|
||||
tcp = tor
|
||||
|
||||
``private/servers.yaml``::
|
||||
|
||||
storage:
|
||||
v0-c2ng2pbrmxmlwpijn3mr72ckk5fmzk6uxf6nhowyosaubrt6y5mq:
|
||||
ann:
|
||||
nickname: my-server-1
|
||||
anonymous-storage-FURL: pb://u33m4y7klhz3bypswqkozwetvabelhxt@tcp:10.1.2.3:51298/eiu2i7p6d6mm4ihmss7ieou5hac3wn6b
|
||||
connections:
|
||||
# this overrides the tcp=tor from tahoe.cfg, for just this server
|
||||
tcp: tcp
|
||||
|
||||
The ``connections`` table is needed to override the ``tcp = tor`` mapping
|
||||
that comes from ``tahoe.cfg``. Without it, the client would attempt to use
|
||||
Tor to connect to ``10.1.2.3``, which would fail because it is a
|
||||
local/non-routeable (RFC1918) address.
|
||||
|
||||
|
||||
Other files
|
||||
===========
|
||||
|
12
setup.py
12
setup.py
@ -261,6 +261,18 @@ setup(name="tahoe-lafs", # also set in __init__.py
|
||||
"coverage",
|
||||
"mock",
|
||||
"tox",
|
||||
"foolscap[tor]",
|
||||
"txtorcon", # in case pip's resolver doesn't work
|
||||
"foolscap[i2p]",
|
||||
"txi2p", # in case pip's resolver doesn't work
|
||||
],
|
||||
"tor": [
|
||||
"foolscap[tor]",
|
||||
"txtorcon", # in case pip's resolver doesn't work
|
||||
],
|
||||
"i2p": [
|
||||
"foolscap[i2p]",
|
||||
"txi2p", # in case pip's resolver doesn't work
|
||||
],
|
||||
},
|
||||
package_data={"allmydata.web": ["*.xhtml",
|
||||
|
@ -39,7 +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.10.1",
|
||||
# * foolscap >= 0.12.2 provides tcp/tor/i2p connection handlers we need
|
||||
"foolscap >= 0.12.2",
|
||||
|
||||
# Needed for SFTP.
|
||||
# pycrypto 2.2 doesn't work due to <https://bugs.launchpad.net/pycrypto/+bug/620253>
|
||||
|
@ -1,6 +1,7 @@
|
||||
import datetime, os.path, re, types, ConfigParser, tempfile
|
||||
from base64 import b32decode, b32encode
|
||||
|
||||
from twisted.internet import reactor, endpoints
|
||||
from twisted.python import log as twlog
|
||||
from twisted.application import service
|
||||
from foolscap.api import Tub, app_versions
|
||||
@ -83,6 +84,7 @@ class Node(service.MultiService):
|
||||
assert type(self.nickname) is unicode
|
||||
|
||||
self.init_tempdir()
|
||||
self.init_connections()
|
||||
self.set_tub_options()
|
||||
self.create_main_tub()
|
||||
self.create_control_tub()
|
||||
@ -164,6 +166,101 @@ class Node(service.MultiService):
|
||||
twlog.msg(e)
|
||||
raise e
|
||||
|
||||
def _make_tcp_handler(self):
|
||||
# this is always available
|
||||
from foolscap.connections.tcp import default
|
||||
return default()
|
||||
|
||||
def _make_tor_handler(self):
|
||||
enabled = self.get_config("tor", "enable", True, boolean=True)
|
||||
if not enabled:
|
||||
return None
|
||||
try:
|
||||
from foolscap.connections import tor
|
||||
except ImportError:
|
||||
return None
|
||||
|
||||
if self.get_config("tor", "launch", False, boolean=True):
|
||||
executable = self.get_config("tor", "tor.executable", None)
|
||||
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)
|
||||
|
||||
controlport = self.get_config("tor", "control.port", None)
|
||||
if controlport:
|
||||
ep = endpoints.clientFromString(reactor, controlport)
|
||||
return tor.control_endpoint(ep)
|
||||
|
||||
return tor.default_socks()
|
||||
|
||||
def _make_i2p_handler(self):
|
||||
enabled = self.get_config("i2p", "enable", True, boolean=True)
|
||||
if not enabled:
|
||||
return None
|
||||
try:
|
||||
from foolscap.connections import i2p
|
||||
except ImportError:
|
||||
return None
|
||||
|
||||
samport = self.get_config("i2p", "sam.port", None)
|
||||
launch = self.get_config("i2p", "launch", False, boolean=True)
|
||||
configdir = self.get_config("i2p", "i2p.configdir", None)
|
||||
|
||||
if samport:
|
||||
if launch:
|
||||
raise ValueError("tahoe.cfg [i2p] must not set both "
|
||||
"sam.port and launch")
|
||||
ep = endpoints.clientFromString(reactor, samport)
|
||||
return i2p.sam_endpoint(ep)
|
||||
|
||||
if launch:
|
||||
executable = self.get_config("i2p", "i2p.executable", None)
|
||||
return i2p.launch(i2p_configdir=configdir, i2p_binary=executable)
|
||||
|
||||
if configdir:
|
||||
return i2p.local_i2p(configdir)
|
||||
|
||||
return i2p.default(reactor)
|
||||
|
||||
def init_connections(self):
|
||||
# We store handlers for everything. None means we were unable to
|
||||
# create that handler, so hints which want it will be ignored.
|
||||
handlers = self._foolscap_connection_handlers = {
|
||||
"tcp": self._make_tcp_handler(),
|
||||
"tor": self._make_tor_handler(),
|
||||
"i2p": self._make_i2p_handler(),
|
||||
}
|
||||
self.log("built Foolscap connection handlers for: %(known_handlers)s",
|
||||
known_handlers=sorted([k for k,v in handlers.items() if v]),
|
||||
facility="tahoe.node", umid="PuLh8g")
|
||||
|
||||
# then we remember the default mappings from tahoe.cfg
|
||||
self._default_connection_handlers = {"tor": "tor", "i2p": "i2p"}
|
||||
tcp_handler_name = self.get_config("connections", "tcp", "tcp").lower()
|
||||
if tcp_handler_name not in handlers:
|
||||
raise ValueError("'tahoe.cfg [connections] tcp='"
|
||||
" uses unknown handler type '%s'"
|
||||
% tcp_handler_name)
|
||||
self._default_connection_handlers["tcp"] = tcp_handler_name
|
||||
|
||||
def set_tub_options(self):
|
||||
self.tub_options = {
|
||||
"logLocalFailures": True,
|
||||
@ -181,12 +278,18 @@ class Node(service.MultiService):
|
||||
self.tub_options["disconnectTimeout"] = int(disconnect_timeout_s)
|
||||
|
||||
def _create_tub(self, handler_overrides={}, **kwargs):
|
||||
assert not handler_overrides
|
||||
# Create a Tub with the right options and handlers. It will be
|
||||
# ephemeral unless the caller provides certFile=
|
||||
tub = Tub(**kwargs)
|
||||
for (name, value) in self.tub_options.items():
|
||||
tub.setOption(name, value)
|
||||
handlers = self._default_connection_handlers.copy()
|
||||
handlers.update(handler_overrides)
|
||||
tub.removeAllConnectionHintHandlers()
|
||||
for hint_type, handler_name in handlers.items():
|
||||
handler = self._foolscap_connection_handlers.get(handler_name)
|
||||
if handler:
|
||||
tub.addConnectionHintHandler(hint_type, handler)
|
||||
return tub
|
||||
|
||||
def _convert_tub_port(self, s):
|
||||
|
@ -86,6 +86,8 @@ class StorageFarmBroker(service.MultiService):
|
||||
|
||||
def set_static_servers(self, servers):
|
||||
for (server_id, server) in servers.items():
|
||||
assert isinstance(server_id, unicode) # from YAML
|
||||
server_id = server_id.encode("ascii")
|
||||
self._static_server_ids.add(server_id)
|
||||
handler_overrides = server.get("connections", {})
|
||||
s = NativeStorageServer(server_id, server["ann"],
|
||||
@ -275,6 +277,7 @@ class NativeStorageServer(service.MultiService):
|
||||
|
||||
def __init__(self, server_id, ann, tub_maker, handler_overrides):
|
||||
service.MultiService.__init__(self)
|
||||
assert isinstance(server_id, str)
|
||||
self._server_id = server_id
|
||||
self.announcement = ann
|
||||
self._tub_maker = tub_maker
|
||||
|
@ -180,6 +180,8 @@ class NoNetworkStorageBroker:
|
||||
|
||||
class NoNetworkClient(Client):
|
||||
|
||||
def init_connections(self):
|
||||
pass
|
||||
def create_main_tub(self):
|
||||
pass
|
||||
def init_introducer_client(self):
|
||||
|
211
src/allmydata/test/test_connections.py
Normal file
211
src/allmydata/test/test_connections.py
Normal file
@ -0,0 +1,211 @@
|
||||
import os
|
||||
import mock
|
||||
from io import BytesIO
|
||||
from twisted.trial import unittest
|
||||
from twisted.internet import reactor, endpoints
|
||||
from ConfigParser import SafeConfigParser
|
||||
from foolscap.connections import tcp
|
||||
from ..node import Node
|
||||
|
||||
class FakeNode(Node):
|
||||
def __init__(self, config_str):
|
||||
self.config = SafeConfigParser()
|
||||
self.config.readfp(BytesIO(config_str))
|
||||
|
||||
BASECONFIG = ("[client]\n"
|
||||
"introducer.furl = \n"
|
||||
)
|
||||
|
||||
|
||||
class TCP(unittest.TestCase):
|
||||
def test_default(self):
|
||||
n = FakeNode(BASECONFIG)
|
||||
h = n._make_tcp_handler()
|
||||
self.assertIsInstance(h, tcp.DefaultTCP)
|
||||
|
||||
class Tor(unittest.TestCase):
|
||||
def test_disabled(self):
|
||||
n = FakeNode(BASECONFIG+"[tor]\nenable = false\n")
|
||||
h = n._make_tor_handler()
|
||||
self.assertEqual(h, None)
|
||||
|
||||
def test_default(self):
|
||||
n = FakeNode(BASECONFIG)
|
||||
h1 = mock.Mock()
|
||||
with mock.patch("foolscap.connections.tor.default_socks",
|
||||
return_value=h1) as f:
|
||||
h = n._make_tor_handler()
|
||||
self.assertEqual(f.mock_calls, [mock.call()])
|
||||
self.assertIdentical(h, h1)
|
||||
|
||||
def test_launch(self):
|
||||
n = FakeNode(BASECONFIG+"[tor]\nlaunch = true\n")
|
||||
n.basedir = "BASEDIR"
|
||||
h1 = mock.Mock()
|
||||
with mock.patch("foolscap.connections.tor.launch",
|
||||
return_value=h1) as f:
|
||||
h = n._make_tor_handler()
|
||||
data_directory = os.path.join(n.basedir, "private", "tor-statedir")
|
||||
exp = mock.call(data_directory=data_directory,
|
||||
tor_binary=None)
|
||||
self.assertEqual(f.mock_calls, [exp])
|
||||
self.assertIdentical(h, h1)
|
||||
|
||||
def test_launch_executable(self):
|
||||
n = FakeNode(BASECONFIG+"[tor]\nlaunch = true\ntor.executable = tor")
|
||||
n.basedir = "BASEDIR"
|
||||
h1 = mock.Mock()
|
||||
with mock.patch("foolscap.connections.tor.launch",
|
||||
return_value=h1) as f:
|
||||
h = n._make_tor_handler()
|
||||
data_directory = os.path.join(n.basedir, "private", "tor-statedir")
|
||||
exp = mock.call(data_directory=data_directory,
|
||||
tor_binary="tor")
|
||||
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")
|
||||
h1 = mock.Mock()
|
||||
with mock.patch("foolscap.connections.tor.socks_port",
|
||||
return_value=h1) as f:
|
||||
h = n._make_tor_handler()
|
||||
self.assertEqual(f.mock_calls, [mock.call("127.0.0.1", 1234)])
|
||||
self.assertIdentical(h, h1)
|
||||
|
||||
def test_socksport_otherhost(self):
|
||||
n = FakeNode(BASECONFIG+"[tor]\nsocks.port = tcp:otherhost:1234\n")
|
||||
h1 = mock.Mock()
|
||||
with mock.patch("foolscap.connections.tor.socks_port",
|
||||
return_value=h1) as f:
|
||||
h = n._make_tor_handler()
|
||||
self.assertEqual(f.mock_calls, [mock.call("otherhost", 1234)])
|
||||
self.assertIdentical(h, h1)
|
||||
|
||||
def test_socksport_bad_endpoint(self):
|
||||
n = FakeNode(BASECONFIG+"[tor]\nsocks.port = unix:unsupported\n")
|
||||
e = self.assertRaises(ValueError, n._make_tor_handler)
|
||||
self.assertIn("is currently limited to 'tcp:HOST:PORT'", 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))
|
||||
|
||||
def test_controlport(self):
|
||||
n = FakeNode(BASECONFIG+"[tor]\ncontrol.port = tcp:localhost:1234\n")
|
||||
h1 = mock.Mock()
|
||||
with mock.patch("foolscap.connections.tor.control_endpoint",
|
||||
return_value=h1) as f:
|
||||
h = n._make_tor_handler()
|
||||
self.assertEqual(len(f.mock_calls), 1)
|
||||
ep = f.mock_calls[0][1][0]
|
||||
self.assertIsInstance(ep, endpoints.TCP4ClientEndpoint)
|
||||
self.assertIdentical(h, h1)
|
||||
|
||||
class I2P(unittest.TestCase):
|
||||
def test_disabled(self):
|
||||
n = FakeNode(BASECONFIG+"[i2p]\nenable = false\n")
|
||||
h = n._make_i2p_handler()
|
||||
self.assertEqual(h, None)
|
||||
|
||||
def test_default(self):
|
||||
n = FakeNode(BASECONFIG)
|
||||
h1 = mock.Mock()
|
||||
with mock.patch("foolscap.connections.i2p.default",
|
||||
return_value=h1) as f:
|
||||
h = n._make_i2p_handler()
|
||||
self.assertEqual(f.mock_calls, [mock.call(reactor)])
|
||||
self.assertIdentical(h, h1)
|
||||
|
||||
def test_samport(self):
|
||||
n = FakeNode(BASECONFIG+"[i2p]\nsam.port = tcp:localhost:1234\n")
|
||||
h1 = mock.Mock()
|
||||
with mock.patch("foolscap.connections.i2p.sam_endpoint",
|
||||
return_value=h1) as f:
|
||||
h = n._make_i2p_handler()
|
||||
self.assertEqual(len(f.mock_calls), 1)
|
||||
ep = f.mock_calls[0][1][0]
|
||||
self.assertIsInstance(ep, endpoints.TCP4ClientEndpoint)
|
||||
self.assertIdentical(h, h1)
|
||||
|
||||
def test_samport_and_launch(self):
|
||||
n = FakeNode(BASECONFIG+"[i2p]\n" +
|
||||
"sam.port = tcp:localhost:1234\n"
|
||||
+"launch = true\n")
|
||||
e = self.assertRaises(ValueError, n._make_i2p_handler)
|
||||
self.assertIn("must not set both sam.port and launch", str(e))
|
||||
|
||||
def test_launch(self):
|
||||
n = FakeNode(BASECONFIG+"[i2p]\nlaunch = true\n")
|
||||
h1 = mock.Mock()
|
||||
with mock.patch("foolscap.connections.i2p.launch",
|
||||
return_value=h1) as f:
|
||||
h = n._make_i2p_handler()
|
||||
exp = mock.call(i2p_configdir=None, i2p_binary=None)
|
||||
self.assertEqual(f.mock_calls, [exp])
|
||||
self.assertIdentical(h, h1)
|
||||
|
||||
def test_launch_executable(self):
|
||||
n = FakeNode(BASECONFIG+"[i2p]\nlaunch = true\n" +
|
||||
"i2p.executable = i2p\n")
|
||||
h1 = mock.Mock()
|
||||
with mock.patch("foolscap.connections.i2p.launch",
|
||||
return_value=h1) as f:
|
||||
h = n._make_i2p_handler()
|
||||
exp = mock.call(i2p_configdir=None, i2p_binary="i2p")
|
||||
self.assertEqual(f.mock_calls, [exp])
|
||||
self.assertIdentical(h, h1)
|
||||
|
||||
def test_launch_configdir(self):
|
||||
n = FakeNode(BASECONFIG+"[i2p]\nlaunch = true\n" +
|
||||
"i2p.configdir = cfg\n")
|
||||
h1 = mock.Mock()
|
||||
with mock.patch("foolscap.connections.i2p.launch",
|
||||
return_value=h1) as f:
|
||||
h = n._make_i2p_handler()
|
||||
exp = mock.call(i2p_configdir="cfg", i2p_binary=None)
|
||||
self.assertEqual(f.mock_calls, [exp])
|
||||
self.assertIdentical(h, h1)
|
||||
|
||||
def test_launch_configdir_and_executable(self):
|
||||
n = FakeNode(BASECONFIG+"[i2p]\nlaunch = true\n" +
|
||||
"i2p.executable = i2p\n" +
|
||||
"i2p.configdir = cfg\n")
|
||||
h1 = mock.Mock()
|
||||
with mock.patch("foolscap.connections.i2p.launch",
|
||||
return_value=h1) as f:
|
||||
h = n._make_i2p_handler()
|
||||
exp = mock.call(i2p_configdir="cfg", i2p_binary="i2p")
|
||||
self.assertEqual(f.mock_calls, [exp])
|
||||
self.assertIdentical(h, h1)
|
||||
|
||||
def test_configdir(self):
|
||||
n = FakeNode(BASECONFIG+"[i2p]\ni2p.configdir = cfg\n")
|
||||
h1 = mock.Mock()
|
||||
with mock.patch("foolscap.connections.i2p.local_i2p",
|
||||
return_value=h1) as f:
|
||||
h = n._make_i2p_handler()
|
||||
self.assertEqual(f.mock_calls, [mock.call("cfg")])
|
||||
self.assertIdentical(h, h1)
|
||||
|
||||
class Connections(unittest.TestCase):
|
||||
def test_default(self):
|
||||
n = FakeNode(BASECONFIG)
|
||||
n.init_connections()
|
||||
self.assertEqual(n._default_connection_handlers["tcp"], "tcp")
|
||||
self.assertEqual(n._default_connection_handlers["tor"], "tor")
|
||||
self.assertEqual(n._default_connection_handlers["i2p"], "i2p")
|
||||
|
||||
def test_tor(self):
|
||||
n = FakeNode(BASECONFIG+"[connections]\ntcp = tor\n")
|
||||
n.init_connections()
|
||||
self.assertEqual(n._default_connection_handlers["tcp"], "tor")
|
||||
self.assertEqual(n._default_connection_handlers["tor"], "tor")
|
||||
self.assertEqual(n._default_connection_handlers["i2p"], "i2p")
|
||||
|
||||
def test_unknown(self):
|
||||
n = FakeNode(BASECONFIG+"[connections]\ntcp = unknown\n")
|
||||
e = self.assertRaises(ValueError, n.init_connections)
|
||||
self.assertIn("'tahoe.cfg [connections] tcp='", str(e))
|
||||
self.assertIn("uses unknown handler type 'unknown'", str(e))
|
@ -1,6 +1,6 @@
|
||||
import hashlib
|
||||
from mock import Mock
|
||||
from allmydata.util import base32
|
||||
from allmydata.util import base32, yamlutil
|
||||
|
||||
from twisted.trial import unittest
|
||||
from twisted.internet.defer import succeed, inlineCallbacks
|
||||
@ -48,17 +48,21 @@ class TestStorageFarmBroker(unittest.TestCase):
|
||||
def test_static_servers(self):
|
||||
broker = StorageFarmBroker(True, lambda h: Mock())
|
||||
|
||||
key_s = 'v0-1234-{}'.format(1)
|
||||
ann = {
|
||||
"service-name": "storage",
|
||||
"anonymous-storage-FURL": "pb://{}@nowhere/fake".format(base32.b2a(str(1))),
|
||||
"permutation-seed-base32": "aaaaaaaaaaaaaaaaaaaaaaaa",
|
||||
}
|
||||
key_s = 'v0-1234-1'
|
||||
servers_yaml = """\
|
||||
storage:
|
||||
v0-1234-1:
|
||||
ann:
|
||||
anonymous-storage-FURL: pb://ge@nowhere/fake
|
||||
permutation-seed-base32: aaaaaaaaaaaaaaaaaaaaaaaa
|
||||
"""
|
||||
servers = yamlutil.safe_load(servers_yaml)
|
||||
permseed = base32.a2b("aaaaaaaaaaaaaaaaaaaaaaaa")
|
||||
broker.set_static_servers({key_s: {"ann": ann}})
|
||||
broker.set_static_servers(servers["storage"])
|
||||
self.failUnlessEqual(len(broker._static_server_ids), 1)
|
||||
s = broker.servers[key_s]
|
||||
self.failUnlessEqual(s.announcement, ann)
|
||||
self.failUnlessEqual(s.announcement,
|
||||
servers["storage"]["v0-1234-1"]["ann"])
|
||||
self.failUnlessEqual(s.get_serverid(), key_s)
|
||||
self.assertEqual(s.get_permutation_seed(), permseed)
|
||||
|
||||
@ -82,7 +86,7 @@ class TestStorageFarmBroker(unittest.TestCase):
|
||||
ann = {
|
||||
"anonymous-storage-FURL": "pb://abcde@nowhere/fake",
|
||||
}
|
||||
broker.set_static_servers({server_id: {"ann": ann}})
|
||||
broker.set_static_servers({server_id.decode("ascii"): {"ann": ann}})
|
||||
s = broker.servers[server_id]
|
||||
self.assertEqual(s.get_permutation_seed(), base32.a2b(k))
|
||||
|
||||
@ -94,7 +98,7 @@ class TestStorageFarmBroker(unittest.TestCase):
|
||||
"anonymous-storage-FURL": "pb://abcde@nowhere/fake",
|
||||
"permutation-seed-base32": k,
|
||||
}
|
||||
broker.set_static_servers({server_id: {"ann": ann}})
|
||||
broker.set_static_servers({server_id.decode("ascii"): {"ann": ann}})
|
||||
s = broker.servers[server_id]
|
||||
self.assertEqual(s.get_permutation_seed(), base32.a2b(k))
|
||||
|
||||
@ -104,7 +108,7 @@ class TestStorageFarmBroker(unittest.TestCase):
|
||||
ann = {
|
||||
"anonymous-storage-FURL": "pb://abcde@nowhere/fake",
|
||||
}
|
||||
broker.set_static_servers({server_id: {"ann": ann}})
|
||||
broker.set_static_servers({server_id.decode("ascii"): {"ann": ann}})
|
||||
s = broker.servers[server_id]
|
||||
self.assertEqual(s.get_permutation_seed(),
|
||||
hashlib.sha256(server_id).digest())
|
||||
|
Loading…
x
Reference in New Issue
Block a user