update docs and cleanup files
This commit is contained in:
		
							parent
							
								
									175286e791
								
							
						
					
					
						commit
						6b2b9607ec
					
				
							
								
								
									
										131
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										131
									
								
								README.md
									
									
									
									
									
								
							@ -2,32 +2,123 @@
 | 
			
		||||
 | 
			
		||||
| Sponsored by [ppl](https://ppl.family) |
 | 
			
		||||
 | 
			
		||||
A strategy for packing and unpacking tunneled network messages (or any stream) in node.js
 | 
			
		||||
"The M-PROXY Protocol" for node.js
 | 
			
		||||
 | 
			
		||||
Examples
 | 
			
		||||
A strategy for packing and unpacking multiplexed streams.
 | 
			
		||||
<small>Where you have distinct clients on one side trying to reach distinct servers on the other.</small>
 | 
			
		||||
 | 
			
		||||
```
 | 
			
		||||
Browser <--\                   /--> Device
 | 
			
		||||
Browser <---- M-PROXY Service ----> Device
 | 
			
		||||
Browser <--/                   \--> Device
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
It's the kind of thing you'd use to build a poor man's VPN, or port-forward router.
 | 
			
		||||
 | 
			
		||||
The M-PROXY Protocol
 | 
			
		||||
===================
 | 
			
		||||
 | 
			
		||||
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).
 | 
			
		||||
 | 
			
		||||
It also has a backchannel for communicating with the proxy itself.
 | 
			
		||||
 | 
			
		||||
Each message has a header with a socket identifier (family, addr, port), and may have additional information.
 | 
			
		||||
 | 
			
		||||
```
 | 
			
		||||
<version><headerlen><family>,<address>,<port>,<datalen>,<service>,<port>,<name>
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
```
 | 
			
		||||
<254><45>IPv4,127.0.1.1,4321,199,https,443,example.com
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
```
 | 
			
		||||
version                  (8 bits) 254 is version 1
 | 
			
		||||
 | 
			
		||||
header length            (8 bits) the remaining length of the header before data begins
 | 
			
		||||
 | 
			
		||||
                                  These values are used to identify a specific client among many
 | 
			
		||||
socket family            (string) the IPv4 or IPv6 connection from a client
 | 
			
		||||
socket address           (string) the x.x.x.x remote address of the client
 | 
			
		||||
socket port              (string) the 1-65536 source port of the remote client
 | 
			
		||||
 | 
			
		||||
data length              (string) the number of bytes in the wrapped packet, in case the network re-chunks the packet
 | 
			
		||||
 | 
			
		||||
                                  These optional values can be very useful at the start of a new connection
 | 
			
		||||
service name             (string) Either a standard service name (port + protocol), such as 'https'
 | 
			
		||||
                                  as listed in /etc/services, otherwise 'tls', 'tcp', or 'udp' for generics
 | 
			
		||||
                                  Also 'control' is used for messages to the proxy (such as 'pause' events)
 | 
			
		||||
service port             (string) The listening port, such as 443. Useful for non-standard or dynamic services.
 | 
			
		||||
host or server name      (string) Useful for services that can be routed by name, such as http, https, smtp, and dns.
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
v1 is text-based. Future versions may be binary.
 | 
			
		||||
 | 
			
		||||
API
 | 
			
		||||
===
 | 
			
		||||
 | 
			
		||||
```js
 | 
			
		||||
var Packer = require('proxy-packer');
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
Packer.create({
 | 
			
		||||
  onmessage: function (msg) {
 | 
			
		||||
    // msg = { family, address, port, service, data };
 | 
			
		||||
  }
 | 
			
		||||
, onend: function (msg) {
 | 
			
		||||
    // msg = { family, address, port };
 | 
			
		||||
  }
 | 
			
		||||
, onerror: function (err) {
 | 
			
		||||
```js
 | 
			
		||||
unpacker = Packer.create(handlers);                       // Create a state machine for unpacking
 | 
			
		||||
 | 
			
		||||
handlers.oncontrol = function (tun) { }                   // for communicating with the proxy
 | 
			
		||||
                                                          // tun.data is an array
 | 
			
		||||
                                                          //     '[ -1, "[Error] bad hello" ]'
 | 
			
		||||
                                                          //     '[ 0, "[Error] out-of-band error message" ]'
 | 
			
		||||
                                                          //     '[ 1, "hello", 254, [ "add_token", "delete_token" ] ]'
 | 
			
		||||
                                                          //     '[ 1, "add_token" ]'
 | 
			
		||||
                                                          //     '[ 1, "delete_token" ]'
 | 
			
		||||
 | 
			
		||||
handlers.onmessage = function (tun) { }                   // a client has sent a message
 | 
			
		||||
                                                          // tun = { family, address, port, data
 | 
			
		||||
                                                          //       , service, serviceport, name };
 | 
			
		||||
 | 
			
		||||
handlers.onpause = function (tun) { }                     // proxy requests to pause upload to a client
 | 
			
		||||
                                                          // tun = { family, address, port };
 | 
			
		||||
 | 
			
		||||
handlers.onresume = function (tun) { }                    // proxy requests to resume upload to a client
 | 
			
		||||
                                                          // tun = { family, address, port };
 | 
			
		||||
 | 
			
		||||
handlers.onend = function (tun) { }                       // proxy requests to close a client's socket
 | 
			
		||||
                                                          // tun = { family, address, port };
 | 
			
		||||
 | 
			
		||||
handlers.onerror = function (err) { }                     // proxy is relaying a client's error
 | 
			
		||||
                                                          // err = { message, family, address, port };
 | 
			
		||||
  }
 | 
			
		||||
});
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
var chunk = Packer.pack(address, data, service);
 | 
			
		||||
