v2.0.4: make prettier

This commit is contained in:
AJ ONeal 2019-09-03 09:21:08 -06:00
parent e699c44480
commit 2b763f8606
6 changed files with 673 additions and 607 deletions

105
README.md
View File

@ -17,8 +17,7 @@ Browser <--/ \--> Device
It's the kind of thing you'd use to build a poor man's VPN, or port-forward router. It's the kind of thing you'd use to build a poor man's VPN, or port-forward router.
The M-PROXY Protocol # The M-PROXY Protocol
===================
This is similar to "The PROXY Protocol" (a la HAProxy), but desgined for multiplexed tls, http, tcp, and udp This is similar to "The PROXY Protocol" (a la HAProxy), but desgined for multiplexed tls, http, tcp, and udp
tunneled over arbitrary streams (such as WebSockets). tunneled over arbitrary streams (such as WebSockets).
@ -60,8 +59,7 @@ service port (string) The listening port, such as 443. Useful for no
host or server name (string) Useful for services that can be routed by name, such as http, https, smtp, and dns. host or server name (string) Useful for services that can be routed by name, such as http, https, smtp, and dns.
``` ```
Tunneled TCP SNI Packet ## Tunneled TCP SNI Packet
-----------------------
You should see that the result is simply all of the original packet with a leading header. You should see that the result is simply all of the original packet with a leading header.
@ -91,15 +89,13 @@ Note that `16 03 01 00` starts at the 29th byte (at index 28 or 0x1C) instead of
The v1 header uses strings for address and service descriptor information, The v1 header uses strings for address and service descriptor information,
but future versions may be binary. but future versions may be binary.
API # API
===
```js ```js
var Packer = require('proxy-packer'); var Packer = require('proxy-packer');
``` ```
Unpacker / Parser State Machine ## Unpacker / Parser State Machine
-----------------------
The unpacker creates a state machine. The unpacker creates a state machine.
@ -108,52 +104,52 @@ composing a full message with header and data (unless data length is 0).
The state machine progresses through these states: The state machine progresses through these states:
* version - version
* headerLength - headerLength
* header - header
* data - data
At the end of the data event (which may or may not contain a buffer of data) At the end of the data event (which may or may not contain a buffer of data)
one of the appropriate handlers will be called. one of the appropriate handlers will be called.
* control - control
* connection - connection
* message - message
* pause - pause
* resume - resume
* end - end
* error - error
```js ```js
unpacker = Packer.create(handlers); // Create a state machine for unpacking unpacker = Packer.create(handlers); // Create a state machine for unpacking
unpacker.fns.addData(chunk); // process a chunk of data unpacker.fns.addData(chunk); // process a chunk of data
handlers.oncontrol = function (tun) { } // for communicating with the proxy handlers.oncontrol = function(tun) {}; // for communicating with the proxy
// tun.data is an array // tun.data is an array
// '[ -1, "[Error] bad hello" ]' // '[ -1, "[Error] bad hello" ]'
// '[ 0, "[Error] out-of-band error message" ]' // '[ 0, "[Error] out-of-band error message" ]'
// '[ 1, "hello", 254, [ "add_token", "delete_token" ] ]' // '[ 1, "hello", 254, [ "add_token", "delete_token" ] ]'
// '[ 1, "add_token" ]' // '[ 1, "add_token" ]'
// '[ 1, "delete_token" ]' // '[ 1, "delete_token" ]'
handlers.onconnection = function (tun) { } // a client has established a connection handlers.onconnection = function(tun) {}; // a client has established a connection
handlers.onmessage = function (tun) { } // a client has sent a message handlers.onmessage = function(tun) {}; // a client has sent a message
// tun = { family, address, port, data // tun = { family, address, port, data
// , service, serviceport, name }; // , service, serviceport, name };
handlers.onpause = function (tun) { } // proxy requests to pause upload to a client handlers.onpause = function(tun) {}; // proxy requests to pause upload to a client
// tun = { family, address, port }; // tun = { family, address, port };
handlers.onresume = function (tun) { } // proxy requests to resume upload to a client handlers.onresume = function(tun) {}; // proxy requests to resume upload to a client
// tun = { family, address, port }; // tun = { family, address, port };
handlers.onend = function (tun) { } // proxy requests to close a client's socket handlers.onend = function(tun) {}; // proxy requests to close a client's socket
// tun = { family, address, port }; // tun = { family, address, port };
handlers.onerror = function (err) { } // proxy is relaying a client's error handlers.onerror = function(err) {}; // proxy is relaying a client's error
// err = { message, family, address, port }; // err = { message, family, address, port };
``` ```
<!-- <!--
@ -163,18 +159,17 @@ handlers.onconnect = function (tun) { } // a new client has co
--> -->
Packer & Extras ## Packer & Extras
------
Packs header metadata about connection into a buffer (potentially with original data), ready to send. Packs header metadata about connection into a buffer (potentially with original data), ready to send.
```js ```js
var headerAndBody = Packer.pack(tun, data); // Add M-PROXY header to data var headerAndBody = Packer.pack(tun, data); // Add M-PROXY header to data
// tun = { family, address, port // tun = { family, address, port
// , service, serviceport, name } // , service, serviceport, name }
var headerBuf = Packer.packHeader(tun, data); // Same as above, but creates a buffer for header only var headerBuf = Packer.packHeader(tun, data); // Same as above, but creates a buffer for header only
// (data can be converted to a buffer or sent as-is) // (data can be converted to a buffer or sent as-is)
var addr = Packer.socketToAddr(socket); // Probe raw, raw socket for address info var addr = Packer.socketToAddr(socket); // Probe raw, raw socket for address info
@ -187,20 +182,20 @@ var id = Packer.socketToId(socket); // Turn raw, raw socke
```js ```js
var socket = Packer.Stream.wrapSocket(socketOrStream); // workaround for https://github.com/nodejs/node/issues/8854 var socket = Packer.Stream.wrapSocket(socketOrStream); // workaround for https://github.com/nodejs/node/issues/8854
// which was just closed recently, but probably still needs // which was just closed recently, but probably still needs
// something more like this (below) to work as intended // something more like this (below) to work as intended
// https://github.com/findhit/proxywrap/blob/master/lib/proxywrap.js // https://github.com/findhit/proxywrap/blob/master/lib/proxywrap.js
``` ```
```js ```js
var myTransform = Packer.Transform.create({ var myTransform = Packer.Transform.create({
address: { address: {
family: '...' family: '...',
, address: '...' address: '...',
, port: '...' port: '...'
} },
// hint at the service to be used // hint at the service to be used
, service: 'https' service: 'https'
}); });
``` ```
@ -217,6 +212,7 @@ hexdump output.bin
Where `input.json` looks something like this: Where `input.json` looks something like this:
`input.json`: `input.json`:
``` ```
{ "version": 1 { "version": 1
, "address": { , "address": {
@ -231,12 +227,12 @@ Where `input.json` looks something like this:
} }
``` ```
Raw TCP SNI Packet ## Raw TCP SNI Packet
------------------
and `sni.tcp.bin` is any captured tcp packet, such as this one with a tls hello: and `sni.tcp.bin` is any captured tcp packet, such as this one with a tls hello:
`sni.tcp.bin`: `sni.tcp.bin`:
``` ```
0 1 2 3 4 5 6 7 8 9 A B C D D F 0 1 2 3 4 5 6 7 8 9 A B C D D F
0000000 16 03 01 00 c2 01 00 00 be 03 03 57 e3 76 50 66 0000000 16 03 01 00 c2 01 00 00 be 03 03 57 e3 76 50 66
@ -255,8 +251,7 @@ and `sni.tcp.bin` is any captured tcp packet, such as this one with a tls hello:
00000c7 00000c7
``` ```
Tunneled TCP SNI Packet ## Tunneled TCP SNI Packet
-----------------------
You should see that the result is simply all of the original packet with a leading header. You should see that the result is simply all of the original packet with a leading header.

216
index.js
View File

@ -3,36 +3,37 @@
var Packer = module.exports; var Packer = module.exports;
var serviceEvents = { var serviceEvents = {
default: 'tunnelData' default: 'tunnelData',
, connection: 'tunnelConnection' connection: 'tunnelConnection',
, control: 'tunnelControl' control: 'tunnelControl',
, error: 'tunnelError' error: 'tunnelError',
, end: 'tunnelEnd' end: 'tunnelEnd',
, pause: 'tunnelPause' pause: 'tunnelPause',
, resume: 'tunnelResume' resume: 'tunnelResume'
}; };
var serviceFuncs = { var serviceFuncs = {
default: 'onmessage' default: 'onmessage',
, connection: 'onconnection' connection: 'onconnection',
, control: 'oncontrol' control: 'oncontrol',
, error: 'onerror' error: 'onerror',
, end: 'onend' end: 'onend',
, pause: 'onpause' pause: 'onpause',
, resume: 'onresume' resume: 'onresume'
}; };
Packer.create = function (opts) { Packer.create = function(opts) {
var machine; var machine;
if (!opts.onMessage && !opts.onmessage) { if (!opts.onMessage && !opts.onmessage) {
machine = new (require('events').EventEmitter)(); machine = new (require('events')).EventEmitter();
} else { } else {
machine = {}; machine = {};
} }
machine.onmessage = opts.onmessage || opts.onMessage; machine.onmessage = opts.onmessage || opts.onMessage;
machine.oncontrol = opts.oncontrol || opts.onControl; machine.oncontrol = opts.oncontrol || opts.onControl;
machine.onconnection = opts.onconnection || opts.onConnection || function () {}; machine.onconnection =
opts.onconnection || opts.onConnection || function() {};
machine.onerror = opts.onerror || opts.onError; machine.onerror = opts.onerror || opts.onError;
machine.onend = opts.onend || opts.onEnd; machine.onend = opts.onend || opts.onEnd;
machine.onpause = opts.onpause || opts.onPause; machine.onpause = opts.onpause || opts.onPause;
@ -44,9 +45,9 @@ Packer.create = function (opts) {
machine.chunkIndex = 0; machine.chunkIndex = 0;
machine.buf = null; machine.buf = null;
machine.bufIndex = 0; machine.bufIndex = 0;
machine.fns.collectData = function (chunk, size) { machine.fns.collectData = function(chunk, size) {
var chunkLeft = chunk.length - machine.chunkIndex; var chunkLeft = chunk.length - machine.chunkIndex;
var hasLen = (size > 0); var hasLen = size > 0;
if (!hasLen) { if (!hasLen) {
return Buffer.alloc(0); return Buffer.alloc(0);
@ -67,7 +68,10 @@ Packer.create = function (opts) {
// Read and mark as read however much data we need from the chunk to complete our buffer. // Read and mark as read however much data we need from the chunk to complete our buffer.
var partLen = size - machine.bufIndex; var partLen = size - machine.bufIndex;
var part = chunk.slice(machine.chunkIndex, machine.chunkIndex+partLen); var part = chunk.slice(
machine.chunkIndex,
machine.chunkIndex + partLen
);
machine.chunkIndex += partLen; machine.chunkIndex += partLen;
// If we had nothing buffered than the part of the chunk we just read is all we need. // If we had nothing buffered than the part of the chunk we just read is all we need.
@ -84,11 +88,11 @@ Packer.create = function (opts) {
return buf; return buf;
}; };
machine.fns.version = function (chunk) { machine.fns.version = function(chunk) {
//console.log(''); //console.log('');
//console.log('[version]'); //console.log('[version]');
if ((255 - machine._version) !== chunk[machine.chunkIndex]) { if (255 - machine._version !== chunk[machine.chunkIndex]) {
console.error("not v" + machine._version + " (or data is corrupt)"); console.error('not v' + machine._version + ' (or data is corrupt)');
// no idea how to fix this yet // no idea how to fix this yet
} }
machine.chunkIndex += 1; machine.chunkIndex += 1;
@ -96,9 +100,8 @@ Packer.create = function (opts) {
return true; return true;
}; };
machine.headerLen = 0; machine.headerLen = 0;
machine.fns.headerLength = function (chunk) { machine.fns.headerLength = function(chunk) {
//console.log(''); //console.log('');
//console.log('[headerLength]'); //console.log('[headerLength]');
machine.headerLen = chunk[machine.chunkIndex]; machine.headerLen = chunk[machine.chunkIndex];
@ -107,7 +110,7 @@ Packer.create = function (opts) {
return true; return true;
}; };
machine.fns.header = function (chunk) { machine.fns.header = function(chunk) {
//console.log(''); //console.log('');
//console.log('[header]'); //console.log('[header]');
var header = machine.fns.collectData(chunk, machine.headerLen); var header = machine.fns.collectData(chunk, machine.headerLen);
@ -132,7 +135,7 @@ Packer.create = function (opts) {
return true; return true;
}; };
machine.fns.data = function (chunk) { machine.fns.data = function(chunk) {
//console.log(''); //console.log('');
//console.log('[data]'); //console.log('[data]');
var data; var data;
@ -146,7 +149,6 @@ Packer.create = function (opts) {
} }
} }
// //
// data, end, error // data, end, error
// //
@ -154,7 +156,7 @@ Packer.create = function (opts) {
if ('error' === machine.service) { if ('error' === machine.service) {
try { try {
msg = JSON.parse(data.toString()); msg = JSON.parse(data.toString());
} catch(e) { } catch (e) {
msg.message = 'e:' + JSON.stringify(data); msg.message = 'e:' + JSON.stringify(data);
msg.code = 'E_UNKNOWN_ERR'; msg.code = 'E_UNKNOWN_ERR';
} }
@ -174,9 +176,15 @@ Packer.create = function (opts) {
//console.log('msn', machine.service); //console.log('msn', machine.service);
if (machine.emit) { if (machine.emit) {
machine.emit(serviceEvents[machine.service] || serviceEvents[msg.service] || serviceEvents.default); machine.emit(
serviceEvents[machine.service] ||
serviceEvents[msg.service] ||
serviceEvents.default
);
} else { } else {
(machine[serviceFuncs[machine.service]] || machine[serviceFuncs[msg.service]] || machine[serviceFuncs.default])(msg); (machine[serviceFuncs[machine.service]] ||
machine[serviceFuncs[msg.service]] ||
machine[serviceFuncs.default])(msg);
} }
return true; return true;
@ -184,7 +192,7 @@ Packer.create = function (opts) {
machine.state = 0; machine.state = 0;
machine.states = ['version', 'headerLength', 'header', 'data']; machine.states = ['version', 'headerLength', 'header', 'data'];
machine.fns.addChunk = function (chunk) { machine.fns.addChunk = function(chunk) {
//console.log(''); //console.log('');
//console.log('[addChunk]'); //console.log('[addChunk]');
machine.chunkIndex = 0; machine.chunkIndex = 0;
@ -197,7 +205,7 @@ Packer.create = function (opts) {
} }
} }
if ('data' === machine.states[machine.state] && 0 === machine.bodyLen) { if ('data' === machine.states[machine.state] && 0 === machine.bodyLen) {
machine.fns[machine.states[machine.state]](chunk) machine.fns[machine.states[machine.state]](chunk);
machine.state += 1; machine.state += 1;
machine.state %= machine.states.length; machine.state %= machine.states.length;
} }
@ -206,7 +214,7 @@ Packer.create = function (opts) {
return machine; return machine;
}; };
Packer.packHeader = function (meta, data, service, andBody, oldways) { Packer.packHeader = function(meta, data, service, andBody, oldways) {
if (oldways && !data) { if (oldways && !data) {
data = Buffer.from(' '); data = Buffer.from(' ');
} }
@ -217,41 +225,57 @@ Packer.packHeader = function (meta, data, service, andBody, oldways) {
data = Buffer.from(' '); data = Buffer.from(' ');
} }
if (service && -1 === ['control','connection'].indexOf(service)) { if (service && -1 === ['control', 'connection'].indexOf(service)) {
//console.log('end?', service); //console.log('end?', service);
meta.service = service; meta.service = service;
} }
var size = data && data.byteLength || 0; var size = (data && data.byteLength) || 0;
var sizeReserve = andBody ? size : 0; var sizeReserve = andBody ? size : 0;
var version = 1; var version = 1;
var header; var header;
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 {
header = Buffer.from(
[
meta.family,
meta.address,
meta.port,
size,
meta.service || '',
meta.serviceport || '',
meta.name || ''
].join(',')
);
} }
else if (service === 'connection') { var metaBuf = Buffer.from([255 - version, header.length]);
header = Buffer.from([ var buf = Buffer.alloc(
meta.family, meta.address, meta.port, size, metaBuf.byteLength + header.byteLength + sizeReserve
'connection', (meta.serviceport || ''), (meta.name || ''), );
(meta.service || '')
].join(','));
}
else {
header = Buffer.from([
meta.family, meta.address, meta.port, size,
(meta.service || ''), (meta.serviceport || ''), (meta.name || '')
].join(','));
}
var metaBuf = Buffer.from([ 255 - version, header.length ]);
var buf = Buffer.alloc(metaBuf.byteLength + header.byteLength + sizeReserve);
metaBuf.copy(buf, 0); metaBuf.copy(buf, 0);
header.copy(buf, 2); header.copy(buf, 2);
if (sizeReserve) { data.copy(buf, 2 + header.byteLength); } if (sizeReserve) {
data.copy(buf, 2 + header.byteLength);
}
return buf; return buf;
}; };
Packer.pack = function (meta, data, service) { Packer.pack = function(meta, data, service) {
return Packer.packHeader(meta, data, service, true, true); return Packer.packHeader(meta, data, service, true, true);
}; };
@ -259,39 +283,40 @@ function extractSocketProps(socket, propNames) {
var props = {}; var props = {};
if (socket.remotePort) { if (socket.remotePort) {
propNames.forEach(function (propName) { propNames.forEach(function(propName) {
props[propName] = socket[propName]; props[propName] = socket[propName];
}); });
} else if (socket._remotePort) { } else if (socket._remotePort) {
propNames.forEach(function (propName) { propNames.forEach(function(propName) {
props[propName] = socket['_' + propName]; props[propName] = socket['_' + propName];
}); });
} else if (socket._handle) { } else if (socket._handle) {
if ( if (
socket._handle._parent socket._handle._parent &&
&& socket._handle._parent.owner socket._handle._parent.owner &&
&& socket._handle._parent.owner.stream socket._handle._parent.owner.stream &&
&& socket._handle._parent.owner.stream.remotePort socket._handle._parent.owner.stream.remotePort
) { ) {
propNames.forEach(function (propName) { propNames.forEach(function(propName) {
props[propName] = socket._handle._parent.owner.stream[propName]; props[propName] = socket._handle._parent.owner.stream[propName];
}); });
} else if ( } else if (
socket._handle._parentWrap socket._handle._parentWrap &&
&& socket._handle._parentWrap.remotePort socket._handle._parentWrap.remotePort
) { ) {
propNames.forEach(function (propName) { propNames.forEach(function(propName) {
props[propName] = socket._handle._parentWrap[propName]; props[propName] = socket._handle._parentWrap[propName];
}); });
} else if ( } else if (
socket._handle._parentWrap socket._handle._parentWrap &&
&& socket._handle._parentWrap._handle socket._handle._parentWrap._handle &&
&& socket._handle._parentWrap._handle.owner socket._handle._parentWrap._handle.owner &&
&& socket._handle._parentWrap._handle.owner.stream socket._handle._parentWrap._handle.owner.stream &&
&& socket._handle._parentWrap._handle.owner.stream.remotePort socket._handle._parentWrap._handle.owner.stream.remotePort
) { ) {
propNames.forEach(function (propName) { propNames.forEach(function(propName) {
props[propName] = socket._handle._parentWrap._handle.owner.stream[propName]; props[propName] =
socket._handle._parentWrap._handle.owner.stream[propName];
}); });
} }
} }
@ -306,41 +331,46 @@ function extractSocketProp(socket, propName) {
try { try {
value = value || socket._handle._parentWrap[propName]; value = value || socket._handle._parentWrap[propName];
value = value || socket._handle._parentWrap._handle.owner.stream[propName]; value =
value || socket._handle._parentWrap._handle.owner.stream[propName];
} catch (e) {} } catch (e) {}
return value || ''; return value || '';
} }
Packer.socketToAddr = function (socket) { Packer.socketToAddr = function(socket) {
// TODO BUG XXX // TODO BUG XXX
// https://github.com/nodejs/node/issues/8854 // https://github.com/nodejs/node/issues/8854
// tlsSocket.remoteAddress = remoteAddress; // causes core dump // tlsSocket.remoteAddress = remoteAddress; // causes core dump
// console.log(tlsSocket.remoteAddress); // console.log(tlsSocket.remoteAddress);
var props = extractSocketProps(socket, [ 'remoteFamily', 'remoteAddress', 'remotePort', 'localPort' ]); var props = extractSocketProps(socket, [
'remoteFamily',
'remoteAddress',
'remotePort',
'localPort'
]);
return { return {
family: props.remoteFamily family: props.remoteFamily,
, address: props.remoteAddress address: props.remoteAddress,
, port: props.remotePort port: props.remotePort,
, serviceport: props.localPort serviceport: props.localPort
}; };
}; };
Packer.addrToId = function (address) { Packer.addrToId = function(address) {
return address.family + ',' + address.address + ',' + address.port; return address.family + ',' + address.address + ',' + address.port;
}; };
Packer.socketToId = function (socket) { Packer.socketToId = function(socket) {
return Packer.addrToId(Packer.socketToAddr(socket)); return Packer.addrToId(Packer.socketToAddr(socket));
}; };
var addressNames = [ var addressNames = [
'remoteAddress' 'remoteAddress',
, 'remotePort' 'remotePort',
, 'remoteFamily' 'remoteFamily',
, 'localAddress' 'localAddress',
, 'localPort' 'localPort'
]; ];
/* /*
var sockFuncs = [ var sockFuncs = [
@ -355,9 +385,9 @@ var sockFuncs = [
]; ];
*/ */
// 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
addressNames.forEach(function (name) { addressNames.forEach(function(name) {
Object.defineProperty(socket, name, { Object.defineProperty(socket, name, {
enumerable: false, enumerable: false,
configurable: true, configurable: true,
@ -429,7 +459,7 @@ function MyTransform(options) {
} }
util.inherits(MyTransform, Transform); util.inherits(MyTransform, Transform);
MyTransform.prototype._transform = function (data, encoding, callback) { MyTransform.prototype._transform = function(data, encoding, callback) {
var address = this.__my_addr; var address = this.__my_addr;
address.service = address.service || this.__my_service; address.service = address.service || this.__my_service;
@ -441,11 +471,11 @@ MyTransform.prototype._transform = function (data, encoding, callback) {
Packer.Stream = {}; Packer.Stream = {};
var Dup = { var Dup = {
write: function (chunk, encoding, cb) { write: function(chunk, encoding, cb) {
//console.log('_write', chunk.byteLength); //console.log('_write', chunk.byteLength);
this.__my_socket.write(chunk, encoding, cb); this.__my_socket.write(chunk, encoding, cb);
} },
, read: function (size) { read: function(size) {
//console.log('_read'); //console.log('_read');
var x = this.__my_socket.read(size); var x = this.__my_socket.read(size);
if (x) { if (x) {
@ -454,7 +484,7 @@ var Dup = {
} }
} }
}; };
Packer.Stream.create = function (socket) { Packer.Stream.create = function(socket) {
if (!Packer.Stream.warned) { if (!Packer.Stream.warned) {
console.warn('`Stream.create` deprecated, use `wrapSocket` instead'); console.warn('`Stream.create` deprecated, use `wrapSocket` instead');
Packer.Stream.warned = true; Packer.Stream.warned = true;
@ -488,7 +518,7 @@ Packer.Stream.create = function (socket) {
}; };
Packer.Transform = {}; Packer.Transform = {};
Packer.Transform.create = function (opts) { Packer.Transform.create = function(opts) {
// Note: service refers to the port that the incoming request was from, // Note: service refers to the port that the incoming request was from,
// if known (smtps, smtp, https, http, etc) // if known (smtps, smtp, https, http, etc)
// { address: '127.0.0.1', service: 'https' } // { address: '127.0.0.1', service: 'https' }

View File

@ -1,6 +1,6 @@
{ {
"name": "proxy-packer", "name": "proxy-packer",
"version": "2.0.3", "version": "2.0.4",
"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

@ -1,10 +1,11 @@
{ "version": 1 {
, "address": { "version": 1,
"family": "IPv4" "address": {
, "address": "127.0.1.1" "family": "IPv4",
, "port": 4321 "address": "127.0.1.1",
, "service": "https" "port": 4321,
, "serviceport": 443 "service": "https",
} "serviceport": 443
, "filepath": "./sni.hello.bin" },
"filepath": "./sni.hello.bin"
} }

View File

@ -1,28 +1,31 @@
;(function () { (function() {
'use strict'; 'use strict';
var fs = require('fs'); var fs = require('fs');
var infile = process.argv[2]; var infile = process.argv[2];
var outfile = process.argv[3]; var outfile = process.argv[3];
var sni = require('sni'); var sni = require('sni');
if (!infile || !outfile) { if (!infile || !outfile) {
console.error("Usage:"); console.error('Usage:');
console.error("node test/pack.js test/input.json test/output.bin"); console.error('node test/pack.js test/input.json test/output.bin');
process.exit(1); process.exit(1);
return; return;
} }
var path = require('path'); var path = require('path');
var json = JSON.parse(fs.readFileSync(infile, 'utf8')); var json = JSON.parse(fs.readFileSync(infile, 'utf8'));
var data = require('fs').readFileSync(path.resolve(path.dirname(infile), json.filepath), null); var data = require('fs').readFileSync(
var Packer = require('../index.js'); path.resolve(path.dirname(infile), json.filepath),
null
);
var Packer = require('../index.js');
var servername = sni(data); var servername = sni(data);
var m = data.toString().match(/(?:^|[\r\n])Host: ([^\r\n]+)[\r\n]*/im); var m = data.toString().match(/(?:^|[\r\n])Host: ([^\r\n]+)[\r\n]*/im);
var hostname = (m && m[1].toLowerCase() || '').split(':')[0]; var hostname = ((m && m[1].toLowerCase()) || '').split(':')[0];
/* /*
function pack() { function pack() {
var version = json.version; var version = json.version;
var address = json.address; var address = json.address;
@ -37,9 +40,16 @@ function pack() {
} }
*/ */
json.address.name = servername || hostname; json.address.name = servername || hostname;
var buf = Packer.pack(json.address, data); var buf = Packer.pack(json.address, data);
fs.writeFileSync(outfile, buf, null); fs.writeFileSync(outfile, buf, null);
console.log("wrote " + buf.byteLength + " bytes to '" + outfile + "' ('hexdump " + outfile + "' to inspect)"); console.log(
'wrote ' +
}()); buf.byteLength +
" bytes to '" +
outfile +
"' ('hexdump " +
outfile +
"' to inspect)"
);
})();

View File

@ -5,59 +5,91 @@ var hello = require('fs').readFileSync(__dirname + '/sni.hello.bin');
var version = 1; var version = 1;
function getAddress() { function getAddress() {
return { 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 addr = getAddress(); var addr = getAddress();
var connectionHeader = addr.family + ',' + addr.address + ',' + addr.port var connectionHeader =
+ ',0,connection,' addr.family +
+ (addr.serviceport || '') + ',' + (addr.name || '') + ',' + (addr.service || '') ',' +
; addr.address +
var header = addr.family + ',' + addr.address + ',' + addr.port ',' +
+ ',' + hello.byteLength + ',' + (addr.service || '') + ',' addr.port +
+ (addr.serviceport || '') + ',' + (addr.name || '') ',0,connection,' +
; (addr.serviceport || '') +
var endHeader = addr.family + ',' + addr.address + ',' + addr.port ',' +
+ ',0,end,' (addr.name || '') +
+ (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, connectionHeader.length ]) Buffer.from([255 - version, connectionHeader.length]),
, Buffer.from(connectionHeader) Buffer.from(connectionHeader),
, Buffer.from([ 255 - version, header.length ]) Buffer.from([255 - version, header.length]),
, Buffer.from(header) Buffer.from(header),
, hello hello,
, Buffer.from([ 255 - version, endHeader.length ]) Buffer.from([255 - version, endHeader.length]),
, Buffer.from(endHeader) 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({
onconnection: function (tun) { onconnection: function(tun) {
console.info(''); console.info('');
if (!tun.service || 'connection' === tun.service) { if (!tun.service || 'connection' === tun.service) {
throw new Error("missing service: " + JSON.stringify(tun)); throw new Error('missing service: ' + JSON.stringify(tun));
} }
console.info('[onConnection]'); console.info('[onConnection]');
count += 1; count += 1;
} },
, onmessage: function (tun) { onmessage: function(tun) {
//console.log('onmessage', 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.info('[onMessage]', service, port, servername, tun.data.byteLength); console.info(
'[onMessage]',
service,
port,
servername,
tun.data.byteLength
);
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);
@ -72,11 +104,11 @@ var machine = packer.create({
} }
count += 1; count += 1;
} },
, onerror: function () { onerror: function() {
throw new Error("Did not expect onerror"); throw new Error('Did not expect onerror');
} },
, onend: function () { onend: function() {
console.info('[onEnd]'); console.info('[onEnd]');
count += 1; count += 1;
} }
@ -93,16 +125,16 @@ packts.push(packer.packHeader(getAddress(), null, 'end'));
packed = Buffer.concat(packts); packed = Buffer.concat(packts);
if (!packed.equals(buf)) { if (!packed.equals(buf)) {
console.error(""); console.error('');
console.error(buf.toString('hex') === packed.toString('hex')); console.error(buf.toString('hex') === packed.toString('hex'));
console.error(""); console.error('');
console.error("auto-packed:"); console.error('auto-packed:');
console.error(packed.toString('hex'), packed.byteLength); console.error(packed.toString('hex'), packed.byteLength);
console.error(""); console.error('');
console.error("hand-packed:"); console.error('hand-packed:');
console.error(buf.toString('hex'), buf.byteLength); console.error(buf.toString('hex'), buf.byteLength);
console.error(""); console.error('');
throw new Error("packer (new) did not pack as expected"); throw new Error('packer (new) did not pack as expected');
} }
packts = []; packts = [];
@ -126,27 +158,26 @@ packed = Buffer.concat(packts);
// maching a few things on either side. // maching a few things on either side.
// //
// Only 6 bytes are changed - two 1 => 0, four ' ' => '' // Only 6 bytes are changed - two 1 => 0, four ' ' => ''
var hex = packed.toString('hex') var hex = packed
.toString('hex')
//.replace(/2c313939/, '2c30') //.replace(/2c313939/, '2c30')
.replace(/32312c312c636f/, '32312c302c636f') .replace(/32312c312c636f/, '32312c302c636f')
.replace(/3332312c312c656e64/, '3332312c302c656e64') .replace(/3332312c312c656e64/, '3332312c302c656e64')
.replace(/7320/, '73') .replace(/7320/, '73')
.replace(/20$/, '') .replace(/20$/, '');
;
if (hex !== buf.toString('hex')) { if (hex !== buf.toString('hex')) {
console.error(""); console.error('');
console.error(buf.toString('hex') === hex); console.error(buf.toString('hex') === hex);
console.error(""); console.error('');
console.error("auto-packed:"); console.error('auto-packed:');
console.error(hex, packed.byteLength); console.error(hex, packed.byteLength);
console.error(""); console.error('');
console.error("hand-packed:"); console.error('hand-packed:');
console.error(buf.toString('hex'), buf.byteLength); console.error(buf.toString('hex'), buf.byteLength);
console.error(""); console.error('');
throw new Error("packer (old) did not pack as expected"); throw new Error('packer (old) did not pack as expected');
} }
console.info(''); console.info('');
// full message in one go // full message in one go
@ -156,16 +187,14 @@ clients = {};
machine.fns.addChunk(buf); machine.fns.addChunk(buf);
console.info(''); console.info('');
// messages one byte at a time // messages one byte at a time
console.info('[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.info(''); console.info('');
// split messages in overlapping thirds // split messages in overlapping thirds
// 0-2 (2) // 0-2 (2)
// 2-24 (22) // 2-24 (22)
@ -173,25 +202,26 @@ console.info('');
// 223-225 (2) // 223-225 (2)
// 225-247 (22) // 225-247 (22)
// 247-446 (199) // 247-446 (199)
buf = Buffer.concat([ buf, buf ]); buf = Buffer.concat([buf, buf]);
console.info('[OVERLAPPING BUFFERS]', buf.length); console.info('[OVERLAPPING BUFFERS]', buf.length);
clients = {}; clients = {};
[ buf.slice(0, 7) // version + header [
, buf.slice(7, 14) // header buf.slice(0, 7), // version + header
, buf.slice(14, 21) // header buf.slice(7, 14), // header
, buf.slice(21, 28) // header + body buf.slice(14, 21), // header
, buf.slice(28, 217) // body buf.slice(21, 28), // header + body
, buf.slice(217, 224) // body + version buf.slice(28, 217), // body
, buf.slice(224, 238) // version + header buf.slice(217, 224), // body + version
, buf.slice(238, buf.byteLength) // header + body buf.slice(224, 238), // version + header
].forEach(function (buf) { buf.slice(238, buf.byteLength) // header + body
].forEach(function(buf) {
machine.fns.addChunk(Buffer.from(buf)); machine.fns.addChunk(Buffer.from(buf));
}); });
console.info(''); console.info('');
process.on('exit', function () { process.on('exit', function() {
if (count !== 12) { if (count !== 12) {
throw new Error("should have delivered 12 messages, not " + count); throw new Error('should have delivered 12 messages, not ' + count);
} }
console.info('TESTS PASS'); console.info('TESTS PASS');
console.info(''); console.info('');