config: change syntax of no-listen mode

We now use::

  tub.port = disabled
  tub.location = disabled

instead of using an empty value (but the key still being present, since
if the key is missing entirely, that means "be automatic").

closes ticket:2816
This commit is contained in:
Brian Warner 2016-08-31 17:16:23 -07:00
parent a1594df0a6
commit 076b3895dc
5 changed files with 238 additions and 77 deletions

View File

@ -138,13 +138,20 @@ set the ``tub.location`` option described below.
``http://127.0.0.1:3456/static/foo.html`` will serve the contents of
``BASEDIR/public_html/foo.html`` .
``tub.port = (endpoint specification string, optional)``
``tub.port = (endpoint specification string or "disabled", optional)``
This controls which port the node uses to accept Foolscap connections
from other nodes. It is parsed as a Twisted "server endpoint descriptor",
which accepts values like ``tcp:12345`` and
``tcp:23456:interface=127.0.0.1``.
If ``tub.port`` is the string ``disabled``, the node will not listen at
all, and thus cannot accept connections from other nodes. If ``[storage]
enabled = true``, or ``[helper] enabled = true``, or the node is an
Introducer, then it is an error to have ``tub.port`` be empty. If
``tub.port`` is disabled, then ``tub.location`` must also be disabled,
and vice versa.
For backwards compatibility, if this contains a simple integer, it will
be used as a TCP port number, like ``tcp:%d`` (which will accept
connections on all interfaces). However ``tub.port`` cannot be ``0`` or
@ -152,11 +159,6 @@ set the ``tub.location`` option described below.
willing to ask Twisted to allocate port numbers in this way). To
automatically allocate a TCP port, leave ``tub.port`` blank.
If the ``tub.port`` key is empty (i.e. ``tub.port =``), the node will not
listen at all, and thus cannot accept connections from other nodes. If
``[storage] enabled = true``, or ``[helper] enabled = true``, or the node
is an Introducer, then it is an error to have ``tub.port`` be empty.
If the ``tub.port`` config key is not provided (e.g. ``tub.port`` appears
nowhere in the ``[node]`` section, or is commented out), the node will
look in ``BASEDIR/client.port`` (or ``BASEDIR/introducer.port``, for
@ -168,7 +170,7 @@ set the ``tub.location`` option described below.
string in ``BASEDIR/client.port`` (or ``introducer.port``), so that
subsequent runs will re-use the same port.
``tub.location = (string, optional)``
``tub.location = (hint string or "disabled", optional)``
In addition to running as a client, each Tahoe-LAFS node can also run as
a server, listening for connections from other Tahoe-LAFS clients. The
@ -180,6 +182,9 @@ set the ``tub.location`` option described below.
If your node is meant to run as a server, you should fill this in, using
a hostname or IP address that is reachable from your intended clients.
If ``tub.port`` is set to ``disabled``, then ``tub.location`` must also
be ``disabled``.
If you don't provide ``tub.location``, the node will try to figure out a
useful one by itself, by using tools like "``ifconfig``" to determine the
set of IP addresses on which it can be reached from nodes both near and
@ -213,6 +218,11 @@ set the ``tub.location`` option described below.
A few examples:
* Don't listen at all (client-only mode)::
tub.port = disabled
tub.location = disabled
* Use a DNS name so you can change the IP address more easily::
tub.port = tcp:8098

View File

@ -314,28 +314,52 @@ class Node(service.MultiService):
return "tcp:%d" % int(s)
return s
def get_tub_port(self):
# return a descriptor string
MISSING = object()
cfg_tubport = self.get_config("node", "tub.port", MISSING)
if cfg_tubport is not MISSING:
if cfg_tubport.strip() == "":
return None # don't listen at all
return self._convert_tub_port(cfg_tubport)
# For 'tub.port', tahoe.cfg overrides the individual file on disk. So
# only read self._portnumfile if tahoe.cfg doesn't provide a value.
if os.path.exists(self._portnumfile):
file_tubport = fileutil.read(self._portnumfile).strip()
return self._convert_tub_port(file_tubport)
tubport = "tcp:%d" % iputil.allocate_tcp_port()
fileutil.write_atomically(self._portnumfile, tubport + "\n", mode="")
return tubport
def get_tub_portlocation(self, cfg_tubport, cfg_location):
# return None, or tuple of (port, location)
tubport_disabled = False
if cfg_tubport is not None:
cfg_tubport = cfg_tubport.strip()
if cfg_tubport == "":
raise ValueError("tub.port must not be empty")
if cfg_tubport == "disabled":
tubport_disabled = True
location_disabled = False
if cfg_location is not None:
cfg_location = cfg_location.strip()
if cfg_location == "":
raise ValueError("tub.location must not be empty")
if cfg_location == "disabled":
location_disabled = True
if tubport_disabled and location_disabled:
return None
if tubport_disabled and not location_disabled:
raise ValueError("tub.port is disabled, but not tub.location")
if location_disabled and not tubport_disabled:
raise ValueError("tub.location is disabled, but not tub.port")
if cfg_tubport is None:
# For 'tub.port', tahoe.cfg overrides the individual file on
# disk. So only read self._portnumfile if tahoe.cfg doesn't
# provide a value.
if os.path.exists(self._portnumfile):
file_tubport = fileutil.read(self._portnumfile).strip()
tubport = self._convert_tub_port(file_tubport)
else:
tubport = "tcp:%d" % iputil.allocate_tcp_port()
fileutil.write_atomically(self._portnumfile, tubport + "\n",
mode="")
else:
tubport = self._convert_tub_port(cfg_tubport)
if cfg_location is None:
cfg_location = "AUTO"
def get_tub_location(self, tubport):
location = self.get_config("node", "tub.location", "AUTO")
# Replace the location "AUTO", if present, with the detected local
# addresses. Don't probe for local addresses unless necessary.
split_location = location.split(",")
split_location = cfg_location.split(",")
if "AUTO" in split_location:
if not self._reveal_ip:
raise PrivacyError("tub.location uses AUTO")
@ -353,7 +377,9 @@ class Node(service.MultiService):
if hint_type == "tcp":
raise PrivacyError("tub.location includes tcp: hint")
new_locations.append(loc)
return ",".join(new_locations)
location = ",".join(new_locations)
return tubport, location
def create_main_tub(self):
certfile = os.path.join(self.basedir, "private", self.CERTFILE)
@ -361,13 +387,15 @@ class Node(service.MultiService):
self.nodeid = b32decode(self.tub.tubID.upper()) # binary format
self.write_config("my_nodeid", b32encode(self.nodeid).lower() + "\n")
self.short_nodeid = b32encode(self.nodeid).lower()[:8] # ready for printing
tubport = self.get_tub_port()
if tubport:
self.short_nodeid = b32encode(self.nodeid).lower()[:8] # for printing
cfg_tubport = self.get_config("node", "tub.port", None)
cfg_location = self.get_config("node", "tub.location", None)
portlocation = self.get_tub_portlocation(cfg_tubport, cfg_location)
if portlocation:
tubport, location = portlocation
if tubport in ("0", "tcp:0"):
raise ValueError("tub.port cannot be 0: you must choose")
self.tub.listenOn(tubport)
location = self.get_tub_location(tubport)
self.tub.setLocation(location)
self._tub_is_listening = True
self.log("Tub location set to %s" % (location,))

View File

@ -75,6 +75,9 @@ def write_node_config(c, config):
webport = ""
c.write("web.port = %s\n" % (webport.encode('utf-8'),))
c.write("web.static = public_html\n")
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")
c.write("#tub.port =\n")
c.write("#tub.location = \n")
c.write("#log_gatherer.furl =\n")

View File

@ -270,29 +270,29 @@ class Privacy(unittest.TestCase):
n = FakeNode(BASECONFIG+"[node]\nreveal-IP-address = false\n")
n._portnumfile = "missing"
n.check_privacy()
e = self.assertRaises(PrivacyError, n.get_tub_location, None)
e = self.assertRaises(PrivacyError, n.get_tub_portlocation, None, None)
self.assertEqual(str(e), "tub.location uses AUTO")
n = FakeNode(BASECONFIG+"[node]\nreveal-IP-address = false\n" +
"tub.location = AUTO\n")
n = FakeNode(BASECONFIG+"[node]\nreveal-IP-address = false\n")
n._portnumfile = "missing"
n.check_privacy()
e = self.assertRaises(PrivacyError, n.get_tub_location, None)
e = self.assertRaises(PrivacyError, n.get_tub_portlocation,
None, "AUTO")
self.assertEqual(str(e), "tub.location uses AUTO")
n = FakeNode(BASECONFIG+"[node]\nreveal-IP-address = false\n" +
"tub.location = AUTO,tcp:hostname:1234\n")
n = FakeNode(BASECONFIG+"[node]\nreveal-IP-address = false\n")
n._portnumfile = "missing"
n.check_privacy()
e = self.assertRaises(PrivacyError, n.get_tub_location, None)
e = self.assertRaises(PrivacyError, n.get_tub_portlocation,
None, "AUTO,tcp:hostname:1234")
self.assertEqual(str(e), "tub.location uses AUTO")
def test_tub_location_tcp(self):
n = FakeNode(BASECONFIG+"[node]\nreveal-IP-address = false\n" +
"tub.location = tcp:hostname:1234\n")
n = FakeNode(BASECONFIG+"[node]\nreveal-IP-address = false\n")
n._portnumfile = "missing"
n.check_privacy()
e = self.assertRaises(PrivacyError, n.get_tub_location, None)
e = self.assertRaises(PrivacyError, n.get_tub_portlocation,
None, "tcp:hostname:1234")
self.assertEqual(str(e), "tub.location includes tcp: hint")

View File

@ -1,5 +1,5 @@
import os, stat, sys, time
import os, stat, sys, time, mock
from twisted.trial import unittest
from twisted.internet import defer
@ -181,58 +181,178 @@ class TestCase(testutil.SignalMixin, unittest.TestCase):
TestNode(basedir)
self.failUnless(ns.called)
NO_LISTEN_CONFIG = """
[node]
tub.port =
#tub.location =
class EmptyNode(Node):
def __init__(self):
pass
EXPECTED = {
# top-level key is tub.port category
"missing": {
# 2nd-level key is tub.location category
"missing": "alloc/auto",
"empty": "ERR2",
"disabled": "ERR4",
"hintstring": "alloc/file",
},
"empty": {
"missing": "ERR1",
"empty": "ERR1",
"disabled": "ERR1",
"hintstring": "ERR1",
},
"disabled": {
"missing": "ERR3",
"empty": "ERR2",
"disabled": "no-listen",
"hintstring": "ERR3",
},
"endpoint": {
"missing": "auto",
"empty": "ERR2",
"disabled": "ERR4",
"hintstring": "manual",
},
}
class PortLocation(unittest.TestCase):
def test_all(self):
for tp in EXPECTED.keys():
for tl in EXPECTED[tp].keys():
exp = EXPECTED[tp][tl]
self._try(tp, tl, exp)
def _try(self, tp, tl, exp):
log.msg("PortLocation._try:", tp, tl, exp)
cfg_tubport = {"missing": None,
"empty": "",
"disabled": "disabled",
"endpoint": "tcp:777",
}[tp]
cfg_location = {"missing": None,
"empty": "",
"disabled": "disabled",
"hintstring": "tcp:HOST:888,AUTO",
}[tl]
n = EmptyNode()
basedir = os.path.join("test_node/portlocation/%s/%s" % (tp, tl))
fileutil.make_dirs(basedir)
n._portnumfile = os.path.join(basedir, "node.port")
n._reveal_ip = True
if exp in ("ERR1", "ERR2", "ERR3", "ERR4"):
e = self.assertRaises(ValueError, n.get_tub_portlocation,
cfg_tubport, cfg_location)
if exp == "ERR1":
self.assertEqual("tub.port must not be empty", str(e))
elif exp == "ERR2":
self.assertEqual("tub.location must not be empty", str(e))
elif exp == "ERR3":
self.assertEqual("tub.port is disabled, but not tub.location",
str(e))
elif exp == "ERR4":
self.assertEqual("tub.location is disabled, but not tub.port",
str(e))
else:
self.assert_(False)
elif exp == "no-listen":
res = n.get_tub_portlocation(cfg_tubport, cfg_location)
self.assertEqual(res, None)
elif exp in ("alloc/auto", "alloc/file", "auto", "manual"):
with mock.patch("allmydata.util.iputil.get_local_addresses_sync",
return_value=["LOCAL"]):
with mock.patch("allmydata.util.iputil.allocate_tcp_port",
return_value=999):
port, location = n.get_tub_portlocation(cfg_tubport,
cfg_location)
try:
with open(n._portnumfile, "r") as f:
saved_port = f.read().strip()
except EnvironmentError:
saved_port = None
if exp == "alloc/auto":
self.assertEqual(port, "tcp:999")
self.assertEqual(location, "tcp:LOCAL:999")
self.assertEqual(saved_port, "tcp:999")
elif exp == "alloc/file":
self.assertEqual(port, "tcp:999")
self.assertEqual(location, "tcp:HOST:888,tcp:LOCAL:999")
self.assertEqual(saved_port, "tcp:999")
elif exp == "auto":
self.assertEqual(port, "tcp:777")
self.assertEqual(location, "tcp:LOCAL:777")
self.assertEqual(saved_port, None)
elif exp == "manual":
self.assertEqual(port, "tcp:777")
self.assertEqual(location, "tcp:HOST:888,tcp:LOCAL:777")
self.assertEqual(saved_port, None)
else:
self.assert_(False)
else:
self.assert_(False)
BASE_CONFIG = """
[client]
introducer.furl = empty
[tor]
enabled = false
[i2p]
enabled = false
[node]
"""
NOLISTEN = """
[node]
tub.port = disabled
tub.location = disabled
"""
DISABLE_STORAGE = """
[storage]
enabled = false
"""
ENABLE_STORAGE = """
[storage]
enabled = true
"""
ENABLE_HELPER = """
[helper]
enabled = true
"""
class ClientNotListening(unittest.TestCase):
def test_port_none(self):
basedir = "test_node/test_port_none"
def test_disabled(self):
basedir = "test_node/test_disabled"
fileutil.make_dirs(basedir)
f = open(os.path.join(basedir, 'tahoe.cfg'), 'wt')
f.write(NO_LISTEN_CONFIG)
f.write("[storage]\n")
f.write("enabled = false\n")
f.write(BASE_CONFIG)
f.write(NOLISTEN)
f.write(DISABLE_STORAGE)
f.close()
n = Client(basedir)
self.assertEqual(n.tub.getListeners(), [])
def test_port_none_location_none(self):
basedir = "test_node/test_port_none_location_none"
def test_disabled_but_storage(self):
basedir = "test_node/test_disabled_but_storage"
fileutil.make_dirs(basedir)
f = open(os.path.join(basedir, 'tahoe.cfg'), 'wt')
f.write(NO_LISTEN_CONFIG)
f.write("tub.location =\n")
f.write("[storage]\n")
f.write("enabled = false\n")
f.close()
n = Client(basedir)
self.assertEqual(n.tub.getListeners(), [])
def test_port_none_storage(self):
basedir = "test_node/test_port_none_storage"
fileutil.make_dirs(basedir)
f = open(os.path.join(basedir, 'tahoe.cfg'), 'wt')
f.write(NO_LISTEN_CONFIG)
f.write("[storage]\n")
f.write("enabled = true")
f.write(BASE_CONFIG)
f.write(NOLISTEN)
f.write(ENABLE_STORAGE)
f.close()
e = self.assertRaises(ValueError, Client, basedir)
self.assertIn("storage is enabled, but tub is not listening", str(e))
def test_port_none_helper(self):
basedir = "test_node/test_port_none_helper"
def test_disabled_but_helper(self):
basedir = "test_node/test_disabled_but_helper"
fileutil.make_dirs(basedir)
f = open(os.path.join(basedir, 'tahoe.cfg'), 'wt')
f.write(NO_LISTEN_CONFIG)
f.write("[storage]\n")
f.write("enabled = false\n")
f.write("[helper]\n")
f.write("enabled = true")
f.write(BASE_CONFIG)
f.write(NOLISTEN)
f.write(DISABLE_STORAGE)
f.write(ENABLE_HELPER)
f.close()
e = self.assertRaises(ValueError, Client, basedir)
self.assertIn("helper is enabled, but tub is not listening", str(e))
@ -243,8 +363,8 @@ class IntroducerNotListening(unittest.TestCase):
fileutil.make_dirs(basedir)
f = open(os.path.join(basedir, 'tahoe.cfg'), 'wt')
f.write("[node]\n")
f.write("tub.port = \n")
f.write("#tub.location = \n")
f.write("tub.port = disabled\n")
f.write("tub.location = disabled\n")
f.close()
e = self.assertRaises(ValueError, IntroducerNode, basedir)
self.assertIn("we are Introducer, but tub is not listening", str(e))