diff --git a/index.js b/index.js index cb9afb2..a3d6836 100644 --- a/index.js +++ b/index.js @@ -126,6 +126,7 @@ Packer.create = function (opts) { machine.service = machine._headers[4]; machine.serviceport = machine._headers[5]; machine.name = machine._headers[6]; + machine.servicename = machine._headers[7]; //console.log('machine.service', machine.service); return true; @@ -167,10 +168,15 @@ Packer.create = function (opts) { msg.name = machine.name; msg.data = data; + if ('connection' === machine.service) { + msg.service = machine.servicename; + } + + //console.log('msn', machine.service); if (machine.emit) { - machine.emit(serviceEvents[msg.service] || serviceEvents.default); + machine.emit(serviceEvents[machine.service] || serviceEvents[msg.service] || serviceEvents.default); } else { - (machine[serviceFuncs[msg.service]] || machine[serviceFuncs.default])(msg); + (machine[serviceFuncs[machine.service]] || machine[serviceFuncs[msg.service]] || machine[serviceFuncs.default])(msg); } return true; @@ -190,14 +196,19 @@ Packer.create = function (opts) { machine.state %= machine.states.length; } } + if ('data' === machine.states[machine.state] && 0 === machine.bodyLen) { + machine.fns[machine.states[machine.state]](chunk) + machine.state += 1; + machine.state %= machine.states.length; + } }; return machine; }; Packer.packHeader = function (meta, data, service, andBody, oldways) { - if (oldways) { - data = data || Buffer.from(' '); + if (oldways && !data) { + data = Buffer.from(' '); } if (data && !Buffer.isBuffer(data)) { data = new Buffer(JSON.stringify(data)); @@ -206,7 +217,8 @@ Packer.packHeader = function (meta, data, service, andBody, oldways) { data = Buffer.from(' '); } - if (service && service !== 'control') { + if (service && -1 === ['control','connection'].indexOf(service)) { + //console.log('end?', service); meta.service = service; } @@ -217,6 +229,13 @@ Packer.packHeader = function (meta, data, service, andBody, oldways) { if (service === 'control') { header = Buffer.from(['', '', '', size, service].join(',')); } + else if (service === 'connection') { + header = Buffer.from([ + meta.family, meta.address, meta.port, size, + 'connection', (meta.serviceport || ''), (meta.name || ''), + (meta.service || '') + ].join(',')); + } else { header = Buffer.from([ meta.family, meta.address, meta.port, size, @@ -323,6 +342,7 @@ var addressNames = [ , 'localAddress' , 'localPort' ]; +/* var sockFuncs = [ 'address' , 'destroy' @@ -333,6 +353,7 @@ var sockFuncs = [ , 'setNoDelay' , 'setTimeout' ]; +*/ // Unlike Packer.Stream.create this should handle all of the events needed to make everything work. Packer.wrapSocket = function (socket) { // node v10.2+ doesn't need a workaround for https://github.com/nodejs/node/issues/8854 diff --git a/package.json b/package.json index f0e959c..dac7301 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "proxy-packer", - "version": "1.5.0", + "version": "2.0.0", "description": "A strategy for packing and unpacking a proxy stream (i.e. packets through a tunnel). Handles multiplexed and tls connections. Used by telebit and telebitd.", "main": "index.js", "scripts": { diff --git a/test/parse.js b/test/parse.js index 25ab0b0..5b48111 100644 --- a/test/parse.js +++ b/test/parse.js @@ -3,48 +3,72 @@ var sni = require('sni'); var hello = require('fs').readFileSync(__dirname + '/sni.hello.bin'); var version = 1; -var address = { - family: 'IPv4' -, address: '127.0.1.1' -, port: 4321 -, service: 'foo-https' -, serviceport: 443 -, name: 'foo-pokemap.hellabit.com' -}; -var header = address.family + ',' + address.address + ',' + address.port + ',' + hello.byteLength - + ',' + (address.service || '') + ',' + (address.serviceport || '') + ',' + (address.name || '') +function getAddress() { + return { + family: 'IPv4' + , address: '127.0.1.1' + , port: 4321 + , service: 'foo-https' + , serviceport: 443 + , name: 'foo-pokemap.hellabit.com' + }; +} +var addr = getAddress(); +var connectionHeader = addr.family + ',' + addr.address + ',' + addr.port + + ',0,connection,' + + (addr.serviceport || '') + ',' + (addr.name || '') + ',' + (addr.service || '') + ; +var header = addr.family + ',' + addr.address + ',' + addr.port + + ',' + hello.byteLength + ',' + (addr.service || '') + ',' + + (addr.serviceport || '') + ',' + (addr.name || '') + ; +var endHeader = addr.family + ',' + addr.address + ',' + addr.port + + ',0,end,' + + (addr.serviceport || '') + ',' + (addr.name || '') ; var buf = Buffer.concat([ - Buffer.from([ 255 - version, header.length ]) + Buffer.from([ 255 - version, connectionHeader.length ]) +, Buffer.from(connectionHeader) +, Buffer.from([ 255 - version, header.length ]) , Buffer.from(header) , hello +, Buffer.from([ 255 - version, endHeader.length ]) +, Buffer.from(endHeader) ]); var services = { 'ssh': 22, 'http': 4080, 'https': 8443 }; var clients = {}; var count = 0; var packer = require('../'); var machine = packer.create({ - onmessage: function (tun) { + onconnection: function (tun) { + console.info(''); + if (!tun.service || 'connection' === tun.service) { + throw new Error("missing service: " + JSON.stringify(tun)); + } + console.info('[onConnection]'); + count += 1; + } +, onmessage: function (tun) { + //console.log('onmessage', tun); var id = tun.family + ',' + tun.address + ',' + tun.port; var service = 'https'; var port = services[service]; var servername = sni(tun.data); - console.log(''); - console.log('[onMessage]'); + console.info('[onMessage]', service, port, servername, tun.data.byteLength); if (!tun.data.equals(hello)) { throw new Error("'data' packet is not equal to original 'hello' packet"); } - console.log('all', tun.data.byteLength, 'bytes are equal'); - console.log('src:', tun.family, tun.address + ':' + tun.port + ':' + tun.serviceport); - console.log('dst:', 'IPv4 127.0.0.1:' + port); + //console.log('all', tun.data.byteLength, 'bytes are equal'); + //console.log('src:', tun.family, tun.address + ':' + tun.port + ':' + tun.serviceport); + //console.log('dst:', 'IPv4 127.0.0.1:' + port); if (!clients[id]) { clients[id] = true; if (!servername) { throw new Error("no servername found for '" + id + "'"); } - console.log("servername: '" + servername + "'", tun.name); + //console.log("servername: '" + servername + "'", tun.name); } count += 1; @@ -53,36 +77,93 @@ var machine = packer.create({ throw new Error("Did not expect onerror"); } , onend: function () { - throw new Error("Did not expect onend"); + console.info('[onEnd]'); + count += 1; } }); -var packed = packer.pack(address, hello); + +var packts, packed; + +packts = []; +packts.push(packer.packHeader(getAddress(), null, 'connection')); +//packts.push(packer.pack(address, hello)); +packts.push(packer.packHeader(getAddress(), hello)); +packts.push(hello); +packts.push(packer.packHeader(getAddress(), null, 'end')); +packed = Buffer.concat(packts); if (!packed.equals(buf)) { + console.error(""); console.error(buf.toString('hex') === packed.toString('hex')); + console.error(""); + console.error("auto-packed:"); console.error(packed.toString('hex'), packed.byteLength); + console.error(""); + console.error("hand-packed:"); console.error(buf.toString('hex'), buf.byteLength); - throw new Error("packer did not pack as expected"); + console.error(""); + throw new Error("packer (new) did not pack as expected"); +} + +packts = []; +packts.push(packer.pack(getAddress(), null, 'connection')); +packts.push(packer.pack(getAddress(), hello)); +//packts.push(packer.packHeader(getAddress(), hello)); +//packts.push(hello); +packts.push(packer.pack(getAddress(), null, 'end')); +packed = Buffer.concat(packts); + +// XXX TODO REMOVE +// +// Nasty fix for short-term backwards-compat +// +// In the old way of doing things we always have at least one byte +// of data (due to a parser bug which has now been fixed) and so +// there are two strings padded with a space which gives the +// data a length of 1 rather than 0 +// +// Here all four of those instances are replaced, but it requires +// maching a few things on either side. +// +// Only 6 bytes are changed - two 1 => 0, four ' ' => '' +var hex = packed.toString('hex') + //.replace(/2c313939/, '2c30') + .replace(/32312c312c636f/, '32312c302c636f') + .replace(/3332312c312c656e64/, '3332312c302c656e64') + .replace(/7320/, '73') + .replace(/20$/, '') + ; +if (hex !== buf.toString('hex')) { + console.error(""); + console.error(buf.toString('hex') === hex); + console.error(""); + console.error("auto-packed:"); + console.error(hex, packed.byteLength); + console.error(""); + console.error("hand-packed:"); + console.error(buf.toString('hex'), buf.byteLength); + console.error(""); + throw new Error("packer (old) did not pack as expected"); } -console.log(''); +console.info(''); // full message in one go // 223 = 2 + 22 + 199 -console.log('[WHOLE BUFFER]', 2, header.length, hello.length, buf.byteLength); +console.info('[WHOLE BUFFER]', 2, header.length, hello.length, buf.byteLength); clients = {}; machine.fns.addChunk(buf); -console.log(''); +console.info(''); // messages one byte at a time -console.log('[BYTE-BY-BYTE BUFFER]', 1); +console.info('[BYTE-BY-BYTE BUFFER]', 1); clients = {}; buf.forEach(function (byte) { machine.fns.addChunk(Buffer.from([ byte ])); }); -console.log(''); +console.info(''); // split messages in overlapping thirds @@ -93,7 +174,7 @@ console.log(''); // 225-247 (22) // 247-446 (199) buf = Buffer.concat([ buf, buf ]); -console.log('[OVERLAPPING BUFFERS]', buf.length); +console.info('[OVERLAPPING BUFFERS]', buf.length); clients = {}; [ buf.slice(0, 7) // version + header , buf.slice(7, 14) // header @@ -106,12 +187,12 @@ clients = {}; ].forEach(function (buf) { machine.fns.addChunk(Buffer.from(buf)); }); -console.log(''); +console.info(''); process.on('exit', function () { - if (count !== 4) { - throw new Error("should have delivered 4 messages, not", count); + if (count !== 12) { + throw new Error("should have delivered 12 messages, not " + count); } - console.log('TESTS PASS'); - console.log(''); + console.info('TESTS PASS'); + console.info(''); });