v2.0.0: add connection event, fix 0-byte data parser issue

This commit is contained in:
AJ ONeal 2018-08-07 03:44:15 -06:00
parent ff93145be2
commit ece89be3dd
3 changed files with 140 additions and 38 deletions

View File

@ -126,6 +126,7 @@ Packer.create = function (opts) {
machine.service = machine._headers[4]; machine.service = machine._headers[4];
machine.serviceport = machine._headers[5]; machine.serviceport = machine._headers[5];
machine.name = machine._headers[6]; machine.name = machine._headers[6];
machine.servicename = machine._headers[7];
//console.log('machine.service', machine.service); //console.log('machine.service', machine.service);
return true; return true;
@ -167,10 +168,15 @@ Packer.create = function (opts) {
msg.name = machine.name; msg.name = machine.name;
msg.data = data; msg.data = data;
if ('connection' === machine.service) {
msg.service = machine.servicename;
}
//console.log('msn', machine.service);
if (machine.emit) { if (machine.emit) {
machine.emit(serviceEvents[msg.service] || serviceEvents.default); machine.emit(serviceEvents[machine.service] || serviceEvents[msg.service] || serviceEvents.default);
} else { } else {
(machine[serviceFuncs[msg.service]] || machine[serviceFuncs.default])(msg); (machine[serviceFuncs[machine.service]] || machine[serviceFuncs[msg.service]] || machine[serviceFuncs.default])(msg);
} }
return true; return true;
@ -190,14 +196,19 @@ Packer.create = function (opts) {
machine.state %= machine.states.length; 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; return machine;
}; };
Packer.packHeader = function (meta, data, service, andBody, oldways) { Packer.packHeader = function (meta, data, service, andBody, oldways) {
if (oldways) { if (oldways && !data) {
data = data || Buffer.from(' '); data = Buffer.from(' ');
} }
if (data && !Buffer.isBuffer(data)) { if (data && !Buffer.isBuffer(data)) {
data = new Buffer(JSON.stringify(data)); data = new Buffer(JSON.stringify(data));
@ -206,7 +217,8 @@ Packer.packHeader = function (meta, data, service, andBody, oldways) {
data = Buffer.from(' '); data = Buffer.from(' ');
} }
if (service && service !== 'control') { if (service && -1 === ['control','connection'].indexOf(service)) {
//console.log('end?', service);
meta.service = service; meta.service = service;
} }
@ -217,6 +229,13 @@ Packer.packHeader = function (meta, data, service, andBody, oldways) {
if (service === 'control') { if (service === 'control') {
header = Buffer.from(['', '', '', size, service].join(',')); 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 { else {
header = Buffer.from([ header = Buffer.from([
meta.family, meta.address, meta.port, size, meta.family, meta.address, meta.port, size,
@ -323,6 +342,7 @@ var addressNames = [
, 'localAddress' , 'localAddress'
, 'localPort' , 'localPort'
]; ];
/*
var sockFuncs = [ var sockFuncs = [
'address' 'address'
, 'destroy' , 'destroy'
@ -333,6 +353,7 @@ var sockFuncs = [
, 'setNoDelay' , 'setNoDelay'
, 'setTimeout' , 'setTimeout'
]; ];
*/
// Unlike Packer.Stream.create this should handle all of the events needed to make everything work. // Unlike Packer.Stream.create this should handle all of the events needed to make everything work.
Packer.wrapSocket = function (socket) { Packer.wrapSocket = function (socket) {
// node v10.2+ doesn't need a workaround for https://github.com/nodejs/node/issues/8854 // node v10.2+ doesn't need a workaround for https://github.com/nodejs/node/issues/8854

View File

@ -1,6 +1,6 @@
{ {
"name": "proxy-packer", "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.", "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", "main": "index.js",
"scripts": { "scripts": {

View File

@ -3,48 +3,72 @@
var sni = require('sni'); var sni = require('sni');
var hello = require('fs').readFileSync(__dirname + '/sni.hello.bin'); var hello = require('fs').readFileSync(__dirname + '/sni.hello.bin');
var version = 1; var version = 1;
var address = { function getAddress() {
return {
family: 'IPv4' family: 'IPv4'
, address: '127.0.1.1' , address: '127.0.1.1'
, port: 4321 , port: 4321
, service: 'foo-https' , service: 'foo-https'
, serviceport: 443 , serviceport: 443
, name: 'foo-pokemap.hellabit.com' , name: 'foo-pokemap.hellabit.com'
}; };
var header = address.family + ',' + address.address + ',' + address.port + ',' + hello.byteLength }
+ ',' + (address.service || '') + ',' + (address.serviceport || '') + ',' + (address.name || '') 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([ 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) , Buffer.from(header)
, hello , hello
, Buffer.from([ 255 - version, endHeader.length ])
, Buffer.from(endHeader)
]); ]);
var services = { 'ssh': 22, 'http': 4080, 'https': 8443 }; var services = { 'ssh': 22, 'http': 4080, 'https': 8443 };
var clients = {}; var clients = {};
var count = 0; var count = 0;
var packer = require('../'); var packer = require('../');
var machine = packer.create({ 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 id = tun.family + ',' + tun.address + ',' + tun.port;
var service = 'https'; var service = 'https';
var port = services[service]; var port = services[service];
var servername = sni(tun.data); var servername = sni(tun.data);
console.log(''); console.info('[onMessage]', service, port, servername, tun.data.byteLength);
console.log('[onMessage]');
if (!tun.data.equals(hello)) { if (!tun.data.equals(hello)) {
throw new Error("'data' packet is not equal to original 'hello' packet"); throw new Error("'data' packet is not equal to original 'hello' packet");
} }
console.log('all', tun.data.byteLength, 'bytes are equal'); //console.log('all', tun.data.byteLength, 'bytes are equal');
console.log('src:', tun.family, tun.address + ':' + tun.port + ':' + tun.serviceport); //console.log('src:', tun.family, tun.address + ':' + tun.port + ':' + tun.serviceport);
console.log('dst:', 'IPv4 127.0.0.1:' + port); //console.log('dst:', 'IPv4 127.0.0.1:' + port);
if (!clients[id]) { if (!clients[id]) {
clients[id] = true; clients[id] = true;
if (!servername) { if (!servername) {
throw new Error("no servername found for '" + id + "'"); throw new Error("no servername found for '" + id + "'");
} }
console.log("servername: '" + servername + "'", tun.name); //console.log("servername: '" + servername + "'", tun.name);
} }
count += 1; count += 1;
@ -53,36 +77,93 @@ var machine = packer.create({
throw new Error("Did not expect onerror"); throw new Error("Did not expect onerror");
} }
, onend: function () { , 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)) { if (!packed.equals(buf)) {
console.error("");
console.error(buf.toString('hex') === packed.toString('hex')); console.error(buf.toString('hex') === packed.toString('hex'));
console.error("");
console.error("auto-packed:");
console.error(packed.toString('hex'), packed.byteLength); console.error(packed.toString('hex'), packed.byteLength);
console.error("");
console.error("hand-packed:");
console.error(buf.toString('hex'), buf.byteLength); 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 // full message in one go
// 223 = 2 + 22 + 199 // 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 = {}; clients = {};
machine.fns.addChunk(buf); machine.fns.addChunk(buf);
console.log(''); console.info('');
// messages one byte at a time // messages one byte at a time
console.log('[BYTE-BY-BYTE BUFFER]', 1); console.info('[BYTE-BY-BYTE BUFFER]', 1);
clients = {}; clients = {};
buf.forEach(function (byte) { buf.forEach(function (byte) {
machine.fns.addChunk(Buffer.from([ byte ])); machine.fns.addChunk(Buffer.from([ byte ]));
}); });
console.log(''); console.info('');
// split messages in overlapping thirds // split messages in overlapping thirds
@ -93,7 +174,7 @@ console.log('');
// 225-247 (22) // 225-247 (22)
// 247-446 (199) // 247-446 (199)
buf = Buffer.concat([ buf, buf ]); buf = Buffer.concat([ buf, buf ]);
console.log('[OVERLAPPING BUFFERS]', buf.length); console.info('[OVERLAPPING BUFFERS]', buf.length);
clients = {}; clients = {};
[ buf.slice(0, 7) // version + header [ buf.slice(0, 7) // version + header
, buf.slice(7, 14) // header , buf.slice(7, 14) // header
@ -106,12 +187,12 @@ clients = {};
].forEach(function (buf) { ].forEach(function (buf) {
machine.fns.addChunk(Buffer.from(buf)); machine.fns.addChunk(Buffer.from(buf));
}); });
console.log(''); console.info('');
process.on('exit', function () { process.on('exit', function () {
if (count !== 4) { if (count !== 12) {
throw new Error("should have delivered 4 messages, not", count); throw new Error("should have delivered 12 messages, not " + count);
} }
console.log('TESTS PASS'); console.info('TESTS PASS');
console.log(''); console.info('');
}); });