implement --listen=none, use it for create-client

Improve docs on server configuration to explain --listen options.
This commit is contained in:
Brian Warner 2016-09-14 15:47:08 -07:00
parent d69757e069
commit 02ba2a05c3
5 changed files with 137 additions and 39 deletions

View File

@ -26,6 +26,54 @@ The first step when setting up a server is to figure out how clients will
reach it. Then you need to configure the server to listen on some ports, and
then configure the location properly.
Manual Configuration
====================
Each server has two settings in their ``tahoe.cfg`` file: ``tub.port``, and
``tub.location``. The "port" controls what the server node listens to: this
is generally a TCP port.
The "location" controls what is advertised to the outside world. This is a
"foolscap connection hint", and it includes both the type of the connection
(tcp, tor, or i2p) and the connection details (hostname/address, port
number). Various proxies, port-forwardings, and privacy networks might be
involved, so it's not uncommon for ``tub.port`` and ``tub.location`` to look
different.
You can directly control the ``tub.port`` and ``tub.location`` configuration
settings by providing ``--port=`` and ``--location=`` when running ``tahoe
create-node``.
Automatic Configuration
=======================
Instead of providing ``--port=/--location=``, you can use ``--listen=``.
Servers can listen on TCP, Tor, I2P, a combination of those, or none at all.
The ``--listen=`` argument controls which kinds of listeners the new server
will use.
``--listen=none`` means the server should not listen at all. This doesn't
make sense for a server, but is appropriate for a client-only node. The
``tahoe create-client`` command automatically includes ``--listen=none``.
``--listen=tcp`` is the default, and turns on a standard TCP listening port.
Using ``--listen=tcp`` requires a ``--hostname=`` argument too, which will be
incorporated into the node's advertised location. We've found that computers
cannot reliably determine their externally-reachable hostname, so rather than
having the server make a guess (or scanning its interfaces for IP addresses
that might or might not be appropriate), node creation requires the user to
provide the hostname.
``--listen=tor`` will talk to a local Tor daemon and create a new "onion
server" address (which look like ``alzrgrdvxct6c63z.onion``). Likewise
``--listen=i2p`` will talk to a local I2P daemon and create a new server
address. See :doc:`anonymity-configuration` for details.
You could listen on all three by using ``--listen=tcp,tor,i2p``.
Deployment Scenarios
====================
The following are some suggested scenarios for configuring servers using
various network transports. These examples do not include specifying an
introducer FURL which normally you would want when provisioning storage
@ -39,7 +87,7 @@ nodes. For these and other configuration details please refer to
Server has a public DNS name
============================
----------------------------
The simplest case is where your server host is directly connected to the
internet, without a firewall or NAT box in the way. Most VPS (Virtual Private
@ -72,7 +120,7 @@ support for IPv6 is new, and may still have problems. Please see ticket
Server has a public IPv4/IPv6 address
=====================================
-------------------------------------
If the host has a routeable (public) IPv4 address (e.g. ``203.0.113.1``), but
no DNS name, you will need to choose a TCP port (e.g. ``3457``), and use the
@ -113,7 +161,7 @@ IPv6-enabled port with this::
Server is behind a firewall with port forwarding
================================================
------------------------------------------------
To configure a storage node behind a firewall with port forwarding you will
need to know:
@ -140,7 +188,7 @@ port 3457, then do this::
Using I2P/Tor to Avoid Port-Forwarding
======================================
--------------------------------------
I2P and Tor onion services, among other great properties, also provide NAT
penetration without port-forwarding, hostnames, or IP addresses. So setting

View File

@ -30,6 +30,12 @@ WHERE_OPTS = [
]
def validate_where_options(o):
if o['listen'] == "none":
# no other arguments are accepted
if o['hostname']:
raise UsageError("--hostname cannot be used when --listen=none")
if o['port'] or o['location']:
raise UsageError("--port/--location cannot be used when --listen=none")
# --location and --port: overrides all others, rejects all others
if o['location'] and not o['port']:
raise UsageError("--location must be used with --port")
@ -50,15 +56,16 @@ def validate_where_options(o):
# then change parseArgs() to transform the None into "tcp"
else:
# no --location and --port? expect --listen= (maybe the default), and
# --listen=tcp requires --hostname
listeners = o['listen'].split(",")
if 'tcp' in listeners and not o['hostname']:
raise UsageError("--listen=tcp requires --hostname=")
if 'tcp' not in listeners and o['hostname']:
raise UsageError("--listen= must be tcp to use --hostname")
for l in listeners:
if l not in ["tcp", "tor", "i2p"]:
raise UsageError("--listen= must be: tcp, tor, i2p")
# --listen=tcp requires --hostname. But --listen=none is special.
if o['listen'] != "none":
listeners = o['listen'].split(",")
for l in listeners:
if l not in ["tcp", "tor", "i2p"]:
raise UsageError("--listen= must be none, or one/some of: tcp, tor, i2p")
if 'tcp' in listeners and not o['hostname']:
raise UsageError("--listen=tcp requires --hostname=")
if 'tcp' not in listeners and o['hostname']:
raise UsageError("--listen= must be tcp to use --hostname")
class _CreateBaseOptions(BasedirOptions):
optFlags = [
@ -139,27 +146,27 @@ def write_node_config(c, config):
c.write("web.port = %s\n" % (webport.encode('utf-8'),))
c.write("web.static = public_html\n")
if 'hostname' in config and config['hostname'] is not None:
new_port = iputil.allocate_tcp_port()
c.write("tub.port = tcp:%s\n" % new_port)
c.write("tub.location = tcp:%s:%s\n" % (config.get('hostname').encode('utf-8'), new_port))
elif 'listen' in config and config['listen'] == "tor":
raise NotImplementedError("This feature addition is being tracked by this ticket:" +
"https://tahoe-lafs.org/trac/tahoe-lafs/ticket/2490")
elif 'listen' in config and config['listen'] == "i2p":
raise NotImplementedError("This feature addition is being tracked by this ticket:" +
"https://tahoe-lafs.org/trac/tahoe-lafs/ticket/2490")
elif config.get('port') is not None:
c.write("tub.port = %s\n" % config.get('port').encode('utf-8'))
c.write("tub.location = %s\n" % config.get('location').encode('utf-8'))
else:
listeners = config['listen'].split(",")
if listeners == ["none"]:
c.write("tub.port = disabled\n")
c.write("tub.location = disabled\n")
if config.get('hostname', None) or config.get('listen', None):
c.write("# to prevent the Tub from listening at all, use this:\n")
c.write("# tub.port = disabled\n")
c.write("# tub.location = disabled\n")
else:
if "tor" in listeners:
raise NotImplementedError("--listen=tor is under development, "
"see ticket #2490 for details")
if "i2p" in listeners:
raise NotImplementedError("--listen=i2p is under development, "
"see ticket #2490 for details")
if "tcp" in listeners:
if config["port"]: # --port/--location are a pair
c.write("tub.port = %s\n" % config["port"].encode('utf-8'))
c.write("tub.location = %s\n" % config["location"].encode('utf-8'))
else:
assert "hostname" in config
hostname = config["hostname"]
new_port = iputil.allocate_tcp_port()
c.write("tub.port = tcp:%s\n" % new_port)
c.write("tub.location = tcp:%s:%s\n" % (hostname.encode('utf-8'), new_port))
c.write("#log_gatherer.furl =\n")
c.write("#timeout.keepalive =\n")
@ -234,6 +241,7 @@ def create_node(config):
def create_client(config):
config['no-storage'] = True
config['listen'] = "none"
return create_node(config)

View File

@ -94,21 +94,59 @@ class Config(unittest.TestCase):
"create-node", "--listen=tcp", basedir)
self.assertIn("--listen=tcp requires --hostname=", str(e))
@defer.inlineCallbacks
def test_node_listen_none(self):
basedir = self.mktemp()
rc, out, err = yield run_cli("create-node", "--listen=none", basedir)
cfg = self.read_config(basedir)
self.assertEqual(cfg.get("node", "tub.port"), "disabled")
self.assertEqual(cfg.get("node", "tub.location"), "disabled")
def test_node_listen_none_errors(self):
basedir = self.mktemp()
e = self.assertRaises(usage.UsageError,
parse_cli,
"create-node", "--listen=none",
"--hostname=foo",
basedir)
self.assertEqual(str(e), "--hostname cannot be used when --listen=none")
e = self.assertRaises(usage.UsageError,
parse_cli,
"create-node", "--listen=none",
"--port=foo", "--location=foo",
basedir)
self.assertEqual(str(e), "--port/--location cannot be used when --listen=none")
e = self.assertRaises(usage.UsageError,
parse_cli,
"create-node", "--listen=tcp,none",
basedir)
self.assertEqual(str(e), "--listen= must be none, or one/some of: tcp, tor, i2p")
def test_node_listen_bad(self):
basedir = self.mktemp()
e = self.assertRaises(usage.UsageError,
parse_cli,
"create-node", "--listen=XYZZY,tcp",
basedir)
self.assertEqual(str(e), "--listen= must be none, or one/some of: tcp, tor, i2p")
@defer.inlineCallbacks
def test_node_listen_tor(self):
basedir = self.mktemp()
d = run_cli("create-node", "--listen=tor", basedir)
e = yield self.assertFailure(d, NotImplementedError)
self.assertEqual(str(e), "This feature addition is being tracked by this ticket:" +
"https://tahoe-lafs.org/trac/tahoe-lafs/ticket/2490")
self.assertEqual(str(e), "--listen=tor is under development, "
"see ticket #2490 for details")
@defer.inlineCallbacks
def test_node_listen_i2p(self):
basedir = self.mktemp()
d = run_cli("create-node", "--listen=i2p", basedir)
e = yield self.assertFailure(d, NotImplementedError)
self.failUnlessEqual(str(e), "This feature addition is being tracked by this ticket:" +
"https://tahoe-lafs.org/trac/tahoe-lafs/ticket/2490")
self.assertEqual(str(e), "--listen=i2p is under development, "
"see ticket #2490 for details")
def test_node_port_only(self):
e = self.assertRaises(usage.UsageError,

View File

@ -87,6 +87,7 @@ class ConfigUtilTests(GridTestMixin, unittest.TestCase):
opts = {"nickname": "nick",
"webport": "tcp:3456",
"hide-ip": False,
"listen": "none",
}
create_node.write_node_config(f, opts)
create_node.write_client_config(f, opts)

View File

@ -20,7 +20,8 @@ class MultiIntroTests(unittest.TestCase):
# create a custom tahoe.cfg
self.basedir = os.path.dirname(self.mktemp())
c = open(os.path.join(self.basedir, "tahoe.cfg"), "w")
config = {'hide-ip':False}
config = {'hide-ip':False, 'listen': 'tcp',
'port': None, 'location': None, 'hostname': 'example.net'}
write_node_config(c, config)
fake_furl = "furl1"
c.write("[client]\n")
@ -66,7 +67,8 @@ class MultiIntroTests(unittest.TestCase):
the tahoe.cfg file. """
# create a custom tahoe.cfg
c = open(os.path.join(self.basedir, "tahoe.cfg"), "w")
config = {'hide-ip':False}
config = {'hide-ip':False, 'listen': 'tcp',
'port': None, 'location': None, 'hostname': 'example.net'}
write_node_config(c, config)
fake_furl = "furl1"
c.write("[client]\n")
@ -96,7 +98,8 @@ class NoDefault(unittest.TestCase):
# create a custom tahoe.cfg
self.basedir = os.path.dirname(self.mktemp())
c = open(os.path.join(self.basedir, "tahoe.cfg"), "w")
config = {'hide-ip':False}
config = {'hide-ip':False, 'listen': 'tcp',
'port': None, 'location': None, 'hostname': 'example.net'}
write_node_config(c, config)
c.write("[client]\n")
c.write("# introducer.furl =\n") # omit default