Browse Source

Merge remote-tracking branch 'tor-github/pr/34'

teor 3 years ago
parent
commit
0a83ecfaa1

+ 28 - 1
lib/chutney/Templating.py

@@ -230,7 +230,6 @@ class Environ(_DictWrapper):
         s.update(name[5:] for name in dir(self) if name.startswith("_get_"))
         return s
 
-
 class IncluderDict(_DictWrapper):
 
     """Helper to implement ${include:} template substitution.  Acts as a
@@ -279,6 +278,33 @@ class IncluderDict(_DictWrapper):
     def getUpdateTime(self):
         return self._st_mtime
 
+class PathDict(_DictWrapper):
+    """
+       Implements ${path:} patterns, which map ${path:foo} to the location
+       of 'foo' in the PATH environment variable.
+    """
+    def __init__(self, parent, path=None):
+        _DictWrapper.__init__(self, parent)
+        if path is None:
+            path = os.getenv('PATH').split(":")
+        self._path = path
+
+    def _getitem(self, key, my):
+        if not key.startswith("path:"):
+            raise KeyError(key)
+
+        key = key[len("path:"):]
+
+        for location in self._path:
+            p = os.path.join(location, key)
+            try:
+                s = os.stat(p)
+                if s and s.st_mode & 0x111:
+                    return p
+            except OSError:
+                pass
+
+        raise KeyError(key)
 
 class _BetterTemplate(string.Template):
 
@@ -355,6 +381,7 @@ class Template(object):
            values in the mapping 'values'.
         """
         values = IncluderDict(values, self._includePath)
+        values = PathDict(values)
         orig_val = self._pat
         nIterations = 0
         while True:

+ 64 - 11
lib/chutney/TorNet.py

@@ -340,6 +340,13 @@ class Node(object):
     def specialize(self, **kwargs):
         return Node(parent=self, **kwargs)
 
+    def set_runtime(self, key, fn):
+        """Specify a runtime function that gets invoked to find the
+           runtime value of a key.  It should take a single argument, which
+           will be an environment.
+        """
+        setattr(self._env, "_get_"+key, fn)
+
     ######
     # Chutney uses these:
 
@@ -473,9 +480,10 @@ class LocalNodeBuilder(NodeBuilder):
     # tor_gencert -- path to tor_gencert binary
     # tor -- path to tor binary
     # auth_cert_lifetime -- lifetime of authority certs, in months.
-    # ip -- IP to listen on
-    # ipv6_addr -- IPv6 address to listen on
-    # orport, dirport -- used on authorities, relays, and bridges
+    # ip -- primary IP address (usually IPv4) to listen on
+    # ipv6_addr -- secondary IP address (usually IPv6) to listen on
+    # orport, dirport -- used on authorities, relays, and bridges. The orport
+    #                    is used for both IPv4 and IPv6, if present
     # fingerprint -- used only if authority
     # dirserver_flags -- used only if authority
     # nick -- nickname of this router
@@ -700,6 +708,7 @@ class LocalNodeBuilder(NodeBuilder):
                 authopt, self._env['nick'], self._env['orport'])
             # It's ok to give an authority's IPv6 address to an IPv4-only
             # client or relay: it will and must ignore it
+            # and yes, the orport is the same on IPv4 and IPv6
             if self._env['ipv6_addr'] is not None:
                 authlines += " ipv6=%s:%s" % (self._env['ipv6_addr'],
                                               self._env['orport'])
@@ -715,11 +724,29 @@ class LocalNodeBuilder(NodeBuilder):
         if not self._env['bridge']:
             return ""
 
-        bridgelines = "Bridge %s:%s\n" % (self._env['ip'],
-                                          self._env['orport'])
+        if self._env['pt_bridge']:
+            port = self._env['ptport']
+            transport = self._env['pt_transport']
+            extra = self._env['pt_extra']
+        else:
+            # the orport is the same on IPv4 and IPv6
+            port = self._env['orport']
+            transport = ""
+            extra = ""
+
+        BRIDGE_LINE_TEMPLATE = "Bridge %s %s:%s %s %s\n"
+
+        bridgelines = BRIDGE_LINE_TEMPLATE % (transport,
+                                              self._env['ip'],
+                                              port,
+                                              self._env['fingerprint'],
+                                              extra)
         if self._env['ipv6_addr'] is not None:
