commit d5c1258762ff34b369f9fb6cef651122c3436cdc Author: David Fifield david@bamsoftware.com Date: Tue May 24 22:55:34 2011 -0700
Flatten the com/rtmfp hierarchy.
So that parallel files in the master branch are in the same place in this branch. --- Makefile | 2 +- README | 88 ++++--- Utils.as | 25 ++ com/rtmfpcat/Makefile | 11 - com/rtmfpcat/README | 118 --------- com/rtmfpcat/Utils.as | 25 -- com/rtmfpcat/connector.py | 326 ------------------------- com/rtmfpcat/facilitator.py | 146 ----------- com/rtmfpcat/rtmfp/RTMFPSocket.as | 231 ----------------- com/rtmfpcat/rtmfp/RTMFPSocketClient.as | 57 ----- com/rtmfpcat/rtmfp/events/RTMFPSocketEvent.as | 25 -- com/rtmfpcat/rtmfpcat.as | 208 ---------------- connector.py | 4 +- facilitator.py | 11 +- rtmfp/RTMFPSocket.as | 231 +++++++++++++++++ rtmfp/RTMFPSocketClient.as | 57 +++++ rtmfp/events/RTMFPSocketEvent.as | 25 ++ rtmfpcat.as | 208 ++++++++++++++++ swfcat.as | 82 +----- 19 files changed, 622 insertions(+), 1258 deletions(-)
diff --git a/Makefile b/Makefile index 5390327..3595385 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,6 @@ MXMLC ?= mxmlc
-TARGETS = swfcat.swf com/rtmfpcat/rtmfpcat.swf +TARGETS = rtmfpcat.swf
all: $(TARGETS)
diff --git a/README b/README index 755ba01..17fadf5 100644 --- a/README +++ b/README @@ -5,12 +5,24 @@ Adobe Flash proxy running on another computer. The Flash proxy can be run just by opening a web page in a computer that has Flash Player installed.
+This RTMFP version leverages the NAT-punching capabilities of Adobe's +Cirrus server, making it possible for clients behind a NAT to still +get access to Tor. The big operational difference between this version +and the swfcat version is that now the client must maintain a rtmfpcat +running in client mode open in his/her browser. The client's rtmfpcat +talks to the rtmfpcat proxy running on another computer (location uncertain) +via the UDP-based RTMFP to funnel data to a Tor relay/bridge. + There are five main parts. Our terminology for each part is in quotes. 1. The Tor "client," running on someone's localhost. -2. A "connector," which waits for connections from the Flash proxy and +2. A "connector," which waits for connections from the client's rtmfpcat and the Tor client, and joins them together. 3. A Flash "proxy," running in someone's web browser. This piece is - called swfcat because it is like a netcat implemented in Flash. + called rtmfpcat (totally ripped off of swfcat) because it is like a + netcat implemented in Flash. The rtmfpcat exists on both the local + and remote ends since it's the easiest way to take advantage of RTMFP. + We could get rid of the client-side rtmfpcat by making the connector + speak RTMFP, but that's too much work for now. 4. A "facilitator," a pseudo-HTTP server that keeps a list of clients that want a connection, and hands them out to proxies. 5. A Tor "relay," which is just a normal Tor relay except that its host @@ -18,9 +30,7 @@ There are five main parts. Our terminology for each part is in quotes.
== Quick start
-A demonstration page that doesn't require building anything is here: - -http://www.stanford.edu/~dcf/tor/ +Will put up a demo page soon.
=== Building
@@ -29,34 +39,40 @@ Download the (free software) Flex SDK. Put its bin directory in your PATH. The important executable is mxmlc. To build, run $ make -Copy the resulting swfcat.swf file to a web server. +Copy the resulting rtmfpcat.swf file to a web server.
On the computer that will be the facilitator, run - sudo ./crossdomaind.py - ./facilitator.py + sudo ./crossdomaind.py + ./facilitator.py crossdomaind.py needs to be run on any server that will accept connections from a Flash proxy. It serves a chunk of data on port 843. -The facilitator runs on port 9002 by default. +The facilitator runs on port 9002 by default. Note that this is a different +facilitator script than the swfcat one, since this facilitator needs to +deal with Cirrus client IDs instead of ip:port tuples.
The client needs to be running a version of Tor that supports the Socks4Proxy configuration. This means version 0.2.2.1-alpha or later--not the current stable release.
On the client, run - ./connector.py -f <FACILITATOR_IP> -Replace <FACILITATOR_IP> with the IP address of the facilitator. (If you -are running the facilitator locally, be sure to use an external IP -address, not 127.0.0.1.) The connector informs the facilitator that it -wants a connection, then listens on 0.0.0.0:9000 and 127.0.0.1:9001. The -Flash proxy will connect on port 9000 and the local Tor will connect on -9001. - -In a browser somewhere, open swfcat.swf and pass a parameter telling it -the facilitator to use, for example - http://www.example.com/swfcat.swf?facilitator=<FACILITATOR_IP>:9002&debug=1 -The facilitator will return the client address that was registered by -connector.py, and the Flash proxy will open a connection to a hardcoded -Tor relay and the given client. + ./connector.py +This is a modified form of the swfcat connector.py that has different +defaults, equivalent to passing 127.0.0.1:9001 for [LOCAL][:PORT] and +127.0.0.1:3333 [REMOTE][:PORT] to the swfcat connector. + +Also on the client, open up the browser to rtmfpcat.swf. Passing no +arguments should give you good defaults (expects the facilitator running +on Nate's server). rtmfpcat will connect to the Cirrus server to +obtain a client ID which it then registers with the facilitator. + +In a browser somewhere, open rtmfpcat.swf and pass the "?proxy=true" query +string, telling it to operate in proxy mode. + http://www.example.com/rtmfpcat.swf?proxy=true +This rtmfpcat will also connect to the Cirrus server to obtain a client ID, +and then it will ping the facilitator to check if there are any registered +client IDs. If there is one, it will open a RTMFP connection (coordinated +by the Cirrus server) to the client and an additional connection to a +hardcoded Tor relay (David's bridge, nicknamed eRYaZuvY02FpExln).
Back on the client, start Tor with the following configuration: UseBridges 1 @@ -68,8 +84,8 @@ If you see messages like [notice] no known bridge descriptors running yet; stalling try deleting the files in ~/.tor or /var/lib/tor.
-You will be able to see byte counts flowing in the browser displaying -swfcat.swf, and eventually be able to build a circuit. +You will be able to see byte counts flowing in both browsers displaying +rtmfpcat.swf (client and proxy), and eventually be able to build a circuit.
== Rationale
@@ -81,22 +97,26 @@ changing pool of addresses.
== Design notes
-The Tor relay address is hardcoded in swfcat.as. It could be any relay, +The Tor relay address is hardcoded in rtmfpcat.as. It could be any relay, with the caveat that the server also has to serve a crossdomain policy.
-The Tor client needs to be able to listen for an incoming connection, -which generally means not being behind NAT. - -Clients register with the facilitator by sending an HTTP-like message: +Client rtmfpcats register with the facilitator by sending an HTTP-like message: POST / HTTP/1.0\r\n \r\n - client=:9000 + client=<CIRRUS-CLIENT-ID> + +The <CIRRUS-CLIENT-ID> is returned by Adobe's developer Cirrus server +as soon as the rtfmpcat can connect to it. Each rtmfpcat needs to connect +to a server like this to get one of these client IDs, since the Cirrus +server uses these to coordinate RTMFP connections (including NAT punching). +The need to communicate with a Cirrus server in addition to a facilitator is +one of the major weaknesses of this design.
-The Flash proxy also gets a client address using something like HTTP: +The proxy rtmfpcat gets a client id using something like HTTP: GET / HTTP/1.0\r\n \r\n -The server sends back an address specification (no HTTP header): - 192.168.0.102:8888 +The server sends back an id specification (no HTTP header): + 51ae8ed56c3705e4ad3755cdd3328c27720984778bfff71d9ec9f2647331d39b
== ActionScript programming
diff --git a/Utils.as b/Utils.as new file mode 100644 index 0000000..48f9a62 --- /dev/null +++ b/Utils.as @@ -0,0 +1,25 @@ +package +{ + + public class Utils { + + /* Parse an address in the form "host:port". Returns an Object with + keys "host" (String) and "port" (int). Returns null on error. */ + public static function parseAddrSpec(spec:String):Object + { + var parts:Array; + var addr:Object; + + parts = spec.split(":", 2); + if (parts.length != 2 || !parseInt(parts[1])) + return null; + addr = {} + addr.host = parts[0]; + addr.port = parseInt(parts[1]); + + return addr; + } + + } + +} \ No newline at end of file diff --git a/com/rtmfpcat/Makefile b/com/rtmfpcat/Makefile deleted file mode 100644 index fbcfc20..0000000 --- a/com/rtmfpcat/Makefile +++ /dev/null @@ -1,11 +0,0 @@ -MXMLC ?= mxmlc - -TARGETS = rtmfpcat.swf - -all: $(TARGETS) - -%.swf: %.as - $(MXMLC) -output $@ $^ - -clean: - rm -f $(TARGETS) diff --git a/com/rtmfpcat/README b/com/rtmfpcat/README deleted file mode 100644 index 0432a63..0000000 --- a/com/rtmfpcat/README +++ /dev/null @@ -1,118 +0,0 @@ -== Introduction - -This is a set of tools that make it possible to connect Tor through an -Adobe Flash proxy running on another computer. The Flash proxy can be -run just by opening a web page in a computer that has Flash Player -installed. - -This RTMFP version leverages the NAT-punching capabilities of Adobe's -Cirrus server, making it possible for clients behind a NAT to still -get access to Tor. The big operational difference between this version -and the swfcat version is that now the client must maintain a rtmfpcat -running in client mode open in his/her browser. The client's rtmfpcat -talks to the rtmfpcat proxy running on another computer (location uncertain) -via the UDP-based RTMFP to funnel data to a Tor relay/bridge. - -There are five main parts. Our terminology for each part is in quotes. -1. The Tor "client," running on someone's localhost. -2. A "connector," which waits for connections from the client's rtmfpcat and - the Tor client, and joins them together. -3. A Flash "proxy," running in someone's web browser. This piece is - called rtmfpcat (totally ripped off of swfcat) because it is like a - netcat implemented in Flash. The rtmfpcat exists on both the local - and remote ends since it's the easiest way to take advantage of RTMFP. - We could get rid of the client-side rtmfpcat by making the connector - speak RTMFP, but that's too much work for now. -4. A "facilitator," a pseudo-HTTP server that keeps a list of clients - that want a connection, and hands them out to proxies. -5. A Tor "relay," which is just a normal Tor relay except that its host - must also serve a Flash crossdomain policy. - -== Quick start - -Will put up a demo page soon. - -=== Building - -Download the (free software) Flex SDK. - http://opensource.adobe.com/wiki/display/flexsdk/Flex+SDK -Put its bin directory in your PATH. The important executable is mxmlc. -To build, run - $ make -Copy the resulting rtmfpcat.swf file to a web server. - -On the computer that will be the facilitator, run - sudo ./crossdomaind.py - ./facilitator.py -crossdomaind.py needs to be run on any server that will accept -connections from a Flash proxy. It serves a chunk of data on port 843. -The facilitator runs on port 9002 by default. Note that this is a different -facilitator script than the swfcat one, since this facilitator needs to -deal with Cirrus client IDs instead of ip:port tuples. - -On the client, run - ./connector.py -This is a modified form of the swfcat connector.py that has different -defaults, equivalent to passing 127.0.0.1:9001 for [LOCAL][:PORT] and -127.0.0.1:3333 [REMOTE][:PORT] to the swfcat connector. - -Also on the client, open up the browser to rtmfpcat.swf. Passing no -arguments should give you good defaults (expects the facilitator running -on Nate's server). rtmfpcat will connect to the Cirrus server to -obtain a client ID which it then registers with the facilitator. - -In a browser somewhere, open rtmfpcat.swf and pass the "?proxy=true" query -string, telling it to operate in proxy mode. - http://www.example.com/rtmfpcat.swf?proxy=true -This rtmfpcat will also connect to the Cirrus server to obtain a client ID, -and then it will ping the facilitator to check if there are any registered -client IDs. If there is one, it will open a RTMFP connection (coordinated -by the Cirrus server) to the client and an additional connection to a -hardcoded Tor relay (David's bridge, nicknamed eRYaZuvY02FpExln). - -Back on the client, start Tor with the following configuration: - UseBridges 1 - Bridge 127.0.0.1:9001 - Socks4Proxy 127.0.0.1:9001 - -You will be able to see byte counts flowing in both browsers displaying -rtmfpcat.swf (client and proxy), and eventually be able to build a circuit. - -== Rationale - -The purpose of this project is to create many, generally ephemeral -bridge IP addresses, with the goal of outpacing a censor's ability to -block them. Rather than increasing the number of bridges at static -addresses, we aim to make existing bridges reachable by a larger and -changing pool of addresses. - -== Design notes - -The Tor relay address is hardcoded in rtmfpcat.as. It could be any relay, -with the caveat that the server also has to serve a crossdomain policy. - -Client rtmfpcats register with the facilitator by sending an HTTP-like message: - POST / HTTP/1.0\r\n - \r\n - client=<CIRRUS-CLIENT-ID> - -The <CIRRUS-CLIENT-ID> is returned by Adobe's developer Cirrus server -as soon as the rtfmpcat can connect to it. Each rtmfpcat needs to connect -to a server like this to get one of these client IDs, since the Cirrus -server uses these to coordinate RTMFP connections (including NAT punching). -The need to communicate with a Cirrus server in addition to a facilitator is -one of the major weaknesses of this design. - -The proxy rtmfpcat gets a client id using something like HTTP: - GET / HTTP/1.0\r\n - \r\n -The server sends back an id specification (no HTTP header): - 51ae8ed56c3705e4ad3755cdd3328c27720984778bfff71d9ec9f2647331d39b - -== ActionScript programming - -A good tutorial on ActionScript programming with the Flex tools, with -sample code: - -http://www.senocular.com/flash/tutorials/as3withmxmlc/ -http://www.senocular.com/flash/tutorials/as3withmxmlc/AS3Flex2b3StarterFiles... diff --git a/com/rtmfpcat/Utils.as b/com/rtmfpcat/Utils.as deleted file mode 100644 index 48f9a62..0000000 --- a/com/rtmfpcat/Utils.as +++ /dev/null @@ -1,25 +0,0 @@ -package -{ - - public class Utils { - - /* Parse an address in the form "host:port". Returns an Object with - keys "host" (String) and "port" (int). Returns null on error. */ - public static function parseAddrSpec(spec:String):Object - { - var parts:Array; - var addr:Object; - - parts = spec.split(":", 2); - if (parts.length != 2 || !parseInt(parts[1])) - return null; - addr = {} - addr.host = parts[0]; - addr.port = parseInt(parts[1]); - - return addr; - } - - } - -} \ No newline at end of file diff --git a/com/rtmfpcat/connector.py b/com/rtmfpcat/connector.py deleted file mode 100755 index 63bbc5c..0000000 --- a/com/rtmfpcat/connector.py +++ /dev/null @@ -1,326 +0,0 @@ -#!/usr/bin/env python - -import getopt -import httplib -import re -import select -import socket -import struct -import sys -import time -import urllib -import xml.sax.saxutils - -DEFAULT_REMOTE_ADDRESS = "127.0.0.1" -DEFAULT_REMOTE_PORT = 3333 -DEFAULT_LOCAL_ADDRESS = "127.0.0.1" -DEFAULT_LOCAL_PORT = 9001 -DEFAULT_FACILITATOR_PORT = 9002 - -def usage(f = sys.stdout): - print >> f, """\ -Usage: %(progname)s -f FACILITATOR[:PORT] [LOCAL][:PORT] [REMOTE][:PORT] -Wait for connections on a local and a remote port. When any pair of connections -exists, data is ferried between them until one side is closed. By default -LOCAL is "%(local)s" and REMOTE is "%(remote)s". - -The local connection acts as a SOCKS4a proxy, but the host and port in the SOCKS -request are ignored and the local connection is always joined to a remote -connection. - -If the -f option is given, then the REMOTE address is advertised to the given -FACILITATOR. - -f, --facilitator=HOST[:PORT] advertise willingness to receive connections to - HOST:PORT. By default PORT is %(fac_port)d. - -h, --help show this help.\ -""" % { - "progname": sys.argv[0], - "local": format_addr((DEFAULT_LOCAL_ADDRESS, DEFAULT_LOCAL_PORT)), - "remote": format_addr((DEFAULT_REMOTE_ADDRESS, DEFAULT_REMOTE_PORT)), - "fac_port": DEFAULT_FACILITATOR_PORT, -} - -def parse_addr_spec(spec, defhost = None, defport = None): - host = None - port = None - m = None - # IPv6 syntax. - if not m: - m = re.match(r'^[(.+)]:(\d+)$', spec) - if m: - host, port = m.groups() - af = socket.AF_INET6 - if not m: - m = re.match(r'^[(.+)]:?$', spec) - if m: - host, = m.groups() - af = socket.AF_INET6 - # IPv4 syntax. - if not m: - m = re.match(r'^(.+):(\d+)$', spec) - if m: - host, port = m.groups() - af = socket.AF_INET - if not m: - m = re.match(r'^:?(\d+)$', spec) - if m: - port, = m.groups() - af = 0 - if not m: - host = spec - af = 0 - host = host or defhost - port = port or defport - if not (host and port): - raise ValueError("Bad address specification "%s"" % spec) - return host, int(port) - -def format_addr(addr): - host, port = addr - if not host: - return u":%d" % port - # Numeric IPv6 address? - try: - addrs = socket.getaddrinfo(host, port, 0, socket.SOCK_STREAM, socket.IPPROTO_TCP, socket.AI_NUMERICHOST) - af = addrs[0][0] - except socket.gaierror, e: - af = 0 - if af == socket.AF_INET6: - return u"[%s]:%d" % (host, port) - else: - return u"%s:%d" % (host, port) - -facilitator_addr = None - -opts, args = getopt.gnu_getopt(sys.argv[1:], "f:h", ["facilitator", "help"]) -for o, a in opts: - if o == "-f" or o == "--facilitator": - facilitator_addr = parse_addr_spec(a, None, DEFAULT_FACILITATOR_PORT) - elif o == "-h" or o == "--help": - usage() - sys.exit() - -if len(args) == 0: - local_addr = (DEFAULT_LOCAL_ADDRESS, DEFAULT_LOCAL_PORT) - remote_addr = (DEFAULT_REMOTE_ADDRESS, DEFAULT_REMOTE_PORT) -elif len(args) == 1: - local_addr = parse_addr_spec(args[0], DEFAULT_LOCAL_ADDRESS, DEFAULT_LOCAL_PORT) - remote_addr = (DEFAULT_REMOTE_ADDRESS, DEFAULT_REMOTE_PORT) -elif len(args) == 2: - local_addr = parse_addr_spec(args[0], DEFAULT_LOCAL_ADDRESS, DEFAULT_LOCAL_PORT) - remote_addr = parse_addr_spec(args[1], DEFAULT_REMOTE_ADDRESS, DEFAULT_REMOTE_PORT) -else: - usage(sys.stderr) - sys.exit(1) - - -class RemotePending(object): - """A class encapsulating a socket and a time of connection.""" - def __init__(self, fd): - self.fd = fd - self.birthday = time.time() - - def fileno(self): - return self.fd.fileno() - - def is_expired(self, timeout): - return time.time() - self.birthday > timeout - -def listen_socket(addr): - """Return a nonblocking socket listening on the given address.""" - addrinfo = socket.getaddrinfo(addr[0], addr[1], 0, socket.SOCK_STREAM, socket.IPPROTO_TCP)[0] - s = socket.socket(addrinfo[0], addrinfo[1], addrinfo[2]) - s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) - s.bind(addr) - s.listen(10) - s.setblocking(0) - return s - -# How long to wait for a crossdomain policy request before deciding that this is -# a normal socket. -CROSSDOMAIN_TIMEOUT = 2.0 - -# Local socket, accepting SOCKS requests from localhost -local_s = listen_socket(local_addr) -# Remote socket, accepting both crossdomain policy requests and remote proxy -# connections. -remote_s = listen_socket(remote_addr) - -# Sockets that may be crossdomain policy requests or may be normal remote -# connections. -crossdomain_pending = [] -# Remote connection sockets. -remotes = [] -# New local sockets waiting to finish their SOCKS negotiation. -socks_pending = [] -# Local Tor sockets, after SOCKS negotiation. -locals = [] - -# Bidirectional mapping between local sockets and remote sockets. -local_for = {} -remote_for = {} - - -def handle_policy_request(fd): - print "handle_policy_request" - addr = fd.getpeername() - data = fd.recv(100) - if data == "<policy-file-request/>\0": - print "Sending crossdomain policy to %s." % format_addr(addr) - fd.sendall(""" -<cross-domain-policy> -<allow-access-from domain="*" to-ports="%s"/> -</cross-domain-policy> -\0""" % xml.sax.saxutils.escape(str(remote_addr[1]))) - elif data == "": - print "No data from %s." % format_addr(addr) - else: - print "Unexpected data from %s." % format_addr(addr) - -def grab_string(s, pos): - """Grab a NUL-terminated string from the given string, starting at the given - offset. Return (pos, str) tuple, or (pos, None) on error.""" - i = pos - while i < len(s): - if s[i] == '\0': - return (i + 1, s[pos:i]) - i += 1 - return pos, None - -def parse_socks_request(data): - try: - ver, cmd, dport, o1, o2, o3, o4 = struct.unpack(">BBHBBBB", data[:8]) - except struct.error: - print "Couldn't unpack SOCKS4 header." - return None - if ver != 4: - print "SOCKS header has wrong version (%d)." % ver - return None - if cmd != 1: - print "SOCKS header had wrong command (%d)." % cmd - return None - pos, userid = grab_string(data, 8) - if userid is None: - print "Couldn't read userid from SOCKS header." - return None - if o1 == 0 and o2 == 0 and o3 == 0 and o4 != 0: - pos, dest = grab_string(data, pos) - if dest is None: - print "Couldn't read destination from SOCKS4a header." - return None - else: - dest = "%d.%d.%d.%d" % (o1, o2, o3, o4) - return dest, dport - -def handle_socks_request(fd): - print "handle_socks_request" - addr = fd.getpeername() - data = fd.recv(100) - dest_addr = parse_socks_request(data) - if dest_addr is None: - # Error reply. - fd.sendall(struct.pack(">BBHBBBB", 0, 91, 0, 0, 0, 0, 0)) - return False - print "Got SOCKS request for %s." % format_addr(dest_addr) - fd.sendall(struct.pack(">BBHBBBB", 0, 90, dest_addr[1], 127, 0, 0, 1)) - # Note we throw away the requested address and port. - return True - -def handle_remote_connection(fd): - print "handle_remote_connection" - match_proxies() - -def handle_local_connection(fd): - print "handle_local_connection" - if facilitator_addr: - register(facilitator_addr, remote_addr[1]) - match_proxies() - -def register(addr, port): - spec = format_addr((None, port)) - print "Registering "%s" with %s." % (spec, format_addr(addr)) - http = httplib.HTTPConnection(*addr) - http.request("POST", "/", urllib.urlencode({"client": spec})) - http.close() - -def match_proxies(): - while locals and remotes: - remote = remotes.pop(0) - local = locals.pop(0) - remote_addr, remote_port = remote.getpeername() - local_addr, local_port = local.getpeername() - print "Linking %s and %s." % (format_addr(local.getpeername()), format_addr(remote.getpeername())) - remote_for[local] = remote - local_for[remote] = local - -if facilitator_addr: - register(facilitator_addr, remote_addr[1]) - -while True: - rset = [remote_s, local_s] + crossdomain_pending + socks_pending + remote_for.keys() + local_for.keys() + remotes - rset, _, _ = select.select(rset, [], [], CROSSDOMAIN_TIMEOUT) - for fd in rset: - if fd == remote_s: - remote_c, addr = fd.accept() - print "Remote connection from %s." % format_addr(addr) - crossdomain_pending.append(RemotePending(remote_c)) - elif fd == local_s: - local_c, addr = fd.accept() - print "Local connection from %s." % format_addr(addr) - socks_pending.append(local_c) - if facilitator_addr: - register(facilitator_addr, remote_addr[1]) - elif fd in crossdomain_pending: - print "Data from crossdomain-pending %s." % format_addr(addr) - handle_policy_request(fd.fd) - fd.fd.close() - crossdomain_pending.remove(fd) - elif fd in socks_pending: - print "SOCKS request from %s." % format_addr(addr) - if handle_socks_request(fd): - locals.append(fd) - handle_local_connection(fd) - else: - fd.close() - socks_pending.remove(fd) - elif fd in local_for: - local = local_for[fd] - data = fd.recv(1024) - if not data: - print "EOF from remote %s." % format_addr(fd.getpeername()) - fd.close() - local.close() - del local_for[fd] - del remote_for[local] - if facilitator_addr: - register(facilitator_addr, remote_addr[1]) - else: - local.sendall(data) - elif fd in remote_for: - remote = remote_for[fd] - data = fd.recv(1024) - if not data: - print "EOF from local %s." % format_addr(fd.getpeername()) - fd.close() - remote.close() - del remote_for[fd] - del local_for[remote] - else: - remote.sendall(data) - elif fd in remotes: - data = fd.recv(1024) - if not data: - print "EOF from unconnected remote %s." % format_addr(fd.getpeername()) - else: - print "Data from unconnected remote %s." % format_addr(fd.getpeername()) - fd.close() - remotes.remove(fd) - match_proxies() - while crossdomain_pending: - pending = crossdomain_pending[0] - if not pending.is_expired(CROSSDOMAIN_TIMEOUT): - break - print "Expired pending crossdomain from %s." % format_addr(pending.fd.getpeername()) - crossdomain_pending.pop(0) - remotes.append(pending.fd) - handle_remote_connection(pending.fd) diff --git a/com/rtmfpcat/facilitator.py b/com/rtmfpcat/facilitator.py deleted file mode 100755 index 8c6a44e..0000000 --- a/com/rtmfpcat/facilitator.py +++ /dev/null @@ -1,146 +0,0 @@ -#!/usr/bin/env python - -import BaseHTTPServer -import getopt -import cgi -import re -import sys -import socket -from collections import deque - -DEFAULT_ADDRESS = "0.0.0.0" -DEFAULT_PORT = 9002 - -def usage(f = sys.stdout): - print >> f, """\ -Usage: %(progname)s <OPTIONS> [HOST] [PORT] -Flash bridge facilitator: Register client addresses with HTTP POST requests -and serve them out again with HTTP GET. Listen on HOST and PORT, by default -%(addr)s %(port)d. - -h, --help show this help.\ -""" % { - "progname": sys.argv[0], - "addr": DEFAULT_ADDRESS, - "port": DEFAULT_PORT, -} - -REGS = deque() - -class Reg(object): - def __init__(self, id): - self.id = id - - def __unicode__(self): - return u"%s" % (self.id) - - def __str__(self): - return unicode(self).encode("UTF-8") - - def __cmp__(self, other): - return cmp((self.id), (other.id)) - - @staticmethod - def parse(spec, defhost = None, defport = None): - host = None - port = None - m = re.match(r'^[(.+)]:(\d*)$', spec) - if m: - host, port = m.groups() - af = socket.AF_INET6 - else: - m = re.match(r'^(.*):(\d*)$', spec) - if m: - host, port = m.groups() - if host: - af = socket.AF_INET - else: - # Has to be guessed from format of defhost. - af = 0 - host = host or defhost - port = port or defport - if not (host and port): - raise ValueError("Bad address specification "%s"" % spec) - - try: - addrs = socket.getaddrinfo(host, port, af, socket.SOCK_STREAM, socket.IPPROTO_TCP, socket.AI_NUMERICHOST) - except socket.gaierror, e: - raise ValueError("Bad host or port: "%s" "%s": %s" % (host, port, str(e))) - if not addrs: - raise ValueError("Bad host or port: "%s" "%s"" % (host, port)) - - af = addrs[0][0] - host, port = socket.getnameinfo(addrs[0][4], socket.NI_NUMERICHOST | socket.NI_NUMERICSERV) - return Reg(af, host, int(port)) - -def fetch_reg(): - """Get a client registration, or None if none is available.""" - if not REGS: - return None - return REGS.popleft() - -class Handler(BaseHTTPServer.BaseHTTPRequestHandler): - def do_GET(self): - print "From " + str(self.client_address) + " received: GET:", - reg = fetch_reg() - if reg: - print "Handing out " + str(reg) + ". Clients: " + str(len(REGS)) - self.request.send(str(reg)) - else: - print "Registration list is empty" - self.request.send("Registration list empty") - - def do_POST(self): - print "From " + str(self.client_address) + " received: POST:", - data = self.rfile.readline().strip() - print data + " :", - try: - vals = cgi.parse_qs(data, False, True) - except ValueError, e: - print "Syntax error in POST:", str(e) - return - - client_specs = vals.get("client") - if client_specs is None or len(client_specs) != 1: - print "In POST: need exactly one "client" param" - return - val = client_specs[0] - - try: - reg = Reg(val) - except ValueError, e: - print "Can't parse client "%s": %s" % (val, str(e)) - return - - if reg not in list(REGS): - REGS.append(reg) - print "Registration " + str(reg) + " added. Registrations: " + str(len(REGS)) - else: - print "Registration " + str(reg) + " already present. Registrations: " + str(len(REGS)) - -opts, args = getopt.gnu_getopt(sys.argv[1:], "h", ["help"]) -for o, a in opts: - if o == "-h" or o == "--help": - usage() - sys.exit() - -if len(args) == 0: - address = (DEFAULT_ADDRESS, DEFAULT_PORT) -elif len(args) == 1: - # Either HOST or PORT may be omitted; figure out which one. - if args[0].isdigit(): - address = (DEFAULT_ADDRESS, args[0]) - else: - address = (args[0], DEFAULT_PORT) -elif len(args) == 2: - address = (args[0], args[1]) -else: - usage(sys.stderr) - sys.exit(1) - -# Setup the server -server = BaseHTTPServer.HTTPServer(address, Handler) - -print "Starting Facilitator on " + str(address) + "..." - -# Run server... Single threaded serving of requests... -server.serve_forever() diff --git a/com/rtmfpcat/rtmfp/RTMFPSocket.as b/com/rtmfpcat/rtmfp/RTMFPSocket.as deleted file mode 100644 index bb00994..0000000 --- a/com/rtmfpcat/rtmfp/RTMFPSocket.as +++ /dev/null @@ -1,231 +0,0 @@ -/* RTMFPSocket abstraction - * Author: Nate Hardison, May 2011 - * - * This code is heavily based off of BelugaFile, an open-source - * Air file-transfer application written by Nicholas Bliyk. - * Website: http://www.belugafile.com/ - * Source: http://code.google.com/p/belugafile/ - * - */ - -package rtmfp -{ - import flash.events.Event; - import flash.events.EventDispatcher; - import flash.events.IOErrorEvent; - import flash.events.NetStatusEvent; - import flash.events.ProgressEvent; - import flash.events.SecurityErrorEvent; - import flash.net.NetConnection; - import flash.net.NetStream; - import flash.utils.ByteArray; - import flash.utils.clearInterval; - import flash.utils.setInterval; - import flash.utils.setTimeout; - - import rtmfp.RTMFPSocketClient; - import rtmfp.events.RTMFPSocketEvent; - - [Event(name="connectSuccess", type="com.jscat.rtmfp.events.RTMFPSocketEvent")] - [Event(name="connectFail", type="com.jscat.rtmfp.events.RTMFPSocketEvent")] - [Event(name="publishStart", type="com.jscat.rtmfp.events.RTMFPSocketEvent")] - [Event(name="peerConnected", type="com.jscat.rtmfp.events.RTMFPSocketEvent")] - [Event(name="peeringSuccess", type="com.jscat.rtmfp.events.RTMFPSocketEvent")] - [Event(name="peeringFail", type="com.jscat.rtmfp.events.RTMFPSocketEvent")] - [Event(name="peerDisconnected", type="com.jscat.rtmfp.events.RTMFPSocketEvent")] - public class RTMFPSocket extends EventDispatcher - { - /* The name of the "media" to pass between peers */ - private static const DATA:String = "data"; - private static const DEFAULT_CIRRUS_ADDRESS:String = "rtmfp://p2p.rtmfp.net"; - private static const DEFAULT_CIRRUS_KEY:String = RTMFP::CIRRUS_KEY; - private static const DEFAULT_CONNECT_TIMEOUT:uint = 4000; - - /* Connection to the Cirrus rendezvous service */ - private var connection:NetConnection; - - /* ID of the peer to connect to */ - private var peerID:String; - - /* Data streams to be established with peer */ - private var sendStream:NetStream; - private var recvStream:NetStream; - - /* Timeouts */ - private var connectionTimeout:int; - private var peerConnectTimeout:uint; - - public function RTMFPSocket(){} - - public function connect(addr:String = DEFAULT_CIRRUS_ADDRESS, key:String = DEFAULT_CIRRUS_KEY):void - { - connection = new NetConnection(); - connection.addEventListener(NetStatusEvent.NET_STATUS, onNetStatusEvent); - connection.addEventListener(IOErrorEvent.IO_ERROR, onIOErrorEvent); - connection.addEventListener(SecurityErrorEvent.SECURITY_ERROR, onSecurityErrorEvent); - connection.connect(addr + "/" + key); - connectionTimeout = setInterval(fail, DEFAULT_CONNECT_TIMEOUT); - } - - public function close():void - { - connection.close(); - } - - public function get id():String - { - if (connection != null && connection.connected) { - return connection.nearID; - } - - return null; - } - - public function get connected():Boolean - { - return (connection != null && connection.connected); - } - - public function readBytes(bytes:ByteArray):void - { - recvStream.client.bytes.readBytes(bytes); - } - - public function writeBytes(bytes:ByteArray):void - { - sendStream.send("dataAvailable", bytes); - } - - public function get peer():String - { - return this.peerID; - } - - public function set peer(peerID:String):void - { - if (peerID == null || peerID.length == 0) { - throw new Error("Peer ID is null/empty.") - } else if (peerID == connection.nearID) { - throw new Error("Peer ID cannot be the same as our ID."); - } else if (this.peerID == peerID) { - throw new Error("Already connected to peer " + peerID + "."); - } else if (this.recvStream != null) { - throw new Error("Cannot connect to a second peer."); - } - - this.peerID = peerID; - - recvStream = new NetStream(connection, peerID); - var client:RTMFPSocketClient = new RTMFPSocketClient(); - client.addEventListener(ProgressEvent.SOCKET_DATA, onDataAvailable, false, 0, true); - client.addEventListener(RTMFPSocketClient.PEER_CONNECT_ACKNOWLEDGED, onPeerConnectAcknowledged, false, 0, true); - recvStream.client = client; - recvStream.addEventListener(NetStatusEvent.NET_STATUS, onRecvStreamEvent); - recvStream.play(DATA); - setTimeout(onPeerConnectTimeout, peerConnectTimeout, recvStream); - } - - private function startPublishStream():void - { - sendStream = new NetStream(connection, NetStream.DIRECT_CONNECTIONS); - sendStream.addEventListener(NetStatusEvent.NET_STATUS, onSendStreamEvent); - var o:Object = new Object(); - o.onPeerConnect = onPeerConnect; - sendStream.client = o; - sendStream.publish(DATA); - } - - private function fail():void - { - clearInterval(connectionTimeout); - dispatchEvent(new RTMFPSocketEvent(RTMFPSocketEvent.CONNECT_FAIL)); - } - - private function onDataAvailable(event:ProgressEvent):void - { - dispatchEvent(event); - } - - private function onIOErrorEvent(event:IOErrorEvent):void - { - fail(); - } - - private function onNetStatusEvent(event:NetStatusEvent):void - { - switch (event.info.code) { - case "NetConnection.Connect.Success" : - clearInterval(connectionTimeout); - startPublishStream(); - dispatchEvent(new RTMFPSocketEvent(RTMFPSocketEvent.CONNECT_SUCCESS)); - break; - case "NetStream.Connect.Success" : - break; - case "NetStream.Publish.BadName" : - fail(); - break; - case "NetStream.Connect.Closed" : - // we've disconnected from the peer - // can reset to accept another - // clear the publish stream and re-publish another - dispatchEvent(new RTMFPSocketEvent(RTMFPSocketEvent.PEER_DISCONNECTED, recvStream)); - break; - } - } - - private function onPeerConnect(peer:NetStream):Boolean - { - // establish a bidirectional stream with the peer - if (peerID == null) { - this.peer = peer.farID; - } - - // disallow additional peers connecting to us - if (peer.farID != peerID) return false; - - peer.send("setPeerConnectAcknowledged"); - dispatchEvent(new RTMFPSocketEvent(RTMFPSocketEvent.PEER_CONNECTED, peer)); - - return true; - } - - private function onPeerConnectAcknowledged(event:Event):void - { - dispatchEvent(new RTMFPSocketEvent(RTMFPSocketEvent.PEERING_SUCCESS, recvStream)); - } - - private function onPeerConnectTimeout(peer:NetStream):void - { - if (!recvStream.client) return; - if (!RTMFPSocketClient(recvStream.client).peerConnectAcknowledged) { - dispatchEvent(new RTMFPSocketEvent(RTMFPSocketEvent.PEERING_FAIL, recvStream)); - } - } - - private function onSecurityErrorEvent(event:SecurityErrorEvent):void - { - fail(); - } - - private function onSendStreamEvent(event:NetStatusEvent):void - { - switch (event.info.code) { - case ("NetStream.Publish.Start") : - dispatchEvent(new RTMFPSocketEvent(RTMFPSocketEvent.PUBLISH_START)); - break; - case ("NetStream.Play.Reset") : - case ("NetStream.Play.Start") : - break; - } - } - private function onRecvStreamEvent(event:NetStatusEvent):void - { - switch (event.info.code) { - case ("NetStream.Publish.Start") : - case ("NetStream.Play.Reset") : - case ("NetStream.Play.Start") : - break; - } - } - } -} diff --git a/com/rtmfpcat/rtmfp/RTMFPSocketClient.as b/com/rtmfpcat/rtmfp/RTMFPSocketClient.as deleted file mode 100644 index d9fcffa..0000000 --- a/com/rtmfpcat/rtmfp/RTMFPSocketClient.as +++ /dev/null @@ -1,57 +0,0 @@ -package rtmfp -{ - import flash.events.Event; - import flash.events.EventDispatcher; - import flash.events.ProgressEvent; - import flash.utils.ByteArray; - - [Event(name="peerConnectAcknowledged", type="flash.events.Event")] - public dynamic class RTMFPSocketClient extends EventDispatcher { - public static const PEER_CONNECT_ACKNOWLEDGED:String = "peerConnectAcknowledged"; - - private var _bytes:ByteArray; - private var _peerID:String; - private var _peerConnectAcknowledged:Boolean; - - public function RTMFPSocketClient() - { - super(); - _bytes = new ByteArray(); - _peerID = null; - _peerConnectAcknowledged = false; - } - - public function get bytes():ByteArray - { - return _bytes; - } - - public function dataAvailable(bytes:ByteArray):void - { - this._bytes.clear(); - bytes.readBytes(this._bytes); - dispatchEvent(new ProgressEvent(ProgressEvent.SOCKET_DATA, false, false, this._bytes.bytesAvailable, this._bytes.length)); - } - - public function get peerConnectAcknowledged():Boolean - { - return _peerConnectAcknowledged; - } - - public function setPeerConnectAcknowledged():void - { - _peerConnectAcknowledged = true; - dispatchEvent(new Event(PEER_CONNECT_ACKNOWLEDGED)); - } - - public function get peerID():String - { - return _peerID; - } - - public function set peerID(id:String):void - { - _peerID = id; - } - } -} \ No newline at end of file diff --git a/com/rtmfpcat/rtmfp/events/RTMFPSocketEvent.as b/com/rtmfpcat/rtmfp/events/RTMFPSocketEvent.as deleted file mode 100644 index c5b4af1..0000000 --- a/com/rtmfpcat/rtmfp/events/RTMFPSocketEvent.as +++ /dev/null @@ -1,25 +0,0 @@ -package rtmfp.events -{ - import flash.events.Event; - import flash.net.NetStream; - - public class RTMFPSocketEvent extends Event - { - public static const CONNECT_SUCCESS:String = "connectSuccess"; - public static const CONNECT_FAIL:String = "connectFail"; - public static const PUBLISH_START:String = "publishStart"; - public static const PEER_CONNECTED:String = "peerConnected"; - public static const PEER_DISCONNECTED:String = "peerDisconnected"; - public static const PEERING_SUCCESS:String = "peeringSuccess"; - public static const PEERING_FAIL:String = "peeringFail"; - - public var stream:NetStream; - - public function RTMFPSocketEvent(type:String, streamVal:NetStream = null, bubbles:Boolean = false, cancelable:Boolean = false) - { - super(type, bubbles, cancelable); - stream = streamVal; - } - - } -} \ No newline at end of file diff --git a/com/rtmfpcat/rtmfpcat.as b/com/rtmfpcat/rtmfpcat.as deleted file mode 100644 index 328274a..0000000 --- a/com/rtmfpcat/rtmfpcat.as +++ /dev/null @@ -1,208 +0,0 @@ -package -{ - import flash.display.Sprite; - import flash.text.TextField; - import flash.net.Socket; - import flash.events.Event; - import flash.events.EventDispatcher; - import flash.events.IOErrorEvent; - import flash.events.NetStatusEvent; - import flash.events.ProgressEvent; - import flash.events.SecurityErrorEvent; - import flash.utils.ByteArray; - import flash.utils.setTimeout; - - import rtmfp.RTMFPSocket; - import rtmfp.events.RTMFPSocketEvent; - import Utils; - - public class rtmfpcat extends Sprite { - - /* Nate's facilitator -- also serving a crossdomain policy */ - private const DEFAULT_FAC_ADDR:Object = { - host: "128.12.179.80", - port: 9002 - }; - - private const DEFAULT_TOR_CLIENT_ADDR:Object = { - host: "127.0.0.1", - port: 3333 - }; - - /* David's relay (nickname 3VXRyxz67OeRoqHn) that also serves a - crossdomain policy. */ - private const DEFAULT_TOR_PROXY_ADDR:Object = { - host: "173.255.221.44", - port: 9001 - }; - - // Milliseconds. - private const FACILITATOR_POLL_INTERVAL:int = 10000; - - private var output_text:TextField; - - private var s_f:Socket; - private var s_r:RTMFPSocket; - private var s_t:Socket; - - private var fac_addr:Object; - private var tor_addr:Object; - - private var proxy_mode:Boolean; - - public function rtmfpcat() - { - output_text = new TextField(); - output_text.width = 400; - output_text.height = 300; - output_text.background = true; - output_text.backgroundColor = 0x001f0f; - output_text.textColor = 0x44CC44; - addChild(output_text); - - puts("Starting."); - - this.loaderInfo.addEventListener(Event.COMPLETE, onLoaderInfoComplete); - } - - private function puts(s:String):void - { - output_text.appendText(s + "\n"); - output_text.scrollV = output_text.maxScrollV; - } - - private function onLoaderInfoComplete(e:Event):void - { - var fac_spec:String; - var tor_spec:String; - - puts("Parameters loaded."); - - proxy_mode = (this.loaderInfo.parameters["proxy"] != null); - - fac_spec = this.loaderInfo.parameters["facilitator"]; - if (!fac_spec) { - puts("No "facilitator" specification provided...using default."); - fac_addr = DEFAULT_FAC_ADDR; - } else { - puts("Facilitator spec: "" + fac_spec + """); - fac_addr = Utils.parseAddrSpec(fac_spec); - } - - if (!fac_addr) { - puts("Error: Facilitator spec must be in the form "host:port"."); - return; - } - - tor_spec = this.loaderInfo.parameters["tor"]; - if (!tor_spec) { - puts("No Tor specification provided...using default."); - if (proxy_mode) tor_addr = DEFAULT_TOR_PROXY_ADDR; - else tor_addr = DEFAULT_TOR_CLIENT_ADDR; - } else { - puts("Tor spec: "" + tor_spec + """) - tor_addr = Utils.parseAddrSpec(tor_spec); - } - - if (!tor_addr) { - puts("Error: Tor spec must be in the form "host:port"."); - return; - } - - establishRTMFPConnection(); - } - - private function establishRTMFPConnection():void - { - s_r = new RTMFPSocket(); - s_r.addEventListener(RTMFPSocketEvent.CONNECT_SUCCESS, function (e:Event):void { - puts("Cirrus: connected with id " + s_r.id + "."); - establishFacilitatorConnection(); - }); - s_r.addEventListener(RTMFPSocketEvent.CONNECT_FAIL, function (e:Event):void { - puts("Error: failed to connect to Cirrus."); - }); - s_r.addEventListener(RTMFPSocketEvent.PUBLISH_START, function(e:RTMFPSocketEvent):void { - puts("Publishing started."); - }); - s_r.addEventListener(RTMFPSocketEvent.PEER_CONNECTED, function(e:RTMFPSocketEvent):void { - puts("Peer connected."); - }); - s_r.addEventListener(RTMFPSocketEvent.PEER_DISCONNECTED, function(e:RTMFPSocketEvent):void { - puts("Peer disconnected."); - }); - s_r.addEventListener(RTMFPSocketEvent.PEERING_SUCCESS, function(e:RTMFPSocketEvent):void { - puts("Peering success."); - establishTorConnection(); - }); - s_r.addEventListener(RTMFPSocketEvent.PEERING_FAIL, function(e:RTMFPSocketEvent):void { - puts("Peering fail."); - }); - s_r.addEventListener(ProgressEvent.SOCKET_DATA, function (e:ProgressEvent):void { - var bytes:ByteArray = new ByteArray(); - s_r.readBytes(bytes); - puts("RTMFP: read " + bytes.length + " bytes."); - s_t.writeBytes(bytes); - }); - - s_r.connect(); - } - - private function establishTorConnection():void - { - s_t = new Socket(); - s_t.addEventListener(Event.CONNECT, function (e:Event):void { - puts("Tor: connected to " + tor_addr.host + ":" + tor_addr.port + "."); - }); - s_t.addEventListener(Event.CLOSE, function (e:Event):void { - puts("Tor: closed connection."); - }); - s_t.addEventListener(IOErrorEvent.IO_ERROR, function (e:IOErrorEvent):void { - puts("Tor: I/O error: " + e.text + "."); - }); - s_t.addEventListener(ProgressEvent.SOCKET_DATA, function (e:ProgressEvent):void { - var bytes:ByteArray = new ByteArray(); - s_t.readBytes(bytes, 0, e.bytesLoaded); - puts("Tor: read " + bytes.length + " bytes."); - s_r.writeBytes(bytes); - }); - s_t.addEventListener(SecurityErrorEvent.SECURITY_ERROR, function (e:SecurityErrorEvent):void { - puts("Tor: security error: " + e.text + "."); - }); - - s_t.connect(tor_addr.host, tor_addr.port); - } - - private function establishFacilitatorConnection():void - { - s_f = new Socket(); - s_f.addEventListener(Event.CONNECT, function (e:Event):void { - puts("Facilitator: connected to " + fac_addr.host + ":" + fac_addr.port + "."); - if (proxy_mode) s_f.writeUTFBytes("GET / HTTP/1.0\r\n\r\n"); - else s_f.writeUTFBytes("POST / HTTP/1.0\r\n\r\nclient=" + s_r.id + "\r\n"); - }); - s_f.addEventListener(Event.CLOSE, function (e:Event):void { - puts("Facilitator: connection closed."); - if (proxy_mode) { - setTimeout(establishFacilitatorConnection, FACILITATOR_POLL_INTERVAL); - } - }); - s_f.addEventListener(IOErrorEvent.IO_ERROR, function (e:IOErrorEvent):void { - puts("Facilitator: I/O error: " + e.text + "."); - }); - s_f.addEventListener(ProgressEvent.SOCKET_DATA, function (e:ProgressEvent):void { - var clientID:String = s_f.readMultiByte(e.bytesLoaded, "utf-8"); - puts("Facilitator: got "" + clientID + """); - if (clientID != "Registration list empty") { - puts("Connecting to " + clientID + "."); - s_r.peer = clientID; - } - }); - s_f.addEventListener(SecurityErrorEvent.SECURITY_ERROR, function (e:SecurityErrorEvent):void { - puts("Facilitator: security error: " + e.text + "."); - }); - - s_f.connect(fac_addr.host, fac_addr.port); - } - } -} \ No newline at end of file diff --git a/connector.py b/connector.py index f836d5f..df8f929 100755 --- a/connector.py +++ b/connector.py @@ -12,8 +12,8 @@ import time import urllib import xml.sax.saxutils
-DEFAULT_REMOTE_ADDRESS = "0.0.0.0" -DEFAULT_REMOTE_PORT = 9000 +DEFAULT_REMOTE_ADDRESS = "127.0.0.1" +DEFAULT_REMOTE_PORT = 3333 DEFAULT_LOCAL_ADDRESS = "127.0.0.1" DEFAULT_LOCAL_PORT = 9001 DEFAULT_FACILITATOR_PORT = 9002 diff --git a/facilitator.py b/facilitator.py index d909f5f..fde81b7 100755 --- a/facilitator.py +++ b/facilitator.py @@ -63,18 +63,17 @@ def format_addr(addr): return u"%s:%d" % (host, port)
class Reg(object): - def __init__(self, host, port): - self.host = host - self.port = port + def __init__(self, id): + self.id = id
def __unicode__(self): - return format_addr((self.host, self.port)) + return u"%s" % (self.id)
def __str__(self): return unicode(self).encode("UTF-8")
def __cmp__(self, other): - return cmp((self.host, self.port), (other.host, other.port)) + return cmp((self.id), (other.id))
@staticmethod def parse(spec, defhost = None, defport = None): @@ -167,7 +166,7 @@ class Handler(BaseHTTPServer.BaseHTTPRequestHandler): val = client_specs[0]
try: - reg = Reg.parse(val, self.client_address[0]) + reg = Reg(val) except ValueError, e: log(u"client %s syntax error in %s: %s" % (format_addr(self.client_address), repr(val), repr(str(e)))) return diff --git a/rtmfp/RTMFPSocket.as b/rtmfp/RTMFPSocket.as new file mode 100644 index 0000000..bb00994 --- /dev/null +++ b/rtmfp/RTMFPSocket.as @@ -0,0 +1,231 @@ +/* RTMFPSocket abstraction + * Author: Nate Hardison, May 2011 + * + * This code is heavily based off of BelugaFile, an open-source + * Air file-transfer application written by Nicholas Bliyk. + * Website: http://www.belugafile.com/ + * Source: http://code.google.com/p/belugafile/ + * + */ + +package rtmfp +{ + import flash.events.Event; + import flash.events.EventDispatcher; + import flash.events.IOErrorEvent; + import flash.events.NetStatusEvent; + import flash.events.ProgressEvent; + import flash.events.SecurityErrorEvent; + import flash.net.NetConnection; + import flash.net.NetStream; + import flash.utils.ByteArray; + import flash.utils.clearInterval; + import flash.utils.setInterval; + import flash.utils.setTimeout; + + import rtmfp.RTMFPSocketClient; + import rtmfp.events.RTMFPSocketEvent; + + [Event(name="connectSuccess", type="com.jscat.rtmfp.events.RTMFPSocketEvent")] + [Event(name="connectFail", type="com.jscat.rtmfp.events.RTMFPSocketEvent")] + [Event(name="publishStart", type="com.jscat.rtmfp.events.RTMFPSocketEvent")] + [Event(name="peerConnected", type="com.jscat.rtmfp.events.RTMFPSocketEvent")] + [Event(name="peeringSuccess", type="com.jscat.rtmfp.events.RTMFPSocketEvent")] + [Event(name="peeringFail", type="com.jscat.rtmfp.events.RTMFPSocketEvent")] + [Event(name="peerDisconnected", type="com.jscat.rtmfp.events.RTMFPSocketEvent")] + public class RTMFPSocket extends EventDispatcher + { + /* The name of the "media" to pass between peers */ + private static const DATA:String = "data"; + private static const DEFAULT_CIRRUS_ADDRESS:String = "rtmfp://p2p.rtmfp.net"; + private static const DEFAULT_CIRRUS_KEY:String = RTMFP::CIRRUS_KEY; + private static const DEFAULT_CONNECT_TIMEOUT:uint = 4000; + + /* Connection to the Cirrus rendezvous service */ + private var connection:NetConnection; + + /* ID of the peer to connect to */ + private var peerID:String; + + /* Data streams to be established with peer */ + private var sendStream:NetStream; + private var recvStream:NetStream; + + /* Timeouts */ + private var connectionTimeout:int; + private var peerConnectTimeout:uint; + + public function RTMFPSocket(){} + + public function connect(addr:String = DEFAULT_CIRRUS_ADDRESS, key:String = DEFAULT_CIRRUS_KEY):void + { + connection = new NetConnection(); + connection.addEventListener(NetStatusEvent.NET_STATUS, onNetStatusEvent); + connection.addEventListener(IOErrorEvent.IO_ERROR, onIOErrorEvent); + connection.addEventListener(SecurityErrorEvent.SECURITY_ERROR, onSecurityErrorEvent); + connection.connect(addr + "/" + key); + connectionTimeout = setInterval(fail, DEFAULT_CONNECT_TIMEOUT); + } + + public function close():void + { + connection.close(); + } + + public function get id():String + { + if (connection != null && connection.connected) { + return connection.nearID; + } + + return null; + } + + public function get connected():Boolean + { + return (connection != null && connection.connected); + } + + public function readBytes(bytes:ByteArray):void + { + recvStream.client.bytes.readBytes(bytes); + } + + public function writeBytes(bytes:ByteArray):void + { + sendStream.send("dataAvailable", bytes); + } + + public function get peer():String + { + return this.peerID; + } + + public function set peer(peerID:String):void + { + if (peerID == null || peerID.length == 0) { + throw new Error("Peer ID is null/empty.") + } else if (peerID == connection.nearID) { + throw new Error("Peer ID cannot be the same as our ID."); + } else if (this.peerID == peerID) { + throw new Error("Already connected to peer " + peerID + "."); + } else if (this.recvStream != null) { + throw new Error("Cannot connect to a second peer."); + } + + this.peerID = peerID; + + recvStream = new NetStream(connection, peerID); + var client:RTMFPSocketClient = new RTMFPSocketClient(); + client.addEventListener(ProgressEvent.SOCKET_DATA, onDataAvailable, false, 0, true); + client.addEventListener(RTMFPSocketClient.PEER_CONNECT_ACKNOWLEDGED, onPeerConnectAcknowledged, false, 0, true); + recvStream.client = client; + recvStream.addEventListener(NetStatusEvent.NET_STATUS, onRecvStreamEvent); + recvStream.play(DATA); + setTimeout(onPeerConnectTimeout, peerConnectTimeout, recvStream); + } + + private function startPublishStream():void + { + sendStream = new NetStream(connection, NetStream.DIRECT_CONNECTIONS); + sendStream.addEventListener(NetStatusEvent.NET_STATUS, onSendStreamEvent); + var o:Object = new Object(); + o.onPeerConnect = onPeerConnect; + sendStream.client = o; + sendStream.publish(DATA); + } + + private function fail():void + { + clearInterval(connectionTimeout); + dispatchEvent(new RTMFPSocketEvent(RTMFPSocketEvent.CONNECT_FAIL)); + } + + private function onDataAvailable(event:ProgressEvent):void + { + dispatchEvent(event); + } + + private function onIOErrorEvent(event:IOErrorEvent):void + { + fail(); + } + + private function onNetStatusEvent(event:NetStatusEvent):void + { + switch (event.info.code) { + case "NetConnection.Connect.Success" : + clearInterval(connectionTimeout); + startPublishStream(); + dispatchEvent(new RTMFPSocketEvent(RTMFPSocketEvent.CONNECT_SUCCESS)); + break; + case "NetStream.Connect.Success" : + break; + case "NetStream.Publish.BadName" : + fail(); + break; + case "NetStream.Connect.Closed" : + // we've disconnected from the peer + // can reset to accept another + // clear the publish stream and re-publish another + dispatchEvent(new RTMFPSocketEvent(RTMFPSocketEvent.PEER_DISCONNECTED, recvStream)); + break; + } + } + + private function onPeerConnect(peer:NetStream):Boolean + { + // establish a bidirectional stream with the peer + if (peerID == null) { + this.peer = peer.farID; + } + + // disallow additional peers connecting to us + if (peer.farID != peerID) return false; + + peer.send("setPeerConnectAcknowledged"); + dispatchEvent(new RTMFPSocketEvent(RTMFPSocketEvent.PEER_CONNECTED, peer)); + + return true; + } + + private function onPeerConnectAcknowledged(event:Event):void + { + dispatchEvent(new RTMFPSocketEvent(RTMFPSocketEvent.PEERING_SUCCESS, recvStream)); + } + + private function onPeerConnectTimeout(peer:NetStream):void + { + if (!recvStream.client) return; + if (!RTMFPSocketClient(recvStream.client).peerConnectAcknowledged) { + dispatchEvent(new RTMFPSocketEvent(RTMFPSocketEvent.PEERING_FAIL, recvStream)); + } + } + + private function onSecurityErrorEvent(event:SecurityErrorEvent):void + { + fail(); + } + + private function onSendStreamEvent(event:NetStatusEvent):void + { + switch (event.info.code) { + case ("NetStream.Publish.Start") : + dispatchEvent(new RTMFPSocketEvent(RTMFPSocketEvent.PUBLISH_START)); + break; + case ("NetStream.Play.Reset") : + case ("NetStream.Play.Start") : + break; + } + } + private function onRecvStreamEvent(event:NetStatusEvent):void + { + switch (event.info.code) { + case ("NetStream.Publish.Start") : + case ("NetStream.Play.Reset") : + case ("NetStream.Play.Start") : + break; + } + } + } +} diff --git a/rtmfp/RTMFPSocketClient.as b/rtmfp/RTMFPSocketClient.as new file mode 100644 index 0000000..d9fcffa --- /dev/null +++ b/rtmfp/RTMFPSocketClient.as @@ -0,0 +1,57 @@ +package rtmfp +{ + import flash.events.Event; + import flash.events.EventDispatcher; + import flash.events.ProgressEvent; + import flash.utils.ByteArray; + + [Event(name="peerConnectAcknowledged", type="flash.events.Event")] + public dynamic class RTMFPSocketClient extends EventDispatcher { + public static const PEER_CONNECT_ACKNOWLEDGED:String = "peerConnectAcknowledged"; + + private var _bytes:ByteArray; + private var _peerID:String; + private var _peerConnectAcknowledged:Boolean; + + public function RTMFPSocketClient() + { + super(); + _bytes = new ByteArray(); + _peerID = null; + _peerConnectAcknowledged = false; + } + + public function get bytes():ByteArray + { + return _bytes; + } + + public function dataAvailable(bytes:ByteArray):void + { + this._bytes.clear(); + bytes.readBytes(this._bytes); + dispatchEvent(new ProgressEvent(ProgressEvent.SOCKET_DATA, false, false, this._bytes.bytesAvailable, this._bytes.length)); + } + + public function get peerConnectAcknowledged():Boolean + { + return _peerConnectAcknowledged; + } + + public function setPeerConnectAcknowledged():void + { + _peerConnectAcknowledged = true; + dispatchEvent(new Event(PEER_CONNECT_ACKNOWLEDGED)); + } + + public function get peerID():String + { + return _peerID; + } + + public function set peerID(id:String):void + { + _peerID = id; + } + } +} \ No newline at end of file diff --git a/rtmfp/events/RTMFPSocketEvent.as b/rtmfp/events/RTMFPSocketEvent.as new file mode 100644 index 0000000..c5b4af1 --- /dev/null +++ b/rtmfp/events/RTMFPSocketEvent.as @@ -0,0 +1,25 @@ +package rtmfp.events +{ + import flash.events.Event; + import flash.net.NetStream; + + public class RTMFPSocketEvent extends Event + { + public static const CONNECT_SUCCESS:String = "connectSuccess"; + public static const CONNECT_FAIL:String = "connectFail"; + public static const PUBLISH_START:String = "publishStart"; + public static const PEER_CONNECTED:String = "peerConnected"; + public static const PEER_DISCONNECTED:String = "peerDisconnected"; + public static const PEERING_SUCCESS:String = "peeringSuccess"; + public static const PEERING_FAIL:String = "peeringFail"; + + public var stream:NetStream; + + public function RTMFPSocketEvent(type:String, streamVal:NetStream = null, bubbles:Boolean = false, cancelable:Boolean = false) + { + super(type, bubbles, cancelable); + stream = streamVal; + } + + } +} \ No newline at end of file diff --git a/rtmfpcat.as b/rtmfpcat.as new file mode 100644 index 0000000..328274a --- /dev/null +++ b/rtmfpcat.as @@ -0,0 +1,208 @@ +package +{ + import flash.display.Sprite; + import flash.text.TextField; + import flash.net.Socket; + import flash.events.Event; + import flash.events.EventDispatcher; + import flash.events.IOErrorEvent; + import flash.events.NetStatusEvent; + import flash.events.ProgressEvent; + import flash.events.SecurityErrorEvent; + import flash.utils.ByteArray; + import flash.utils.setTimeout; + + import rtmfp.RTMFPSocket; + import rtmfp.events.RTMFPSocketEvent; + import Utils; + + public class rtmfpcat extends Sprite { + + /* Nate's facilitator -- also serving a crossdomain policy */ + private const DEFAULT_FAC_ADDR:Object = { + host: "128.12.179.80", + port: 9002 + }; + + private const DEFAULT_TOR_CLIENT_ADDR:Object = { + host: "127.0.0.1", + port: 3333 + }; + + /* David's relay (nickname 3VXRyxz67OeRoqHn) that also serves a + crossdomain policy. */ + private const DEFAULT_TOR_PROXY_ADDR:Object = { + host: "173.255.221.44", + port: 9001 + }; + + // Milliseconds. + private const FACILITATOR_POLL_INTERVAL:int = 10000; + + private var output_text:TextField; + + private var s_f:Socket; + private var s_r:RTMFPSocket; + private var s_t:Socket; + + private var fac_addr:Object; + private var tor_addr:Object; + + private var proxy_mode:Boolean; + + public function rtmfpcat() + { + output_text = new TextField(); + output_text.width = 400; + output_text.height = 300; + output_text.background = true; + output_text.backgroundColor = 0x001f0f; + output_text.textColor = 0x44CC44; + addChild(output_text); + + puts("Starting."); + + this.loaderInfo.addEventListener(Event.COMPLETE, onLoaderInfoComplete); + } + + private function puts(s:String):void + { + output_text.appendText(s + "\n"); + output_text.scrollV = output_text.maxScrollV; + } + + private function onLoaderInfoComplete(e:Event):void + { + var fac_spec:String; + var tor_spec:String; + + puts("Parameters loaded."); + + proxy_mode = (this.loaderInfo.parameters["proxy"] != null); + + fac_spec = this.loaderInfo.parameters["facilitator"]; + if (!fac_spec) { + puts("No "facilitator" specification provided...using default."); + fac_addr = DEFAULT_FAC_ADDR; + } else { + puts("Facilitator spec: "" + fac_spec + """); + fac_addr = Utils.parseAddrSpec(fac_spec); + } + + if (!fac_addr) { + puts("Error: Facilitator spec must be in the form "host:port"."); + return; + } + + tor_spec = this.loaderInfo.parameters["tor"]; + if (!tor_spec) { + puts("No Tor specification provided...using default."); + if (proxy_mode) tor_addr = DEFAULT_TOR_PROXY_ADDR; + else tor_addr = DEFAULT_TOR_CLIENT_ADDR; + } else { + puts("Tor spec: "" + tor_spec + """) + tor_addr = Utils.parseAddrSpec(tor_spec); + } + + if (!tor_addr) { + puts("Error: Tor spec must be in the form "host:port"."); + return; + } + + establishRTMFPConnection(); + } + + private function establishRTMFPConnection():void + { + s_r = new RTMFPSocket(); + s_r.addEventListener(RTMFPSocketEvent.CONNECT_SUCCESS, function (e:Event):void { + puts("Cirrus: connected with id " + s_r.id + "."); + establishFacilitatorConnection(); + }); + s_r.addEventListener(RTMFPSocketEvent.CONNECT_FAIL, function (e:Event):void { + puts("Error: failed to connect to Cirrus."); + }); + s_r.addEventListener(RTMFPSocketEvent.PUBLISH_START, function(e:RTMFPSocketEvent):void { + puts("Publishing started."); + }); + s_r.addEventListener(RTMFPSocketEvent.PEER_CONNECTED, function(e:RTMFPSocketEvent):void { + puts("Peer connected."); + }); + s_r.addEventListener(RTMFPSocketEvent.PEER_DISCONNECTED, function(e:RTMFPSocketEvent):void { + puts("Peer disconnected."); + }); + s_r.addEventListener(RTMFPSocketEvent.PEERING_SUCCESS, function(e:RTMFPSocketEvent):void { + puts("Peering success."); + establishTorConnection(); + }); + s_r.addEventListener(RTMFPSocketEvent.PEERING_FAIL, function(e:RTMFPSocketEvent):void { + puts("Peering fail."); + }); + s_r.addEventListener(ProgressEvent.SOCKET_DATA, function (e:ProgressEvent):void { + var bytes:ByteArray = new ByteArray(); + s_r.readBytes(bytes); + puts("RTMFP: read " + bytes.length + " bytes."); + s_t.writeBytes(bytes); + }); + + s_r.connect(); + } + + private function establishTorConnection():void + { + s_t = new Socket(); + s_t.addEventListener(Event.CONNECT, function (e:Event):void { + puts("Tor: connected to " + tor_addr.host + ":" + tor_addr.port + "."); + }); + s_t.addEventListener(Event.CLOSE, function (e:Event):void { + puts("Tor: closed connection."); + }); + s_t.addEventListener(IOErrorEvent.IO_ERROR, function (e:IOErrorEvent):void { + puts("Tor: I/O error: " + e.text + "."); + }); + s_t.addEventListener(ProgressEvent.SOCKET_DATA, function (e:ProgressEvent):void { + var bytes:ByteArray = new ByteArray(); + s_t.readBytes(bytes, 0, e.bytesLoaded); + puts("Tor: read " + bytes.length + " bytes."); + s_r.writeBytes(bytes); + }); + s_t.addEventListener(SecurityErrorEvent.SECURITY_ERROR, function (e:SecurityErrorEvent):void { + puts("Tor: security error: " + e.text + "."); + }); + + s_t.connect(tor_addr.host, tor_addr.port); + } + + private function establishFacilitatorConnection():void + { + s_f = new Socket(); + s_f.addEventListener(Event.CONNECT, function (e:Event):void { + puts("Facilitator: connected to " + fac_addr.host + ":" + fac_addr.port + "."); + if (proxy_mode) s_f.writeUTFBytes("GET / HTTP/1.0\r\n\r\n"); + else s_f.writeUTFBytes("POST / HTTP/1.0\r\n\r\nclient=" + s_r.id + "\r\n"); + }); + s_f.addEventListener(Event.CLOSE, function (e:Event):void { + puts("Facilitator: connection closed."); + if (proxy_mode) { + setTimeout(establishFacilitatorConnection, FACILITATOR_POLL_INTERVAL); + } + }); + s_f.addEventListener(IOErrorEvent.IO_ERROR, function (e:IOErrorEvent):void { + puts("Facilitator: I/O error: " + e.text + "."); + }); + s_f.addEventListener(ProgressEvent.SOCKET_DATA, function (e:ProgressEvent):void { + var clientID:String = s_f.readMultiByte(e.bytesLoaded, "utf-8"); + puts("Facilitator: got "" + clientID + """); + if (clientID != "Registration list empty") { + puts("Connecting to " + clientID + "."); + s_r.peer = clientID; + } + }); + s_f.addEventListener(SecurityErrorEvent.SECURITY_ERROR, function (e:SecurityErrorEvent):void { + puts("Facilitator: security error: " + e.text + "."); + }); + + s_f.connect(fac_addr.host, fac_addr.port); + } + } +} \ No newline at end of file diff --git a/swfcat.as b/swfcat.as index fd3625d..76de948 100644 --- a/swfcat.as +++ b/swfcat.as @@ -249,7 +249,6 @@ import flash.events.ProgressEvent; import flash.events.SecurityErrorEvent; import flash.net.Socket; import flash.utils.ByteArray; -import flash.utils.setInterval;
/* An instance of a client-relay connection. */ class ProxyPair extends EventDispatcher @@ -266,19 +265,6 @@ class ProxyPair extends EventDispatcher
// Parent swfcat, for UI updates. private var ui:swfcat; - - // Proxy transfer rate limit in KB/s - private var rate_limit:uint; - - // Default limit is 10KB/s - private const DEFAULT_RATE_LIMIT:uint = 10000; - - private var curr_rate:Number; - private var historical_rate:Number; - private var lifetime:uint; - - // in milliseconds - private const DEFAULT_RATE_TIMEOUT:uint = 1000;
public function log(msg:String):void { @@ -292,16 +278,11 @@ class ProxyPair extends EventDispatcher "," + this.addr_r.host + ":" + this.addr_r.port + ">"; }
- public function ProxyPair(ui:swfcat, addr_c:Object, addr_r:Object, rate_limit:uint = DEFAULT_RATE_LIMIT) + public function ProxyPair(ui:swfcat, addr_c:Object, addr_r:Object) { this.ui = ui; this.addr_c = addr_c; this.addr_r = addr_r; - this.rate_limit = rate_limit; - this.curr_rate = 0.0; - this.historical_rate = 0.0; - this.lifetime = 0; - setInterval(update_transfer_rate, DEFAULT_RATE_TIMEOUT) }
public function connect():void @@ -365,53 +346,18 @@ class ProxyPair extends EventDispatcher private function client_connected(e:Event):void { log("Client: connected."); - add_data_listeners(); - } - - private function add_data_listeners():void - { - s_r.addEventListener(ProgressEvent.SOCKET_DATA, transfer_to_client); - s_c.addEventListener(ProgressEvent.SOCKET_DATA, transfer_to_relay); - } - - private function remove_data_listeners():void - { - s_r.removeEventListener(ProgressEvent.SOCKET_DATA, transfer_to_client); - s_c.removeEventListener(ProgressEvent.SOCKET_DATA, transfer_to_relay); - } - - private function transfer_to_client(e:ProgressEvent):void - { - log("Tor: read " + e.bytesLoaded + "."); - transfer_bytes(s_r, s_c, e.bytesLoaded); - } - - private function transfer_to_relay(e:ProgressEvent):void - { - log("Client: read " + e.bytesLoaded + "."); - transfer_bytes(s_c, s_r, e.bytesLoaded); - } - - private function transfer_bytes(src:Socket, dst:Socket, num_bytes:uint):void - { - var bytes:ByteArray = new ByteArray(); - src.readBytes(bytes, 0, num_bytes); - dst.writeBytes(bytes); - curr_rate += num_bytes; - if (rate_limit_exceeded()) remove_data_listeners(); - } - - private function rate_limit_exceeded():Boolean - { - return curr_rate > rate_limit; - } - - private function update_transfer_rate():void - { - lifetime++; - historical_rate = (0.7 * historical_rate + 0.3 * curr_rate) / lifetime; - curr_rate = historical_rate; - log("Historical rate: " + historical_rate); - log("Current rate: " + curr_rate); + + s_r.addEventListener(ProgressEvent.SOCKET_DATA, function (e:ProgressEvent):void { + var bytes:ByteArray = new ByteArray(); + s_r.readBytes(bytes, 0, e.bytesLoaded); + log("Tor: read " + bytes.length + "."); + s_c.writeBytes(bytes); + }); + s_c.addEventListener(ProgressEvent.SOCKET_DATA, function (e:ProgressEvent):void { + var bytes:ByteArray = new ByteArray(); + s_c.readBytes(bytes, 0, e.bytesLoaded); + log("Client: read " + bytes.length + "."); + s_r.writeBytes(bytes); + }); } }
tor-commits@lists.torproject.org