[tor-commits] [obfs-flash/master] Added a configuration class which reads in a fog-file. Added example fog-file. Added launching from a fog-file.

infinity0 at torproject.org infinity0 at torproject.org
Wed Jun 4 18:26:57 UTC 2014


commit a0688866101db87c5e4692dd8523c3c338efbcb5
Author: Quinn Jarrell <qjarrell at gosynapsify.com>
Date:   Mon Jun 2 15:30:46 2014 -0400

    Added a configuration class which reads in a fog-file. Added example fog-file. Added launching from a fog-file.
---
 example-fog-config |   13 ++++
 obfs-flash-client  |  206 +++++++++++++++++++++++++++++++++++++++++-----------
 2 files changed, 178 insertions(+), 41 deletions(-)

diff --git a/example-fog-config b/example-fog-config
new file mode 100644
index 0000000..746e5ad
--- /dev/null
+++ b/example-fog-config
@@ -0,0 +1,13 @@
+#Based off of ticket #9744
+#Client transports are setup like so:
+#ClientTransportPlugin name commandline
+#For instance to launch obfs3, the client transport line should be this
+#ClientTransportPlugin obfs3 obfsproxy managed
+#
+#For chaining transports together, an alias line is used.
+#Alias chainname firsttransportname|secondtransportname
+#tor expects alias to use underscores instead of pipes. So an alias links the tor version of a plugin chain to the actual plugins. See ticket #9580
+
+ClientTransportPlugin dummy obfsproxy managed
+ClientTransportPlugin b64 obfsproxy managed
+Alias dummy_b64 dummy|b64
diff --git a/obfs-flash-client b/obfs-flash-client
index 32c0c9c..cc45d40 100755
--- a/obfs-flash-client
+++ b/obfs-flash-client
@@ -25,7 +25,10 @@ from twisted.protocols.portforward import ProxyServer as _ProxyServer
 from twisted.python import log
 from txsocksx.client import SOCKS4ClientEndpoint
 
-PT_METHOD_NAME = "obfs3_flashproxy"
+import shlex
+
+import logging
+
 
 def pt_child_env(managed_ver, env=os.environ):
     """
@@ -38,7 +41,7 @@ def pt_child_env(managed_ver, env=os.environ):
     cur_env.append(('TOR_PT_MANAGED_TRANSPORT_VER', ','.join(managed_ver)))
     return cur_env
 
-class CMethod(namedtuple('MethodSpec', 'name protocol addrport args opts')):
+class MethodSpec(namedtuple('MethodSpec', 'name protocol addrport args opts')):
     @classmethod
     def fromLine(cls, line):
         args = line.rstrip('\n').split(' ')
@@ -47,7 +50,7 @@ class CMethod(namedtuple('MethodSpec', 'name protocol addrport args opts')):
         addrport = parse_addr_spec(args[2])
         args = args[3][-5:].split(',') if len(args) > 3 and args[3].startswith("ARGS=") else []
         opts = args[4][-9:].split(',') if len(args) > 4 and args[4].startswith("OPT-ARGS=") else []
-        return CMethod(name, protocol, addrport, args, opts)
+        return MethodSpec(name, protocol, addrport, args, opts)
 
 def branch(parent):
     """
@@ -103,7 +106,7 @@ class ManagedTransportProtocolV1(LineReceiver):
             if version != self.protocol_version:
                 self._abort(ValueError("child used unsupported managed transport version: %s" % version))
         elif kw == "CMETHOD":
-            cmethod = CMethod.fromLine(args)
+            cmethod = MethodSpec.fromLine(args)
             self.cmethods[cmethod.name] = cmethod
         elif kw == "CMETHODS" and args == "DONE":
             self._fireCMethodsDone().callback(self.cmethods)
@@ -218,99 +221,220 @@ if sys.platform == "win32":
 
             self.proto.makeConnection(self)
 
-def pt_launch_child(reactor, client, methodnames, cmdline):
+def pt_launch_child(reactor, client, methodnames, pt_method_name, cmdline):
     """Launch a child PT and ensure it has the right transport methods."""
     cur_env = pt_child_env(ManagedTransportProtocolV1.protocol_version)
