diff --git a/apprelays.js b/apprelays.js index 8acd6fec..440060e3 100644 --- a/apprelays.js +++ b/apprelays.js @@ -69,7 +69,7 @@ function SerialTunnel(options) { } // Construct a Web relay object -module.exports.CreateMultiWebRelay = function (parent, db, req, args, domain, userid, nodeid, addr, port, appid) { +module.exports.CreateWebRelaySession = function (parent, db, req, args, domain, userid, nodeid, addr, port, appid) { const obj = {}; obj.parent = parent; obj.lastOperation = Date.now(); @@ -90,6 +90,23 @@ module.exports.CreateMultiWebRelay = function (parent, db, req, args, domain, us obj.closed = false; obj.onclose = null; + // Check if any tunnels need to be cleaned up + obj.checkTimeout = function () { + const limit = Date.now() - (1 * 60 * 1000); // This is is 5 minutes before current time + + // Close any old non-websocket tunnels + const tunnelToRemove = []; + for (var i in tunnels) { if ((tunnels[i].lastOperation < limit) && (tunnels[i].isWebSocket !== true)) { tunnelToRemove.push(tunnels[i]); } } + for (var i in tunnelToRemove) { console.log('session-close-tunnel'); tunnelToRemove[i].close(); } + + // Close this session if no longer used + if (obj.lastOperation < limit) { + var count = 0; + for (var i in tunnels) { count++; } + if (count == 0) { console.log('session-close-self'); close(); } // Time limit reached and no tunnels, clean up. + } + } + // Handle new HTTP request obj.handleRequest = function (req, res) { pendingRequests.push([req, res]); @@ -135,7 +152,7 @@ module.exports.CreateMultiWebRelay = function (parent, db, req, args, domain, us obj.closed = true; for (var i in tunnels) { tunnels[i].close(); } tunnels = null; - if (obj.onclose) { obj.onclose(obj.userid + '/' + obj.multiTunnelId); } + if (obj.onclose) { obj.onclose(obj.userid + '/' + obj.sessionId); } delete obj.userid; delete obj.lastOperation; } @@ -150,6 +167,7 @@ module.exports.CreateWebRelay = function (parent, db, args, domain) { const WebSocket = require('ws') const obj = {}; + obj.lastOperation = Date.now(); obj.relayActive = false; obj.closed = false; obj.isWebSocket = false; @@ -163,6 +181,7 @@ module.exports.CreateWebRelay = function (parent, db, args, domain) { // Process a HTTP request obj.processRequest = function (req, res) { if (obj.relayActive == false) { console.log("ERROR: Attempt to use an unconnected tunnel"); return false; } + parent.lastOperation = obj.lastOperation = Date.now(); // Construct the HTTP request var request = req.method + ' ' + req.url + ' HTTP/' + req.httpVersion + '\r\n'; @@ -265,6 +284,7 @@ module.exports.CreateWebRelay = function (parent, db, args, domain) { obj.tls = require('tls').connect(tlsoptions, function () { parent.parent.debug('relay', "Web Relay Secure TLS Connection"); obj.relayActive = true; + parent.lastOperation = obj.lastOperation = Date.now(); // Update time of last opertion performed if (obj.onconnect) { obj.onconnect(obj.tunnelId); } // Event connection }); obj.tls.setEncoding('binary'); @@ -275,6 +295,7 @@ module.exports.CreateWebRelay = function (parent, db, args, domain) { } else { // No TLS needed, tunnel is now active obj.relayActive = true; + parent.lastOperation = obj.lastOperation = Date.now(); // Update time of last opertion performed if (obj.onconnect) { obj.onconnect(obj.tunnelId); } // Event connection } } @@ -376,7 +397,8 @@ module.exports.CreateWebRelay = function (parent, db, args, domain) { obj.res.set('Content-Security-Policy', "default-src 'self' 'unsafe-inline' 'unsafe-eval' data: blob:;"); // Set an "allow all" policy, see if the can restrict this in the future obj.res.end(data, 'binary'); // Write the data delete obj.res; - + parent.lastOperation = obj.lastOperation = Date.now(); // Update time of last opertion performed + // Event completion if (obj.oncompleted) { obj.oncompleted(obj.tunnelId); } } diff --git a/webrelayserver.js b/webrelayserver.js index f8f5ef44..e826b314 100644 --- a/webrelayserver.js +++ b/webrelayserver.js @@ -26,8 +26,9 @@ module.exports.CreateWebRelayServer = function (parent, db, args, certificates, obj.app = obj.express(); obj.webRelayServer = null; obj.port = 0; - var nextMultiTunnelId = 1; - var relayMultiTunnels = {} // RelayID --> Web Mutli-Tunnel + obj.cleanupTimer = null; + var nextSessionId = 1; + var relaySessions = {} // RelayID --> Web Mutli-Tunnel const constants = (require('crypto').constants ? require('crypto').constants : require('constants')); // require('constants') is deprecated in Node 11.10, use require('crypto').constants instead. var tlsSessionStore = {}; // Store TLS session information for quick resume. var tlsSessionStoreCount = 0; // Number of cached TLS session information in store. @@ -115,10 +116,10 @@ module.exports.CreateWebRelayServer = function (parent, db, args, certificates, return next(); } else { if ((req.session.userid != null) && (req.session.rid != null)) { - var relayMultiTunnel = relayMultiTunnels[req.session.userid + '/' + req.session.rid]; - if (relayMultiTunnel != null) { + var relaySession = relaySessions[req.session.userid + '/' + req.session.rid]; + if (relaySession != null) { // The multi-tunnel session is valid, use it - relayMultiTunnel.handleRequest(req, res); + relaySession.handleRequest(req, res); } else { // No multi-tunnel session with this relay identifier, close the HTTP request. res.end(); @@ -149,26 +150,34 @@ module.exports.CreateWebRelayServer = function (parent, db, args, certificates, const appid = parseInt(req.query.appid); // Check to see if we already have a multi-relay session that matches exactly this device and port for this user - var relayMultiTunnel = null; - for (var i in relayMultiTunnels) { - const xrelayMultiTunnel = relayMultiTunnels[i]; - if ((xrelayMultiTunnel.domain.id == domain.id) && (xrelayMultiTunnel.userid == userid) && (xrelayMultiTunnel.nodeid == nodeid) && (xrelayMultiTunnel.addr == addr) && (xrelayMultiTunnel.port == port) && (xrelayMultiTunnel.appid == appid)) { - relayMultiTunnel = xrelayMultiTunnel; // We found an exact match + var relaySession = null; + for (var i in relaySessions) { + const xrelaySession = relaySessions[i]; + if ((xrelaySession.domain.id == domain.id) && (xrelaySession.userid == userid) && (xrelaySession.nodeid == nodeid) && (xrelaySession.addr == addr) && (xrelaySession.port == port) && (xrelaySession.appid == appid)) { + relaySession = xrelaySession; // We found an exact match } } - if (relayMultiTunnel != null) { + if (relaySession != null) { // Since we found a match, use it - req.session.rid = relayMultiTunnel.multiTunnelId; + req.session.rid = relaySession.sessionId; } else { - // Create the multi-tunnel - relayMultiTunnel = require('./apprelays.js').CreateMultiWebRelay(parent, db, req, args, domain, userid, nodeid, addr, port, appid); - relayMultiTunnel.onclose = function (multiTunnelId) { delete obj.relayTunnels[multiTunnelId]; } - relayMultiTunnel.multiTunnelId = nextMultiTunnelId++; + // Create a web relay session + relaySession = require('./apprelays.js').CreateWebRelaySession(parent, db, req, args, domain, userid, nodeid, addr, port, appid); + relaySession.onclose = function (sessionId) { + // Remove the relay session + delete relaySessions[sessionId]; + // If there are not more relay sessions, clear the cleanup timer + if ((Object.keys(relaySessions).length == 0) && (obj.cleanupTimer != null)) { clearInterval(obj.cleanupTimer); obj.cleanupTimer = null; } + } + relaySession.sessionId = nextSessionId++; - // Set the tunnel - relayMultiTunnels[userid + '/' + relayMultiTunnel.multiTunnelId] = relayMultiTunnel; - req.session.rid = relayMultiTunnel.multiTunnelId; + // Set the multi-tunnel session + relaySessions[userid + '/' + relaySession.sessionId] = relaySession; + req.session.rid = relaySession.sessionId; + + // Setup the cleanup timer if needed + if (obj.cleanupTimer == null) { obj.cleanupTimer = setInterval(checkTimeout, 10000); } } // Redirect to root @@ -191,6 +200,11 @@ module.exports.CreateWebRelayServer = function (parent, db, args, certificates, } } + // Check that everything is cleaned up + function checkTimeout() { + for (var i in relaySessions) { relaySessions[i].checkTimeout(); } + } + // Find a free port starting with the specified one and going up. function CheckListenPort(port, addr, func) { var s = obj.net.createServer(function (socket) { });