var addr = Packer.socketToAddr(socket);
 | 
			
		||||
var id = Packer.addrToId(address);
 | 
			
		||||
var id = Packer.socketToId(socket);
 | 
			
		||||
<!--
 | 
			
		||||
TODO
 | 
			
		||||
 | 
			
		||||
var myDuplex = Packer.Stream.create(socketOrStream);
 | 
			
		||||
handlers.onconnect = function (tun) { }                   // a new client has connected
 | 
			
		||||
 | 
			
		||||
-->
 | 
			
		||||
 | 
			
		||||
```js
 | 
			
		||||
var chunk = Packer.pack(tun, data);                       // Add M-PROXY header to data
 | 
			
		||||
                                                          // tun = { family, address, port
 | 
			
		||||
                                                          //       , service, serviceport, name }
 | 
			
		||||
 | 
			
		||||
var addr = Packer.socketToAddr(socket);                   // Probe raw, raw socket for address info
 | 
			
		||||
 | 
			
		||||
var id = Packer.addrToId(address);                        // Turn M-PROXY address info into a deterministic id
 | 
			
		||||
 | 
			
		||||
var id = Packer.socketToId(socket);                       // Turn raw, raw socket info into a deterministic id
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
## API Helpers
 | 
			
		||||
 | 
			
		||||
```js
 | 
			
		||||
var socket = Packer.Stream.wrapSocket(socketOrStream);   // workaround for https://github.com/nodejs/node/issues/8854
 | 
			
		||||
                                                         // which was just closed recently, but probably still needs
 | 
			
		||||
                                                         // something more like this (below) to work as intended
 | 
			
		||||
                                                         // https://github.com/findhit/proxywrap/blob/master/lib/proxywrap.js
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
```js
 | 
			
		||||
var myTransform = Packer.Transform.create({
 | 
			
		||||
  address: {
 | 
			
		||||
    family: '...'
 | 
			
		||||
@ -45,7 +136,7 @@ If you want to write a compatible packer, just make sure that for any given inpu
 | 
			
		||||
you get the same output as the packer does.
 | 
			
		||||
 | 
			
		||||
```bash
 | 
			
		||||
