From 9976bd439ac50be56908f11f3e3bd0896641c8f7 Mon Sep 17 00:00:00 2001
From: Brian Warner <warner@allmydata.com>
Date: Wed, 12 Nov 2008 18:44:58 -0700
Subject: [PATCH] tahoe.cfg: add tub.location, to override the location hints
 we include in our FURL. This replaces advertised_ip_addresses, which doesn't
 remain useful enough to retain it. Helps with #517 (Tor).

---
 NEWS                                 |   7 +-
 docs/configuration.txt               | 104 ++++++++++++++++++++++-----
 src/allmydata/node.py                |  30 ++------
 src/allmydata/scripts/create_node.py |   2 +-
 src/allmydata/test/test_node.py      |  36 ++++------
 5 files changed, 111 insertions(+), 68 deletions(-)

diff --git a/NEWS b/NEWS
index d12e50db..3302133c 100644
--- a/NEWS
+++ b/NEWS
@@ -53,9 +53,10 @@ connection. docs/frontends/webapi.txt has details.
 The Tahoe node is now configured with a single INI-format file, named
 "tahoe.cfg", in the node's base directory. Most of the previous
 multiple-separate-files are still read for backwards compatibility (the
-embedded SSH debug server is the exception), but new directives will only be
-added to tahoe.cfg . The "tahoe create-client" command will create a
-tahoe.cfg for you, with sample values commented out. (ticket #518)
+embedded SSH debug server and the advertised_ip_addresses files are the
+exceptions), but new directives will only be added to tahoe.cfg . The "tahoe
+create-client" command will create a tahoe.cfg for you, with sample values
+commented out. (ticket #518)
 
 tahoe.cfg now has controls for the foolscap "keepalive" and "disconnect"
 timeouts (#521).
diff --git a/docs/configuration.txt b/docs/configuration.txt
index 2bfdc481..2d92fef1 100644
--- a/docs/configuration.txt
+++ b/docs/configuration.txt
@@ -77,22 +77,83 @@ tub.port = (integer, optional)
  port. The port will be written to a separate file (named client.port or
  introducer.port), so that subsequent runs will re-use the same port.
 
-advertised_ip_addresses = (comma-separated host[:port] string, optional)
+tub.location = (string, optional)
 
- The node normally uses tools like 'ifconfig' to determine the set of IP
- addresses on which it can be reached from nodes both near and far. The node
- introduces itself to the rest of the grid with a FURL that contains a series
- of (ipaddr, port) pairs which other nodes will use to contact this one. By
- providing this file, you can add to this list. This can be useful if your
- node is running behind a firewall, but you have created a port-forwarding to
- allow the outside world to access it. Each line must have a dotted-quad IP
- address and an optional :portnum specification, like:
+ In addition to running as a client, each Tahoe node also runs as a server,
+ listening for connections from other Tahoe clients. The node announces its
+ location by publishing a "FURL" (a string with some connection hints) to the
+ Introducer. The string it publishes can be found in
+ $BASEDIR/private/storage.furl . The "tub.location" configuration controls
+ what location is published in this announcement.
 
-  123.45.67.89
-  44.55.66.77:8098
+ 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 far. It will
+ also include the TCP port number on which it is listening (either the one
+ specified by tub.port, or whichever port was assigned by the kernel when
+ tub.port is left unspecified).
 
- Lines that do not provide a port number will use the same client.port as the
- automatically-discovered addresses.
+ You might want to override this value if your node lives behind a firewall
+ that is doing inbound port forwarding, or if you are using other proxies
+ such that the local IP address or port number is not the same one that
+ remote clients should use to connect. You might also want to control this
+ when using a Tor proxy to avoid revealing your actual IP address through the
+ Introducer announcement.
+
+ The value is a comma-separated string of host:port location hints, like
+ this:
+
+  123.45.67.89:8098,tahoe.example.com:8098,127.0.0.1:8098
+
+ A few examples:
+
+  Emulate default behavior, assuming your host has IP address 123.45.67.89
+  and the kernel-allocated port number was 8098:
+
+   tub.port = 8098
+   tub.location = 123.45.67.89:8098,127.0.0.1:8098
+
+  Use a DNS name so you can change the IP address more easily:
+
+   tub.port = 8098
+   tub.location = tahoe.example.com:8098
+
+  Run a node behind a firewall (which has an external IP address) that has
+  been configured to forward port 7912 to our internal node's port 8098:
+
+   tub.port = 8098
+   tub.location = external-firewall.example.com:7912
+
+  Run a node behind a Tor proxy (perhaps via tsocks), in client-only mode
+  (i.e. we can make outbound connections, but other nodes will not be able to
+  connect to us). The literal 'unreachable.example.org' will not resolve, but
+  will serve as a reminder to human observers that this node cannot be
+  reached. "Don't call us.. we'll call you":
+
+   tub.port = 8098
+   tub.location = unreachable.example.org:0
+
+  Run a node behind a Tor proxy, and make the server available as a Tor
+  "hidden service". (this assumes that other clients are running their node
+  with tsocks, such that they are prepared to connect to a .onion address).
+  The hidden service must first be configured in Tor, by giving it a local
+  port number and then obtaining a .onion name, using something in the torrc
+  file like:
+
+    HiddenServiceDir /var/lib/tor/hidden_services/tahoe
+    HiddenServicePort 29212 127.0.0.1:8098
+
+   once Tor is restarted, the .onion hostname will be in
+   /var/lib/tor/hidden_services/tahoe/hostname . Then set up your tahoe.cfg
+   like:
+
+    tub.port = 8098
+    tub.location = ualhejtq2p7ohfbb.onion:29212
+
+ Most users will not need to set tub.location .
+
+ Note that the old 'advertised_ip_addresses' file from earlier releases is no
+ longer supported. Tahoe 1.3.0 and later will ignore this file.
 
 log_gatherer.furl = (FURL, optional)
 
@@ -345,7 +406,7 @@ exists, it will take precedence over the corresponding item in tahoe.cfg .
 [node]tub.port : BASEDIR/client.port  (for Clients, not Introducers)
 [node]tub.port : BASEDIR/introducer.port  (for Introducers, not Clients)
       (note that, unlike other keys, tahoe.cfg overrides the *.port file)
-[node]advertised_ip_addresses : BASEDIR/advertised_ip_addresses (one per line)
+[node]tub.location : replaces BASEDIR/advertised_ip_addresses
 [node]log_gatherer.furl : BASEDIR/log_gatherer.furl (one per line)
 [node]timeout.keepalive : BASEDIR/keepalive_timeout
 [node]timeout.disconnect : BASEDIR/disconnect_timeout
@@ -367,6 +428,13 @@ file provided the ssh public keys to accept. Support for these files has been
 removed completely. To ssh into your Tahoe node, add [node]ssh.port and
 [node].ssh_authorized_keys_file statements to your tahoe.cfg .
 
+Likewise, the functionality of [node]tub.location is a variant of the
+now-unsupported BASEDIR/advertised_ip_addresses . The old file was additive
+(the addresses specified in advertised_ip_addresses were used in addition to
+any that were automatically discovered), whereas the new tahoe.cfg directive
+is not (tub.location is used verbatim).
+
+
 == Example ==
 
 The following is a sample tahoe.cfg file, containing values for all keys
@@ -374,8 +442,10 @@ described above. Note that this is not a recommended configuration (most of
 these are not the default values), merely a legal one.
 
 [node]
-port = 34912
-advertised_ip_addresses = 123.45.67.89,44.55.66.77:8098
+nickname = Bob's Tahoe Node
+tub.port = 34912
+tub.location = 123.45.67.89:8098,44.55.66.77:8098
+web.port = 8123
 log_gatherer.furl = pb://soklj4y7eok5c3xkmjeqpw@192.168.69.247:44801/eqpwqtzm
 timeout.keepalive = 240
 timeout.disconnect = 1800
@@ -384,8 +454,6 @@ ssh.authorized_keys_file = ~/.ssh/authorized_keys
 
 [client]
 introducer.furl = pb://ok45ssoklj4y7eok5c3xkmj@tahoe.example:44801/ii3uumo
-nickname = Bob's Tahoe Node
-web.port = 8123
 helper.furl = pb://ggti5ssoklj4y7eok5c3xkmj@helper.tahoe.example:7054/kk8lhr
 
 [storage]
diff --git a/src/allmydata/node.py b/src/allmydata/node.py
index 0ba5899c..da4b860c 100644
--- a/src/allmydata/node.py
+++ b/src/allmydata/node.py
@@ -50,7 +50,6 @@ class Node(service.MultiService):
     NODETYPE = "unknown NODETYPE"
     PORTNUMFILE = None
     CERTFILE = "node.pem"
-    LOCAL_IP_FILE = "advertised_ip_addresses"
 
     def __init__(self, basedir="."):
         service.MultiService.__init__(self)
@@ -116,21 +115,6 @@ class Node(service.MultiService):
             except EnvironmentError:
                 pass
 
-        try:
-            addresses = []
-            ipfile = os.path.join(self.basedir, self.LOCAL_IP_FILE)
-            tubport = int(self.get_config("node", "tub.port", "0"))
-            for addrline in open(ipfile, "rU"):
-                mo = ADDR_RE.search(addrline)
-                if mo:
-                    (addr, dummy, aportnum,) = mo.groups()
-                    if aportnum is None:
-                        aportnum = tubport
-                    addresses.append("%s:%d" % (addr, int(aportnum),))
-            self.set_config("node", "advertised_ip_addresses",
-                            ",".join(addresses))
-        except EnvironmentError:
-            pass
         copy("keepalive_timeout", "node", "timeout.keepalive")
         copy("disconnect_timeout", "node", "timeout.disconnect")
 
@@ -320,18 +304,16 @@ class Node(service.MultiService):
         # running, which means after startService.
         l = self.tub.getListeners()[0]
         portnum = l.getPortnum()
-        # record which port we're listening on, so we can grab the same one next time
+        # record which port we're listening on, so we can grab the same one
+        # next time
         open(self._portnumfile, "w").write("%d\n" % portnum)
 
-        addresses = [ "%s:%d" % (addr, portnum,) for addr in local_addresses ]
-        extra_addresses = self.get_config("node", "advertised_ip_addresses", "")
-        if extra_addresses:
-            extra_addresses = extra_addresses.split(",")
-            addresses.extend(extra_addresses)
-
-        location = ",".join(addresses)
+        base_location = ",".join([ "%s:%d" % (addr, portnum)
+                                   for addr in local_addresses ])
+        location = self.get_config("node", "tub.location", base_location)
         self.log("Tub location set to %s" % location)
         self.tub.setLocation(location)
+
         return self.tub
 
     def when_tub_ready(self):
diff --git a/src/allmydata/scripts/create_node.py b/src/allmydata/scripts/create_node.py
index 798a1ccc..b7938280 100644
--- a/src/allmydata/scripts/create_node.py
+++ b/src/allmydata/scripts/create_node.py
@@ -73,7 +73,7 @@ def write_node_config(c, config):
     c.write("web.port = %s\n" % webport)
     c.write("web.static = public_html\n")
     c.write("#tub.port =\n")
-    c.write("#advertised_ip_addresses =\n")
+    c.write("#tub.location = \n")
     c.write("#log_gatherer.furl =\n")
     c.write("#timeout.keepalive =\n")
     c.write("#timeout.disconnect =\n")
diff --git a/src/allmydata/test/test_node.py b/src/allmydata/test/test_node.py
index 391883eb..1542402b 100644
--- a/src/allmydata/test/test_node.py
+++ b/src/allmydata/test/test_node.py
@@ -29,11 +29,12 @@ class TestCase(unittest.TestCase, testutil.SignalMixin):
         d.addCallback(flushEventualQueue)
         return d
 
-    def test_advertised_ip_addresses(self):
-        basedir = "test_node/test_advertised_ip_addresses"
+    def test_location(self):
+        basedir = "test_node/test_location"
         fileutil.make_dirs(basedir)
-        f = open(os.path.join(basedir, 'advertised_ip_addresses'),'w')
-        f.write('1.2.3.4:5')
+        f = open(os.path.join(basedir, 'tahoe.cfg'), 'wt')
+        f.write("[node]\n")
+        f.write("tub.location = 1.2.3.4:5\n")
         f.close()
 
         n = TestNode(basedir)
@@ -47,31 +48,22 @@ class TestCase(unittest.TestCase, testutil.SignalMixin):
         d.addCallback(_check_addresses)
         return d
 
-    def test_advertised_ip_addresses2(self):
-        basedir = "test_node/test_advertised_ip_addresses2"
+    def test_location2(self):
+        basedir = "test_node/test_location2"
         fileutil.make_dirs(basedir)
+        f = open(os.path.join(basedir, 'tahoe.cfg'), 'wt')
+        f.write("[node]\n")
+        f.write("tub.location = 1.2.3.4:5,example.org:8091\n")
+        f.close()
 
         n = TestNode(basedir)
         n.setServiceParent(self.parent)
         d = n.when_tub_ready()
-        # this lets the 'port' file get written
-        d.addCallback(lambda res: n.disownServiceParent())
-        def _new_node(res):
-            f = open(os.path.join(basedir, 'advertised_ip_addresses'),'w')
-            f.write('1.2.3.4\n')
-            f.write("6.7.8.9\n")
-            f.close()
-            n2 = self.node = TestNode(basedir)
-            n2.setServiceParent(self.parent)
-            return n2.when_tub_ready()
-        d.addCallback(_new_node)
 
         def _check_addresses(ignored_result):
-            portfile = os.path.join(basedir, self.node.PORTNUMFILE)
-            port = int(open(portfile, "r").read().strip())
-            furl = self.node.tub.registerReference(n)
-            self.failUnless(("1.2.3.4:%d" % port) in furl, furl)
-            self.failUnless(("6.7.8.9:%d" % port) in furl, furl)
+            furl = n.tub.registerReference(n)
+            self.failUnless("1.2.3.4:5" in furl, furl)
+            self.failUnless("example.org:8091" in furl, furl)
 
         d.addCallback(_check_addresses)
         return d
-- 
2.45.2