[Author Prev][Author Next][Thread Prev][Thread Next][Author Index][Thread Index]
[tor-commits] [fog/master] The files have been reorganized into a python and go project.
commit 0612c1405d8e0d17b9cc82c530fa5a9557b49d6d
Author: Quinn Jarrell <qjarrell@xxxxxxxxxxxxxxx>
Date: Fri Jul 4 23:49:50 2014 -0400
The files have been reorganized into a python and go project.
---
.gitignore | 3 +-
Makefile | 12 -
fog-client/fog-client | 538 +++++++++++++++++++++++++++++++++++++++++++++
fog-client/fog/socks.py | 59 +++++
fog-client/fogrc | 17 ++
fog-client/setup.py | 23 ++
fog-client/torrc | 5 +
fog-server/Makefile | 12 +
fog-server/fog-server.go | 548 ++++++++++++++++++++++++++++++++++++++++++++++
fog-server/pt_test.go | 39 ++++
fog-server/stack.go | 57 +++++
fog-server/stack_test.go | 120 ++++++++++
fog-server/torrc | 5 +
fog/socks.py | 59 -----
fogrc | 17 --
obfs-flash-client | 538 ---------------------------------------------
obfs-flash-server.go | 548 ----------------------------------------------
pt_test.go | 39 ----
setup.py | 23 --
stack.go | 57 -----
stack_test.go | 120 ----------
torrc | 5 -
torrc-server | 5 -
23 files changed, 1425 insertions(+), 1424 deletions(-)
diff --git a/.gitignore b/.gitignore
index 452a1a4..8184caa 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1 +1,2 @@
-/mkii/obfs-flash-server
+/fog-server/fog-server
+*.pyc
diff --git a/Makefile b/Makefile
deleted file mode 100644
index b8a4d24..0000000
--- a/Makefile
+++ /dev/null
@@ -1,12 +0,0 @@
-GOBUILDFLAGS =
-
-obfs-flash-server: obfs-flash-server.go stack.go
- go build $(GOBUILDFLAGS) -o "$@" $^
-
-test:
- go test -v
-
-clean:
- rm -f obfs-flash-server
-
-.PHONY: test clean
diff --git a/fog-client/fog-client b/fog-client/fog-client
new file mode 100755
index 0000000..9b2e7ef
--- /dev/null
+++ b/fog-client/fog-client
@@ -0,0 +1,538 @@
+#!/usr/bin/python
+
+import argparse
+import os
+import sys
+
+from collections import namedtuple
+from functools import partial
+
+# TODO(infinity0): this is temporary workaround until we do #10047
+if sys.platform == 'win32':
+ os.environ["KILL_CHILDREN_ON_DEATH"] = "1"
+from pyptlib.util import parse_addr_spec
+from pyptlib.util.subproc import auto_killall, Popen
+from pyptlib.client import ClientTransportPlugin
+
+from subprocess import PIPE
+
+from twisted.internet.defer import Deferred, DeferredList
+from twisted.internet.stdio import StandardIO
+from twisted.internet.protocol import Factory, connectionDone
+from twisted.internet.endpoints import TCP4ClientEndpoint
+from twisted.protocols.basic import LineReceiver
+from twisted.protocols.portforward import ProxyServer as _ProxyServer
+from twisted.python import log
+from txsocksx.client import SOCKS4ClientEndpoint, SOCKS5ClientEndpoint
+from fog.socks import SOCKSv4InterceptorFactory
+
+import shlex
+
+import logging
+
+DEFAULT_CONFIG_FILE_NAME = os.path.dirname(os.path.realpath(__file__)) + '/fogrc'
+
+logger = None
+def pt_setup_logger():
+ global logger
+ logger = logging.getLogger('fog-logger')
+ logger.setLevel(logging.WARNING)
+ ch = logging.StreamHandler()
+ ch.setLevel(logging.DEBUG)
+ logger.addHandler(ch)
+
+def pt_child_env(managed_ver, env=os.environ):
+ """
+ Prepare the environment for a child PT process, by clearing all TOR_PT_*
+ envvars except TOR_PT_STATE_LOCATION and TOR_PT_MANAGED_TRANSPORT_VER.
+ """
+ exempt = ['TOR_PT_STATE_LOCATION']
+ cur_env = [(k, v) for k, v in env.iteritems()
+ if not k.startswith('TOR_PT_') or k in exempt]
+ cur_env.append(('TOR_PT_MANAGED_TRANSPORT_VER', ','.join(managed_ver)))
+ return cur_env
+
+class MethodSpec(namedtuple('MethodSpec', 'name protocol addrport args opts')):
+ @classmethod
+ def fromLine(cls, line):
+ args = line.rstrip('\n').split(' ')
+ name = args[0]
+ protocol = args[1]
+ 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 MethodSpec(name, protocol, addrport, args, opts)
+
+def branch(parent):
+ """
+ Returns a new Deferred that does not advance the callback-chain of the parent.
+
+ See http://xph.us/2009/12/10/asynchronous-programming-in-python.html for motivation.
+ """
+ d = Deferred()
+ parent.addCallback(lambda v: (v, d.callback(v))[0])
+ parent.addErrback(lambda f: (f, d.errback(f))[1])
+ return d
+
+class ManagedTransportProtocolV1(LineReceiver):
+ """
+ A Twisted IProtocol to read PT output.
+
+ See pt-spec.txt and others for details of the protocol.
+ """
+ # TODO(infinity0): eventually this could be padded out and moved to pyptlib
+
+ delimiter = os.linesep
+ protocol_version = "1"
+
+ def __init__(self):
+ self.cmethods = {}
+ self._dCMethodsDone = Deferred()
+ self._dPluginError = Deferred()
+ # dPluginError triggers errors on all sub-events, not the other way round
+ # so fatal sub-events should call _abort rather than errback on their Deferreds
+ self._dPluginError.addErrback(lambda f: (f, self._fireCMethodsDone().errback(f))[0])
+ # TODO(infinity0): call _abort if we don't recv CMETHODS DONE within n sec
+
+ def whenCMethodsDone(self):
+ """
+ Return a new Deferred that calls-back when CMETHODS DONE is received.
+ """
+ return branch(self._dCMethodsDone)
+
+ def whenPluginError(self):
+ """
+ Return a new Deferred that errors-back when the remote plugin fails.
+
+ Note: the success chain (callback) is never fired.
+ """
+ return branch(self._dPluginError)
+
+ def lineReceived(self, line):
+ if not line: return
+
+ (kw, args) = line.split(' ', 1)
+ if kw == "VERSION":
+ version = args.strip()
+ if version != self.protocol_version:
+ self._abort(ValueError("child used unsupported managed transport version: %s" % version))
+ elif kw == "CMETHOD":
+ cmethod = MethodSpec.fromLine(args)
+ self.cmethods[cmethod.name] = cmethod
+ elif kw == "CMETHODS" and args == "DONE":
+ self._fireCMethodsDone().callback(self.cmethods)
+ else:
+ pass # ignore unrecognised line
+
+ def connectionLost(self, reason=connectionDone):
+ self._firePluginError().errback(reason)
+
+ def _abort(self, exc):
+ self._firePluginError().errback(exc)
+ self.transport.loseConnection()
+
+ def _fireCMethodsDone(self):
+ """Return dCMethodsDone or a dummy if it was already called."""
+ if self._dCMethodsDone:
+ d = self._dCMethodsDone
+ self._dCMethodsDone = None
+ return d
+ return Deferred().addErrback(lambda *args: None)
+
+ def _firePluginError(self):
+ """Return dPluginError or a dummy if it was already called."""
+ if self._dPluginError:
+ d = self._dPluginError
+ self._dPluginError = None
+ return d
+ return Deferred().addErrback(lambda *args: None)
+
+# TODO(infinity0): remove this class when twisted update their side
+class ProxyServer(_ProxyServer):
+
+ def connectionMade(self):
+ # code copied from super class, except instead of connecting
+ # to a TCP endpoint we abstract that out to a child method
+ self.transport.pauseProducing()
+
+ client = self.clientProtocolFactory()
+ client.setServer(self)
+
+ if self.reactor is None:
+ from twisted.internet import reactor
+ self.reactor = reactor
+
+ self.connectProxyClient(client)
+
+ def connectProxyClient(self, client):
+ raise NotImplementedError()
+
+class OneUseSOCKSWrapper(ProxyServer):
+
+ def connectProxyClient(self, client):
+ local_host, local_port = self.factory.method_spec.addrport
+ TCPPoint = TCP4ClientEndpoint(
+ self.reactor,
+ local_host,
+ local_port)
+ # Next PT may need either SOCKS4 or SOCKS5 so check its protocol and get the required class
+ socks_endpoint_class = self.getSocksEndpointClass()
+ SOCKSPoint = socks_endpoint_class(
+ self.factory.remote_host,
+ self.factory.remote_port,
+ TCPPoint)
+ # Store port for debugging messages before stopListening is called.
+ # listen_port will not have a port after stopListening is called.
+ stored_port = self.factory.listen_port.getHost().port
+ d_port_closed = self.factory.listen_port.stopListening()
+ d_port_closed.addCallback(
+ lambda x: logger.debug("Closed factory listener %s on port %s" % (self.factory, stored_port)))
+ d_port_closed.addErrback(
+ lambda x: logger.warn("Failed to close factory listener %s listening on port %s" % (self.factory, stored_port)))
+ d = SOCKSPoint.connect(client)
+ d.chainDeferred(self.factory.d_connected)
+ @d.addErrback
+ def _gotError(error):
+ log.err(error, "error connecting to SOCKS server")
+
+ def getSocksEndpointClass(self):
+ """
+ Checks self.factory.method_spec.protocol and returns the appropriate socks endpoint class.
+ """
+ socks_endpoint_class = None
+ if self.factory.method_spec.protocol == 'socks4':
+ socks_endpoint_class = SOCKS4ClientEndpoint
+ elif self.factory.method_spec.protocol == 'socks5':
+ socks_endpoint_class = SOCKS5ClientEndpoint
+ else:
+ raise ValueError("Pluggable transport requires unknown protocol %s. Supported protocols are %s" %
+ (self.factory.method_spec.protocol, ('socks4', 'socks5')))
+ return socks_endpoint_class
+
+class OneUseSOCKSFactory(Factory):
+ protocol = OneUseSOCKSWrapper
+ def __init__(self, method_spec, remote_host, remote_port):
+ self._connected_once = False
+ self.method_spec = method_spec
+ self.remote_host = remote_host
+ self.remote_port = remote_port
+ self.d_connected = Deferred()
+ self.listen_port = None
+
+ def __str__(self):
+ return "OneUseSOCKSFactory connecting %s to %s:%s" % (self.method_spec, self.remote_host, self.remote_port)
+
+ def __repr__(self):
+ return "OneUseSOCKSFactory(%s, %s, %s)" % (self.method_spec, self.remote_host, self.remote_port)
+
+ def setListenPort(self, listen_port):
+ """
+ Sets the listen_port object.
+ :param function listen_port: The function returned from a ListenTCP call. Used to shutdown the port when a connection is made.
+ """
+ self.listen_port = listen_port
+
+ def whenConnected(self):
+ """
+ Returns a new Deferred that triggers when a connection is successfully made.
+ """
+ return branch(self.d_connected)
+
+ def buildProtocol(self, addr):
+ """
+ Only allows one protocol to be created. After that it always returns None
+ :param twisted.internet.interfaces.IAddress addr: an object implementing L{twisted.internet.interfaces.IAddress}
+ """
+ if self._connected_once:
+ return None
+ else:
+ self._connected_once = True
+ return Factory.buildProtocol(self, addr)
+
+if sys.platform == "win32":
+ # TODO(infinity0): push this upstream to Twisted
+ from twisted.internet import _pollingfile
+ import msvcrt
+
+ _StandardIO = StandardIO
+ class StandardIO(_StandardIO):
+
+ def __init__(self, proto, stdin=None, stdout=None, reactor=None):
+ """
+ Start talking to standard IO with the given protocol.
+
+ Also, put it stdin/stdout/stderr into binary mode.
+ """
+ if reactor is None:
+ import twisted.internet.reactor
+ reactor = twisted.internet.reactor
+
+ _pollingfile._PollingTimer.__init__(self, reactor)
+ self.proto = proto
+
+ fdstdin = stdin or sys.stdin.fileno()
+ fdstdout = stdout or sys.stdout.fileno()
+
+ for stdfd in (fdstdin, fdstdout):
+ msvcrt.setmode(stdfd, os.O_BINARY)
+
+ hstdin = msvcrt.get_osfhandle(fdstdin)
+ self.stdin = _pollingfile._PollableReadPipe(
+ hstdin, self.dataReceived, self.readConnectionLost)
+
+ hstdout = msvcrt.get_osfhandle(fdstdout)
+ self.stdout = _pollingfile._PollableWritePipe(
+ hstdout, self.writeConnectionLost)
+
+ self._addPollableResource(self.stdin)
+ self._addPollableResource(self.stdout)
+
+ self.proto.makeConnection(self)
+
+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 = 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, pt_method_name))
+ for name in methodnames]
+ return sub_proc, sub_protocol, methoddefers
+
+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)
+ raise ValueError()
+ return cmethods[childmethod]
+
+def pt_setup_socks_shim(pt_name, pt_chain, success_list, dest_address, dest_port, reactor, proxy_deferreds):
+ """
+ Launches a socks proxy server to link two PTs together.
+ :param str pt_name: The name of the pt to send traffic to.
+ :param list pt_chain: The list of PTs in this chain.
+ :param list 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=[]))]
+ :param str dest_address: The address for the next PT to send its results to.
+ :param int dest_port: The port for the next PT to send to.
+ :param twisted.internet.interfaces.IReactor reactor: Reactor to attack the TCP server to.
+
+ :param list proxy_deferreds: This list has each factorys' deferred appended to it.
+
+ :returns twisted.internet.interfaces.IListeningPort: An IListeningPort used for shutting down a factory after a connection is made.
+ """
+ methodspec = [r[1] for r in success_list if r[1].name == pt_name][0] # Returns the resulting methodspec.
+ factory = OneUseSOCKSFactory(methodspec, dest_address, dest_port)
+ # TODO switch to using endpoints instead of listenTCP
+ proxy_server = reactor.listenTCP(interface='127.0.0.1', port=0, factory=factory)
+ factory.setListenPort(proxy_server)
+ proxy_deferreds.append(factory.whenConnected())
+ logger.debug("launched %s on port %s with dest %s:%s" % (pt_name, proxy_server.getHost().port, dest_address, dest_port))
+ return proxy_server
+
+def pt_launch_chain(dest_address, dest_port, pt_chain, _chain_set_up, reactor, success_list):
+ """
+ Launches a chain of pluggable transports by connecting each pt with SOCKS proxies.
+ :param str dest_address: The bridge address to connect to.
+ :param int dest_port: The bridge port to connect to.
+ :param list pt_chain: The list of pt names to launch.
+ :param function _chain_set_up: The function to call when the shims have been set up.
+ :param twisted.internet.interfaces.IReactor reactor: Reactor to install this PT to.
+ :param list 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=[]))]
+ """
+ proxy_deferreds = []
+ last_pt_name = pt_chain[-1]
+ logger.debug("launching chain %s" % pt_chain)
+ # Initialize prev_server to the port picked by the last proxy server as that's the only one we know yet.
+ last_server = pt_setup_socks_shim(last_pt_name, pt_chain, success_list, dest_address, dest_port,
+ reactor, proxy_deferreds)
+ prev_server = last_server
+ for pt_name in reversed(pt_chain[:-1]):
+ # Loops through the pts linking them together through SOCKS proxies, skipping the last pt.
+ prev_server = pt_setup_socks_shim(pt_name, pt_chain, success_list, '127.0.0.1', prev_server.getHost().port,
+ reactor, proxy_deferreds)
+ def check_chain_all_connected(protocol_list):
+ """
+ Checks all the shims launched to see if they successfully connected.
+ :param list protocol_list: A list of tuples containing status boolean, twisted.protocols.portforward.ProxyClient pairs.
+ Ex: [(True, <twisted.protocols.portforward.ProxyClient instance at 0x10b825518>),
+ (True, <twisted.protocols.portforward.ProxyClient instance at 0x10b829518>)]
+ """
+ if all([result[0] for result in protocol_list]):
+ logger.debug("All PT shims connected correctly")
+ else:
+ # At this point the SOCKS protocol is in communication mode so no need to call makeReply(91)
+ # This assumes that the child pluggable transport will shut down the connection cleanly.
+ failed_protocols = [x[1] for x in protocol_list if x[0] == False]
+ logger.error("Shims %s failed to connect." % failed_protocols)
+ raise ValueError()
+
+ finished = DeferredList(proxy_deferreds)
+ finished.addCallback(check_chain_all_connected)
+ _chain_set_up(prev_server.getHost().host, prev_server.getHost().port)
+
+def pt_launch_interceptor(reactor, client, configuration, pt_method_name, success_list):
+ """
+ Launches a SOCKS interceptor.
+ :param twisted.internet.interfaces.IReactor reactor: Reactor to install this PT to.
+ :param pyptlib.client.ClientTransportPlugin client: PT client API.
+ :param Config configuration: The configuration structure for this pair.
+ :param str pt_method_name: The name of the pt chain to launch. Ex: "obfs3_flashproxy"
+ :param list 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=[]))]
+ """
+ logger.debug("launching interceptor")
+ pt_chain = configuration.alias_map[pt_method_name]
+ 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
+ socks_interceptor = SOCKSv4InterceptorFactory(pt_method_name,
+ lambda dest_address, dest_port, pt_method_name, chain_finished:
+ pt_launch_chain(dest_address, dest_port, pt_chain, chain_finished, reactor, success_list))
+ # TODO switch to using endpoints instead of listenTCP
+ interceptor = reactor.listenTCP(interface='127.0.0.1', port=0, factory=socks_interceptor)
+ interceptor_port = interceptor.getHost().port
+ client.reportMethodSuccess(pt_method_name, "socks4", ("127.0.0.1", interceptor_port))
+ client.reportMethodsEnd()
+
+def pt_setup_transports(reactor, client, configuration, pt_method_name):
+ """
+ Launches the PT processes.
+ :param twisted.internet.interfaces.IReactor reactor: Reactor to install this PT to.
+ :param pyptlib.client.ClientTransportPlugin client: PT client API.
+ :param Config configuration: The configuration structure for this pair.
+ :param str pt_method_name: The name of the pt chain to launch. Ex: "obfs3_flashproxy"
+ """
+ logger.debug("Setting up transports %s" % pt_method_name)
+ if pt_method_name in configuration.alias_map:
+ pt_chain = configuration.alias_map[pt_method_name]
+ else:
+ logger.error('Pluggable Transport Combination %s not found in configuration alias map.' % pt_method_name)
+ raise KeyError()
+
+ defer_list = []
+
+ if len(pt_chain) < 2:
+ raise ValueError("PT Chain %s does not contain enough transports." % pt_chain)
+
+ for pt in pt_chain:
+ if pt in configuration.transport_map:
+ pt_cmdline = configuration.transport_map[pt]
+ else:
+ raise ValueError("Pluggable transport %s not found in transport_map. Check your configuration file." % pt)
+ _, _, defer = pt_launch_child(reactor, client, [pt], pt_method_name, pt_cmdline)
+ defer_list.extend(defer)
+ whenAllDone = DeferredList(defer_list, consumeErrors=False)
+ whenAllDone.addCallback(lambda success_list: pt_launch_interceptor(reactor, client, configuration, pt_method_name, success_list))
+
+
+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 str 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:
+ logger.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 main(*args):
+ parser = argparse.ArgumentParser()
+ parser.add_argument("-f", help="fog configuration file path",
+ metavar='FOGFILE', type=argparse.FileType('r'), default=DEFAULT_CONFIG_FILE_NAME)
+
+ pt_setup_logger()
+ # TODO(infinity0): add an "external" mode, which would require us to run
+ # obfsproxy in external mode too.
+
+ opts = parser.parse_args(args)
+ configuration = None
+ file_contents = opts.f.read()
+ configuration = Config.parse(file_contents)
+ pt_method_names = configuration.alias_map.keys()
+ client = ClientTransportPlugin()
+ 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():
+ logger.error("no transports to serve. pt_method_names may be invalid.")
+ return 1
+
+ from twisted.internet import reactor
+ auto_killall(1, cleanup=reactor.stop)
+ #TODO Change from launching a single pair to launching multiple chains.
+ pt_setup_transports(reactor, client, configuration, pt_method_names[0])
+ reactor.run(installSignalHandlers=0)
+ return 0
+
+if __name__ == "__main__":
+ sys.exit(main(*sys.argv[1:]))
+
diff --git a/fog-client/fog/__init__.py b/fog-client/fog/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/fog-client/fog/socks.py b/fog-client/fog/socks.py
new file mode 100644
index 0000000..b44135d
--- /dev/null
+++ b/fog-client/fog/socks.py
@@ -0,0 +1,59 @@
+from twisted.protocols import socks
+from twisted.internet.protocol import Factory
+import logging
+
+logger = logging.getLogger('fog-logger')
+
+class SOCKSv4InterceptorProtocol(socks.SOCKSv4):
+ """
+ A modified SOCKS protocol which extracts the requested ip and port
+ and redirects connections to the first pluggable transport in the chain.
+ """
+
+ def __init__(self, factory, pt_method_name):
+ """
+ :param twisted.internet.protocol.factory factory: The factory that launched this protocol
+ :param pt_method_name: The name of the chain to be launched when a new connection is received
+ """
+ self.factory = factory
+ self._pt_method_name = pt_method_name
+ socks.SOCKSv4.__init__(self)
+
+ def _dataReceived2(self, server, user, version, code, port):
+ """
+ Extracts the requested ip and port and redirects to a different address
+ """
+ if code == 1: # CONNECT
+ assert version == 4, "Bad version code: %s" % version
+ if not self.authorize(code, server, port, user):
+ self.makeReply(91)
+ return
+ def _chain_set_up(remote_address, remote_port):
+ logger.debug("chain finished, connecting %s:%s" % (remote_address, remote_port))
+ # Connect to our remote address instead of the requested one
+ d = self.connectClass(remote_address, remote_port, socks.SOCKSv4Outgoing, self)
+ d.addErrback(lambda result, self = self: self.makeReply(91))
+ self.factory._new_conn_callback(server, port, self._pt_method_name, _chain_set_up)
+ assert self.buf == "", "hmm, still stuff in buffer... %s" % repr(self.buf)
+ else:
+ super(SOCKSv4InterceptorProtocol, self)._dataReceived2(server, user, version, code, port)
+
+class SOCKSv4InterceptorFactory(Factory):
+
+ def __init__(self, pt_method_name, new_conn_callback):
+ """
+ :param str pt_method_name: The name of the pt_method that this factory is launching.
+ :param function new_conn_callback: The function to be called when a connection is made.
+ def new_conn_callback
+ :param str server: The ip address requested by the SOCKS client.
+ :param int port: The port requested by the SOCKS client.
+ :param str pt_method_name: The name of the pt_method this factory is a part of.
+ :param function chain_set_up: The function to be called when the chain has finished setting up.
+ :param str remote_address: The address to relay the SOCKS request to.
+ :param int remote_port: The port to to send the SOCKS request to.
+ """
+ self._pt_method_name = pt_method_name
+ self._new_conn_callback = new_conn_callback
+
+ def buildProtocol(self, addr):
+ return SOCKSv4InterceptorProtocol(self, self._pt_method_name)
\ No newline at end of file
diff --git a/fog-client/fogrc b/fog-client/fogrc
new file mode 100644
index 0000000..ee28514
--- /dev/null
+++ b/fog-client/fogrc
@@ -0,0 +1,17 @@
+#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 obfs3 obfsproxy managed
+ClientTransportPlugin flashproxy flashproxy-client --transport obfs3|websocket --register 127.0.0.1:0 :9000
+# If port 9000 cannot be portforwarded change it to a port that can be ported like so:
+#ClientTransportPlugin flashproxy flashproxy-client --transport obfs3|websocket --register 127.0.0.1:0 :3923
+# use a different facilitator
+#ClientTransportPlugin flashproxy flashproxy-client --transport obfs3|websocket -f http://siteb.fp-facilitator.org/fac/ --register â??-register-methods=http 127.0.0.1:0 :3923
+Alias obfs3_flashproxy obfs3|flashproxy
diff --git a/fog-client/setup.py b/fog-client/setup.py
new file mode 100644
index 0000000..125da2d
--- /dev/null
+++ b/fog-client/setup.py
@@ -0,0 +1,23 @@
+from distutils.core import setup
+import py2exe
+
+# if py2exe complains "can't find P", try one of the following workarounds:
+#
+# a. py2exe doesn't support zipped eggs - http://www.py2exe.org/index.cgi/ExeWithEggs
+# You should give the --always-unzip option to easy_install, or you can use setup.py directly
+# $ python setup.py install --record install.log --single-version-externally-managed
+# Don't forget to remove the previous zipped egg.
+#
+# b. Add an empty __init__.py to the P/ top-level directory, if it's missing
+# - this is due to a bug (or misleading documentation) in python's imp.find_module()
+
+setup(
+ console=["fog-client"],
+ zipfile="py2exe-fog-client.zip",
+ options={
+ "py2exe": {
+ "includes": ["pyptlib", "twisted", "txsocksx"],
+ "packages": ["ometa", "terml", "zope.interface"],
+ },
+ },
+)
diff --git a/fog-client/torrc b/fog-client/torrc
new file mode 100644
index 0000000..98b1f47
--- /dev/null
+++ b/fog-client/torrc
@@ -0,0 +1,5 @@
+UseBridges 1
+Bridge obfs3_flashproxy 127.0.0.1:9000
+LearnCircuitBuildTimeout 0
+CircuitBuildTimeout 300
+ClientTransportPlugin obfs3_flashproxy exec ./fog-client
diff --git a/fog-server/Makefile b/fog-server/Makefile
new file mode 100644
index 0000000..c7d474b
--- /dev/null
+++ b/fog-server/Makefile
@@ -0,0 +1,12 @@
+GOBUILDFLAGS =
+
+./fog-server: ./fog-server.go ./stack.go
+ go build $(GOBUILDFLAGS) -o "$@" $^
+
+test:
+ go test -v
+
+clean:
+ rm -f ./fog-server
+
+.PHONY: test clean
diff --git a/fog-server/fog-server.go b/fog-server/fog-server.go
new file mode 100644
index 0000000..bb34219
--- /dev/null
+++ b/fog-server/fog-server.go
@@ -0,0 +1,548 @@
+package main
+
+import (
+ "bufio"
+ "errors"
+ "flag"
+ "fmt"
+ "io"
+ "net"
+ "os"
+ "os/exec"
+ "os/signal"
+ "strings"
+ "sync"
+ "syscall"
+ "sort"
+ "time"
+)
+
+import "git.torproject.org/pluggable-transports/goptlib.git"
+
+const connStackSize = 1000
+const subprocessWaitTimeout = 30 * time.Second
+
+var logFile = os.Stderr
+
+var ptInfo pt.ServerInfo
+
+// When a connection handler starts, +1 is written to this channel; when it
+// ends, -1 is written.
+var handlerChan = make(chan int)
+
+func usage() {
+ fmt.Printf("Usage: %s [OPTIONS]\n", os.Args[0])
+ fmt.Printf("Chains websocket and obfsproxy server transports. pt-websocket-server and\n")
+ fmt.Printf("obfsproxy must be in PATH.\n")
+ fmt.Printf("\n")
+ fmt.Printf(" -h, --help show this help.\n")
+ fmt.Printf(" --log FILE log messages to FILE (default stderr).\n")
+ fmt.Printf(" --port PORT listen on PORT (overrides Tor's requested port).\n")
+}
+
+var logMutex sync.Mutex
+
+func log(format string, v ...interface{}) {
+ dateStr := time.Now().Format("2006-01-02 15:04:05")
+ logMutex.Lock()
+ defer logMutex.Unlock()
+ msg := fmt.Sprintf(format, v...)
+ fmt.Fprintf(logFile, "%s %s\n", dateStr, msg)
+}
+
+type ProcList []*os.Process
+
+func (procs ProcList) Signal(sig os.Signal) {
+ for _, p := range procs {
+ log("Sending signal %q to process with pid %d.", sig, p.Pid)
+ err := p.Signal(sig)
+ if err != nil {
+ log("Error sending signal %q to process with pid %d: %s.", sig, p.Pid, err)
+ }
+ }
+}
+
+func (procs ProcList) Kill() {
+ for _, p := range procs {
+ log("Killing process with pid %d.", p.Pid)
+ err := p.Kill()
+ if err != nil {
+ log("Error killing process with pid %d: %s.", p.Pid, err)
+ continue
+ }
+ state, err := p.Wait()
+ if err != nil {
+ log("Error waiting on process with pid %d: %s.", state.Pid(), err)
+ continue
+ }
+ if !state.Exited() {
+ log("Process with pid %d didn't exit.", state.Pid())
+ continue
+ }
+ }
+}
+
+type Chain struct {
+ MethodName string
+ ExtLn, IntLn *net.TCPListener
+ ProcsAddr *net.TCPAddr
+ Procs ProcList
+ // This stack forwards external IP addresses to the extended ORPort.
+ Conns *Stack
+}
+
+func (chain *Chain) CloseListeners() {
+ if chain.ExtLn != nil {
+ err := chain.ExtLn.Close()
+ if err != nil {
+ log("Error closing external listener: %s.", err)
+ }
+ }
+ if chain.IntLn != nil {
+ err := chain.IntLn.Close()
+ if err != nil {
+ log("Error closing internal listener: %s.", err)
+ }
+ }
+}
+
+func (chain *Chain) Shutdown() {
+ chain.CloseListeners()
+ chain.Procs.Kill()
+ for {
+ elem, ok := chain.Conns.Pop()
+ if !ok {
+ break
+ }
+ conn := elem.(*net.TCPConn)
+ log("Closing stale connection from %s.", conn.RemoteAddr())
+ err := conn.Close()
+ if err != nil {
+ }
+ }
+}
+
+func findBindAddr(r io.Reader, methodName string) (*net.TCPAddr, error) {
+ br := bufio.NewReader(r)
+ for {
+ line, err := br.ReadString('\n')
+ if err != nil {
+ return nil, err
+ }
+ log("Received from sub-transport: %q.", line)
+ fields := strings.Fields(strings.TrimRight(line, "\n"))
+ if len(fields) < 1 {
+ continue
+ }
+ keyword := fields[0]
+ args := fields[1:]
+ if keyword == "SMETHOD" && len(args) >= 2 && args[0] == methodName {
+ bindaddr, err := net.ResolveTCPAddr("tcp", args[1])
+ if err != nil {
+ return nil, err
+ }
+ return bindaddr, nil
+ } else if keyword == "SMETHODS" && len(args) == 1 && args[0] == "DONE" {
+ break
+ }
+ }
+ return nil, errors.New(fmt.Sprintf("no SMETHOD %s found before SMETHODS DONE", methodName))
+}
+
+// Escape a string for a ServerTransportOptions serialization.
+func escape(s string) string {
+ repl := strings.NewReplacer(":", "\\:", ";", "\\;", "=", "\\=", "\\", "\\\\")
+ return repl.Replace(s)
+}
+
+func encodeServerTransportOptions(methodName string, opts pt.Args) string {
+ if opts == nil {
+ return ""
+ }
+ keys := make([]string, 0, len(opts))
+ for key, _ := range opts {
+ keys = append(keys, key)
+ }
+ sort.Strings(keys)
+ parts := make([]string, 0, len(keys))
+ for _, key := range keys {
+ for _, value := range opts[key] {
+ parts = append(parts, escape(methodName) + ":" + escape(key) + "=" + escape(value))
+ }
+ }
+ return strings.Join(parts, ";")
+}
+
+// Represents a server transport plugin configuration like:
+// ServerTransportPlugin MethodName exec Command
+type ServerTransportPlugin struct {
+ MethodName string
+ Command []string
+ Options pt.Args
+}
+
+func startProcesses(connectBackAddr net.Addr, plugins []ServerTransportPlugin) (bindAddr *net.TCPAddr, procs ProcList, err error) {
+ var stdout io.ReadCloser
+
+ defer func() {
+ if err != nil {
+ // Kill subprocesses before returning error.
+ procs.Kill()
+ procs = procs[:0]
+ }
+ }()
+
+ bindAddr = connectBackAddr.(*net.TCPAddr)
+ for _, plugin := range plugins {
+ // This plugin has its TOR_PT_ORPORT set to the previous
+ // bindAddr.
+ cmd := exec.Command(plugin.Command[0], plugin.Command[1:]...)
+ cmd.Env = []string{
+ "TOR_PT_MANAGED_TRANSPORT_VER=1",
+ "TOR_PT_STATE_LOCATION=" + os.Getenv("TOR_PT_STATE_LOCATION"),
+ "TOR_PT_EXTENDED_SERVER_PORT=",
+ "TOR_PT_ORPORT=" + bindAddr.String(),
+ "TOR_PT_SERVER_TRANSPORTS=" + plugin.MethodName,
+ "TOR_PT_SERVER_BINDADDR=" + plugin.MethodName + "-127.0.0.1:0",
+ }
+ serverTransportOptions := encodeServerTransportOptions(plugin.MethodName, plugin.Options)
+ if serverTransportOptions != "" {
+ cmd.Env = append(cmd.Env, "TOR_PT_SERVER_TRANSPORT_OPTIONS=" + serverTransportOptions)
+ }
+ log("%s environment %q", cmd.Args[0], cmd.Env)
+ stdout, err = cmd.StdoutPipe()
+ if err != nil {
+ log("Failed to open %s stdout pipe: %s.", cmd.Args[0], err)
+ return
+ }
+ err = cmd.Start()
+ if err != nil {
+ log("Failed to start %s: %s.", cmd.Args[0], err)
+ return
+ }
+ log("Exec %s with args %q pid %d.", cmd.Path, cmd.Args, cmd.Process.Pid)
+ procs = append(procs, cmd.Process)
+
+ bindAddr, err = findBindAddr(stdout, plugin.MethodName)
+ if err != nil {
+ log("Failed to find %s bindaddr: %s.", cmd.Args[0], err)
+ return
+ }
+ log("%s bindaddr is %s.", cmd.Args[0], bindAddr)
+ }
+
+ return bindAddr, procs, err
+}
+
+func acceptLoop(name string, ln *net.TCPListener, ch chan *net.TCPConn) {
+ for {
+ conn, err := ln.AcceptTCP()
+ if err != nil {
+ log("%s accept: %s.", name, err)
+ break
+ }
+ log("%s connection from %s.", name, conn.RemoteAddr())
+ ch <- conn
+ }
+ close(ch)
+}
+
+func copyLoop(a, b *net.TCPConn) error {
+ var wg sync.WaitGroup
+
+ wg.Add(2)
+
+ go func() {
+ n, err := io.Copy(b, a)
+ if err != nil {
+ log("After %d bytes from %s to %s: %s.", n, a.RemoteAddr(), b.RemoteAddr(), err)
+ }
+ a.CloseRead()
+ b.CloseWrite()
+ wg.Done()
+ }()
+
+ go func() {
+ n, err := io.Copy(a, b)
+ if err != nil {
+ log("After %d bytes from %s to %s: %s.", n, b.RemoteAddr(), a.RemoteAddr(), err)
+ }
+ b.CloseRead()
+ a.CloseWrite()
+ wg.Done()
+ }()
+
+ wg.Wait()
+
+ return nil
+}
+
+func handleExternalConnection(conn *net.TCPConn, chain *Chain) error {
+ handlerChan <- 1
+ defer func() {
+ handlerChan <- -1
+ }()
+
+ chain.Conns.Push(conn)
+ log("handleExternalConnection: now %d conns buffered.", chain.Conns.Length())
+ procsConn, err := net.DialTCP("tcp", nil, chain.ProcsAddr)
+ if err != nil {
+ log("error dialing proxy chain: %s.", err)
+ return err
+ }
+ err = copyLoop(conn, procsConn)
+ if err != nil {
+ log("error copying between ext and proxy chain: %s.", err)
+ return err
+ }
+ return nil
+}
+
+func handleInternalConnection(conn *net.TCPConn, chain *Chain) error {
+ handlerChan <- 1
+ defer func() {
+ handlerChan <- -1
+ }()
+
+ elem, ok := chain.Conns.Pop()
+ if !ok {
+ log("Underflow of connection stack, closing connection.")
+ err := conn.Close()
+ if err != nil {
+ log("Error in close: %s.", err)
+ }
+ return errors.New("connection stack underflow")
+ }
+ extConn := elem.(*net.TCPConn)
+ log("Connecting to ORPort using remote addr %s.", extConn.RemoteAddr())
+ log("handleInternalConnection: now %d conns buffered.", chain.Conns.Length())
+ or, err := pt.DialOr(&ptInfo, extConn.RemoteAddr().String(), chain.MethodName)
+ if err != nil {
+ log("Error connecting to ORPort: %s.", err)
+ return err
+ }
+ err = copyLoop(or, conn)
+ if err != nil {
+ log("Error copying between int and ORPort: %s.", err)
+ return err
+ }
+ return nil
+}
+
+func listenerLoop(chain *Chain) {
+ extChan := make(chan *net.TCPConn)
+ intChan := make(chan *net.TCPConn)
+ go acceptLoop("external", chain.ExtLn, extChan)
+ go acceptLoop("internal", chain.IntLn, intChan)
+
+loop:
+ for {
+ select {
+ case conn, ok := <-extChan:
+ if !ok {
+ break loop
+ }
+ go handleExternalConnection(conn, chain)
+ case conn, ok := <-intChan:
+ if !ok {
+ break loop
+ }
+ go handleInternalConnection(conn, chain)
+ }
+ }
+}
+
+func startChain(methodName string, bindaddr *net.TCPAddr, plugins []ServerTransportPlugin) (*Chain, error) {
+ chain := &Chain{}
+ var err error
+
+ chain.MethodName = methodName
+ chain.Conns = NewStack(connStackSize)
+
+ // Start internal listener (the proxy chain connects back to this).
+ chain.IntLn, err = net.ListenTCP("tcp", &net.TCPAddr{IP: net.ParseIP("127.0.0.1"), Port: 0})
+ if err != nil {
+ log("Error opening internal listener: %s.", err)
+ chain.Shutdown()
+ return nil, err
+ }
+ log("Internal listener on %s.", chain.IntLn.Addr())
+
+ // Start subprocesses.
+ chain.ProcsAddr, chain.Procs, err = startProcesses(chain.IntLn.Addr(), plugins)
+ if err != nil {
+ log("Error starting proxy chain: %s.", err)
+ chain.Shutdown()
+ return nil, err
+ }
+ log("Proxy chain on %s.", chain.ProcsAddr)
+
+ // Start external Internet listener (listens on bindaddr and connects to
+ // proxy chain).
+ chain.ExtLn, err = net.ListenTCP("tcp", bindaddr)
+ if err != nil {
+ log("Error opening external listener: %s.", err)
+ chain.Shutdown()
+ return nil, err
+ }
+ log("External listener on %s.", chain.ExtLn.Addr())
+
+ go listenerLoop(chain)
+
+ return chain, nil
+}
+
+type Configuration struct {
+ // Map from method names to command strings.
+ Transports map[string][]string
+ // Map from method names to ServerTransportOptions.
+ Options map[string]pt.Args
+ // Map from tor-friendly names like "obfs3_websocket" to systematic
+ // names like "obfs3|websocket".
+ Aliases map[string]string
+}
+
+func (conf *Configuration) MethodNames() []string {
+ result := make([]string, 0)
+ // We understand all the single transports
+ for k, _ := range conf.Transports {
+ result = append(result, k)
+ }
+ // and aliases.
+ for k, _ := range conf.Aliases {
+ result = append(result, k)
+ }
+ return result
+}
+
+// Parse a (possibly composed) method name into a slice of single method names.
+func (conf *Configuration) ParseMethodName(methodName string) []string {
+ if name, ok := conf.Aliases[methodName]; ok {
+ methodName = name
+ }
+ return strings.Split(methodName, "|")
+}
+
+func (conf *Configuration) PluginList(methodName string) ([]ServerTransportPlugin, error) {
+ names := conf.ParseMethodName(methodName)
+ stp := make([]ServerTransportPlugin, 0)
+ for _, name := range names {
+ command, ok := conf.Transports[name]
+ if !ok {
+ return nil, errors.New(fmt.Sprintf("no transport named %q", name))
+ }
+ options := conf.Options[name]
+ stp = append(stp, ServerTransportPlugin{name, command, options})
+ }
+ return stp, nil
+}
+
+// Simulate loading a configuration file.
+func getConfiguration() (conf *Configuration) {
+ conf = new(Configuration)
+ conf.Transports = make(map[string][]string)
+ conf.Aliases = make(map[string]string)
+ conf.Options = make(map[string]pt.Args)
+ conf.Transports["obfs3"] = []string{"obfsproxy", "managed"}
+ conf.Transports["websocket"] = []string{"pt-websocket-server"}
+ // conf.Options["obfs3"] = make(pt.Args)
+ // conf.Options["obfs3"]["secret"] = []string{"foo"}
+ conf.Aliases["obfs3_websocket"] = "obfs3|websocket"
+ return conf
+}
+
+func main() {
+ var logFilename string
+ var port int
+
+ flag.Usage = usage
+ flag.StringVar(&logFilename, "log", "", "log file to write to")
+ flag.IntVar(&port, "port", 0, "port to listen on if unspecified by Tor")
+ flag.Parse()
+
+ if logFilename != "" {
+ f, err := os.OpenFile(logFilename, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0600)
+ if err != nil {
+ fmt.Fprintf(os.Stderr, "Can't open log file %q: %s.\n", logFilename, err.Error())
+ os.Exit(1)
+ }
+ logFile = f
+ }
+
+ log("Starting.")
+
+ var err error
+ conf := getConfiguration()
+ ptInfo, err = pt.ServerSetup(conf.MethodNames())
+ if err != nil {
+ log("Error in ServerSetup: %s", err)
+ os.Exit(1)
+ }
+
+ chains := make([]*Chain, 0)
+ for _, bindaddr := range ptInfo.Bindaddrs {
+ // Override tor's requested port (which is 0 if this transport
+ // has not been run before) with the one requested by the --port
+ // option.
+ if port != 0 {
+ bindaddr.Addr.Port = port
+ }
+
+ plugins, err := conf.PluginList(bindaddr.MethodName)
+ if err != nil {
+ pt.SmethodError(bindaddr.MethodName, err.Error())
+ continue
+ }
+
+ chain, err := startChain(bindaddr.MethodName, bindaddr.Addr, plugins)
+ if err != nil {
+ pt.SmethodError(bindaddr.MethodName, err.Error())
+ continue
+ }
+ pt.Smethod(bindaddr.MethodName, chain.ExtLn.Addr())
+ chains = append(chains, chain)
+ }
+ pt.SmethodsDone()
+
+ var numHandlers int = 0
+ var sig os.Signal
+ sigChan := make(chan os.Signal, 1)
+ signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM)
+
+ sig = nil
+ for sig == nil {
+ select {
+ case n := <-handlerChan:
+ numHandlers += n
+ case sig = <-sigChan:
+ }
+ }
+ log("Got first signal %q with %d running handlers.", sig, numHandlers)
+ for _, chain := range chains {
+ chain.CloseListeners()
+ chain.Procs.Signal(sig)
+ }
+
+ if sig == syscall.SIGTERM {
+ log("Caught signal %q, exiting.", sig)
+ return
+ }
+
+ sig = nil
+ for sig == nil && numHandlers != 0 {
+ select {
+ case n := <-handlerChan:
+ numHandlers += n
+ log("%d remaining handlers.", numHandlers)
+ case sig = <-sigChan:
+ }
+ }
+ if sig != nil {
+ log("Got second signal %q with %d running handlers.", sig, numHandlers)
+ for _, chain := range chains {
+ chain.Procs.Signal(sig)
+ }
+ }
+
+ log("Exiting.")
+}
diff --git a/fog-server/pt_test.go b/fog-server/pt_test.go
new file mode 100644
index 0000000..35d3aab
--- /dev/null
+++ b/fog-server/pt_test.go
@@ -0,0 +1,39 @@
+package main
+
+import "testing"
+
+import "git.torproject.org/pluggable-transports/goptlib.git"
+
+func TestEncodeServerTransportOptions(t *testing.T) {
+ tests := [...]struct {
+ methodName string
+ opts pt.Args
+ expected string
+ }{
+ {
+ "foo",
+ pt.Args{},
+ "",
+ },
+ {
+ "foo",
+ pt.Args{
+ "key": []string{"value1", "value2"},
+ "something": []string{"value1", "value2"},
+ },
+ "foo:key=value1;foo:key=value2;foo:something=value1;foo:something=value2",
+ },
+ {
+ "m:m",
+ pt.Args{"k;k": []string{"v=v", "b\\b"}},
+ "m\\:m:k\\;k=v\\=v;m\\:m:k\\;k=b\\\\b",
+ },
+ }
+
+ for _, test := range tests {
+ output := encodeServerTransportOptions(test.methodName, test.opts)
+ if output != test.expected {
+ t.Errorf("%q %q â?? %q (expected %q)", test.methodName, test.opts, output, test.expected)
+ }
+ }
+}
diff --git a/fog-server/stack.go b/fog-server/stack.go
new file mode 100644
index 0000000..16cddd6
--- /dev/null
+++ b/fog-server/stack.go
@@ -0,0 +1,57 @@
+package main
+
+import "sync"
+
+// A fixed-size stack. If a push exceeds the capacity of the underlying slice,
+// the least recently added element is lost.
+type Stack struct {
+ buf []interface{}
+ base, head int
+ m sync.Mutex
+}
+
+// Create a stack with the given capacity.
+func NewStack(capacity int) *Stack {
+ return &Stack{buf: make([]interface{}, capacity+1)}
+}
+
+func (s *Stack) clamp(x int) int {
+ x = x % len(s.buf)
+ if x < 0 {
+ x += len(s.buf)
+ }
+ return x
+}
+
+func (s *Stack) Length() int {
+ s.m.Lock()
+ defer s.m.Unlock()
+ return s.clamp(s.head - s.base)
+}
+
+// If this push causes the stack to overflow, the first return value is the
+// discarded element and the second return value is false. Otherwise the second
+// return value is true.
+func (s *Stack) Push(x interface{}) (interface{}, bool) {
+ s.m.Lock()
+ defer s.m.Unlock()
+ s.buf[s.head] = x
+ s.head = s.clamp(s.head + 1)
+ if s.head == s.base {
+ s.base = s.clamp(s.base + 1)
+ return s.buf[s.head], false
+ }
+ return nil, true
+}
+
+// The second return value is false if the stack was empty, and true otherwise.
+// The first return value is defined only when the second is true.
+func (s *Stack) Pop() (interface{}, bool) {
+ s.m.Lock()
+ defer s.m.Unlock()
+ if s.head == s.base {
+ return nil, false
+ }
+ s.head = s.clamp(s.head - 1)
+ return s.buf[s.head], true
+}
diff --git a/fog-server/stack_test.go b/fog-server/stack_test.go
new file mode 100644
index 0000000..3188a2d
--- /dev/null
+++ b/fog-server/stack_test.go
@@ -0,0 +1,120 @@
+package main
+
+import "testing"
+
+// Test operations on a zero-capacity stack.
+func TestZeroCapacity(t *testing.T) {
+ var ok bool
+
+ s := NewStack(0)
+ if s.Length() != 0 {
+ t.Fatal("initial length is not 0")
+ }
+ _, ok = s.Push("a")
+ if ok || s.Length() != 0 {
+ t.Fatal()
+ }
+ _, ok = s.Pop()
+ if ok || s.Length() != 0 {
+ t.Fatal()
+ }
+}
+
+func TestPushPop(t *testing.T) {
+ var x interface{}
+ var ok bool
+
+ s := NewStack(3)
+
+ // Push elems.
+ if s.Length() != 0 {
+ t.Fatal("initial length is not 0")
+ }
+ _, ok = s.Push("a")
+ if !ok || s.Length() != 1 {
+ t.Fatal()
+ }
+ _, ok = s.Push("b")
+ if !ok || s.Length() != 2 {
+ t.Fatal()
+ }
+
+ // Pop to empty.
+ x, ok = s.Pop()
+ if !ok || x != "b" || s.Length() != 1 {
+ t.Fatal()
+ }
+ x, ok = s.Pop()
+ if !ok || x != "a" || s.Length() != 0 {
+ t.Fatal()
+ }
+ // Pop one past empty.
+ x, ok = s.Pop()
+ if ok {
+ t.Fatal()
+ }
+
+ // Push to capacity.
+ s.Push("c")
+ s.Push("d")
+ _, ok = s.Push("e")
+ if !ok || s.Length() != 3 {
+ t.Fatal("push to capacity is not at capacity")
+ }
+ // Push one past capacity.
+ x, ok = s.Push("f")
+ if ok || s.Length() != 3 {
+ t.Fatal()
+ }
+ if x != "c" {
+ t.Fatal("mismatch in overwritten element")
+ }
+
+ // Pop to empty.
+ x, ok = s.Pop()
+ if !ok || x != "f" || s.Length() != 2 {
+ t.Fatal()
+ }
+ x, ok = s.Pop()
+ if !ok || x != "e" || s.Length() != 1 {
+ t.Fatal()
+ }
+ x, ok = s.Pop()
+ if !ok || x != "d" || s.Length() != 0 {
+ t.Fatal()
+ }
+ // Pop one past empty.
+ x, ok = s.Pop()
+ if ok {
+ t.Fatal()
+ }
+}
+
+// Test underflow of an initially empty stack.
+func TestUnderflowEmpty(t *testing.T) {
+ var ok bool
+
+ s := NewStack(3)
+ _, ok = s.Pop()
+ if ok {
+ t.Fatal()
+ }
+}
+
+// Test underflow of a stack that had been full.
+func TestUnderflowFull(t *testing.T) {
+ var ok bool
+
+ s := NewStack(3)
+ s.Push("a")
+ s.Push("b")
+ s.Push("c")
+ s.Push("d")
+ s.Pop()
+ s.Pop()
+ s.Pop()
+ _, ok = s.Pop()
+ if ok {
+ t.Fatal()
+ }
+}
diff --git a/fog-server/torrc b/fog-server/torrc
new file mode 100644
index 0000000..f103ddc
--- /dev/null
+++ b/fog-server/torrc
@@ -0,0 +1,5 @@
+ORPort 9999
+ExtORPort 5555
+BridgeRelay 1
+SocksPort 0
+ServerTransportPlugin obfs3_websocket exec ./bin/fog-server
diff --git a/fog/__init__.py b/fog/__init__.py
deleted file mode 100644
index e69de29..0000000
diff --git a/fog/socks.py b/fog/socks.py
deleted file mode 100644
index e3b483b..0000000
--- a/fog/socks.py
+++ /dev/null
@@ -1,59 +0,0 @@
-from twisted.protocols import socks
-from twisted.internet.protocol import Factory
-import logging
-
-logger = logging.getLogger('obfs-flash-logger')
-
-class SOCKSv4InterceptorProtocol(socks.SOCKSv4):
- """
- A modified SOCKS protocol which extracts the requested ip and port
- and redirects connections to the first pluggable transport in the chain.
- """
-
- def __init__(self, factory, pt_method_name):
- """
- :param twisted.internet.protocol.factory factory: The factory that launched this protocol
- :param pt_method_name: The name of the chain to be launched when a new connection is received
- """
- self.factory = factory
- self._pt_method_name = pt_method_name
- socks.SOCKSv4.__init__(self)
-
- def _dataReceived2(self, server, user, version, code, port):
- """
- Extracts the requested ip and port and redirects to a different address
- """
- if code == 1: # CONNECT
- assert version == 4, "Bad version code: %s" % version
- if not self.authorize(code, server, port, user):
- self.makeReply(91)
- return
- def _chain_set_up(remote_address, remote_port):
- logger.debug("chain finished, connecting %s:%s" % (remote_address, remote_port))
- # Connect to our remote address instead of the requested one
- d = self.connectClass(remote_address, remote_port, socks.SOCKSv4Outgoing, self)
- d.addErrback(lambda result, self = self: self.makeReply(91))
- self.factory._new_conn_callback(server, port, self._pt_method_name, _chain_set_up)
- assert self.buf == "", "hmm, still stuff in buffer... %s" % repr(self.buf)
- else:
- super(SOCKSv4InterceptorProtocol, self)._dataReceived2(server, user, version, code, port)
-
-class SOCKSv4InterceptorFactory(Factory):
-
- def __init__(self, pt_method_name, new_conn_callback):
- """
- :param str pt_method_name: The name of the pt_method that this factory is launching.
- :param function new_conn_callback: The function to be called when a connection is made.
- def new_conn_callback
- :param str server: The ip address requested by the SOCKS client.
- :param int port: The port requested by the SOCKS client.
- :param str pt_method_name: The name of the pt_method this factory is a part of.
- :param function chain_set_up: The function to be called when the chain has finished setting up.
- :param str remote_address: The address to relay the SOCKS request to.
- :param int remote_port: The port to to send the SOCKS request to.
- """
- self._pt_method_name = pt_method_name
- self._new_conn_callback = new_conn_callback
-
- def buildProtocol(self, addr):
- return SOCKSv4InterceptorProtocol(self, self._pt_method_name)
\ No newline at end of file
diff --git a/fogrc b/fogrc
deleted file mode 100644
index ee28514..0000000
--- a/fogrc
+++ /dev/null
@@ -1,17 +0,0 @@
-#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 obfs3 obfsproxy managed
-ClientTransportPlugin flashproxy flashproxy-client --transport obfs3|websocket --register 127.0.0.1:0 :9000
-# If port 9000 cannot be portforwarded change it to a port that can be ported like so:
-#ClientTransportPlugin flashproxy flashproxy-client --transport obfs3|websocket --register 127.0.0.1:0 :3923
-# use a different facilitator
-#ClientTransportPlugin flashproxy flashproxy-client --transport obfs3|websocket -f http://siteb.fp-facilitator.org/fac/ --register â??-register-methods=http 127.0.0.1:0 :3923
-Alias obfs3_flashproxy obfs3|flashproxy
diff --git a/obfs-flash-client b/obfs-flash-client
deleted file mode 100755
index 18adff4..0000000
--- a/obfs-flash-client
+++ /dev/null
@@ -1,538 +0,0 @@
-#!/usr/bin/python
-
-import argparse
-import os
-import sys
-
-from collections import namedtuple
-from functools import partial
-
-# TODO(infinity0): this is temporary workaround until we do #10047
-if sys.platform == 'win32':
- os.environ["KILL_CHILDREN_ON_DEATH"] = "1"
-from pyptlib.util import parse_addr_spec
-from pyptlib.util.subproc import auto_killall, Popen
-from pyptlib.client import ClientTransportPlugin
-
-from subprocess import PIPE
-
-from twisted.internet.defer import Deferred, DeferredList
-from twisted.internet.stdio import StandardIO
-from twisted.internet.protocol import Factory, connectionDone
-from twisted.internet.endpoints import TCP4ClientEndpoint
-from twisted.protocols.basic import LineReceiver
-from twisted.protocols.portforward import ProxyServer as _ProxyServer
-from twisted.python import log
-from txsocksx.client import SOCKS4ClientEndpoint, SOCKS5ClientEndpoint
-from fog.socks import SOCKSv4InterceptorFactory
-
-import shlex
-
-import logging
-
-DEFAULT_CONFIG_FILE_NAME = 'fogrc'
-
-logger = None
-def pt_setup_logger():
- global logger
- logger = logging.getLogger('obfs-flash-logger')
- logger.setLevel(logging.WARNING)
- ch = logging.StreamHandler()
- ch.setLevel(logging.DEBUG)
- logger.addHandler(ch)
-
-def pt_child_env(managed_ver, env=os.environ):
- """
- Prepare the environment for a child PT process, by clearing all TOR_PT_*
- envvars except TOR_PT_STATE_LOCATION and TOR_PT_MANAGED_TRANSPORT_VER.
- """
- exempt = ['TOR_PT_STATE_LOCATION']
- cur_env = [(k, v) for k, v in env.iteritems()
- if not k.startswith('TOR_PT_') or k in exempt]
- cur_env.append(('TOR_PT_MANAGED_TRANSPORT_VER', ','.join(managed_ver)))
- return cur_env
-
-class MethodSpec(namedtuple('MethodSpec', 'name protocol addrport args opts')):
- @classmethod
- def fromLine(cls, line):
- args = line.rstrip('\n').split(' ')
- name = args[0]
- protocol = args[1]
- 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 MethodSpec(name, protocol, addrport, args, opts)
-
-def branch(parent):
- """
- Returns a new Deferred that does not advance the callback-chain of the parent.
-
- See http://xph.us/2009/12/10/asynchronous-programming-in-python.html for motivation.
- """
- d = Deferred()
- parent.addCallback(lambda v: (v, d.callback(v))[0])
- parent.addErrback(lambda f: (f, d.errback(f))[1])
- return d
-
-class ManagedTransportProtocolV1(LineReceiver):
- """
- A Twisted IProtocol to read PT output.
-
- See pt-spec.txt and others for details of the protocol.
- """
- # TODO(infinity0): eventually this could be padded out and moved to pyptlib
-
- delimiter = os.linesep
- protocol_version = "1"
-
- def __init__(self):
- self.cmethods = {}
- self._dCMethodsDone = Deferred()
- self._dPluginError = Deferred()
- # dPluginError triggers errors on all sub-events, not the other way round
- # so fatal sub-events should call _abort rather than errback on their Deferreds
- self._dPluginError.addErrback(lambda f: (f, self._fireCMethodsDone().errback(f))[0])
- # TODO(infinity0): call _abort if we don't recv CMETHODS DONE within n sec
-
- def whenCMethodsDone(self):
- """
- Return a new Deferred that calls-back when CMETHODS DONE is received.
- """
- return branch(self._dCMethodsDone)
-
- def whenPluginError(self):
- """
- Return a new Deferred that errors-back when the remote plugin fails.
-
- Note: the success chain (callback) is never fired.
- """
- return branch(self._dPluginError)
-
- def lineReceived(self, line):
- if not line: return
-
- (kw, args) = line.split(' ', 1)
- if kw == "VERSION":
- version = args.strip()
- if version != self.protocol_version:
- self._abort(ValueError("child used unsupported managed transport version: %s" % version))
- elif kw == "CMETHOD":
- cmethod = MethodSpec.fromLine(args)
- self.cmethods[cmethod.name] = cmethod
- elif kw == "CMETHODS" and args == "DONE":
- self._fireCMethodsDone().callback(self.cmethods)
- else:
- pass # ignore unrecognised line
-
- def connectionLost(self, reason=connectionDone):
- self._firePluginError().errback(reason)
-
- def _abort(self, exc):
- self._firePluginError().errback(exc)
- self.transport.loseConnection()
-
- def _fireCMethodsDone(self):
- """Return dCMethodsDone or a dummy if it was already called."""
- if self._dCMethodsDone:
- d = self._dCMethodsDone
- self._dCMethodsDone = None
- return d
- return Deferred().addErrback(lambda *args: None)
-
- def _firePluginError(self):
- """Return dPluginError or a dummy if it was already called."""
- if self._dPluginError:
- d = self._dPluginError
- self._dPluginError = None
- return d
- return Deferred().addErrback(lambda *args: None)
-
-# TODO(infinity0): remove this class when twisted update their side
-class ProxyServer(_ProxyServer):
-
- def connectionMade(self):
- # code copied from super class, except instead of connecting
- # to a TCP endpoint we abstract that out to a child method
- self.transport.pauseProducing()
-
- client = self.clientProtocolFactory()
- client.setServer(self)
-
- if self.reactor is None:
- from twisted.internet import reactor
- self.reactor = reactor
-
- self.connectProxyClient(client)
-
- def connectProxyClient(self, client):
- raise NotImplementedError()
-
-class OneUseSOCKSWrapper(ProxyServer):
-
- def connectProxyClient(self, client):
- local_host, local_port = self.factory.method_spec.addrport
- TCPPoint = TCP4ClientEndpoint(
- self.reactor,
- local_host,
- local_port)
- # Next PT may need either SOCKS4 or SOCKS5 so check its protocol and get the required class
- socks_endpoint_class = self.getSocksEndpointClass()
- SOCKSPoint = socks_endpoint_class(
- self.factory.remote_host,
- self.factory.remote_port,
- TCPPoint)
- # Store port for debugging messages before stopListening is called.
- # listen_port will not have a port after stopListening is called.
- stored_port = self.factory.listen_port.getHost().port
- d_port_closed = self.factory.listen_port.stopListening()
- d_port_closed.addCallback(
- lambda x: logger.debug("Closed factory listener %s on port %s" % (self.factory, stored_port)))
- d_port_closed.addErrback(
- lambda x: logger.warn("Failed to close factory listener %s listening on port %s" % (self.factory, stored_port)))
- d = SOCKSPoint.connect(client)
- d.chainDeferred(self.factory.d_connected)
- @d.addErrback
- def _gotError(error):
- log.err(error, "error connecting to SOCKS server")
-
- def getSocksEndpointClass(self):
- """
- Checks self.factory.method_spec.protocol and returns the appropriate socks endpoint class.
- """
- socks_endpoint_class = None
- if self.factory.method_spec.protocol == 'socks4':
- socks_endpoint_class = SOCKS4ClientEndpoint
- elif self.factory.method_spec.protocol == 'socks5':
- socks_endpoint_class = SOCKS5ClientEndpoint
- else:
- raise ValueError("Pluggable transport requires unknown protocol %s. Supported protocols are %s" %
- (self.factory.method_spec.protocol, ('socks4', 'socks5')))
- return socks_endpoint_class
-
-class OneUseSOCKSFactory(Factory):
- protocol = OneUseSOCKSWrapper
- def __init__(self, method_spec, remote_host, remote_port):
- self._connected_once = False
- self.method_spec = method_spec
- self.remote_host = remote_host
- self.remote_port = remote_port
- self.d_connected = Deferred()
- self.listen_port = None
-
- def __str__(self):
- return "OneUseSOCKSFactory connecting %s to %s:%s" % (self.method_spec, self.remote_host, self.remote_port)
-
- def __repr__(self):
- return "OneUseSOCKSFactory(%s, %s, %s)" % (self.method_spec, self.remote_host, self.remote_port)
-
- def setListenPort(self, listen_port):
- """
- Sets the listen_port object.
- :param function listen_port: The function returned from a ListenTCP call. Used to shutdown the port when a connection is made.
- """
- self.listen_port = listen_port
-
- def whenConnected(self):
- """
- Returns a new Deferred that triggers when a connection is successfully made.
- """
- return branch(self.d_connected)
-
- def buildProtocol(self, addr):
- """
- Only allows one protocol to be created. After that it always returns None
- :param twisted.internet.interfaces.IAddress addr: an object implementing L{twisted.internet.interfaces.IAddress}
- """
- if self._connected_once:
- return None
- else:
- self._connected_once = True
- return Factory.buildProtocol(self, addr)
-
-if sys.platform == "win32":
- # TODO(infinity0): push this upstream to Twisted
- from twisted.internet import _pollingfile
- import msvcrt
-
- _StandardIO = StandardIO
- class StandardIO(_StandardIO):
-
- def __init__(self, proto, stdin=None, stdout=None, reactor=None):
- """
- Start talking to standard IO with the given protocol.
-
- Also, put it stdin/stdout/stderr into binary mode.
- """
- if reactor is None:
- import twisted.internet.reactor
- reactor = twisted.internet.reactor
-
- _pollingfile._PollingTimer.__init__(self, reactor)
- self.proto = proto
-
- fdstdin = stdin or sys.stdin.fileno()
- fdstdout = stdout or sys.stdout.fileno()
-
- for stdfd in (fdstdin, fdstdout):
- msvcrt.setmode(stdfd, os.O_BINARY)
-
- hstdin = msvcrt.get_osfhandle(fdstdin)
- self.stdin = _pollingfile._PollableReadPipe(
- hstdin, self.dataReceived, self.readConnectionLost)
-
- hstdout = msvcrt.get_osfhandle(fdstdout)
- self.stdout = _pollingfile._PollableWritePipe(
- hstdout, self.writeConnectionLost)
-
- self._addPollableResource(self.stdin)
- self._addPollableResource(self.stdout)
-
- self.proto.makeConnection(self)
-
-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 = 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, pt_method_name))
- for name in methodnames]
- return sub_proc, sub_protocol, methoddefers
-
-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)
- raise ValueError()
- return cmethods[childmethod]
-
-def pt_setup_socks_shim(pt_name, pt_chain, success_list, dest_address, dest_port, reactor, proxy_deferreds):
- """
- Launches a socks proxy server to link two PTs together.
- :param str pt_name: The name of the pt to send traffic to.
- :param list pt_chain: The list of PTs in this chain.
- :param list 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=[]))]
- :param str dest_address: The address for the next PT to send its results to.
- :param int dest_port: The port for the next PT to send to.
- :param twisted.internet.interfaces.IReactor reactor: Reactor to attack the TCP server to.
-
- :param list proxy_deferreds: This list has each factorys' deferred appended to it.
-
- :returns twisted.internet.interfaces.IListeningPort: An IListeningPort used for shutting down a factory after a connection is made.
- """
- methodspec = [r[1] for r in success_list if r[1].name == pt_name][0] # Returns the resulting methodspec.
- factory = OneUseSOCKSFactory(methodspec, dest_address, dest_port)
- # TODO switch to using endpoints instead of listenTCP
- proxy_server = reactor.listenTCP(interface='127.0.0.1', port=0, factory=factory)
- factory.setListenPort(proxy_server)
- proxy_deferreds.append(factory.whenConnected())
- logger.debug("launched %s on port %s with dest %s:%s" % (pt_name, proxy_server.getHost().port, dest_address, dest_port))
- return proxy_server
-
-def pt_launch_chain(dest_address, dest_port, pt_chain, _chain_set_up, reactor, success_list):
- """
- Launches a chain of pluggable transports by connecting each pt with SOCKS proxies.
- :param str dest_address: The bridge address to connect to.
- :param int dest_port: The bridge port to connect to.
- :param list pt_chain: The list of pt names to launch.
- :param function _chain_set_up: The function to call when the shims have been set up.
- :param twisted.internet.interfaces.IReactor reactor: Reactor to install this PT to.
- :param list 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=[]))]
- """
- proxy_deferreds = []
- last_pt_name = pt_chain[-1]
- logger.debug("launching chain %s" % pt_chain)
- # Initialize prev_server to the port picked by the last proxy server as that's the only one we know yet.
- last_server = pt_setup_socks_shim(last_pt_name, pt_chain, success_list, dest_address, dest_port,
- reactor, proxy_deferreds)
- prev_server = last_server
- for pt_name in reversed(pt_chain[:-1]):
- # Loops through the pts linking them together through SOCKS proxies, skipping the last pt.
- prev_server = pt_setup_socks_shim(pt_name, pt_chain, success_list, '127.0.0.1', prev_server.getHost().port,
- reactor, proxy_deferreds)
- def check_chain_all_connected(protocol_list):
- """
- Checks all the shims launched to see if they successfully connected.
- :param list protocol_list: A list of tuples containing status boolean, twisted.protocols.portforward.ProxyClient pairs.
- Ex: [(True, <twisted.protocols.portforward.ProxyClient instance at 0x10b825518>),
- (True, <twisted.protocols.portforward.ProxyClient instance at 0x10b829518>)]
- """
- if all([result[0] for result in protocol_list]):
- logger.debug("All PT shims connected correctly")
- else:
- # At this point the SOCKS protocol is in communication mode so no need to call makeReply(91)
- # This assumes that the child pluggable transport will shut down the connection cleanly.
- failed_protocols = [x[1] for x in protocol_list if x[0] == False]
- logger.error("Shims %s failed to connect." % failed_protocols)
- raise ValueError()
-
- finished = DeferredList(proxy_deferreds)
- finished.addCallback(check_chain_all_connected)
- _chain_set_up(prev_server.getHost().host, prev_server.getHost().port)
-
-def pt_launch_interceptor(reactor, client, configuration, pt_method_name, success_list):
- """
- Launches a SOCKS interceptor.
- :param twisted.internet.interfaces.IReactor reactor: Reactor to install this PT to.
- :param pyptlib.client.ClientTransportPlugin client: PT client API.
- :param Config configuration: The configuration structure for this pair.
- :param str pt_method_name: The name of the pt chain to launch. Ex: "obfs3_flashproxy"
- :param list 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=[]))]
- """
- logger.debug("launching interceptor")
- pt_chain = configuration.alias_map[pt_method_name]
- 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
- socks_interceptor = SOCKSv4InterceptorFactory(pt_method_name,
- lambda dest_address, dest_port, pt_method_name, chain_finished:
- pt_launch_chain(dest_address, dest_port, pt_chain, chain_finished, reactor, success_list))
- # TODO switch to using endpoints instead of listenTCP
- interceptor = reactor.listenTCP(interface='127.0.0.1', port=0, factory=socks_interceptor)
- interceptor_port = interceptor.getHost().port
- client.reportMethodSuccess(pt_method_name, "socks4", ("127.0.0.1", interceptor_port))
- client.reportMethodsEnd()
-
-def pt_setup_transports(reactor, client, configuration, pt_method_name):
- """
- Launches the PT processes.
- :param twisted.internet.interfaces.IReactor reactor: Reactor to install this PT to.
- :param pyptlib.client.ClientTransportPlugin client: PT client API.
- :param Config configuration: The configuration structure for this pair.
- :param str pt_method_name: The name of the pt chain to launch. Ex: "obfs3_flashproxy"
- """
- logger.debug("Setting up transports %s" % pt_method_name)
- if pt_method_name in configuration.alias_map:
- pt_chain = configuration.alias_map[pt_method_name]
- else:
- logger.error('Pluggable Transport Combination %s not found in configuration alias map.' % pt_method_name)
- raise KeyError()
-
- defer_list = []
-
- if len(pt_chain) < 2:
- raise ValueError("PT Chain %s does not contain enough transports." % pt_chain)
-
- for pt in pt_chain:
- if pt in configuration.transport_map:
- pt_cmdline = configuration.transport_map[pt]
- else:
- raise ValueError("Pluggable transport %s not found in transport_map. Check your configuration file." % pt)
- _, _, defer = pt_launch_child(reactor, client, [pt], pt_method_name, pt_cmdline)
- defer_list.extend(defer)
- whenAllDone = DeferredList(defer_list, consumeErrors=False)
- whenAllDone.addCallback(lambda success_list: pt_launch_interceptor(reactor, client, configuration, pt_method_name, success_list))
-
-
-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 str 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:
- logger.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 main(*args):
- parser = argparse.ArgumentParser()
- parser.add_argument("-f", help="fog configuration file path",
- metavar='FOGFILE', type=argparse.FileType('r'), default=DEFAULT_CONFIG_FILE_NAME)
-
- pt_setup_logger()
- # TODO(infinity0): add an "external" mode, which would require us to run
- # obfsproxy in external mode too.
-
- opts = parser.parse_args(args)
- configuration = None
- file_contents = opts.f.read()
- configuration = Config.parse(file_contents)
- pt_method_names = configuration.alias_map.keys()
- client = ClientTransportPlugin()
- 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():
- logger.error("no transports to serve. pt_method_names may be invalid.")
- return 1
-
- from twisted.internet import reactor
- auto_killall(1, cleanup=reactor.stop)
- #TODO Change from launching a single pair to launching multiple chains.
- pt_setup_transports(reactor, client, configuration, pt_method_names[0])
- reactor.run(installSignalHandlers=0)
- return 0
-
-if __name__ == "__main__":
- sys.exit(main(*sys.argv[1:]))
-
diff --git a/obfs-flash-server.go b/obfs-flash-server.go
deleted file mode 100644
index bb34219..0000000
--- a/obfs-flash-server.go
+++ /dev/null
@@ -1,548 +0,0 @@
-package main
-
-import (
- "bufio"
- "errors"
- "flag"
- "fmt"
- "io"
- "net"
- "os"
- "os/exec"
- "os/signal"
- "strings"
- "sync"
- "syscall"
- "sort"
- "time"
-)
-
-import "git.torproject.org/pluggable-transports/goptlib.git"
-
-const connStackSize = 1000
-const subprocessWaitTimeout = 30 * time.Second
-
-var logFile = os.Stderr
-
-var ptInfo pt.ServerInfo
-
-// When a connection handler starts, +1 is written to this channel; when it
-// ends, -1 is written.
-var handlerChan = make(chan int)
-
-func usage() {
- fmt.Printf("Usage: %s [OPTIONS]\n", os.Args[0])
- fmt.Printf("Chains websocket and obfsproxy server transports. pt-websocket-server and\n")
- fmt.Printf("obfsproxy must be in PATH.\n")
- fmt.Printf("\n")
- fmt.Printf(" -h, --help show this help.\n")
- fmt.Printf(" --log FILE log messages to FILE (default stderr).\n")
- fmt.Printf(" --port PORT listen on PORT (overrides Tor's requested port).\n")
-}
-
-var logMutex sync.Mutex
-
-func log(format string, v ...interface{}) {
- dateStr := time.Now().Format("2006-01-02 15:04:05")
- logMutex.Lock()
- defer logMutex.Unlock()
- msg := fmt.Sprintf(format, v...)
- fmt.Fprintf(logFile, "%s %s\n", dateStr, msg)
-}
-
-type ProcList []*os.Process
-
-func (procs ProcList) Signal(sig os.Signal) {
- for _, p := range procs {
- log("Sending signal %q to process with pid %d.", sig, p.Pid)
- err := p.Signal(sig)
- if err != nil {
- log("Error sending signal %q to process with pid %d: %s.", sig, p.Pid, err)
- }
- }
-}
-
-func (procs ProcList) Kill() {
- for _, p := range procs {
- log("Killing process with pid %d.", p.Pid)
- err := p.Kill()
- if err != nil {
- log("Error killing process with pid %d: %s.", p.Pid, err)
- continue
- }
- state, err := p.Wait()
- if err != nil {
- log("Error waiting on process with pid %d: %s.", state.Pid(), err)
- continue
- }
- if !state.Exited() {
- log("Process with pid %d didn't exit.", state.Pid())
- continue
- }
- }
-}
-
-type Chain struct {
- MethodName string
- ExtLn, IntLn *net.TCPListener
- ProcsAddr *net.TCPAddr
- Procs ProcList
- // This stack forwards external IP addresses to the extended ORPort.
- Conns *Stack
-}
-
-func (chain *Chain) CloseListeners() {
- if chain.ExtLn != nil {
- err := chain.ExtLn.Close()
- if err != nil {
- log("Error closing external listener: %s.", err)
- }
- }
- if chain.IntLn != nil {
- err := chain.IntLn.Close()
- if err != nil {
- log("Error closing internal listener: %s.", err)
- }
- }
-}
-
-func (chain *Chain) Shutdown() {
- chain.CloseListeners()
- chain.Procs.Kill()
- for {
- elem, ok := chain.Conns.Pop()
- if !ok {
- break
- }
- conn := elem.(*net.TCPConn)
- log("Closing stale connection from %s.", conn.RemoteAddr())
- err := conn.Close()
- if err != nil {
- }
- }
-}
-
-func findBindAddr(r io.Reader, methodName string) (*net.TCPAddr, error) {
- br := bufio.NewReader(r)
- for {
- line, err := br.ReadString('\n')
- if err != nil {
- return nil, err
- }
- log("Received from sub-transport: %q.", line)
- fields := strings.Fields(strings.TrimRight(line, "\n"))
- if len(fields) < 1 {
- continue
- }
- keyword := fields[0]
- args := fields[1:]
- if keyword == "SMETHOD" && len(args) >= 2 && args[0] == methodName {
- bindaddr, err := net.ResolveTCPAddr("tcp", args[1])
- if err != nil {
- return nil, err
- }
- return bindaddr, nil
- } else if keyword == "SMETHODS" && len(args) == 1 && args[0] == "DONE" {
- break
- }
- }
- return nil, errors.New(fmt.Sprintf("no SMETHOD %s found before SMETHODS DONE", methodName))
-}
-
-// Escape a string for a ServerTransportOptions serialization.
-func escape(s string) string {
- repl := strings.NewReplacer(":", "\\:", ";", "\\;", "=", "\\=", "\\", "\\\\")
- return repl.Replace(s)
-}
-
-func encodeServerTransportOptions(methodName string, opts pt.Args) string {
- if opts == nil {
- return ""
- }
- keys := make([]string, 0, len(opts))
- for key, _ := range opts {
- keys = append(keys, key)
- }
- sort.Strings(keys)
- parts := make([]string, 0, len(keys))
- for _, key := range keys {
- for _, value := range opts[key] {
- parts = append(parts, escape(methodName) + ":" + escape(key) + "=" + escape(value))
- }
- }
- return strings.Join(parts, ";")
-}
-
-// Represents a server transport plugin configuration like:
-// ServerTransportPlugin MethodName exec Command
-type ServerTransportPlugin struct {
- MethodName string
- Command []string
- Options pt.Args
-}
-
-func startProcesses(connectBackAddr net.Addr, plugins []ServerTransportPlugin) (bindAddr *net.TCPAddr, procs ProcList, err error) {
- var stdout io.ReadCloser
-
- defer func() {
- if err != nil {
- // Kill subprocesses before returning error.
- procs.Kill()
- procs = procs[:0]
- }
- }()
-
- bindAddr = connectBackAddr.(*net.TCPAddr)
- for _, plugin := range plugins {
- // This plugin has its TOR_PT_ORPORT set to the previous
- // bindAddr.
- cmd := exec.Command(plugin.Command[0], plugin.Command[1:]...)
- cmd.Env = []string{
- "TOR_PT_MANAGED_TRANSPORT_VER=1",
- "TOR_PT_STATE_LOCATION=" + os.Getenv("TOR_PT_STATE_LOCATION"),
- "TOR_PT_EXTENDED_SERVER_PORT=",
- "TOR_PT_ORPORT=" + bindAddr.String(),
- "TOR_PT_SERVER_TRANSPORTS=" + plugin.MethodName,
- "TOR_PT_SERVER_BINDADDR=" + plugin.MethodName + "-127.0.0.1:0",
- }
- serverTransportOptions := encodeServerTransportOptions(plugin.MethodName, plugin.Options)
- if serverTransportOptions != "" {
- cmd.Env = append(cmd.Env, "TOR_PT_SERVER_TRANSPORT_OPTIONS=" + serverTransportOptions)
- }
- log("%s environment %q", cmd.Args[0], cmd.Env)
- stdout, err = cmd.StdoutPipe()
- if err != nil {
- log("Failed to open %s stdout pipe: %s.", cmd.Args[0], err)
- return
- }
- err = cmd.Start()
- if err != nil {
- log("Failed to start %s: %s.", cmd.Args[0], err)
- return
- }
- log("Exec %s with args %q pid %d.", cmd.Path, cmd.Args, cmd.Process.Pid)
- procs = append(procs, cmd.Process)
-
- bindAddr, err = findBindAddr(stdout, plugin.MethodName)
- if err != nil {
- log("Failed to find %s bindaddr: %s.", cmd.Args[0], err)
- return
- }
- log("%s bindaddr is %s.", cmd.Args[0], bindAddr)
- }
-
- return bindAddr, procs, err
-}
-
-func acceptLoop(name string, ln *net.TCPListener, ch chan *net.TCPConn) {
- for {
- conn, err := ln.AcceptTCP()
- if err != nil {
- log("%s accept: %s.", name, err)
- break
- }
- log("%s connection from %s.", name, conn.RemoteAddr())
- ch <- conn
- }
- close(ch)
-}
-
-func copyLoop(a, b *net.TCPConn) error {
- var wg sync.WaitGroup
-
- wg.Add(2)
-
- go func() {
- n, err := io.Copy(b, a)
- if err != nil {
- log("After %d bytes from %s to %s: %s.", n, a.RemoteAddr(), b.RemoteAddr(), err)
- }
- a.CloseRead()
- b.CloseWrite()
- wg.Done()
- }()
-
- go func() {
- n, err := io.Copy(a, b)
- if err != nil {
- log("After %d bytes from %s to %s: %s.", n, b.RemoteAddr(), a.RemoteAddr(), err)
- }
- b.CloseRead()
- a.CloseWrite()
- wg.Done()
- }()
-
- wg.Wait()
-
- return nil
-}
-
-func handleExternalConnection(conn *net.TCPConn, chain *Chain) error {
- handlerChan <- 1
- defer func() {
- handlerChan <- -1
- }()
-
- chain.Conns.Push(conn)
- log("handleExternalConnection: now %d conns buffered.", chain.Conns.Length())
- procsConn, err := net.DialTCP("tcp", nil, chain.ProcsAddr)
- if err != nil {
- log("error dialing proxy chain: %s.", err)
- return err
- }
- err = copyLoop(conn, procsConn)
- if err != nil {
- log("error copying between ext and proxy chain: %s.", err)
- return err
- }
- return nil
-}
-
-func handleInternalConnection(conn *net.TCPConn, chain *Chain) error {
- handlerChan <- 1
- defer func() {
- handlerChan <- -1
- }()
-
- elem, ok := chain.Conns.Pop()
- if !ok {
- log("Underflow of connection stack, closing connection.")
- err := conn.Close()
- if err != nil {
- log("Error in close: %s.", err)
- }
- return errors.New("connection stack underflow")
- }
- extConn := elem.(*net.TCPConn)
- log("Connecting to ORPort using remote addr %s.", extConn.RemoteAddr())
- log("handleInternalConnection: now %d conns buffered.", chain.Conns.Length())
- or, err := pt.DialOr(&ptInfo, extConn.RemoteAddr().String(), chain.MethodName)
- if err != nil {
- log("Error connecting to ORPort: %s.", err)
- return err
- }
- err = copyLoop(or, conn)
- if err != nil {
- log("Error copying between int and ORPort: %s.", err)
- return err
- }
- return nil
-}
-
-func listenerLoop(chain *Chain) {
- extChan := make(chan *net.TCPConn)
- intChan := make(chan *net.TCPConn)
- go acceptLoop("external", chain.ExtLn, extChan)
- go acceptLoop("internal", chain.IntLn, intChan)
-
-loop:
- for {
- select {
- case conn, ok := <-extChan:
- if !ok {
- break loop
- }
- go handleExternalConnection(conn, chain)
- case conn, ok := <-intChan:
- if !ok {
- break loop
- }
- go handleInternalConnection(conn, chain)
- }
- }
-}
-
-func startChain(methodName string, bindaddr *net.TCPAddr, plugins []ServerTransportPlugin) (*Chain, error) {
- chain := &Chain{}
- var err error
-
- chain.MethodName = methodName
- chain.Conns = NewStack(connStackSize)
-
- // Start internal listener (the proxy chain connects back to this).
- chain.IntLn, err = net.ListenTCP("tcp", &net.TCPAddr{IP: net.ParseIP("127.0.0.1"), Port: 0})
- if err != nil {
- log("Error opening internal listener: %s.", err)
- chain.Shutdown()
- return nil, err
- }
- log("Internal listener on %s.", chain.IntLn.Addr())
-
- // Start subprocesses.
- chain.ProcsAddr, chain.Procs, err = startProcesses(chain.IntLn.Addr(), plugins)
- if err != nil {
- log("Error starting proxy chain: %s.", err)
- chain.Shutdown()
- return nil, err
- }
- log("Proxy chain on %s.", chain.ProcsAddr)
-
- // Start external Internet listener (listens on bindaddr and connects to
- // proxy chain).
- chain.ExtLn, err = net.ListenTCP("tcp", bindaddr)
- if err != nil {
- log("Error opening external listener: %s.", err)
- chain.Shutdown()
- return nil, err
- }
- log("External listener on %s.", chain.ExtLn.Addr())
-
- go listenerLoop(chain)
-
- return chain, nil
-}
-
-type Configuration struct {
- // Map from method names to command strings.
- Transports map[string][]string
- // Map from method names to ServerTransportOptions.
- Options map[string]pt.Args
- // Map from tor-friendly names like "obfs3_websocket" to systematic
- // names like "obfs3|websocket".
- Aliases map[string]string
-}
-
-func (conf *Configuration) MethodNames() []string {
- result := make([]string, 0)
- // We understand all the single transports
- for k, _ := range conf.Transports {
- result = append(result, k)
- }
- // and aliases.
- for k, _ := range conf.Aliases {
- result = append(result, k)
- }
- return result
-}
-
-// Parse a (possibly composed) method name into a slice of single method names.
-func (conf *Configuration) ParseMethodName(methodName string) []string {
- if name, ok := conf.Aliases[methodName]; ok {
- methodName = name
- }
- return strings.Split(methodName, "|")
-}
-
-func (conf *Configuration) PluginList(methodName string) ([]ServerTransportPlugin, error) {
- names := conf.ParseMethodName(methodName)
- stp := make([]ServerTransportPlugin, 0)
- for _, name := range names {
- command, ok := conf.Transports[name]
- if !ok {
- return nil, errors.New(fmt.Sprintf("no transport named %q", name))
- }
- options := conf.Options[name]
- stp = append(stp, ServerTransportPlugin{name, command, options})
- }
- return stp, nil
-}
-
-// Simulate loading a configuration file.
-func getConfiguration() (conf *Configuration) {
- conf = new(Configuration)
- conf.Transports = make(map[string][]string)
- conf.Aliases = make(map[string]string)
- conf.Options = make(map[string]pt.Args)
- conf.Transports["obfs3"] = []string{"obfsproxy", "managed"}
- conf.Transports["websocket"] = []string{"pt-websocket-server"}
- // conf.Options["obfs3"] = make(pt.Args)
- // conf.Options["obfs3"]["secret"] = []string{"foo"}
- conf.Aliases["obfs3_websocket"] = "obfs3|websocket"
- return conf
-}
-
-func main() {
- var logFilename string
- var port int
-
- flag.Usage = usage
- flag.StringVar(&logFilename, "log", "", "log file to write to")
- flag.IntVar(&port, "port", 0, "port to listen on if unspecified by Tor")
- flag.Parse()
-
- if logFilename != "" {
- f, err := os.OpenFile(logFilename, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0600)
- if err != nil {
- fmt.Fprintf(os.Stderr, "Can't open log file %q: %s.\n", logFilename, err.Error())
- os.Exit(1)
- }
- logFile = f
- }
-
- log("Starting.")
-
- var err error
- conf := getConfiguration()
- ptInfo, err = pt.ServerSetup(conf.MethodNames())
- if err != nil {
- log("Error in ServerSetup: %s", err)
- os.Exit(1)
- }
-
- chains := make([]*Chain, 0)
- for _, bindaddr := range ptInfo.Bindaddrs {
- // Override tor's requested port (which is 0 if this transport
- // has not been run before) with the one requested by the --port
- // option.
- if port != 0 {
- bindaddr.Addr.Port = port
- }
-
- plugins, err := conf.PluginList(bindaddr.MethodName)
- if err != nil {
- pt.SmethodError(bindaddr.MethodName, err.Error())
- continue
- }
-
- chain, err := startChain(bindaddr.MethodName, bindaddr.Addr, plugins)
- if err != nil {
- pt.SmethodError(bindaddr.MethodName, err.Error())
- continue
- }
- pt.Smethod(bindaddr.MethodName, chain.ExtLn.Addr())
- chains = append(chains, chain)
- }
- pt.SmethodsDone()
-
- var numHandlers int = 0
- var sig os.Signal
- sigChan := make(chan os.Signal, 1)
- signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM)
-
- sig = nil
- for sig == nil {
- select {
- case n := <-handlerChan:
- numHandlers += n
- case sig = <-sigChan:
- }
- }
- log("Got first signal %q with %d running handlers.", sig, numHandlers)
- for _, chain := range chains {
- chain.CloseListeners()
- chain.Procs.Signal(sig)
- }
-
- if sig == syscall.SIGTERM {
- log("Caught signal %q, exiting.", sig)
- return
- }
-
- sig = nil
- for sig == nil && numHandlers != 0 {
- select {
- case n := <-handlerChan:
- numHandlers += n
- log("%d remaining handlers.", numHandlers)
- case sig = <-sigChan:
- }
- }
- if sig != nil {
- log("Got second signal %q with %d running handlers.", sig, numHandlers)
- for _, chain := range chains {
- chain.Procs.Signal(sig)
- }
- }
-
- log("Exiting.")
-}
diff --git a/pt_test.go b/pt_test.go
deleted file mode 100644
index 35d3aab..0000000
--- a/pt_test.go
+++ /dev/null
@@ -1,39 +0,0 @@
-package main
-
-import "testing"
-
-import "git.torproject.org/pluggable-transports/goptlib.git"
-
-func TestEncodeServerTransportOptions(t *testing.T) {
- tests := [...]struct {
- methodName string
- opts pt.Args
- expected string
- }{
- {
- "foo",
- pt.Args{},
- "",
- },
- {
- "foo",
- pt.Args{
- "key": []string{"value1", "value2"},
- "something": []string{"value1", "value2"},
- },
- "foo:key=value1;foo:key=value2;foo:something=value1;foo:something=value2",
- },
- {
- "m:m",
- pt.Args{"k;k": []string{"v=v", "b\\b"}},
- "m\\:m:k\\;k=v\\=v;m\\:m:k\\;k=b\\\\b",
- },
- }
-
- for _, test := range tests {
- output := encodeServerTransportOptions(test.methodName, test.opts)
- if output != test.expected {
- t.Errorf("%q %q â?? %q (expected %q)", test.methodName, test.opts, output, test.expected)
- }
- }
-}
diff --git a/setup.py b/setup.py
deleted file mode 100644
index 66f2262..0000000
--- a/setup.py
+++ /dev/null
@@ -1,23 +0,0 @@
-from distutils.core import setup
-import py2exe
-
-# if py2exe complains "can't find P", try one of the following workarounds:
-#
-# a. py2exe doesn't support zipped eggs - http://www.py2exe.org/index.cgi/ExeWithEggs
-# You should give the --always-unzip option to easy_install, or you can use setup.py directly
-# $ python setup.py install --record install.log --single-version-externally-managed
-# Don't forget to remove the previous zipped egg.
-#
-# b. Add an empty __init__.py to the P/ top-level directory, if it's missing
-# - this is due to a bug (or misleading documentation) in python's imp.find_module()
-
-setup(
- console=["obfs-flash-client"],
- zipfile="py2exe-obfs-flash-client.zip",
- options={
- "py2exe": {
- "includes": ["pyptlib", "twisted", "txsocksx"],
- "packages": ["ometa", "terml", "zope.interface"],
- },
- },
-)
diff --git a/stack.go b/stack.go
deleted file mode 100644
index 16cddd6..0000000
--- a/stack.go
+++ /dev/null
@@ -1,57 +0,0 @@
-package main
-
-import "sync"
-
-// A fixed-size stack. If a push exceeds the capacity of the underlying slice,
-// the least recently added element is lost.
-type Stack struct {
- buf []interface{}
- base, head int
- m sync.Mutex
-}
-
-// Create a stack with the given capacity.
-func NewStack(capacity int) *Stack {
- return &Stack{buf: make([]interface{}, capacity+1)}
-}
-
-func (s *Stack) clamp(x int) int {
- x = x % len(s.buf)
- if x < 0 {
- x += len(s.buf)
- }
- return x
-}
-
-func (s *Stack) Length() int {
- s.m.Lock()
- defer s.m.Unlock()
- return s.clamp(s.head - s.base)
-}
-
-// If this push causes the stack to overflow, the first return value is the
-// discarded element and the second return value is false. Otherwise the second
-// return value is true.
-func (s *Stack) Push(x interface{}) (interface{}, bool) {
- s.m.Lock()
- defer s.m.Unlock()
- s.buf[s.head] = x
- s.head = s.clamp(s.head + 1)
- if s.head == s.base {
- s.base = s.clamp(s.base + 1)
- return s.buf[s.head], false
- }
- return nil, true
-}
-
-// The second return value is false if the stack was empty, and true otherwise.
-// The first return value is defined only when the second is true.
-func (s *Stack) Pop() (interface{}, bool) {
- s.m.Lock()
- defer s.m.Unlock()
- if s.head == s.base {
- return nil, false
- }
- s.head = s.clamp(s.head - 1)
- return s.buf[s.head], true
-}
diff --git a/stack_test.go b/stack_test.go
deleted file mode 100644
index 3188a2d..0000000
--- a/stack_test.go
+++ /dev/null
@@ -1,120 +0,0 @@
-package main
-
-import "testing"
-
-// Test operations on a zero-capacity stack.
-func TestZeroCapacity(t *testing.T) {
- var ok bool
-
- s := NewStack(0)
- if s.Length() != 0 {
- t.Fatal("initial length is not 0")
- }
- _, ok = s.Push("a")
- if ok || s.Length() != 0 {
- t.Fatal()
- }
- _, ok = s.Pop()
- if ok || s.Length() != 0 {
- t.Fatal()
- }
-}
-
-func TestPushPop(t *testing.T) {
- var x interface{}
- var ok bool
-
- s := NewStack(3)
-
- // Push elems.
- if s.Length() != 0 {
- t.Fatal("initial length is not 0")
- }
- _, ok = s.Push("a")
- if !ok || s.Length() != 1 {
- t.Fatal()
- }
- _, ok = s.Push("b")
- if !ok || s.Length() != 2 {
- t.Fatal()
- }
-
- // Pop to empty.
- x, ok = s.Pop()
- if !ok || x != "b" || s.Length() != 1 {
- t.Fatal()
- }
- x, ok = s.Pop()
- if !ok || x != "a" || s.Length() != 0 {
- t.Fatal()
- }
- // Pop one past empty.
- x, ok = s.Pop()
- if ok {
- t.Fatal()
- }
-
- // Push to capacity.
- s.Push("c")
- s.Push("d")
- _, ok = s.Push("e")
- if !ok || s.Length() != 3 {
- t.Fatal("push to capacity is not at capacity")
- }
- // Push one past capacity.
- x, ok = s.Push("f")
- if ok || s.Length() != 3 {
- t.Fatal()
- }
- if x != "c" {
- t.Fatal("mismatch in overwritten element")
- }
-
- // Pop to empty.
- x, ok = s.Pop()
- if !ok || x != "f" || s.Length() != 2 {
- t.Fatal()
- }
- x, ok = s.Pop()
- if !ok || x != "e" || s.Length() != 1 {
- t.Fatal()
- }
- x, ok = s.Pop()
- if !ok || x != "d" || s.Length() != 0 {
- t.Fatal()
- }
- // Pop one past empty.
- x, ok = s.Pop()
- if ok {
- t.Fatal()
- }
-}
-
-// Test underflow of an initially empty stack.
-func TestUnderflowEmpty(t *testing.T) {
- var ok bool
-
- s := NewStack(3)
- _, ok = s.Pop()
- if ok {
- t.Fatal()
- }
-}
-
-// Test underflow of a stack that had been full.
-func TestUnderflowFull(t *testing.T) {
- var ok bool
-
- s := NewStack(3)
- s.Push("a")
- s.Push("b")
- s.Push("c")
- s.Push("d")
- s.Pop()
- s.Pop()
- s.Pop()
- _, ok = s.Pop()
- if ok {
- t.Fatal()
- }
-}
diff --git a/torrc b/torrc
deleted file mode 100644
index 32c558e..0000000
--- a/torrc
+++ /dev/null
@@ -1,5 +0,0 @@
-UseBridges 1
-Bridge obfs3_flashproxy 127.0.0.1:9000
-LearnCircuitBuildTimeout 0
-CircuitBuildTimeout 300
-ClientTransportPlugin obfs3_flashproxy exec ./obfs-flash-client
diff --git a/torrc-server b/torrc-server
deleted file mode 100644
index 55b4a31..0000000
--- a/torrc-server
+++ /dev/null
@@ -1,5 +0,0 @@
-ORPort 9999
-ExtORPort 5555
-BridgeRelay 1
-SocksPort 0
-ServerTransportPlugin obfs3_websocket exec ./obfs-flash-server
_______________________________________________
tor-commits mailing list
tor-commits@xxxxxxxxxxxxxxxxxxxx
https://lists.torproject.org/cgi-bin/mailman/listinfo/tor-commits