+    environment = dict(cur_env + {
+            "TOR_PT_CLIENT_TRANSPORTS": ",".join(methodnames),
+        }.items())
     sub_proc = Popen(cmdline,
         stdout = PIPE,
-        env = dict(cur_env + {
-            "TOR_PT_CLIENT_TRANSPORTS": ",".join(methodnames),
-        }.items()),
+        env = environment,
         )
     sub_protocol = ManagedTransportProtocolV1()
     # we ought to pass reactor=reactor in below, but this breaks Twisted 12
     StandardIO(sub_protocol, stdin=sub_proc.stdout.fileno())
     methoddefers = [sub_protocol.whenCMethodsDone().addCallback(
-                        partial(pt_require_child, client, name))
+                        partial(pt_require_child, client, name, pt_method_name))
                     for name in methodnames]
     return sub_proc, sub_protocol, methoddefers
 
-def pt_require_child(client, childmethod, cmethods):
+def pt_require_child(client, childmethod, pt_method_name, cmethods):
     """Callback for checking a child PT has the right transport methods."""
     if childmethod not in cmethods:
-        client.reportMethodError(PT_METHOD_NAME, "failed to start required child transport: %s" % childmethod)
+        client.reportMethodError(pt_method_name, "failed to start required child transport: %s" % childmethod)
         raise ValueError()
     return cmethods[childmethod]
 
-def obfs3_flashproxy(reactor, client, obfs_out, fp_remote, fp_args=[], fp_local=0):
+def pt_launch_pair(reactor, client, callback_port, configuration, pt_method_name):
     """
-    Set up the obfs3_flashproxy combined PT.
-
+    Launches a pair of pluggable transports. Currently only supports chaining two transports together.
+    The pair is set by pt_method_names
     :param twisted.internet.interfaces.IReactor reactor: Reactor to install this PT to.
     :param pyptlib.client.ClientTransportPlugin client: PT client API.
-    :param int obfs_out: Local listen port for obfsproxy to connect to.
-    :param str fp_remote: Listen address for remote flashproxy connections.
-    :param int fp_local: Local listen port for local flashproxy connections.
+    :param int callback_port: Local listen port for the first PT to connect to.
+    :param Config configuration: The configuration structure for this pair.
+    :param String pt_method_name: The name of the pt chain to launch. Ex: "obfs3_flashproxy"
     """
-    ob_client = os.getenv("OBFSPROXY", "obfsproxy")
-    fp_client = os.getenv("FLASHPROXY_CLIENT", "flashproxy-client")
+    #TODO Modify pt_launch_pair to launch more than 2 transports
+
+    if pt_method_name in configuration.alias_map:
+        pt_chain = configuration.alias_map[pt_method_name]
+    else:
+        logging.error('Pluggable Transport Combination %s not found in configuration alias map.' % pt_method_name)
+        raise KeyError()
 
-    # start fp and ensure that it launches OK
-    _, _, fp_defer = pt_launch_child(reactor, client,
-        ["flashproxy"], [fp_client, "--transport", "obfs3|websocket"] + fp_args +
-                       ['127.0.0.1:%s' % fp_local, fp_remote])
+    defer_list = []
 
-    # start ob and ensure that it launches OK
-    _, _, ob_defer = pt_launch_child(reactor, client,
-        ["obfs3"], [ob_client, "managed"])
+    for pt in pt_chain:
+        if pt in configuration.transport_map:
+            pt_cmdline = configuration.transport_map[pt]
+        else:
+            logging.error("Pluggable transport %s not found in transport_map. Check your configuration file." % str(pt))
+            raise ValueError()
+        _, _, defer = pt_launch_child(reactor, client, [pt], pt_method_name, pt_cmdline)
+        defer_list.extend(defer)
+    whenAllDone = DeferredList(defer_list, consumeErrors=False)
 
-    whenAllDone = DeferredList(fp_defer + ob_defer, consumeErrors=True)
     def allDone(success_list):
-        success = all(r[0] for r in success_list)
+        """
+        :param success_list: A list of tuples containing a launch status boolean, MethodSpec pairs.
+        Ex: [(True, MethodSpec(name='dummy', protocol='socks4', addrport=('127.0.0.1', 58982), args=[], opts=[])),
+         (True, MethodSpec(name='b64', protocol='socks4', addrport=('127.0.0.1', 58981), args=[], opts=[]))]
+        """
+
+        success = all(r[0] for r in success_list if r[1].name in pt_chain)
         # failure was already reported by pt_require_child, just return
         if not success: return