node test-pack.js input.json output.bin
 | 
			
		||||
node test/pack.js input.json output.bin
 | 
			
		||||
hexdump output.bin
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
@ -57,8 +148,10 @@ Where `input.json` looks something like this:
 | 
			
		||||
, "address": {
 | 
			
		||||
    "family": "IPv4"
 | 
			
		||||
  , "address": "127.0.1.1"
 | 
			
		||||
  , "port": 443
 | 
			
		||||
  , "port": 4321
 | 
			
		||||
  , "service": "foo"
 | 
			
		||||
  , "serviceport": 443
 | 
			
		||||
  , "name": 'example.com'
 | 
			
		||||
  }
 | 
			
		||||
, "filepath": "./sni.tcp.bin"
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										21
									
								
								index.js
									
									
									
									
									
								
							
							
						
						
									
										21
									
								
								index.js
									
									
									
									
									
								
							@ -120,6 +120,8 @@ Packer.create = function (opts) {
 | 
			
		||||
    machine.port    = machine._headers[2];
 | 
			
		||||
    machine.bodyLen = parseInt(machine._headers[3], 10) || 0;
 | 
			
		||||
    machine.service = machine._headers[4];
 | 
			
		||||
    machine.serviceport = machine._headers[5];
 | 
			
		||||
    machine.name = machine._headers[6];
 | 
			
		||||
    //console.log('machine.service', machine.service);
 | 
			
		||||
 | 
			
		||||
    return true;
 | 
			
		||||
@ -152,6 +154,8 @@ Packer.create = function (opts) {
 | 
			
		||||
    msg.address     = machine.address;
 | 
			
		||||
    msg.port        = machine.port;
 | 
			
		||||
    msg.service     = machine.service;
 | 
			
		||||
    msg.serviceport = machine.serviceport;
 | 
			
		||||
    msg.name        = machine.name;
 | 
			
		||||
    msg.data        = data;
 | 
			
		||||
 | 
			
		||||
    if (machine.emit) {
 | 
			
		||||
@ -182,7 +186,7 @@ Packer.create = function (opts) {
 | 
			
		||||
  return machine;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
Packer.pack = function (address, data, service) {
 | 
			
		||||
Packer.pack = function (meta, data, service) {
 | 
			
		||||
  data = data || Buffer.from(' ');
 | 
			
		||||
  if (!Buffer.isBuffer(data)) {
 | 
			
		||||
    data = new Buffer(JSON.stringify(data));
 | 
			
		||||
@ -192,7 +196,7 @@ Packer.pack = function (address, data, service) {
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  if (service && service !== 'control') {
 | 
			
		||||
    address.service = service;
 | 
			
		||||
    meta.service = service;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  var version = 1;
 | 
			
		||||
@ -202,13 +206,14 @@ Packer.pack = function (address, data, service) {
 | 
			
		||||
  }
 | 
			
		||||
  else {
 | 
			
		||||
    header = Buffer.from([
 | 
			
		||||
      address.family, address.address, address.port, data.byteLength, (address.service || '')
 | 
			
		||||
      meta.family, meta.address, meta.port, data.byteLength,
 | 
			
		||||
      (meta.service || ''), (meta.serviceport || ''), (meta.name || '')
 | 
			
		||||
    ].join(','));
 | 
			
		||||
  }
 | 
			
		||||
  var meta = Buffer.from([ 255 - version, header.length ]);
 | 
			
		||||
  var buf = Buffer.alloc(meta.byteLength + header.byteLength + data.byteLength);
 | 
			
		||||
  var metaBuf = Buffer.from([ 255 - version, header.length ]);
 | 
			
		||||
  var buf = Buffer.alloc(metaBuf.byteLength + header.byteLength + data.byteLength);
 | 
			
		||||
 | 
			
		||||
  meta.copy(buf, 0);
 | 
			
		||||
  metaBuf.copy(buf, 0);
 | 
			
		||||
  header.copy(buf, 2);
 | 
			
		||||
  data.copy(buf, 2 + header.byteLength);
 | 
			
		||||
 | 
			
		||||
@ -323,6 +328,8 @@ function MyTransform(options) {
 | 
			
		||||
  }
 | 
			
		||||
  this.__my_addr = options.address;
 | 
			
		||||
  this.__my_service = options.service;
 | 
			
		||||
  this.__my_serviceport = options.serviceport;
 | 
			
		||||
  this.__my_name = options.name;
 | 
			
		||||
  Transform.call(this, options);
 | 
			
		||||
}
 | 
			
		||||
util.inherits(MyTransform, Transform);
 | 
			
		||||
@ -331,6 +338,8 @@ MyTransform.prototype._transform = function (data, encoding, callback) {
 | 
			
		||||
  var address = this.__my_addr;
 | 
			
		||||
 | 
			
		||||
  address.service = address.service || this.__my_service;
 | 
			
		||||
  address.serviceport = address.serviceport || this.__my_serviceport;
 | 
			
		||||
  address.name = address.name || this.__my_name;
 | 
			
		||||
  this.push(Packer.pack(address, data));
 | 
			
		||||
  callback();
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
@ -1,6 +1,6 @@
 | 
			
		||||
{
 | 
			
		||||
  "name": "proxy-packer",
 | 
			
		||||
  "version": "1.4.2",
 | 
			
		||||
  "version": "1.4.3",
 | 
			
		||||
  "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": {
 | 
			
		||||
 | 
			
		||||
@ -2,8 +2,9 @@
 | 
			
		||||
, "address": {
 | 
			
		||||
    "family": "IPv4"
 | 
			
		||||
  , "address": "127.0.1.1"
 | 
			
		||||
  , "port": 443
 | 
			
		||||
  , "service": "foo"
 | 
			
		||||
  , "port": 4321
 | 
			
		||||
  , "service": "https"
 | 
			
		||||
  , "serviceport": 443
 | 
			
		||||
  }
 | 
			
		||||
, "filepath": "./sni.hello.bin"
 | 
			
		||||
}
 | 
			
		||||
										
											Binary file not shown.
										
									
								
							@ -4,24 +4,30 @@
 | 
			
		||||
var fs = require('fs');
 | 
			
		||||
var infile = process.argv[2];
 | 
			
		||||
var outfile = process.argv[3];
 | 
			
		||||
var sni = require('sni');
 | 
			
		||||
 | 
			
		||||
if (!infile || !outfile) {
 | 
			
		||||
  console.error("Usage:");
 | 
			
		||||
  console.error("node test-pack.js input.json output.bin");
 | 
			
		||||
  console.error("node test/pack.js test/input.json test/output.bin");
 | 
			
		||||
  process.exit(1);
 | 
			
		||||
  return;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
var path = require('path');
 | 
			
		||||
var json = JSON.parse(fs.readFileSync(infile, 'utf8'));
 | 
			
		||||
var data = require('fs').readFileSync(json.filepath, null);
 | 
			
		||||
var Packer = require('./index.js');
 | 
			
		||||
var data = require('fs').readFileSync(path.resolve(path.dirname(infile), json.filepath), null);
 | 
			
		||||
var Packer = require('../index.js');
 | 
			
		||||
 | 
			
		||||
var servername = sni(data);
 | 
			
		||||
var m = data.toString().match(/(?:^|[\r\n])Host: ([^\r\n]+)[\r\n]*/im);
 | 
			
		||||
var hostname = (m && m[1].toLowerCase() || '').split(':')[0];
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
function pack() {
 | 
			
		||||
  var version = json.version;
 | 
			
		||||
  var address = json.address;
 | 
			
		||||
  var header = address.family + ',' + address.address + ',' + address.port + ',' + data.byteLength
 | 
			
		||||
    + ',' + (address.service || '')
 | 
			
		||||
    + ',' + (address.service || '') + ',' + (address.serviceport || '') + ',' + (servername || hostname || '')
 | 
			
		||||
    ;
 | 
			
		||||
  var buf = Buffer.concat([
 | 
			
		||||
    Buffer.from([ 255 - version, header.length ])
 | 
			
		||||
@ -31,6 +37,7 @@ function pack() {
 | 
			
		||||
}
 | 
			
		||||
*/
 | 
			
		||||
 | 
			
		||||
json.address.name = servername || hostname;
 | 
			
		||||
var buf = Packer.pack(json.address, data);
 | 
			
		||||
fs.writeFileSync(outfile, buf, null);
 | 
			
		||||
console.log("wrote " + buf.byteLength + " bytes to '" + outfile + "' ('hexdump " + outfile + "' to inspect)");
 | 
			
		||||
@ -1,16 +1,18 @@
 | 
			
		||||
'use strict';
 | 
			
		||||
 | 
			
		||||
var sni = require('sni');
 | 
			
		||||
var hello = require('fs').readFileSync('./sni.hello.bin');
 | 
			
		||||
var hello = require('fs').readFileSync(__dirname + '/sni.hello.bin');
 | 
			
		||||
var version = 1;
 | 
			
		||||
var address = {
 | 
			
		||||
  family: 'IPv4'
 | 
			
		||||
, address: '127.0.1.1'
 | 
			
		||||
, port: 443
 | 
			
		||||
, service: 'foo'
 | 
			
		||||
, 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.service || '') + ',' + (address.serviceport || '') + ',' + (address.name || '')
 | 
			
		||||
  ;
 | 
			
		||||
var buf = Buffer.concat([
 | 
			
		||||
  Buffer.from([ 255 - version, header.length ])
 | 
			
		||||
@ -20,21 +22,21 @@ var buf = Buffer.concat([
 | 
			
		||||
var services = { 'ssh': 22, 'http': 4080, 'https': 8443 };
 | 
			
		||||
var clients = {};
 | 
			
		||||
var count = 0;
 | 
			
		||||
var packer = require('./');
 | 
			
		||||
var packer = require('../');
 | 
			
		||||
var machine = packer.create({
 | 
			
		||||
  onmessage: function (opts) {
 | 
			
		||||
    var id = opts.family + ',' + opts.address + ',' + opts.port;
 | 
			
		||||
  onmessage: function (tun) {
 | 
			
		||||
    var id = tun.family + ',' + tun.address + ',' + tun.port;
 | 
			
		||||
    var service = 'https';
 | 
			
		||||
    var port = services[service];
 | 
			
		||||
    var servername = sni(opts.data);
 | 
			
		||||
    var servername = sni(tun.data);
 | 
			
		||||
 | 
			
		||||
    console.log('');
 | 
			
		||||
    console.log('[onMessage]');
 | 
			
		||||
    if (!opts.data.equals(hello)) {
 | 
			
		||||
    if (!tun.data.equals(hello)) {
 | 
			
		||||
      throw new Error("'data' packet is not equal to original 'hello' packet");
 | 
			
		||||
    }
 | 
			
		||||
    console.log('all', opts.data.byteLength, 'bytes are equal');
 | 
			
		||||
    console.log('src:', opts.family, opts.address + ':' + opts.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]) {
 | 
			
		||||
@ -42,7 +44,7 @@ var machine = packer.create({
 | 
			
		||||
      if (!servername) {
 | 
			
		||||
        throw new Error("no servername found for '" + id + "'");
 | 
			
		||||
      }
 | 
			
		||||
      console.log("servername: '" + servername + "'");
 | 
			
		||||
      console.log("servername: '" + servername + "'", tun.name);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    count += 1;
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user