
commit 9b6bf0774eb5e713bc1c317c8bc28773daabe5b8 Author: Nate Hardison <nate@rescomp-09-154551.stanford.edu> Date: Tue May 10 02:47:26 2011 -0700 Working version of rtmfpcat --- Connector.as | 183 -------------- Makefile | 2 +- Proxy.as | 261 -------------------- RTMFPRelay.as | 99 -------- RTMFPRelayReactor.as | 13 - Utils.as | 24 -- com/jscat/Connector.as | 188 -------------- com/jscat/Utils.as | 25 -- com/jscat/facilitator.py | 146 ----------- com/jscat/rtmfp/RTMFPSocket.as | 216 ---------------- com/jscat/rtmfp/RTMFPSocketClient.as | 57 ----- com/jscat/rtmfp/events/RTMFPSocketEvent.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 | 216 ++++++++++++++++ com/rtmfpcat/rtmfp/RTMFPSocketClient.as | 57 +++++ com/rtmfpcat/rtmfp/events/RTMFPSocketEvent.as | 25 ++ com/rtmfpcat/rtmfpcat.as | 208 ++++++++++++++++ 21 files changed, 1133 insertions(+), 1238 deletions(-) diff --git a/Connector.as b/Connector.as deleted file mode 100644 index fc18692..0000000 --- a/Connector.as +++ /dev/null @@ -1,183 +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.net.NetConnection; - import flash.net.NetStream; - import flash.utils.ByteArray; - import flash.utils.clearInterval; - import flash.utils.setInterval; - import flash.utils.setTimeout; - - import RTMFPRelay; - import RTMFPRelayReactor; - import Utils; - - public class Connector extends Sprite implements RTMFPRelayReactor { - - /* David's relay (nickname 3VXRyxz67OeRoqHn) that also serves a - crossdomain policy. */ - private const DEFAULT_TOR_ADDR:Object = { - host: "173.255.221.44", - port: 9001 - }; - - private var output_text:TextField; - - // Socket to Tor relay. - private var s_t:Socket; - // Socket to facilitator. - private var s_f:Socket; - // RTMFP data relay - private var relay:RTMFPRelay; - - private var fac_addr:Object; - private var tor_addr:Object; - - public function Connector() { - 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."); - - // Wait until the query string parameters are loaded. - this.loaderInfo.addEventListener(Event.COMPLETE, loaderinfo_complete); - } - - private function facilitator_is(host:String, port:int):void - { - if (s_f != null && s_f.connected) { - puts("Error: already connected to Facilitator!"); - return; - } - - s_f = new Socket(); - - s_f.addEventListener(Event.CONNECT, function (e:Event):void { - puts("Facilitator: connected."); - onConnectionEvent(); - }); - s_f.addEventListener(Event.CLOSE, function (e:Event):void { - puts("Facilitator: closed connection."); - }); - s_f.addEventListener(IOErrorEvent.IO_ERROR, function (e:IOErrorEvent):void { - puts("Facilitator: I/O error: " + e.text + "."); - }); - s_f.addEventListener(SecurityErrorEvent.SECURITY_ERROR, function (e:SecurityErrorEvent):void { - puts("Facilitator: security error: " + e.text + "."); - }); - - puts("Facilitator: connecting to " + host + ":" + port + "."); - s_f.connect(host, port); - } - - private function tor_relay_is(host:String, port:int):void - { - if (s_t != null && s_t.connected) { - puts("Error: already connected to Tor relay!"); - return; - } - - s_t = new Socket(); - - s_t.addEventListener(Event.CONNECT, function (e:Event):void { - puts("Tor: connected."); - onConnectionEvent(); - }); - s_t.addEventListener(Event.CLOSE, function (e:Event):void { - puts("Tor: closed."); - }); - s_t.addEventListener(IOErrorEvent.IO_ERROR, function (e:IOErrorEvent):void { - puts("Tor: I/O error: " + e.text + "."); - }); - s_t.addEventListener(SecurityErrorEvent.SECURITY_ERROR, function (e:SecurityErrorEvent):void { - puts("Tor: security error: " + e.text + "."); - }); - - puts("Tor: connecting to " + host + ":" + port + "."); - s_t.connect(host, port); - } - - private function puts(s:String):void - { - output_text.appendText(s + "\n"); - output_text.scrollV = output_text.maxScrollV; - } - - private function loaderinfo_complete(e:Event):void - { - var fac_spec:String; - var tor_spec:String; - - puts("Parameters loaded."); - - fac_spec = this.loaderInfo.parameters["facilitator"]; - if (!fac_spec) { - puts("Error: no \"facilitator\" specification provided."); - return; - } - puts("Facilitator spec: \"" + fac_spec + "\""); - fac_addr = Utils.parse_addr_spec(fac_spec); - if (!fac_addr) { - puts("Error: Facilitator spec must be in the form \"host:port\"."); - return; - } - - relay = new RTMFPRelay(this); - - tor_addr = DEFAULT_TOR_ADDR; - tor_relay_is(tor_addr.host, tor_addr.port); - facilitator_is(fac_addr.host, fac_addr.port); - } - - public function onConnectionEvent():void - { - if (s_f != null && s_f.connected && s_t != null && s_t.connected && - relay != null && relay.connected) { - s_f.writeUTFBytes("POST / HTTP/1.1\r\n\r\nclient=%3A"+ relay.cirrus_id + "\r\n"); - } - } - - public function onIOErrorEvent(event:IOErrorEvent):void - { - puts("Cirrus: I/O error: " + event.text + "."); - } - - public function onNetStatusEvent(event:NetStatusEvent):void - { - switch (event.info.code) { - case "NetConnection.Connect.Success" : - puts("Cirrus: connected with ID " + relay.cirrus_id + "."); - onConnectionEvent(); - break; - case "NetStream.Connect.Success" : - puts("Peer: connected."); - break; - case "NetStream.Publish.BadName" : - puts(event.info.code); - break; - case "NetStream.Connect.Closed" : - puts(event.info.code); - break; - } - } - - public function onSecurityErrorEvent(event:SecurityErrorEvent):void - { - puts("Cirrus: security error: " + event.text + "."); - } - } -} - \ No newline at end of file diff --git a/Makefile b/Makefile index 92047fb..0c3beba 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,6 @@ MXMLC ?= mxmlc -TARGETS = swfcat.swf Connector.swf +TARGETS = swfcat.swf com/rtmfpcat/rtmfpcat.swf all: $(TARGETS) diff --git a/Proxy.as b/Proxy.as deleted file mode 100644 index 01b2fe3..0000000 --- a/Proxy.as +++ /dev/null @@ -1,261 +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.net.NetConnection; - import flash.net.NetStream; - import flash.utils.ByteArray; - import flash.utils.clearInterval; - import flash.utils.setInterval; - import flash.utils.setTimeout; - - public class Proxy extends Sprite - { - /* David's relay (nickname 3VXRyxz67OeRoqHn) that also serves a - crossdomain policy. */ - private const DEFAULT_TOR_ADDR:Object = { - host: "173.255.221.44", - port: 9001 - }; - - /* Address of the Cirrus rendezvous service with Nate's - developer key appended */ - private static const CIRRUS_ADDRESS:String = "rtmfp://p2p.rtmfp.net/" + RTMFP::CIRRUS_KEY + "/"; - - // The name of our data stream - public static const DATA:String = "data"; - - private var output_text:TextField; - - // Socket to Tor relay. - private var s_t:Socket; - // Socket to facilitator. - private var s_f:Socket; - - /* Connection to the Cirrus rendezvous service that will hook - us up with the Tor client */ - public var cirrus:NetConnection; - - // The Tor client's peerID (used when establishing the client_stream) - private var client_id:String; - - // The data streams to be established with the Tor client - public var send_stream:NetStream; - public var recv_stream:NetStream; - - private var fac_addr:Object; - private var tor_addr:Object; - - private function puts(s:String):void - { - output_text.appendText(s + "\n"); - output_text.scrollV = output_text.maxScrollV; - } - - public function Proxy() - { - 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."); - // Wait until the query string parameters are loaded. - this.loaderInfo.addEventListener(Event.COMPLETE, loaderinfo_complete); - } - - private function loaderinfo_complete(e:Event):void - { - var fac_spec:String; - var tor_spec:String; - - puts("Parameters loaded."); - fac_spec = this.loaderInfo.parameters["facilitator"]; - if (!fac_spec) { - puts("Error: no \"facilitator\" specification provided."); - return; - } - puts("Facilitator spec: \"" + fac_spec + "\""); - fac_addr = parse_addr_spec(fac_spec); - if (!fac_addr) { - puts("Error: Facilitator spec must be in the form \"host:port\"."); - return; - } - - tor_addr = DEFAULT_TOR_ADDR; - - go(); - } - - /* We connect first to the Tor relay; once that happens we connect to - the facilitator to get a client address; once we have the address - of a waiting client then we connect to the client and BAM! we're in business. */ - private function go():void - { - s_t = new Socket(); - - s_t.addEventListener(Event.CONNECT, tor_connected); - s_t.addEventListener(Event.CLOSE, function (e:Event):void { - puts("Tor: closed."); - }); - s_t.addEventListener(IOErrorEvent.IO_ERROR, function (e:IOErrorEvent):void { - puts("Tor: I/O error: " + e.text + "."); - }); - s_t.addEventListener(SecurityErrorEvent.SECURITY_ERROR, function (e:SecurityErrorEvent):void { - puts("Tor: security error: " + e.text + "."); - - }); - - puts("Tor: connecting to " + tor_addr.host + ":" + tor_addr.port + "."); - s_t.connect(tor_addr.host, tor_addr.port); - } - - private function tor_connected(e:Event):void - { - /* Got a connection to tor, now let's get served a client from the facilitator */ - s_f = new Socket(); - - puts("Tor: connected."); - s_f.addEventListener(Event.CONNECT, fac_connected); - s_f.addEventListener(Event.CLOSE, function (e:Event):void { - puts("Facilitator: closed connection."); - }); - s_f.addEventListener(IOErrorEvent.IO_ERROR, function (e:IOErrorEvent):void { - puts("Facilitator: I/O error: " + e.text + "."); - if (s_t.connected) - s_t.close(); - }); - s_f.addEventListener(SecurityErrorEvent.SECURITY_ERROR, function (e:SecurityErrorEvent):void { - puts("Facilitator: security error: " + e.text + "."); - if (s_t.connected) - s_t.close(); - }); - - puts("Facilitator: connecting to " + fac_addr.host + ":" + fac_addr.port + "."); - s_f.connect(fac_addr.host, fac_addr.port); - } - - private function fac_connected(e:Event):void - { - puts("Facilitator: connected."); - - s_f.addEventListener(ProgressEvent.SOCKET_DATA, function (e:ProgressEvent):void { - var client_spec:String; - var client_addr:Object; - - client_spec = s_f.readMultiByte(e.bytesLoaded, "utf-8"); - puts("Facilitator: got \"" + client_spec + "\""); - - /*client_addr = parse_addr_spec(client_spec); - if (!client_addr) { - puts("Error: Client spec must be in the form \"host:port\"."); - return; - } - if (client_addr.host == "0.0.0.0" && client_addr.port == 0) { - puts("Error: Facilitator has no clients."); - return; - }*/ - - /* Now we have a client, so start up a connection to the Cirrus rendezvous point */ - cirrus = new NetConnection(); - cirrus.addEventListener(NetStatusEvent.NET_STATUS, net_status_event_handler); - cirrus.addEventListener(IOErrorEvent.IO_ERROR, function (e:IOErrorEvent):void { - if (s_t.connected) s_t.close(); - if (s_f.connected) s_f.close(); - }); - - cirrus.addEventListener(SecurityErrorEvent.SECURITY_ERROR, function (e:SecurityError):void { - if (s_t.connected) s_t.close(); - if (s_f.connected) s_f.close(); - }); - - cirrus.connect(CIRRUS_ADDRESS); - }); - - s_f.writeUTFBytes("GET / HTTP/1.0\r\n\r\n"); - } - - private function net_status_event_handler(e:NetStatusEvent):void - { - switch (e.info.code) { - case "NetConnection.Connect.Success" : - // Cirrus is now connected - cirrus_connected(e); - } - - } - - private function cirrus_connected(e:Event):void - { - if (client_id == null) { - puts("Error: Client ID doesn't exist."); - return; - } else if (client_id == cirrus.nearID) { - puts("Error: Client ID is our ID."); - return; - } - - send_stream = new NetStream(cirrus, NetStream.DIRECT_CONNECTIONS); - send_stream.addEventListener(NetStatusEvent.NET_STATUS, net_status_event_handler); - send_stream.publish(DATA); - - recv_stream = new NetStream(cirrus, client_id); - recv_stream.addEventListener(NetStatusEvent.NET_STATUS, net_status_event_handler); - recv_stream.play(DATA); - } - - /*private function client_connected(e:Event):void - { - puts("Client: connected."); - - 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 + "."); - 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); - puts("Client: read " + bytes.length + "."); - s_t.writeBytes(bytes); - }); - }*/ - - private function close_connections():void - { - if (s_t.connected) s_t.close(); - if (s_f.connected) s_f.close(); - if (cirrus.connected) cirrus.close(); - if (send_stream != null) send_stream.close(); - if (recv_stream != null) recv_stream.close(); - } - - /* Parse an address in the form "host:port". Returns an Object with - keys "host" (String) and "port" (int). Returns null on error. */ - private static function parse_addr_spec(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; - } - } -} diff --git a/RTMFPRelay.as b/RTMFPRelay.as deleted file mode 100644 index a33ebbf..0000000 --- a/RTMFPRelay.as +++ /dev/null @@ -1,99 +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.net.NetConnection; - import flash.net.NetStream; - import flash.utils.ByteArray; - import flash.utils.clearInterval; - import flash.utils.setInterval; - import flash.utils.setTimeout; - - public class RTMFPRelay extends Sprite - { - private static const CIRRUS_ADDRESS:String = "rtmfp://p2p.rtmfp.net"; - private static const CIRRUS_DEV_KEY:String = RTMFP::CIRRUS_KEY; - - /* The name of the "media" to pass between peers */ - private static const DATA:String = "data"; - - /* Connection to the Cirrus rendezvous service */ - private var cirrus_conn:NetConnection; - - /* ID of the peer to connect to */ - private var peer_id:String; - - /* Data streams to be established with peer */ - private var send_stream:NetStream; - private var recv_stream:NetStream; - - private var notifiee:RTMFPRelayReactor; - - public function RTMFPRelay(notifiee:RTMFPRelayReactor) - { - this.notifiee = notifiee; - - cirrus_conn = new NetConnection(); - cirrus_conn.addEventListener(NetStatusEvent.NET_STATUS, notifiee.onNetStatusEvent); - cirrus_conn.addEventListener(IOErrorEvent.IO_ERROR, notifiee.onIOErrorEvent); - cirrus_conn.addEventListener(SecurityErrorEvent.SECURITY_ERROR, notifiee.onSecurityErrorEvent); - - cirrus_conn.connect(CIRRUS_ADDRESS + "/" + CIRRUS_DEV_KEY); - } - - public function get cirrus_id():String - { - if (cirrus_conn != null && cirrus_conn.connected) { - return cirrus_conn.nearID; - } - - return null; - } - - public function get connected():Boolean - { - return (cirrus_conn != null && cirrus_conn.connected); - } - - public function data_is(data:ByteArray):void - { - - - } - - public function get peer():String - { - return this.peer_id; - } - - public function set peer(peer_id:String):void - { - if (peer_id == null) { - throw new Error("Peer ID is null.") - } else if (peer_id == cirrus_conn.nearID) { - throw new Error("Peer ID cannot be the same as our ID."); - } else if (this.peer_id == peer_id) { - throw new Error("Already connected to peer " + peer_id + "."); - } else if (this.recv_stream != null) { - throw new Error("Cannot connect to a second peer."); - } - - this.peer_id = peer_id; - - send_stream = new NetStream(cirrus_conn, NetStream.DIRECT_CONNECTIONS); - send_stream.addEventListener(NetStatusEvent.NET_STATUS, notifiee.onNetStatusEvent); - send_stream.publish(DATA); - - recv_stream = new NetStream(cirrus_conn, peer_id); - recv_stream.addEventListener(NetStatusEvent.NET_STATUS, notifiee.onNetStatusEvent); - recv_stream.play(DATA); - } - } -} diff --git a/RTMFPRelayReactor.as b/RTMFPRelayReactor.as deleted file mode 100644 index 5c84779..0000000 --- a/RTMFPRelayReactor.as +++ /dev/null @@ -1,13 +0,0 @@ -package { - - import flash.events.IOErrorEvent; - import flash.events.NetStatusEvent; - import flash.events.SecurityErrorEvent; - - public interface RTMFPRelayReactor { - function onIOErrorEvent(event:IOErrorEvent):void; - function onNetStatusEvent(event:NetStatusEvent):void; - function onSecurityErrorEvent(event:SecurityErrorEvent):void - } - -} \ No newline at end of file diff --git a/Utils.as b/Utils.as deleted file mode 100644 index 75ed44c..0000000 --- a/Utils.as +++ /dev/null @@ -1,24 +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 parse_addr_spec(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/jscat/Connector.as b/com/jscat/Connector.as deleted file mode 100644 index dab7d0a..0000000 --- a/com/jscat/Connector.as +++ /dev/null @@ -1,188 +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 rtmfp.RTMFPSocket; - import rtmfp.events.RTMFPSocketEvent; - import Utils; - - public class Connector extends Sprite { - - /* David's relay (nickname 3VXRyxz67OeRoqHn) that also serves a - crossdomain policy. */ - private const DEFAULT_TOR_RELAY:Object = { - host: "173.255.221.44", - port: 9001 - }; - - 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; - - public function Connector() - { - 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); - } - - protected 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."); - - fac_spec = this.loaderInfo.parameters["facilitator"]; - if (!fac_spec) { - puts("Error: no \"facilitator\" specification provided."); - return; - } - 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("Error: No Tor specification provided."); - return; - } - 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; - } - - s_r = new RTMFPSocket(); - s_r.addEventListener(RTMFPSocketEvent.CONNECT_SUCCESS, onRTMFPSocketConnect); - 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 { - - }); - s_r.addEventListener(RTMFPSocketEvent.PEER_CONNECTED, function(e:RTMFPSocketEvent):void { - - }); - s_r.addEventListener(RTMFPSocketEvent.PEER_DISCONNECTED, function(e:RTMFPSocketEvent):void { - - }); - s_r.addEventListener(RTMFPSocketEvent.PEERING_SUCCESS, function(e:RTMFPSocketEvent):void { - - }); - s_r.addEventListener(RTMFPSocketEvent.PEERING_FAIL, function(e:RTMFPSocketEvent):void { - - }); - 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 onRTMFPSocketConnect(event:RTMFPSocketEvent):void - { - puts("Cirrus: connected with id " + s_r.id + "."); - s_t = new Socket(); - s_t.addEventListener(Event.CONNECT, onTorSocketConnect); - s_t.addEventListener(Event.CLOSE, function (e:Event):void { - - }); - 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); - onTorSocketConnect(new Event("")); - } - - private function onTorSocketConnect(event:Event):void - { - puts("Tor: connected to " + tor_addr.host + ":" + tor_addr.port + "."); - - s_f = new Socket(); - s_f.addEventListener(Event.CONNECT, onFacilitatorSocketConnect); - s_f.addEventListener(Event.CLOSE, function (e:Event):void { - - }); - 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 + "\""); - 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); - } - - private function onFacilitatorSocketConnect(event:Event):void - { - puts("Facilitator: connected to " + fac_addr.host + ":" + fac_addr.port + "."); - onConnectionEvent(); - } - - private function onConnectionEvent():void - { - if (s_f != null && s_f.connected && s_t != null && /*s_t.connected && */ - s_r != null && s_r.connected) { - if (this.loaderInfo.parameters["proxy"]) { - s_f.writeUTFBytes("GET / HTTP/1.0\r\n\r\n"); - } else { - var str:String = "POST / HTTP/1.0\r\n\r\nclient=" + s_r.id + "\r\n" - puts(str); - s_f.writeUTFBytes(str); - } - } - } - } -} \ No newline at end of file diff --git a/com/jscat/Utils.as b/com/jscat/Utils.as deleted file mode 100644 index 48f9a62..0000000 --- a/com/jscat/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/jscat/facilitator.py b/com/jscat/facilitator.py deleted file mode 100755 index 8c6a44e..0000000 --- a/com/jscat/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/jscat/rtmfp/RTMFPSocket.as b/com/jscat/rtmfp/RTMFPSocket.as deleted file mode 100644 index 4b83784..0000000 --- a/com/jscat/rtmfp/RTMFPSocket.as +++ /dev/null @@ -1,216 +0,0 @@ -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 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/jscat/rtmfp/RTMFPSocketClient.as b/com/jscat/rtmfp/RTMFPSocketClient.as deleted file mode 100644 index d9fcffa..0000000 --- a/com/jscat/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/jscat/rtmfp/events/RTMFPSocketEvent.as b/com/jscat/rtmfp/events/RTMFPSocketEvent.as deleted file mode 100644 index c5b4af1..0000000 --- a/com/jscat/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/Makefile b/com/rtmfpcat/Makefile new file mode 100644 index 0000000..fbcfc20 --- /dev/null +++ b/com/rtmfpcat/Makefile @@ -0,0 +1,11 @@ +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 new file mode 100644 index 0000000..0432a63 --- /dev/null +++ b/com/rtmfpcat/README @@ -0,0 +1,118 @@ +== 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 new file mode 100644 index 0000000..48f9a62 --- /dev/null +++ b/com/rtmfpcat/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/connector.py b/com/rtmfpcat/connector.py new file mode 100755 index 0000000..63bbc5c --- /dev/null +++ b/com/rtmfpcat/connector.py @@ -0,0 +1,326 @@ +#!/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 new file mode 100755 index 0000000..8c6a44e --- /dev/null +++ b/com/rtmfpcat/facilitator.py @@ -0,0 +1,146 @@ +#!/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 new file mode 100644 index 0000000..4b83784 --- /dev/null +++ b/com/rtmfpcat/rtmfp/RTMFPSocket.as @@ -0,0 +1,216 @@ +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 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 new file mode 100644 index 0000000..d9fcffa --- /dev/null +++ b/com/rtmfpcat/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/com/rtmfpcat/rtmfp/events/RTMFPSocketEvent.as b/com/rtmfpcat/rtmfp/events/RTMFPSocketEvent.as new file mode 100644 index 0000000..c5b4af1 --- /dev/null +++ b/com/rtmfpcat/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/com/rtmfpcat/rtmfpcat.as b/com/rtmfpcat/rtmfpcat.as new file mode 100644 index 0000000..a2e3e61 --- /dev/null +++ b/com/rtmfpcat/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 bridge (nickname eRYaZuvY02FpExln) that also serves a + crossdomain policy. */ + private const DEFAULT_TOR_PROXY_ADDR:Object = { + host: "69.164.193.231", + 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); + } + + protected 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