From 2608f2e98deabdf89df083699419ab8ba7717533 Mon Sep 17 00:00:00 2001 From: AJ ONeal Date: Wed, 21 Sep 2016 18:42:57 -0600 Subject: [PATCH] initial commit --- client.js | 83 +++++++++++++++++++++++++++++++ machine.js | 138 +++++++++++++++++++++++++++++++++++++++++++++++++++ package.json | 27 ++++++++++ 3 files changed, 248 insertions(+) create mode 100644 client.js create mode 100644 machine.js create mode 100644 package.json diff --git a/client.js b/client.js new file mode 100644 index 0000000..a08fbfa --- /dev/null +++ b/client.js @@ -0,0 +1,83 @@ +'use strict'; + +var net = require('net'); +var jwt = require('jsonwebtoken'); +var sni = require('sni'); +// TODO ask oauth3.org where to connect +// TODO reconnect on disconnect + +function pack(address, data) { + var version = 1; + var header = /*servername + ',' +*/address.family + ',' + address.address + ',' + address.port + ',' + data.byteLength; + var meta = [ 255 - version, header.length ]; + var buf = Buffer.alloc(meta.length + header.length + data.byteLength); + + buf.write(meta[0], 0); + buf.write(meta[1], 1); + buf.write(header, 2); + buf.write(data, 2 + header.length); + + return buf; +} + +// Assumption: will not get next tcp packet unless previous packet succeeded +//var services = { 'ssh': 22, 'http': 80, 'https': 443 }; +var services = { 'ssh': 22, 'http': 4080, 'https': 8443 }; +var tunneler = net.connect({ port: 5443 , host: 'pokemap.hellabit.com' }, function () { + var token = jwt.sign({ name: 'pokemap.hellabit.com' }, 'shhhhh'); + var clients = {}; + + tunneler.write(token); + + // BaaS / Backendless / noBackend / horizon.io + // user authentication + // a place to store data + // file management + // Synergy Teamwork Paradigm = Jabberwocky + var machine = require('./machine.js'); + machine.onMessage = function (opts) { + var id = opts.family + ',' + opts.address + ',' + opts.port; + var service = 'https'; + var port = services[service]; + var client; + + if (clients[id]) { + clients[id].write(opts.data); + return; + } + + var servername = sni(opts.data); + + if (!servername) { + console.warn("no servername found for '" + id + "'"); + tunneler.write(pack(opts, '|__ERROR__|')); + return; + } + + console.log("servername: '" + servername + "'"); + + clients = clients[id] = net.createConnect({ port: port, host: servername }, function () { + client.on('data', function (chunk) { + console.error('client Data'); + tunneler.write(pack(opts, chunk)); + }); + client.on('error', function (err) { + console.error('client Error'); + console.error(err); + tunneler.write(pack(opts, '|__ERROR__|')); + }); + client.on('end', function () { + console.error('client End'); + tunneler.write(pack(opts, '|__END__|')); + }); + + client.write(opts.data); + }); + }; + + tunneler.on('data', machine.fns.addChunk); + + tunneler.on('end', function () { + console.log('end'); + }); +}); diff --git a/machine.js b/machine.js new file mode 100644 index 0000000..cb64140 --- /dev/null +++ b/machine.js @@ -0,0 +1,138 @@ +'use strict'; + +var machine = {}; +machine._version = 1; +machine.state = 0; +machine.states = { 0: 'version', 1: 'headerLength', 2: 'header', 3: 'data'/*, 4: 'error'*/ }; +machine.states_length = Object.keys(machine.states).length; +machine.chunkIndex = 0; +machine.fns = {}; + +function debug(chunk, i, len) { + i = i || 0; + len = len || chunk.length - i; + console.log(chunk.slice(i, len)[0]); + console.log(chunk); + console.log('state:', machine.states[machine.state]); + console.log('statei:', machine.state); + console.log('index:', machine.chunkIndex); +} + +machine.fns.version = function (chunk) { + if ((255 - machine._version) !== chunk[machine.chunkIndex]) { + console.error("not v" + machine._version + " (or data is corrupt)"); + // no idea how to fix this yet + } + machine.chunkIndex += 1; +}; + + +machine.headerLen = 0; +machine.fns.headerLength = function (chunk) { + machine.headerLen = chunk[machine.chunkIndex]; + machine.chunkIndex += 1; +}; + + +machine.buf = null; +machine.bufIndex = 0; +//var buf = Buffer.alloc(4096); +machine.fns.header = function (chunk) { + var curSize = machine.bufIndex + (chunk.length - machine.chunkIndex); + var partLen = 0; + var str = ''; + + if (curSize < machine.headerLen) { + // I still don't have the whole header, + // so just create a large enough buffer, + // write these bits, and wait for the next chunk. + if (!machine.buf) { + machine.buf = Buffer.alloc(machine.headerLen); + } + partLen = machine.headerLen - machine.bufIndex; + machine.buf.write(chunk.slice(machine.chunkIndex, machine.chunkIndex + partLen), machine.bufIndex); + machine.chunkIndex += partLen; // this MUST be chunk.length + machine.bufIndex += partLen; + } + else { + // it's now ready to discover the whole header + if (machine.buf) { + str += machine.buf.slice(0, machine.bufIndex).toString(); + } + partLen = machine.headerLen - str.length; + str += chunk.slice(machine.chunkIndex, machine.chunkIndex + partLen).toString(); + machine.chunkIndex += partLen; + // TODO maybe just use maxlen of buffer + machine.buf = null; // back to null + machine.bufIndex = 0; // back to 0 + + machine._headers = str.split(/,/g); + console.log(partLen); + console.log(''); + console.log(chunk.toString()); + console.log(''); + console.log(str); + console.log(machine._headers); + machine.family = machine._headers[0]; + machine.address = machine._headers[1]; + machine.port = machine._headers[2]; + machine.bodyLen = machine._headers[3]; + } +}; + +machine.fns.data = function (chunk) { + var curSize = machine.bufIndex + (chunk.length - machine.chunkIndex); + var partLen = 0; + var buf; + + if (curSize < machine.bodyLen) { + // I still don't have the whole header, + // so just create a large enough buffer, + // write these bits, and wait for the next chunk. + if (!machine.buf) { + machine.buf = Buffer.alloc(machine.bodyLen); + } + partLen = machine.bodyLen - machine.bufIndex; + machine.buf.write(chunk.slice(machine.chunkIndex, machine.chunkIndex + partLen), machine.bufIndex); + //machine.chunkIndex += partLen; // this MUST be chunk.length + machine.bufIndex += partLen; + return; + } + + // it's now ready to discover the whole header + if (!machine.buf) { + buf = chunk; + machine.chunkIndex = chunk.length; + } + else { + partLen = machine.bodyLen - machine.bufIndex; + machine.buf.write(chunk.slice(machine.chunkIndex, machine.chunkIndex + partLen), machine.bufIndex); + machine.chunkIndex += partLen; + } + + machine.onMessage({ + family: machine.family + , address: machine.address + , port: machine.port + , data: buf + }); + + // TODO maybe just use maxlen of buffer + machine.buf = null; // back to null + machine.bufIndex = 0; // back to 0 +}; +machine.fns.addChunk = function (chunk) { + machine.chunkIndex = 0; + while (machine.chunkIndex < chunk.length) { + console.log(machine.state); + machine.fns[machine.states[machine.state]](chunk); + machine.state += 1; + machine.state %= machine.states_length; + } +}; + +module.exports = machine; + +process.on('uncaughtException', function () { + debug(Buffer.from('0')); +}); diff --git a/package.json b/package.json new file mode 100644 index 0000000..471bf47 --- /dev/null +++ b/package.json @@ -0,0 +1,27 @@ +{ + "name": "tunnel-client", + "version": "1.0.0", + "description": "A naive tunnel client", + "main": "client.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "repository": { + "type": "git", + "url": "git+ssh://git@github.com/Daplie/node-tunnel-client.git" + }, + "keywords": [ + "tcp", + "tunnel" + ], + "author": "AJ ONeal (https://coolaj86.com/)", + "license": "(MIT OR Apache-2.0)", + "bugs": { + "url": "https://github.com/Daplie/node-tunnel-client/issues" + }, + "homepage": "https://github.com/Daplie/node-tunnel-client#readme", + "dependencies": { + "jsonwebtoken": "^7.1.9", + "sni": "^1.0.0" + } +}