From 65df12ecb3f814ca4c9ebb69f9f98b865e3d5f5a Mon Sep 17 00:00:00 2001 From: tigerbot Date: Wed, 26 Apr 2017 12:05:44 -0600 Subject: [PATCH 1/9] changed how browser connections are handled --- package.json | 1 + wstunneld.js | 159 +++++++++++++++++++++++---------------------------- 2 files changed, 73 insertions(+), 87 deletions(-) diff --git a/package.json b/package.json index a23f0fa..c312795 100644 --- a/package.json +++ b/package.json @@ -46,6 +46,7 @@ }, "homepage": "https://github.com/Daplie/node-tunnel-server#readme", "dependencies": { + "bluebird": "^3.5.0", "cluster-store": "^2.0.4", "commander": "^2.9.0", "greenlock": "^2.1.12", diff --git a/wstunneld.js b/wstunneld.js index 867e3a4..5ebb954 100644 --- a/wstunneld.js +++ b/wstunneld.js @@ -2,9 +2,16 @@ var sni = require('sni'); var url = require('url'); +var PromiseA = require('bluebird'); var jwt = require('jsonwebtoken'); var packer = require('tunnel-packer'); +function timeoutPromise(duration) { + return new PromiseA(function (resolve) { + setTimeout(resolve, duration); + }); +} + var Devices = {}; Devices.add = function (store, servername, newDevice) { var devices = Devices.list(store, servername); @@ -115,44 +122,59 @@ module.exports.create = function (copts) { // and we haven't implemented tls in the browser yet // remote.decrypt = token.decrypt; + function closeBrowserConn(cid) { + if (!remote.clients[cid]) { + return; + } + + PromiseA.resolve() + .then(function () { + remote.clients[cid].end(); + return timeoutPromise(500); + }) + .then(function () { + if (remote.clients[cid]) { + console.warn(cid, 'browser connection still present after calling `end`'); + remote.clients[cid].destroy(); + return timeoutPromise(500); + } + }) + .then(function () { + if (remote.clients[cid]) { + console.error(cid, 'browser connection still present after calling `destroy`'); + delete remote.clients[cid]; + } + }) + .catch(function (err) { + console.warn('failed to close browser connection', cid, err); + }) + ; + } + var handlers = { onmessage: function (opts) { // opts.data var cid = packer.addrToId(opts); - var cstream = remote.clients[cid]; + var browserConn = remote.clients[cid]; console.log("remote '" + remote.servername + " : " + remote.id + "' has data for '" + cid + "'", opts.data.byteLength); - if (!cstream) { + if (!browserConn) { remote.ws.send(packer.pack(opts, null, 'error')); return; } - cstream.browser.write(opts.data); + browserConn.write(opts.data); } , onend: function (opts) { var cid = packer.addrToId(opts); console.log('[TunnelEnd]', cid); - handlers._onend(cid); + closeBrowserConn(cid); } , onerror: function (opts) { var cid = packer.addrToId(opts); console.log('[TunnelError]', cid); - handlers._onend(cid); - } - , _onend: function (cid) { - var c = remote.clients[cid]; - delete remote.clients[cid]; - try { - c.browser.end(); - } catch(e) { - // ignore - } - try { - c.wrapped.end(); - } catch(e) { - // ignore - } + closeBrowserConn(cid); } }; remote.unpacker = packer.create(handlers); @@ -214,89 +236,52 @@ module.exports.create = function (copts) { function hangup() { clearTimeout(timeoutId); console.log('home cloud', remote.deviceId || remote.servername, 'connection closing'); - // the remote will handle closing its local connections - Object.keys(remote.clients).forEach(function (cid) { - try { - remote.clients[cid].browser.end(); - } catch(e) { - // ignore - } - }); + // Prevent any more browser connections being sent to this remote, and any existing + // connections from trying to send more data across the connection. token.domains.forEach(function (domainname) { Devices.remove(deviceLists, domainname, remote); }); + remote.ws = null; + + // Close all of the existing browser connections associated with this websocket connection. + Object.keys(remote.clients).forEach(function (cid) { + closeBrowserConn(cid); + }); } ws.on('close', hangup); ws.on('error', hangup); } - function pipeWs(servername, service, browser, remote) { - console.log('pipeWs'); + function pipeWs(servername, service, browserConn, remote) { + console.log('[pipeWs] servername:', servername, 'service:', service); - //var remote = deviceLists[servername]; - var ws = remote.ws; - //var address = packer.socketToAddr(ws.upgradeReq.socket); - var baddress = packer.socketToAddr(browser); - var cid = packer.addrToId(baddress); - console.log('servername:', servername); - console.log('service:', service); - baddress.service = service; - var wrapForRemote = packer.Transform.create({ - id: cid - //, remoteId: remote.id - , address: baddress - , servername: servername - , service: service - }); - console.log('home-cloud is', packer.socketToId(remote.ws.upgradeReq.socket)); - console.log('browser is', cid); - var bstream = remote.clients[cid] = { - wrapped: browser.pipe(wrapForRemote) - , browser: browser - , address: baddress - }; - //var bstream = remote.clients[cid] = wrapForRemote.pipe(browser); - bstream.wrapped.on('data', function (pchunk) { - // var chunk = socket.read(); - console.log('[bstream] data from browser to tunneler', pchunk.byteLength); - //console.log(JSON.stringify(pchunk.toString())); - try { - ws.send(pchunk, { binary: true }); - } catch(e) { + var browserAddr = packer.socketToAddr(browserConn); + browserAddr.service = service; + var cid = packer.addrToId(browserAddr); + console.log('[pipeWs] browser is', cid, 'home-cloud is', packer.socketToId(remote.ws.upgradeReq.socket)); + + function sendWs(data, serviceOverride) { + if (remote.ws) { try { - bstream.browser.end(); - } catch(e) { - // ignore + remote.ws.send(packer.pack(browserAddr, data, serviceOverride), { binary: true }); + } catch (err) { + console.warn('[pipeWs] error sending websocket message', err); } } + } + + remote.clients[cid] = browserConn; + browserConn.on('data', function (chunk) { + console.log('[pipeWs] data from browser to tunneler', chunk.byteLength); + sendWs(chunk); }); - bstream.wrapped.on('error', function (err) { - console.error('[error] bstream.wrapped.error'); - console.error(err); - try { - ws.send(packer.pack(baddress, null, 'error'), { binary: true }); - } catch(e) { - // ignore - } - try { - bstream.browser.end(); - } catch(e) { - // ignore - } - delete remote.clients[cid]; + browserConn.on('error', function (err) { + console.warn('[pipeWs] browser connection error', err); }); - bstream.wrapped.on('end', function () { - try { - ws.send(packer.pack(baddress, null, 'end'), { binary: true }); - } catch(e) { - // ignore - } - try { - bstream.browser.end(); - } catch(e) { - // ignore - } + browserConn.on('close', function (hadErr) { + console.log('[pipeWs] browser connection closing'); + sendWs(null, hadErr ? 'error': 'end'); delete remote.clients[cid]; }); } From 40c797b729e73aeac8e7568f216e2fc30c2371a5 Mon Sep 17 00:00:00 2001 From: tigerbot Date: Wed, 26 Apr 2017 19:52:30 -0600 Subject: [PATCH 2/9] changed token handling to allow multiple per websocket --- wstunneld.js | 225 ++++++++++++++++++++++++++++----------------------- 1 file changed, 125 insertions(+), 100 deletions(-) diff --git a/wstunneld.js b/wstunneld.js index 5ebb954..2e7a507 100644 --- a/wstunneld.js +++ b/wstunneld.js @@ -23,8 +23,7 @@ Devices.remove = function (store, servername, device) { var index = devices.indexOf(device); if (index < 0) { - var id = device.deviceId || device.servername || device.id; - console.warn('attempted to remove non-present device', id, 'from', servername); + console.warn('attempted to remove non-present device', device.deviceId, 'from', servername); return null; } return devices.splice(index, 1)[0]; @@ -55,75 +54,26 @@ module.exports.create = function (copts) { var pongTimeout = copts.pongTimeout || 10*1000; function onWsConnection(ws) { - var location = url.parse(ws.upgradeReq.url, true); - var authn = (ws.upgradeReq.headers.authorization||'').split(/\s+/); - var jwtoken; - var token; + var socketId = packer.socketToId(ws.upgradeReq.socket); + var remotes = {}; - try { - if (authn[0]) { - if ('basic' === authn[0].toLowerCase()) { - authn = new Buffer(authn[1], 'base64').toString('ascii').split(':'); - } - /* - if (-1 !== [ 'bearer', 'jwk' ].indexOf(authn[0].toLowerCase())) { - jwtoken = authn[1]; - } - */ - } - jwtoken = authn[1] || location.query.access_token; - } catch(e) { - jwtoken = null; + function logName() { + var result = Object.keys(remotes).map(function (jwtoken) { + return remotes[jwtoken].deviceId; + }).join(';'); + + return result || socketId; } - try { - token = jwt.verify(jwtoken, copts.secret); - } catch(e) { - token = null; - } - - /* - if (!token || !token.name) { - console.log('location, token'); - console.log(location.query.access_token); - console.log(token); - } - */ - - if (!token) { - ws.send(JSON.stringify({ error: { message: "invalid access token", code: "E_INVALID_TOKEN" } })); - ws.close(); - return; - } - - //console.log('[wstunneld.js] DEBUG', token); - - if (!Array.isArray(token.domains)) { - if ('string' === typeof token.name) { - token.domains = [ token.name ]; - } - } - - if (!Array.isArray(token.domains)) { - ws.send(JSON.stringify({ error: { message: "invalid server name", code: "E_INVALID_NAME" } })); - ws.close(); - return; - } - - var remote = {}; - remote.ws = ws; - remote.servername = (token.device && token.device.hostname) || token.domains.join(','); - remote.deviceId = (token.device && token.device.id) || null; - remote.id = packer.socketToId(ws.upgradeReq.socket); - console.log("remote.id", remote.id); - remote.domains = token.domains; - remote.clients = {}; - // TODO allow tls to be decrypted by server if client is actually a browser - // and we haven't implemented tls in the browser yet - // remote.decrypt = token.decrypt; - function closeBrowserConn(cid) { - if (!remote.clients[cid]) { + var remote; + Object.keys(remotes).some(function (jwtoken) { + if (remotes[jwtoken].clients[cid]) { + remote = remotes[jwtoken]; + return true; + } + }); + if (!remote) { return; } @@ -151,20 +101,110 @@ module.exports.create = function (copts) { ; } + function addToken(jwtoken) { + if (remotes[jwtoken]) { + ws.send(JSON.stringify({ error: { message: "token sent multiple times", code: "E_TOKEN_REPEAT" } })); + return false; + } + + var token; + try { + token = jwt.verify(jwtoken, copts.secret); + } catch (e) { + token = null; + } + + if (!token) { + ws.send(JSON.stringify({ error: { message: "invalid access token", code: "E_INVALID_TOKEN" } })); + return false; + } + + if (!Array.isArray(token.domains)) { + if ('string' === typeof token.name) { + token.domains = [ token.name ]; + } + } + + if (!Array.isArray(token.domains)) { + ws.send(JSON.stringify({ error: { message: "invalid server name", code: "E_INVALID_NAME" } })); + return false; + } + if (token.domains.some(function (name) { return typeof name !== 'string'; })) { + ws.send(JSON.stringify({ error: { message: "invalid server name", code: "E_INVALID_NAME" } })); + return false; + } + + // Add the custom properties we need to manage this remote, then add it to all the relevant + // domains and the list of all this websocket's remotes. + token.deviceId = (token.device && (token.device.id || token.device.hostname)) || token.domains.join(','); + token.ws = ws; + token.clients = {}; + + token.domains.forEach(function (domainname) { + console.log('domainname', domainname); + Devices.add(deviceLists, domainname, token); + }); + remotes[jwtoken] = token; + console.log("added token '" + token.deviceId + "' to websocket", socketId); + return true; + } + + function removeToken(jwtoken) { + var remote = remotes[jwtoken]; + if (!remote) { + return false; + } + + // Prevent any more browser connections being sent to this remote, and any existing + // connections from trying to send more data across the connection. + remote.domains.forEach(function (domainname) { + Devices.remove(deviceLists, domainname, remote); + }); + remote.ws = null; + + // Close all of the existing browser connections associated with this websocket connection. + Object.keys(remote.clients).forEach(function (cid) { + closeBrowserConn(cid); + }); + delete remotes[jwtoken]; + console.log("removed token '" + remote.deviceId + "' from websocket", socketId); + } + + var firstToken; + var authn = (ws.upgradeReq.headers.authorization||'').split(/\s+/); + if (authn[0] && 'basic' === authn[0].toLowerCase()) { + try { + authn = new Buffer(authn[1], 'base64').toString('ascii').split(':'); + firstToken = authn[1]; + } catch (err) { } + } + if (!firstToken) { + firstToken = url.parse(ws.upgradeReq.url, true).query.access_token; + } + if (firstToken && !addToken(firstToken)) { + ws.close(); + return; + } + var handlers = { onmessage: function (opts) { - // opts.data var cid = packer.addrToId(opts); - var browserConn = remote.clients[cid]; + console.log("remote '" + logName() + "' has data for '" + cid + "'", opts.data.byteLength); - console.log("remote '" + remote.servername + " : " + remote.id + "' has data for '" + cid + "'", opts.data.byteLength); + var browserConn; + Object.keys(remotes).some(function (jwtoken) { + if (remotes[jwtoken].clients[cid]) { + browserConn = remotes[jwtoken].clients[cid]; + return true; + } + }); - if (!browserConn) { - remote.ws.send(packer.pack(opts, null, 'error')); - return; + if (browserConn) { + browserConn.write(opts.data); + } + else { + ws.send(packer.pack(opts, null, 'error')); } - - browserConn.write(opts.data); } , onend: function (opts) { var cid = packer.addrToId(opts); @@ -177,14 +217,7 @@ module.exports.create = function (copts) { closeBrowserConn(cid); } }; - remote.unpacker = packer.create(handlers); - - // Now that we have created our remote object we need to store it in the deviceList for - // each domainname we are supposed to be handling. - token.domains.forEach(function (domainname) { - console.log('domainname', domainname); - Devices.add(deviceLists, domainname, remote); - }); + var unpacker = packer.create(handlers); var lastActivity = Date.now(); var timeoutId; @@ -204,11 +237,11 @@ module.exports.create = function (copts) { // Otherwise we check to see if the pong has also timed out, and if not we send a ping // and call this function again when the pong will have timed out. else if (silent < activityTimeout + pongTimeout) { - console.log('pinging', remote.deviceId || remote.servername); + console.log('pinging', logName()); try { - remote.ws.ping(); + ws.ping(); } catch (err) { - console.warn('failed to ping home cloud', remote.deviceId || remote.servername); + console.warn('failed to ping home cloud', logName()); } timeoutId = setTimeout(checkTimeout, pongTimeout); } @@ -216,8 +249,8 @@ module.exports.create = function (copts) { // Last case means the ping we sent before didn't get a response soon enough, so we // need to close the websocket connection. else { - console.log('home cloud', remote.deviceId || remote.servername, 'connection timed out'); - remote.ws.close(1013, 'connection timeout'); + console.log('home cloud', logName(), 'connection timed out'); + ws.close(1013, 'connection timeout'); } } timeoutId = setTimeout(checkTimeout, activityTimeout); @@ -230,22 +263,14 @@ module.exports.create = function (copts) { refreshTimeout(); console.log('message from home cloud to tunneler to browser', chunk.byteLength); //console.log(chunk.toString()); - remote.unpacker.fns.addChunk(chunk); + unpacker.fns.addChunk(chunk); }); function hangup() { clearTimeout(timeoutId); - console.log('home cloud', remote.deviceId || remote.servername, 'connection closing'); - // Prevent any more browser connections being sent to this remote, and any existing - // connections from trying to send more data across the connection. - token.domains.forEach(function (domainname) { - Devices.remove(deviceLists, domainname, remote); - }); - remote.ws = null; - - // Close all of the existing browser connections associated with this websocket connection. - Object.keys(remote.clients).forEach(function (cid) { - closeBrowserConn(cid); + console.log('home cloud', logName(), 'connection closing'); + Object.keys(remotes).forEach(function (jwtoken) { + removeToken(jwtoken); }); } From a1fbde7d8e792f08730f739c5345660c2959609e Mon Sep 17 00:00:00 2001 From: tigerbot Date: Thu, 27 Apr 2017 19:36:28 -0600 Subject: [PATCH 3/9] added ability to add/remove tokens through websocket --- package.json | 2 +- wstunneld.js | 65 +++++++++++++++++++++++++++++++++++++++++----------- 2 files changed, 52 insertions(+), 15 deletions(-) diff --git a/package.json b/package.json index c312795..51720cb 100644 --- a/package.json +++ b/package.json @@ -54,7 +54,7 @@ "localhost.daplie.me-certificates": "^1.3.0", "redirect-https": "^1.1.0", "sni": "^1.0.0", - "tunnel-packer": "^1.0.0", + "tunnel-packer": "^1.2.0", "ws": "^2.2.3" } } diff --git a/wstunneld.js b/wstunneld.js index 2e7a507..27f43fd 100644 --- a/wstunneld.js +++ b/wstunneld.js @@ -103,8 +103,8 @@ module.exports.create = function (copts) { function addToken(jwtoken) { if (remotes[jwtoken]) { - ws.send(JSON.stringify({ error: { message: "token sent multiple times", code: "E_TOKEN_REPEAT" } })); - return false; + // return { message: "token sent multiple times", code: "E_TOKEN_REPEAT" }; + return null; } var token; @@ -115,8 +115,7 @@ module.exports.create = function (copts) { } if (!token) { - ws.send(JSON.stringify({ error: { message: "invalid access token", code: "E_INVALID_TOKEN" } })); - return false; + return { message: "invalid access token", code: "E_INVALID_TOKEN" }; } if (!Array.isArray(token.domains)) { @@ -126,12 +125,10 @@ module.exports.create = function (copts) { } if (!Array.isArray(token.domains)) { - ws.send(JSON.stringify({ error: { message: "invalid server name", code: "E_INVALID_NAME" } })); - return false; + return { message: "invalid server name", code: "E_INVALID_NAME" }; } if (token.domains.some(function (name) { return typeof name !== 'string'; })) { - ws.send(JSON.stringify({ error: { message: "invalid server name", code: "E_INVALID_NAME" } })); - return false; + return { message: "invalid server name", code: "E_INVALID_NAME" }; } // Add the custom properties we need to manage this remote, then add it to all the relevant @@ -146,13 +143,13 @@ module.exports.create = function (copts) { }); remotes[jwtoken] = token; console.log("added token '" + token.deviceId + "' to websocket", socketId); - return true; + return null; } function removeToken(jwtoken) { var remote = remotes[jwtoken]; if (!remote) { - return false; + return { message: 'specified token not present', code: 'E_INVALID_TOKEN'}; } // Prevent any more browser connections being sent to this remote, and any existing @@ -168,6 +165,7 @@ module.exports.create = function (copts) { }); delete remotes[jwtoken]; console.log("removed token '" + remote.deviceId + "' from websocket", socketId); + return null; } var firstToken; @@ -181,13 +179,52 @@ module.exports.create = function (copts) { if (!firstToken) { firstToken = url.parse(ws.upgradeReq.url, true).query.access_token; } - if (firstToken && !addToken(firstToken)) { - ws.close(); - return; + if (firstToken) { + var err = addToken(firstToken); + if (err) { + ws.send(JSON.stringify({ error: err })); + ws.close(); + return; + } } var handlers = { - onmessage: function (opts) { + oncontrol: function (opts) { + var cmd, err; + try { + cmd = JSON.parse(opts.data.toString()); + } catch (err) {} + if (!Array.isArray(cmd) || typeof cmd[0] !== 'number') { + console.warn('received bad command "' + opts.data.toString() + '"'); + return; + } + + if (cmd[0] < 0) { + console.warn('received response to unknown command', cmd); + return; + } + + if (cmd[1] === 'add_token') { + err = addToken(cmd[2]); + } + else if (cmd[1] === 'delete_token') { + if (cmd[2] === '*') { + Object.keys(remotes).some(function (jwtoken) { + err = removeToken(jwtoken); + return err; + }); + } + else { + err = removeToken(cmd[2]); + } + } + else { + err = { message: 'unknown command '+cmd[1], code: 'E_UNKNOWN_COMMAND' }; + } + + ws.send(packer.pack(null, [-cmd[0], err], 'control')); + } + , onmessage: function (opts) { var cid = packer.addrToId(opts); console.log("remote '" + logName() + "' has data for '" + cid + "'", opts.data.byteLength); From b4a300cc647f493d7817d8291d9096c2916928eb Mon Sep 17 00:00:00 2001 From: tigerbot Date: Fri, 28 Apr 2017 12:50:50 -0600 Subject: [PATCH 4/9] implemented hello command on valid connection --- wstunneld.js | 49 ++++++++++++++++++++++++++++++++----------------- 1 file changed, 32 insertions(+), 17 deletions(-) diff --git a/wstunneld.js b/wstunneld.js index 27f43fd..0ae16ca 100644 --- a/wstunneld.js +++ b/wstunneld.js @@ -188,7 +188,22 @@ module.exports.create = function (copts) { } } - var handlers = { + var commandHandlers = { + add_token: addToken + , delete_token: function (token) { + if (token !== '*') { + return removeToken(token); + } + var err; + Object.keys(remotes).some(function (jwtoken) { + err = removeToken(jwtoken); + return err; + }); + return err; + } + }; + + var packerHandlers = { oncontrol: function (opts) { var cmd, err; try { @@ -200,26 +215,23 @@ module.exports.create = function (copts) { } if (cmd[0] < 0) { - console.warn('received response to unknown command', cmd); + // We only ever send one command and we send it once, so we just hard coded the ID as 1. + if (cmd[0] === -1) { + if (cmd[1]) { + console.log('received error response to hello from', socketId, cmd[1]); + } + } + else { + console.warn('received response to unknown command', cmd, 'from', socketId); + } return; } - if (cmd[1] === 'add_token') { - err = addToken(cmd[2]); - } - else if (cmd[1] === 'delete_token') { - if (cmd[2] === '*') { - Object.keys(remotes).some(function (jwtoken) { - err = removeToken(jwtoken); - return err; - }); - } - else { - err = removeToken(cmd[2]); - } + if (commandHandlers[cmd[1]]) { + err = commandHandlers[cmd[1]].apply(null, cmd.slice(2)); } else { - err = { message: 'unknown command '+cmd[1], code: 'E_UNKNOWN_COMMAND' }; + err = { message: 'unknown command "'+cmd[1]+'"', code: 'E_UNKNOWN_COMMAND' }; } ws.send(packer.pack(null, [-cmd[0], err], 'control')); @@ -254,7 +266,7 @@ module.exports.create = function (copts) { closeBrowserConn(cid); } }; - var unpacker = packer.create(handlers); + var unpacker = packer.create(packerHandlers); var lastActivity = Date.now(); var timeoutId; @@ -313,6 +325,9 @@ module.exports.create = function (copts) { ws.on('close', hangup); ws.on('error', hangup); + + // We only ever send one command and we send it once, so we just hard code the ID as 1 + ws.send(packer.pack(null, [1, 'hello', [unpacker._version], Object.keys(commandHandlers)], 'control')); } function pipeWs(servername, service, browserConn, remote) { From 3d5f4a773d561726b3abb4fd23cb6eeb99338d81 Mon Sep 17 00:00:00 2001 From: tigerbot Date: Fri, 28 Apr 2017 14:19:56 -0600 Subject: [PATCH 5/9] implemented sending of errors not directly from requests --- wstunneld.js | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/wstunneld.js b/wstunneld.js index 0ae16ca..d188ce5 100644 --- a/wstunneld.js +++ b/wstunneld.js @@ -182,7 +182,7 @@ module.exports.create = function (copts) { if (firstToken) { var err = addToken(firstToken); if (err) { - ws.send(JSON.stringify({ error: err })); + ws.send(packer.pack(null, [0, err], 'control')); ws.close(); return; } @@ -210,7 +210,9 @@ module.exports.create = function (copts) { cmd = JSON.parse(opts.data.toString()); } catch (err) {} if (!Array.isArray(cmd) || typeof cmd[0] !== 'number') { - console.warn('received bad command "' + opts.data.toString() + '"'); + var msg = 'received bad command "' + opts.data.toString() + '"'; + console.warn(msg, 'from websocket', socketId); + ws.send(packer.pack(null, [0, {message: msg, code: 'E_BAD_COMMAND'}], 'control')); return; } @@ -227,6 +229,11 @@ module.exports.create = function (copts) { return; } + if (cmd[0] === 0) { + console.warn('received dis-associated error from', socketId, cmd[1]); + return; + } + if (commandHandlers[cmd[1]]) { err = commandHandlers[cmd[1]].apply(null, cmd.slice(2)); } From 7112bfdbb28358a0c81923e62401b9de52b54987 Mon Sep 17 00:00:00 2001 From: tigerbot Date: Fri, 26 May 2017 16:33:27 -0600 Subject: [PATCH 6/9] added support for wildcard domains --- wstunneld.js | 29 ++++++++++++++++++++++++----- 1 file changed, 24 insertions(+), 5 deletions(-) diff --git a/wstunneld.js b/wstunneld.js index d188ce5..ee22a1f 100644 --- a/wstunneld.js +++ b/wstunneld.js @@ -14,12 +14,12 @@ function timeoutPromise(duration) { var Devices = {}; Devices.add = function (store, servername, newDevice) { - var devices = Devices.list(store, servername); + var devices = store[servername] || []; devices.push(newDevice); store[servername] = devices; }; Devices.remove = function (store, servername, device) { - var devices = Devices.list(store, servername); + var devices = store[servername] || []; var index = devices.indexOf(device); if (index < 0) { @@ -29,10 +29,29 @@ Devices.remove = function (store, servername, device) { return devices.splice(index, 1)[0]; }; Devices.list = function (store, servername) { - return store[servername] || []; + if (store[servername]) { + return store[servername]; + } + // There wasn't an exact match so check any of the wildcard domains, sorted longest + // first so the one with the biggest natural match with be found first. + var deviceList = []; + Object.keys(store).filter(function (pattern) { + return pattern[0] === '*'; + }).sort(function (a, b) { + return b.length - a.length; + }).some(function (pattern) { + var subPiece = pattern.slice(1); + if (subPiece === servername.slice(-subPiece.length)) { + console.log('"'+servername+'" matches "'+pattern+'"'); + deviceList = store[pattern]; + return true; + } + }); + + return deviceList; }; Devices.exist = function (store, servername) { - return (store[servername] || []).length; + return !!(Devices.list(store, servername).length); }; Devices.next = function (store, servername) { var devices = Devices.list(store, servername); @@ -124,7 +143,7 @@ module.exports.create = function (copts) { } } - if (!Array.isArray(token.domains)) { + if (!Array.isArray(token.domains) || !token.domains.length) { return { message: "invalid server name", code: "E_INVALID_NAME" }; } if (token.domains.some(function (name) { return typeof name !== 'string'; })) { From 8e71ae02cf60861fa1d4375e7d88669ee9d2aecb Mon Sep 17 00:00:00 2001 From: tigerbot Date: Fri, 26 May 2017 17:26:44 -0600 Subject: [PATCH 7/9] fixed problem with not closing websocket There was a problem that prevented socket events like close and error from getting through our duplex and to the websocket so it could close --- handlers.js | 28 +++------------------------- package.json | 4 ++-- wstunneld.js | 1 + 3 files changed, 6 insertions(+), 27 deletions(-) diff --git a/handlers.js b/handlers.js index efd044b..10c8751 100644 --- a/handlers.js +++ b/handlers.js @@ -2,7 +2,7 @@ var http = require('http'); var tls = require('tls'); -var packerStream = require('tunnel-packer').Stream; +var wrapSocket = require('tunnel-packer').wrapSocket; var redirectHttps = require('redirect-https')(); module.exports.create = function (program) { @@ -57,19 +57,8 @@ module.exports.create = function (program) { // tlsServer.emit('connection', socket); // this didn't work either //console.log('chunkLen', firstChunk.byteLength); - var myDuplex = packerStream.create(socket); - console.log('httpsInvalid servername', servername); - program.tlsInvalidSniServer.emit('connection', myDuplex); - - socket.on('data', function (chunk) { - console.log('[' + Date.now() + '] socket data', chunk.byteLength); - myDuplex.push(chunk); - }); - socket.on('error', function (err) { - console.error('[error] httpsInvalid TODO close'); - console.error(err); - }); + program.tlsInvalidSniServer.emit('connection', wrapSocket(socket)); }; // @@ -97,18 +86,7 @@ module.exports.create = function (program) { // tlsServer.emit('connection', socket); // this didn't work either //console.log('chunkLen', firstChunk.byteLength); - var myDuplex = packerStream.create(socket); - console.log('httpsTunnel (Admin) servername', servername); - program.tlsTunnelServer.emit('connection', myDuplex); - - socket.on('data', function (chunk) { - console.log('[' + Date.now() + '] socket data', chunk.byteLength); - myDuplex.push(chunk); - }); - socket.on('error', function (err) { - console.error('[error] httpsTunnel (Admin) TODO close'); - console.error(err); - }); + program.tlsTunnelServer.emit('connection', wrapSocket(socket)); }; }; diff --git a/package.json b/package.json index 51720cb..77a568e 100644 --- a/package.json +++ b/package.json @@ -51,10 +51,10 @@ "commander": "^2.9.0", "greenlock": "^2.1.12", "jsonwebtoken": "^7.1.9", - "localhost.daplie.me-certificates": "^1.3.0", + "localhost.daplie.me-certificates": "^1.3.3", "redirect-https": "^1.1.0", "sni": "^1.0.0", - "tunnel-packer": "^1.2.0", + "tunnel-packer": "^1.3.0", "ws": "^2.2.3" } } diff --git a/wstunneld.js b/wstunneld.js index ee22a1f..c03eef0 100644 --- a/wstunneld.js +++ b/wstunneld.js @@ -347,6 +347,7 @@ module.exports.create = function (copts) { Object.keys(remotes).forEach(function (jwtoken) { removeToken(jwtoken); }); + ws.terminate(); } ws.on('close', hangup); From d82530e1dbc4f3f65986fd78cafe70fa595f8880 Mon Sep 17 00:00:00 2001 From: tigerbot Date: Fri, 26 May 2017 18:18:06 -0600 Subject: [PATCH 8/9] filtering out wildcard domains with no remotes left --- wstunneld.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wstunneld.js b/wstunneld.js index c03eef0..8559621 100644 --- a/wstunneld.js +++ b/wstunneld.js @@ -36,7 +36,7 @@ Devices.list = function (store, servername) { // first so the one with the biggest natural match with be found first. var deviceList = []; Object.keys(store).filter(function (pattern) { - return pattern[0] === '*'; + return pattern[0] === '*' && store[pattern].length; }).sort(function (a, b) { return b.length - a.length; }).some(function (pattern) { From 5c7d65546be161a106b183ea8eba5d7e08fac5ac Mon Sep 17 00:00:00 2001 From: tigerbot Date: Mon, 5 Jun 2017 11:00:54 -0600 Subject: [PATCH 9/9] filter out remote groups with no remotes left --- wstunneld.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wstunneld.js b/wstunneld.js index 8559621..ff75f96 100644 --- a/wstunneld.js +++ b/wstunneld.js @@ -29,7 +29,7 @@ Devices.remove = function (store, servername, device) { return devices.splice(index, 1)[0]; }; Devices.list = function (store, servername) { - if (store[servername]) { + if (store[servername] && store[servername].length) { return store[servername]; } // There wasn't an exact match so check any of the wildcard domains, sorted longest