-            bridgelines += "Bridge %s:%s\n" % (self._env['ipv6_addr'],
-                                               self._env['orport'])
+            bridgelines += BRIDGE_LINE_TEMPLATE % (transport,
+                                                   self._env['ipv6_addr'],
+                                                   port,
+                                                   self._env['fingerprint'],
+                                                   extra)
         return bridgelines
 
 
@@ -943,6 +970,9 @@ DEFAULTS = {
     'hasbridgeauth': False,
     'relay': False,
     'bridge': False,
+    'pt_bridge': False,
+    'pt_transport' : "",
+    'pt_extra' : "",
     'hs': False,
     'hs_directory': 'hidden_service',
     'hs-hostname': None,
@@ -961,6 +991,8 @@ DEFAULTS = {
     'dirport_base': 7000,
     'controlport_base': 8000,
     'socksport_base': 9000,
+    'extorport_base' : 9500,
+    'ptport_base' : 9900,
     'authorities': "AlternateDirAuthority bleargh bad torrc file!",
     'bridges': "Bridge bleargh bad torrc file!",
     'core': True,
@@ -991,6 +1023,15 @@ DEFAULTS = {
     'dns_conf': (os.environ.get('CHUTNEY_DNS_CONF', '/etc/resolv.conf')
                         if 'CHUTNEY_DNS_CONF' in os.environ
                         else None),
+
+    # The phase at which this instance needs to be
+    # configured/launched, if we're doing multiphase
+    # configuration/launch.
+    'config_phase' : 1,
+    'launch_phase' : 1,
+
+    'CUR_CONFIG_PHASE': getenv_int('CHUTNEY_CONFIG_PHASE', 1),
+    'CUR_LAUNCH_PHASE': getenv_int('CHUTNEY_LAUNCH_PHASE', 1),
 }
 
 
@@ -1050,6 +1091,12 @@ class TorEnviron(chutney.Templating.Environ):
     def _get_dirport(self, my):
         return my['dirport_base'] + my['nodenum']
 
+    def _get_extorport(self, my):
+        return my['extorport_base'] + my['nodenum']
+
+    def _get_ptport(self, my):
+        return my['ptport_base'] + my['nodenum']
+
     def _get_dir(self, my):
         return os.path.abspath(os.path.join(my['net_base_dir'],
                                             "nodes",
@@ -1236,17 +1283,21 @@ class Network(object):
             sys.exit(1)
 
     def configure(self):
-        self.create_new_nodes_dir()
+        phase = self._dfltEnv['CUR_CONFIG_PHASE']
+        if phase == 1:
+            self.create_new_nodes_dir()
         network = self
         altauthlines = []
         bridgelines = []
-        builders = [n.getBuilder() for n in self._nodes]
+        all_builders = [ n.getBuilder() for n in self._nodes ]
+        builders = [ b for b in all_builders
+                     if b._env['config_phase'] == phase ]
         self._checkConfig()
 
         # XXX don't change node names or types or count if anything is
         # XXX running!
 
-        for b in builders:
+        for b in all_builders:
             b.preConfig(network)
             altauthlines.append(b._getAltAuthLines(
                 self._dfltEnv['hasbridgeauth']))
@@ -1276,7 +1327,9 @@ class Network(object):
         # format polling correctly - avoid printing a newline
         sys.stdout.write("Starting nodes")
         sys.stdout.flush()
-        rv = all([n.getController().start() for n in self._nodes])
+        rv = all([n.getController().start() for n in self._nodes
+                  if n._env['launch_phase'] ==
+                     self._dfltEnv['CUR_LAUNCH_PHASE']])
         # now print a newline unconditionally - this stops poll()ing
         # output from being squashed together, at the cost of a blank
         # line in wait()ing output

+ 15 - 0
networks/bridges-obfs2

@@ -0,0 +1,15 @@
+# By default, Authorities are not configured as exits
+Authority = Node(tag="a", authority=1, relay=1, torrc="authority.tmpl")
+ExitRelay = Node(tag="r", relay=1, exit=1, torrc="relay.tmpl")
+Client = Node(tag="c", client=1, torrc="client.tmpl")
+
+BridgeAuthority = Node(tag="ba", authority=1, bridgeauthority=1,
+                       relay=1, torrc="bridgeauthority.tmpl")
+Bridge = Node(tag="br", bridge=1, pt_bridge=1, relay=1, pt_transport="obfs2",
+       torrc="bridge-obfs2.tmpl")
+BridgeClient = Node(tag="bc", client=1, bridgeclient=1, torrc="bridgeclient-obfs2.tmpl")
+
+NODES = Authority.getN(3) + BridgeAuthority.getN(1) + ExitRelay.getN(4) + \
+    Bridge.getN(1) + Client.getN(1) + BridgeClient.getN(1)
+
+ConfigureNodes(NODES)

+ 39 - 0
networks/bridges-obfs4

@@ -0,0 +1,39 @@
+# By default, Authorities are not configured as exits
+Authority = Node(tag="a", authority=1, relay=1, torrc="authority.tmpl")
+ExitRelay = Node(tag="r", relay=1, exit=1, torrc="relay.tmpl")
+Client = Node(tag="c", client=1, torrc="client.tmpl")
+
+BridgeAuthority = Node(tag="ba", authority=1, bridgeauthority=1,
+                       relay=1, torrc="bridgeauthority.tmpl")
+Bridge = Node(tag="br", bridge=1, pt_bridge=1, relay=1, pt_transport="obfs4",
+       torrc="bridge-obfs4.tmpl")
+
+def obfs4_extra_args(env):
+    import os, re
+    # find the obfs4_bridgeline file.
+    location = os.path.join(env['dir'],
+                            "pt_state",
+                            "obfs4_bridgeline.txt")
+    if not os.path.exists(location):
+        return ""
+    # read the file and find the actual line
+    with open(location, 'r') as f:
+        for line in f:
+            if line.startswith("#"):
+                continue
+            if line.isspace():
+                continue
+            m = re.match(r'(.*<FINGERPRINT>) (cert.*)', line)
+            if m:
+                return m.group(2)
+    return ""
+
+Bridge.set_runtime("pt_extra", obfs4_extra_args)
+
+BridgeClient = Node(tag="bc", client=1, bridgeclient=1,
+	     torrc="bridgeclient-obfs4.tmpl", config_phase=2, launch_phase=2)
+
+NODES = Authority.getN(3) + BridgeAuthority.getN(1) + ExitRelay.getN(4) + \
+    Bridge.getN(1) + Client.getN(1) + BridgeClient.getN(1)
+
+ConfigureNodes(NODES)

+ 6 - 0
torrc_templates/bridge-obfs2.tmpl

@@ -0,0 +1,6 @@
+${include:bridge.tmpl}
+
+ServerTransportPlugin obfs2 exec ${path:obfs4proxy}
+ExtOrPort $extorport
+ServerTransportListenAddr obfs2 ${ip}:${ptport}
+

+ 6 - 0
torrc_templates/bridge-obfs4.tmpl

@@ -0,0 +1,6 @@
+${include:bridge.tmpl}
+
+ServerTransportPlugin obfs4 exec ${path:obfs4proxy}
+ExtOrPort $extorport
+ServerTransportListenAddr obfs4 ${ip}:${ptport}
+

+ 3 - 0
torrc_templates/bridgeclient-obfs2.tmpl

@@ -0,0 +1,3 @@
+${include:bridgeclient.tmpl}
+
+ClientTransportPlugin obfs2 exec ${path:obfs4proxy}

+ 3 - 0
torrc_templates/bridgeclient-obfs4.tmpl

@@ -0,0 +1,3 @@
+${include:bridgeclient.tmpl}
+
+ClientTransportPlugin obfs4 exec ${path:obfs4proxy}