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.
This commit is contained in:
parent
58b4c9408f
commit
fd758dd335
|
@ -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>
|
||||
|
|
|
@ -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);
|
Loading…
Reference in New Issue