-        fp_result = [r[1] for r in success_list if r[1].name == "flashproxy"][0]
-        ob_result = [r[1] for r in success_list if r[1].name == "obfs3"][0]
 
-        fp_port = fp_result.addrport[1] # if port was 0, read the actual port
+        result1 = [r[1] for r in success_list if r[1].name == pt_chain[0]][0] # Normally obfs3 result
+        result2 = [r[1] for r in success_list if r[1].name == pt_chain[1]][0] # Normally flashproxy result
+        destination_port = result2.addrport[1] # if port was 0, read the actual port
         # shim to wrap obfs data output to SOCKS input for flashproxy
-        reactor.listenTCP(interface='127.0.0.1', port=obfs_out, factory=
-            SOCKS4WrapperFactory('127.0.0.1', fp_port, '0.0.1.0', 1))
-
+        reactor.listenTCP(interface='127.0.0.1', port=callback_port, factory=
+            SOCKS4WrapperFactory('127.0.0.1', destination_port, '0.0.1.0', 1))
         # now report success of the overall composed PT
-        client.reportMethodSuccess(PT_METHOD_NAME, ob_result.protocol, ob_result.addrport)
+        client.reportMethodSuccess(pt_method_name, result1.protocol, result1.addrport)
         client.reportMethodsEnd()
     whenAllDone.addCallback(allDone)
 
+class Config():
+    # Transport map links a pluggable transport name to the a commandline to launch it.
+    # Ex: {'b64' : 'exec obfsproxy managed'}
+    transport_map = None
+
+    #Alias map links a pluggable transport chain name to a list of individual pluggable transports
+    # Ex: {'dummy_b64_dummy2' : ['dummy''b64''dummy2']}
+    alias_map = None
+
+    def __init__(self, transport_map, alias_map):
+        self.transport_map = transport_map
+        self.alias_map = alias_map
+
+    def __repr__(self):
+        return "Config(%s, %s)" % (self.transport_map, self.alias_map)
+
+    def __str__(self):
+        return "Config Object with transport_map: %s, and alias_map %s." % (self.transport_map, self.alias_map)
+
+    @classmethod
+    def parse(cls, config_string):
+        """
+        Reads a configuration string and returns an instance of configuration. Uses shlex to parse configuration lines.
+        :param String config_string: The string which will be parsed to populate the transport_map and alias_map hash tables.
+        See the file example-fog-config for format.
+        """
+        # TODO Add possibility of reading a ClientTransportPlugin with multiple transport types
+        # Ex: ClientTransportPlugin obfs3,scramblesuit obfsclient --option=value
+
+        line_counter = 0
+        lines = config_string.split('\n')
+        transport_map = {}
+        alias_map = {}
+
+        for line in lines:
+            line_counter += 1
+            if len(line) > 0 and line[0] != '#' : # Check for empty lines and comment tags on the first
+                line = line.strip()
+                delimited_tokens = shlex.split(line)
+                if len(delimited_tokens) > 1:
+                    config_line_type = delimited_tokens[0] # This can be either Alias or ClientTransportPlugin
+                    if config_line_type == 'ClientTransportPlugin':
+                        cls.parse_transport_line(transport_map, delimited_tokens, line_counter)
+                    elif config_line_type == 'Alias':
+                        cls.parse_alias_line(alias_map, transport_map, delimited_tokens, line_counter)
+                    else:
+                        logging.warn("Configuration file has unknown line %s: '%s'" % (line_counter, line))
+        return cls(transport_map, alias_map)
+
+    @classmethod
+    def parse_transport_line(cls, transport_map, delimited_tokens, line_counter):
+        transport_name = delimited_tokens[1]
+        transport_cmdline = delimited_tokens[2:]
+        if transport_name in transport_map:
+            raise ValueError('Configuration file has duplicate ClientTransportPlugin lines. Duplicate line is at line number %s' % line_counter)
+        transport_map[transport_name] = transport_cmdline
+
+    @classmethod
+    def parse_alias_line(cls, alias_map, transport_map, delimited_tokens, line_counter):
+        alias_name = delimited_tokens[1] # Example: "obfs3_flashproxy"
+        alias_path = delimited_tokens[2].split('|') # Example: "obfs3|flashproxy"
+        if alias_name in alias_map:
+            raise ValueError('Configuration file has duplicate Alias lines. Duplicate line is at line number %s' % line_counter)
+        for pt_name in alias_path:
+            if pt_name not in transport_map:
+                raise KeyError('Transport map is missing pluggable transport %s needed for chain %s. Check your configuration file for a ClientTransportPlugin line can launch %s' % (pt_name, alias_name, pt_name))
+        alias_map[alias_name] = alias_path
+
+def obfs3_flashproxy(reactor, client, callback_port, fp_remote, fp_args=[], fp_local=0):
+    """
+    Set up the obfs3_flashproxy combined PT.
+
+    :param twisted.internet.interfaces.IReactor reactor: Reactor to install this PT to.
+    :param pyptlib.client.ClientTransportPlugin client: PT client API.
+    :param int callback_port: Local listen port for obfsproxy to connect to.
+    :param str fp_remote: Listen address for remote flashproxy connections.
+    :param int fp_local: Local listen port for local flashproxy connections.
+    """
+
+    ob_client = os.getenv("OBFSPROXY", "obfsproxy")
+    fp_client = os.getenv("FLASHPROXY_CLIENT", "flashproxy-client")
+
+    fp_cmdline =  [fp_client, "--transport", '"obfs3|websocket"'] + fp_args + ['127.0.0.1:%s' % fp_local, fp_remote]
+    obfs_cmd_line = [ob_client, "managed"]
+
+    transport_map = {'obfs3': obfs_cmd_line, 'flashproxy': fp_cmdline}
+    alias_map = {'obfs3_flashproxy': ['obfs3', 'flashproxy']}
+
+    configuration = Config(transport_map, alias_map)
+    pt_launch_pair(reactor, client, callback_port, configuration, "obfs3_flashproxy") # Launch
+
 def main(*args):
     parser = argparse.ArgumentParser()
