浏览代码

First pass at wsproxy using node (node.js).

Node:
    http://nodejs.org/
    https://github.com/ry/node

It mostly works, but it eventually gets an error from the target which
is probably due to missing support for re-assembly of client
WebSockets frames.
Joel Martin 14 年之前
父节点
当前提交
fd758dd335
共有 2 个文件被更改,包括 249 次插入2 次删除
  1. 14 2
      utils/README.md
  2. 235 0
      utils/wsproxy.js

+ 14 - 2
utils/README.md

@@ -40,8 +40,8 @@ These are not necessary for the basic operation.
 
 ### Implementations
 
-There are two implementations of wsproxy included: a python
-implementation and a C implementation.
+There are three implementations of wsproxy included: python, C, and
+Node (node.js).
 
 Here is the feature support matrix for the wsproxy implementations:
 
@@ -50,6 +50,7 @@ Here is the feature support matrix for the wsproxy implementations:
     <tr>
         <th>Implementation</th>
         <th>Basic Proxying</th>
+        <th>Multi-process</th>
         <th>Daemonizing</th>
         <th>SSL/wss</th>
         <th>Flash Policy Server</th>
@@ -58,6 +59,7 @@ Here is the feature support matrix for the wsproxy implementations:
         <td>python</td>
         <td>yes</td>
         <td>yes</td>
+        <td>yes</td>
         <td>yes 1</td>
         <td>yes</td>
         <td>yes</td>
@@ -67,6 +69,16 @@ Here is the feature support matrix for the wsproxy implementations:
         <td>yes</td>
         <td>yes</td>
         <td>yes</td>
+        <td>yes</td>
+        <td>no</td>
+    </tr>
+    </tr> <tr>
+        <td>Node (node.js)</td>
+        <td>yes</td>
+        <td>yes</td>
+        <td>no</td>
+        <td>no</td>
+        <td>no</td>
         <td>no</td>
     </tr>
 </table>

+ 235 - 0
utils/wsproxy.js

