wsproxy.js 6.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235
  1. // A WebSocket to TCP socket proxy
  2. // Copyright 2010 Joel Martin
  3. // Licensed under LGPL version 3 (see docs/LICENSE.LGPL-3)
  4. var net = require('net'),
  5. sys = require('sys'),
  6. crypto = require('crypto'),
  7. source_arg, source_host, source_port,
  8. target_arg, target_host, target_port;
  9. // md5 calculation borrowed from Socket.IO (MIT license)
  10. function gen_md5(headers, k3) {
  11. var k1 = headers['sec-websocket-key1'],
  12. k2 = headers['sec-websocket-key2'],
  13. md5 = crypto.createHash('md5');
  14. [k1, k2].forEach(function(k){
  15. var n = parseInt(k.replace(/[^\d]/g, '')),
  16. spaces = k.replace(/[^ ]/g, '').length;
  17. if (spaces === 0 || n % spaces !== 0){
  18. return false;
  19. }
  20. n /= spaces;
  21. md5.update(String.fromCharCode(
  22. n >> 24 & 0xFF,
  23. n >> 16 & 0xFF,
  24. n >> 8 & 0xFF,
  25. n & 0xFF));
  26. });
  27. md5.update(k3.toString('binary'));
  28. return md5.digest('binary');
  29. }
  30. function encode(buf) {
  31. return String.fromCharCode(0) +
  32. buf.toString('base64', 0) +
  33. String.fromCharCode(255);
  34. }
  35. function decode(str) {
  36. var buf = new Buffer(str.length);
  37. len = buf.write(str.substring(1, str.length-1), 0, 'base64');
  38. return buf.toString('binary', 0, len);
  39. }
  40. var server = net.createServer(function (client) {
  41. var handshake = "", headers = {}, header,
  42. version, path, k1, k2, k3, target = null;
  43. function do_handshake(data) {
  44. var i, idx, dlen = data.length, lines, location, rheaders,
  45. sec_hdr;
  46. //sys.log("received handshake data: " + data);
  47. handshake += data.toString('utf8');
  48. if ((data[dlen-12] != 13) ||
  49. (data[dlen-11] != 10) ||
  50. (data[dlen-10] != 13) ||
  51. (data[dlen-9] != 10)) {
  52. //sys.log("Got partial handshake");
  53. return;
  54. }
  55. //sys.log("Got whole handshake");
  56. if (handshake.indexOf('GET ') != 0) {
  57. sys.error("Got invalid handshake");
  58. client.end();
  59. return;
  60. }
  61. lines = handshake.split('\r\n');
  62. path = lines[0].split(' ')[1];
  63. //sys.log("path: " + path);
  64. k3 = data.slice(dlen-8, dlen);
  65. for (i = 1; i < lines.length; i++) {
  66. //sys.log("lines[i]: " + lines[i]);
  67. if (lines[i].length == 0) { break; }
  68. idx = lines[i].indexOf(': ');
  69. if (idx < 0) {
  70. sys.error("Got invalid handshake header");
  71. client.end();
  72. return;
  73. }
  74. header = lines[i].substring(0, idx).toLowerCase();
  75. headers[header] = lines[i].substring(idx+2);
  76. }
  77. //console.dir(headers);
  78. //sys.log("k3: " + k3 + ", k3.length: " + k3.length);
  79. if (headers.upgrade !== 'WebSocket') {
  80. sys.error("Upgrade header is not 'WebSocket'");
  81. client.end();
  82. return;
  83. }
  84. location = (headers.origin.substr(0, 5) == 'https' ? 'wss' : 'ws')
  85. + '://' + headers.host + path;
  86. //sys.log("location: " + location);
  87. if ('sec-websocket-key1' in headers) {
  88. version = 76;
  89. sec_hdr = "Sec-";
  90. } else {
  91. version = 75;
  92. sec_hdr = "";
  93. }
  94. sys.log("using protocol version " + version);
  95. rheaders = [
  96. 'HTTP/1.1 101 WebSocket Protocol Handshake',
  97. 'Upgrade: WebSocket',
  98. 'Connection: Upgrade',
  99. sec_hdr + 'WebSocket-Origin: ' + headers.origin,
  100. sec_hdr + 'WebSocket-Location: ' + location
  101. ];
  102. if ('sec-websocket-protocol' in headers) {
  103. rheaders.push('Sec-WebSocket-Protocol: ' + headers['sec-websocket-protocol']);
  104. }
  105. rheaders.push('');
  106. if (version === 76) {
  107. rheaders.push(gen_md5(headers, k3));
  108. }
  109. // Switch listener to normal data path
  110. client.on('data', client_data);
  111. client.setEncoding('utf8');
  112. client.removeListener('data', do_handshake);
  113. // Do not delay writes
  114. client.setNoDelay(true);
  115. // Send the handshake response
  116. try {
  117. //sys.log("response: " + rheaders.join('\r\n'));
  118. client.write(rheaders.join('\r\n'), 'binary');
  119. } catch(e) {
  120. sys.error("Failed to send handshake response");
  121. client.end();
  122. return;
  123. }
  124. // Create a connection to the target
  125. target = net.createConnection(target_port, target_host);
  126. target.on('data', target_data);
  127. target.on('end', function () {
  128. sys.log("received target end");
  129. client.end();
  130. if (target) {
  131. target.end();
  132. target = null;
  133. }
  134. });
  135. }
  136. function client_data(data) {
  137. //sys.log("received client data: " + data);
  138. //sys.log(" decoded: " + decode(data));
  139. try {
  140. target.write(decode(data), 'binary');
  141. } catch(e) {
  142. sys.log("fatal error writing to target");
  143. client.end();
  144. if (target) {
  145. target.end();
  146. target = null;
  147. }
  148. }
  149. }
  150. function target_data(data) {
  151. //sys.log("received target data: " + data);
  152. //sys.log(" encoded: " + encode(data));
  153. try {
  154. client.write(encode(data), 'binary');
  155. } catch(e) {
  156. sys.log("fatal error writing to client");
  157. client.end();
  158. target.end();
  159. target = null;
  160. }
  161. }
  162. client.on('connect', function () {
  163. sys.log("Got client connection");
  164. });
  165. client.on('data', do_handshake);
  166. client.on('end', function () {
  167. sys.log("recieved client end");
  168. client.end();
  169. if (target) {
  170. target.end();
  171. target = null;
  172. }
  173. });
  174. });
  175. // parse source and target into parts
  176. source_arg = process.argv[2];
  177. target_arg = process.argv[3];
  178. try {
  179. var idx;
  180. idx = source_arg.indexOf(":");
  181. if (idx >= 0) {
  182. source_host = source_arg.substring(0, idx);
  183. source_port = parseInt(source_arg.substring(idx+1), 10);
  184. } else {
  185. source_host = "";
  186. source_port = parseInt(source_arg, 10);
  187. }
  188. idx = target_arg.indexOf(":");
  189. if (idx < 0) {
  190. throw("target must be host:port");
  191. }
  192. target_host = target_arg.substring(0, idx);
  193. target_port = parseInt(target_arg.substring(idx+1), 10);
  194. if (isNaN(source_port) || isNaN(target_port)) {
  195. throw("illegal port");
  196. }
  197. } catch(e) {
  198. console.error("wsproxy.py [source_addr:]source_port target_addr:target_port");
  199. process.exit(2);
  200. }
  201. sys.log("source: " + source_host + ":" + source_port);
  202. sys.log("target: " + target_host + ":" + target_port);
  203. server.listen(source_port, source_host);