From 0fdd2773b50b07dd1b0284ac4857b0bfa412b638 Mon Sep 17 00:00:00 2001 From: AJ ONeal Date: Thu, 27 Apr 2017 19:23:52 -0600 Subject: [PATCH] prepare to handle tunnel --- lib/app.js | 2 +- lib/goldilocks.js | 109 +++++++++++++++++++++++++++++++++++++--------- 2 files changed, 90 insertions(+), 21 deletions(-) diff --git a/lib/app.js b/lib/app.js index 7f89cf8..6b8edf0 100644 --- a/lib/app.js +++ b/lib/app.js @@ -184,7 +184,7 @@ module.exports = function (deps, conf, overrideHttp) { , stunneld: result.tunnelUrl // we'll provide faux networking and pipe as we please , services: { https: { '*': 443 }, http: { '*': 80 }, smtp: { '*': 25}, smtps: { '*': 587 /*also 465/starttls*/ } /*, ssh: { '*': 22 }*/ } - , net: conf.net + , net: deps.tunnel.net || deps.net }; if (tun) { diff --git a/lib/goldilocks.js b/lib/goldilocks.js index c6108b8..73518cf 100644 --- a/lib/goldilocks.js +++ b/lib/goldilocks.js @@ -81,6 +81,7 @@ module.exports.create = function (deps, config) { firstChunk = firstChunk.toString('utf8'); // JSON.stringify("Host: example.com\r\nNext: Header".replace(/(Host: [^\r\n]*)/i, "$1" + "\r\n" + "X: XYZ")) firstChunk = firstChunk.replace(/(Host: [^\r\n]*)/i, "$1" + "\r\n" + newHeads.join("\r\n")); + process.nextTick(function () { conn.unshift(Buffer.from(firstChunk, 'utf8')); }); @@ -104,6 +105,7 @@ module.exports.create = function (deps, config) { } opts.hostname = hostname; conn.__opts = opts; + modules.http.emit('connection', conn); }; } @@ -123,6 +125,15 @@ module.exports.create = function (deps, config) { , _create: function (address, port/*, nextServer*/) { // port provides hinting for https, smtps, etc return function (socket, firstChunk, opts) { + if (opts.hyperPeek) { + // See "PEEK COMMENT" for more info + // This was peeked at properly, so we don't have to re-wrap it + // in order to get the connection to not hang. + // The real first 'data' and 'readable' events will occur as they should + program.tlsTunnelServer.emit('connection', socket); + return; + } + var servername = opts.servername; var packerStream = require('tunnel-packer').Stream; var myDuplex = packerStream.create(socket); @@ -176,26 +187,49 @@ module.exports.create = function (deps, config) { }; - // opts = { servername, encrypted, remoteAddress, remotePort } - function handler(conn, opts) { + // opts = { servername, encrypted, peek, data, remoteAddress, remotePort } + function peek(conn, firstChunk, opts) { + // TODO port/service-based routing can do here + + // TLS + if (22 === firstChunk[0]) { + console.log('tryTls'); + opts.servername = (parseSni(firstChunk)||'').toLowerCase() || 'localhost.invalid'; + tlsRouter.get(opts.localAddress || conn.localAddress, conn.localPort)(conn, firstChunk, opts); + return; + } + + console.log('tryTcp'); + + if (opts.hyperPeek) { + // even though we've already peeked, this logic is just as well to let be + // since it works properly either way, unlike the tls socket + conn.once('data', function (chunk) { + tcpRouter.get(opts.localAddress || conn.localAddress, conn.localPort)(conn, chunk, opts); + }); + return; + } + + tcpRouter.get(opts.localAddress || conn.localAddress, conn.localPort)(conn, firstChunk, opts); + } + function netHandler(conn, opts) { opts = opts || {}; - console.log('[handler]', conn.localAddres, conn.localPort, opts.encrypted); + console.log('[netHandler]', conn.localAddres, conn.localPort, opts.encrypted); - conn.once('data', function (firstChunk) { - var servername; + // XXX PEEK COMMENT XXX + // TODO we can have our cake and eat it too + // we can skip the need to wrap the TLS connection twice + // because we've already peeked at the data, + // but this needs to be handled better before we enable that + // (because it creates new edge cases) + if (opts.hyperPeek) { + console.log('hyperpeek'); + peek(conn, opts.firstChunk, opts); + return; + } - // TODO port-based routing can do here - - // TLS - if (22 === firstChunk[0]) { - servername = (parseSni(firstChunk)||'').toLowerCase() || 'localhost.invalid'; - console.log('tryTls'); - tlsRouter.get(conn.localAddress, conn.localPort)(conn, firstChunk, opts); - } - else { - console.log('tryTcp'); - tcpRouter.get(conn.localAddress, conn.localPort)(conn, firstChunk, opts); - } + conn.once('data', function (chunk) { + peek(conn, chunk, opts); }); } @@ -238,7 +272,7 @@ module.exports.create = function (deps, config) { // TODO ask http module about the default path (/srv/www/:hostname) // (if it exists, we can allow and add to config) if (!modules.http) { - modules.http = require('./modules/http.js').create(config); + modules.http = require('./modules/http.js').create(deps, config); } modules.http.checkServername(opts.domain).then(function (stuff) { if (!stuff.domains) { @@ -284,6 +318,41 @@ module.exports.create = function (deps, config) { }); } + deps.tunnel = deps.tunnel || {}; + deps.tunnel.net = { + createConnection: function (opts, cb) { + var Duplex = require('stream').Duplex; + var myDuplex = new Duplex(); + var wrapOpts = {}; + + // this has the normal net/tcp stuff plus our custom stuff + // opts = { address, port, + // hostname, servername, encrypted, data, localAddress, localPort, remoteAddress, remotePort, remoteFamily } + Object.keys(opts).forEach(function (key) { + wrapOpts[key] = opts[key]; + myDuplex[key] = opts[key]; + }); + + // A few more extra specialty options + wrapOpts.firstChunk = opts.data; + wrapOpts.hyperPeek = !!opts.data; + wrapOpts.localAddress = wrapOpts.localAddress || '127.0.0.2'; // TODO use the tunnel's external address + wrapOpts.localPort = wrapOpts.localPort || 'tunnel-0'; + + netHandler(myDuplex, wrapOpts); + + process.nextTick(function () { + //opts.data = wrapOpts.data; + + // this cb will cause the stream to emit its (actually) first data event + // (even though it already gave a peek into that first data chunk) + cb(); + }); + + return myDuplex; + } + }; + Object.keys(program.tlsOptions).forEach(function (key) { tunnelAdminTlsOpts[key] = program.tlsOptions[key]; }); @@ -322,7 +391,7 @@ module.exports.create = function (deps, config) { //}); //(program.httpTunnelServer || program.httpServer).emit('connection', tlsSocket); //tcpRouter.get(conn.localAddress, conn.localPort)(conn, firstChunk, { encrypted: false }); - handler(tlsSocket, { + netHandler(tlsSocket, { servername: tlsSocket.servername , encrypted: true // remoteAddress... ugh... https://github.com/nodejs/node/issues/8854 @@ -332,6 +401,6 @@ module.exports.create = function (deps, config) { }); PromiseA.all(config.tcp.ports.map(function (port) { - return listeners.tcp.add(port, handler); + return listeners.tcp.add(port, netHandler); })); };