@@ -0,0 +1,235 @@
+// A WebSocket to TCP socket proxy
+// Copyright 2010 Joel Martin
+// Licensed under LGPL version 3 (see docs/LICENSE.LGPL-3)
+
+var net = require('net'),
+    sys = require('sys'),
+    crypto = require('crypto'),
+    source_arg, source_host, source_port,
+    target_arg, target_host, target_port;
+
+// md5 calculation borrowed from Socket.IO (MIT license)
+function gen_md5(headers, k3) {
+    var k1 = headers['sec-websocket-key1'],
+        k2 = headers['sec-websocket-key2'],
+        md5 = crypto.createHash('md5');
+
+    [k1, k2].forEach(function(k){
+    var n = parseInt(k.replace(/[^\d]/g, '')),
+        spaces = k.replace(/[^ ]/g, '').length;
+
+    if (spaces === 0 || n % spaces !== 0){
+        return false;
+    }
+
+    n /= spaces;
+
+    md5.update(String.fromCharCode(
+        n >> 24 & 0xFF,
+        n >> 16 & 0xFF,
+        n >> 8  & 0xFF,
+        n       & 0xFF));
+    });
+
+    md5.update(k3.toString('binary'));
+
+    return md5.digest('binary');
+}
+
+function encode(buf) {
+    return String.fromCharCode(0) + 
+           buf.toString('base64', 0) +
+           String.fromCharCode(255);
+}
+
+
+function decode(str) {
+    var buf = new Buffer(str.length);
+    len = buf.write(str.substring(1, str.length-1), 0, 'base64');
+    return buf.toString('binary', 0, len);
+}
+
+
+var server = net.createServer(function (client) {
+    var handshake = "", headers = {}, header,
+        version, path, k1, k2, k3, target = null;
+
+    function do_handshake(data) {
+        var i, idx, dlen = data.length, lines, location, rheaders,
+            sec_hdr;
+        //sys.log("received handshake data: " + data);
+        handshake += data.toString('utf8');
+        if ((data[dlen-12] != 13) ||
+            (data[dlen-11] != 10) ||
+            (data[dlen-10] != 13) ||
+            (data[dlen-9] != 10)) {
+            //sys.log("Got partial handshake");
+            return;
+        }
+        //sys.log("Got whole handshake");
+
+        if (handshake.indexOf('GET ') != 0) {
+            sys.error("Got invalid handshake");
+            client.end();
+            return;
+        }
+
+        lines = handshake.split('\r\n');
+        path = lines[0].split(' ')[1];
+        //sys.log("path: " + path);
+
+        k3 = data.slice(dlen-8, dlen);
+        for (i = 1; i < lines.length; i++) {
+            //sys.log("lines[i]: " + lines[i]);
+            if (lines[i].length == 0) { break; }
+            idx = lines[i].indexOf(': ');
+            if (idx < 0) {
+                sys.error("Got invalid handshake header");
+                client.end();
+                return;
+            }
+            header = lines[i].substring(0, idx).toLowerCase();
+            headers[header] = lines[i].substring(idx+2);
+        }
+        //console.dir(headers);
+        //sys.log("k3: " + k3 + ", k3.length: " + k3.length);
+
+        if (headers.upgrade !== 'WebSocket') {
+            sys.error("Upgrade header is not 'WebSocket'");
+            client.end();
+            return;
+        }
+
+        location = (headers.origin.substr(0, 5) == 'https' ? 'wss' : 'ws')
+            + '://' + headers.host + path;
+        //sys.log("location: " + location);
+
+        if ('sec-websocket-key1' in headers) {
+            version = 76;
+            sec_hdr = "Sec-";
+        } else {
+            version = 75;
+            sec_hdr = "";
+        }
+        sys.log("using protocol version " + version);
+
+        rheaders = [
+            'HTTP/1.1 101 WebSocket Protocol Handshake',
+            'Upgrade: WebSocket',
+            'Connection: Upgrade',
+            sec_hdr + 'WebSocket-Origin: ' + headers.origin,
+            sec_hdr + 'WebSocket-Location: ' + location
+        ];
+        if ('sec-websocket-protocol' in headers) {
+            rheaders.push('Sec-WebSocket-Protocol: ' + headers['sec-websocket-protocol']);
+        }
+        rheaders.push('');
+        if (version === 76) {
+            rheaders.push(gen_md5(headers, k3));
+        }
+
+        // Switch listener to normal data path
+        client.on('data', client_data);
+        client.setEncoding('utf8');
+        client.removeListener('data', do_handshake);
+        // Do not delay writes
+        client.setNoDelay(true);
+
+        // Send the handshake response
+        try {
+            //sys.log("response: " + rheaders.join('\r\n'));
+            client.write(rheaders.join('\r\n'), 'binary');
+        } catch(e) {
+            sys.error("Failed to send handshake response");
+            client.end();
+            return;
+        }
+
+        // Create a connection to the target
+        target = net.createConnection(target_port, target_host);
+        target.on('data', target_data);
+        target.on('end', function () {
+            sys.log("received target end");
+            client.end();
+            if (target) {
+                target.end();
+                target = null;
+            }
+        });
+    }
+
+    function client_data(data) {
+        //sys.log("received client data: " + data);
+        //sys.log("             decoded: " + decode(data));
+        try {
+            target.write(decode(data), 'binary');
+        } catch(e) {
+            sys.log("fatal error writing to target");
+            client.end();
+            if (target) {
+                target.end();
+                target = null;
+            }
+        }
+    }
+
+    function target_data(data) {
+        //sys.log("received target data: " + data);
+        //sys.log("             encoded: " + encode(data));
+        try {
+            client.write(encode(data), 'binary');
+        } catch(e) {
+            sys.log("fatal error writing to client");
+            client.end();
+            target.end();
+            target = null;
+        }
+    }
+
+    client.on('connect', function () {
+        sys.log("Got client connection");
+    });
+    client.on('data', do_handshake);
+    client.on('end', function () {
+        sys.log("recieved client end");
+        client.end();
+        if (target) {
+            target.end();
+            target = null;
+        }
+    });
+});
+
+
+// parse source and target into parts
+source_arg = process.argv[2];
+target_arg = process.argv[3];
+try {
+    var idx;
+    idx = source_arg.indexOf(":");
+    if (idx >= 0) {
+        source_host = source_arg.substring(0, idx);
+        source_port = parseInt(source_arg.substring(idx+1), 10);
+    } else {
+        source_host = "";
+        source_port = parseInt(source_arg, 10);
+    }
+
+    idx = target_arg.indexOf(":");
+    if (idx < 0) {
+        throw("target must be host:port");
+    }
+    target_host = target_arg.substring(0, idx);
+    target_port = parseInt(target_arg.substring(idx+1), 10);
+
+    if (isNaN(source_port) || isNaN(target_port)) {
+        throw("illegal port");
+    }
+} catch(e) {
+    console.error("wsproxy.py [source_addr:]source_port target_addr:target_port");
+    process.exit(2);
+}
+
+sys.log("source: " + source_host + ":" + source_port);
+sys.log("target: " + target_host + ":" + target_port);
+server.listen(source_port, source_host);