-    parser.add_argument("obfs_out", help="local listen port for obfsproxy to "
-        "connect to. This must match the appropriate 'Bridge %s' line in your "
-        "torrc." % PT_METHOD_NAME,
+    parser.add_argument("callback_port", help="local listen port for obfsproxy to "
+        "connect to. This must match the appropriate bridge line in your " # TODO print bridge line
+        "torrc.",
         metavar='PORT', type=int)
     parser.add_argument("fp_remote", help="remote connections listen address "
         "for flashproxy, default %(default)s",
         metavar='REMOTE:PORT', nargs='?', default=":9000")
     parser.add_argument("--fp-arg", help="arguments for flashproxy-client",
         metavar='ARG', action='append')
+    parser.add_argument("-f", help="fog configuration file path",
+        metavar='FOGFILE', type=argparse.FileType('r'))
+
     # TODO(infinity0): add an "external" mode, which would require us to run
     # obfsproxy in external mode too.
 
     opts = parser.parse_args(args)
+
     # ensure str address is valid
     _, _, = parse_addr_spec(opts.fp_remote, defhost="0.0.0.0")
+    configuration = None
+
+    if opts.f:
+        file_contents = opts.f.read()
+        configuration = Config.parse(file_contents)
+    pt_method_names = configuration.alias_map.keys()
 
     client = ClientTransportPlugin()
-    client.init([PT_METHOD_NAME])
+    client.init(pt_method_names) # Initialize our possible methods to all the chains listed by the fog file and stored in alias map.
     if not client.getTransports():
-        print >> sys.stderr, "no transports to serve"
+        logging.error("no transports to serve. pt_method_names may be invalid.")
         return 1
 
     from twisted.internet import reactor
     auto_killall(1, cleanup=reactor.stop)
-    obfs3_flashproxy(reactor, client, opts.obfs_out, opts.fp_remote, opts.fp_arg or [])
+    #TODO Change from launching a single pair to launching multiple chains.
+    if configuration:
+        pt_launch_pair(reactor, client, opts.callback_port, configuration, pt_method_names[0])
+    else:
+        logging.warn("No configuration file specified. Defaulting to launching obfs|flashproxy pair.")
+        obfs3_flashproxy(reactor, client, opts.callback_port, opts.fp_remote, opts.fp_arg or [])
+
     reactor.run(installSignalHandlers=0)
     return 0
 
 if __name__ == "__main__":
     sys.exit(main(*sys.argv[1:]))
+





More information about the tor-commits mailing list