diff --git a/package.json b/package.json index 0a4d70f..0e2a047 100644 --- a/package.json +++ b/package.json @@ -23,6 +23,7 @@ "dependencies": { "jsonwebtoken": "^7.1.9", "sni": "^1.0.0", - "tunnel-packer": "^1.0.0" + "tunnel-packer": "^1.0.0", + "ws": "^1.1.1" } } diff --git a/wsclient.js b/wsclient.js new file mode 100644 index 0000000..4d7bee4 --- /dev/null +++ b/wsclient.js @@ -0,0 +1,151 @@ +(function () { +'use strict'; + +var net = require('net'); +var WebSocket = require('ws'); +var jwt = require('jsonwebtoken'); +var sni = require('sni'); +// TODO ask oauth3.org where to connect +// TODO reconnect on disconnect + +// 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 hostname = 'aj.daplie.me'; // 'pokemap.hellabit.com' + +function addrToId(address) { + return address.family + ',' + address.address + ',' + address.port; +} + +/* +function socketToAddr(socket) { + return { family: socket.remoteFamily, address: socket.remoteAddress, port: socket.remotePort }; +} + +function socketToId(socket) { + return addrToId(socketToAddr(socket)); +} +*/ + +var token = jwt.sign({ name: hostname }, 'shhhhh'); + +/* +var request = require('request'); +request.get('https://pokemap.hellabit.com:3000?access_token=' + token, { rejectUnauthorized: false }, function (err, resp) { + console.log('resp.body'); + console.log(resp.body); +}); + +return; +//*/ + +var wstunneler = new WebSocket('wss://pokemap.hellabit.com:3000/?access_token=' + token, { rejectUnauthorized: false }); +wstunneler.on('open', function () { + console.log('[open] tunneler connected'); + var localclients = {}; + + /* + setInterval(function () { + console.log(''); + console.log('localclients.length:', Object.keys(localclients).length); + console.log(''); + }, 5000); + */ + + //wstunneler.send(token); + + // BaaS / Backendless / noBackend / horizon.io + // user authentication + // a place to store data + // file management + // Synergy Teamwork Paradigm = Jabberwocky + var pack = require('tunnel-packer').pack; + + function onMessage(opts) { + var cid = addrToId(opts); + console.log('[wsclient] onMessage:', cid); + var service = opts.service; + var port = services[service]; + var lclient; + var servername; + var str; + var m; + + if (opts.data.byteLength < 20) { + if ('|__ERROR__|' === opts.data.toString('ascii') + || '|__END__|' === opts.data.toString('ascii')) { + + console.log("['" + opts.data.toString('ascii') + "'] '" + cid + "'"); + if (localclients[cid]) { + localclients[cid].end(); + delete localclients[cid]; + } + return; + } + } + + function endWithError() { + wstunneler.send(pack(opts, Buffer.from('|__ERROR__|')), { binary: true }); + } + + if (localclients[cid]) { + console.log("[=>] received data from '" + cid + "' =>", opts.data.byteLength); + localclients[cid].write(opts.data); + return; + } + else if ('http' === service) { + str = opts.data.toString(); + m = str.match(/(?:^|[\r\n])Host: ([^\r\n]+)[\r\n]*/im); + servername = (m && m[1].toLowerCase() || '').split(':')[0]; + } + else if ('https' === service) { + servername = sni(opts.data); + } + else { + endWithError(); + return; + } + + if (!servername) { + console.warn("|__ERROR__| no servername found for '" + cid + "'"); + console.warn(opts.data.toString()); + wstunneler.send(pack(opts, Buffer.from('|__ERROR__|')), { binary: true }); + return; + } + + console.log("servername: '" + servername + "'"); + + lclient = localclients[cid] = net.createConnection({ port: port, host: '127.0.0.1' }, function () { + + lclient.on('data', function (chunk) { + console.log("[<=] local '" + opts.service + "' sent to '" + cid + "' <= ", chunk.byteLength, "bytes"); + console.log(JSON.stringify(chunk.toString())); + wstunneler.send(pack(opts, chunk), { binary: true }); + }); + lclient.on('error', function (err) { + console.error("[error] local '" + opts.service + "' '" + cid + "'"); + console.error(err); + delete localclients[cid]; + wstunneler.send(pack(opts, Buffer.from('|__ERROR__|')), { binary: true }); + }); + lclient.on('end', function () { + console.log("[end] local '" + opts.service + "' '" + cid + "'"); + delete localclients[cid]; + wstunneler.send(pack(opts, Buffer.from('|__END__|')), { binary: true }); + }); + + console.log("[=>] first packet from tunneler to '" + cid + "' as '" + opts.service + "'", opts.data.byteLength); + lclient.write(opts.data); + }); + } + + var machine = require('tunnel-packer').create({ onMessage: onMessage }); + + wstunneler.on('message', machine.fns.addChunk); + + wstunneler.on('close', function () { + console.log('end'); + }); +}); + +}());