mirror of
https://github.com/tahoe-lafs/tahoe-lafs.git
synced 2025-04-08 19:34:18 +00:00
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:
parent
a1594df0a6
commit
076b3895dc
@ -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
|
||||
|
@ -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,))
|
||||
|
@ -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")
|
||||
|
@ -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")
|
||||
|
||||
|
||||
|
@ -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))
|
||||
|
Loading…
x
Reference in New Issue
Block a user