Added web relay session timeout, #4172
Ylian Saint-Hilaire committed
Jun 26, 2022 at 16:55 UTC
76b1c925509ead5959de9f3a42021ef035a0084b
2 files changed
+59
-23
apprelays.js
+25
-3
@@ -69,7 +69,7 @@ function SerialTunnel(options) {
69
}
70
71
// Construct a Web relay object
72
-module.exports.CreateMultiWebRelay = function (parent, db, req, args, domain, userid, nodeid, addr, port, appid) {
72
+module.exports.CreateWebRelaySession = function (parent, db, req, args, domain, userid, nodeid, addr, port, appid) {
73
const obj = {};
74
obj.parent = parent;
75
obj.lastOperation = Date.now();
@@ -90,6 +90,23 @@ module.exports.CreateMultiWebRelay = function (parent, db, req, args, domain, us
90
obj.closed = false;
91
obj.onclose = null;
92
93
+ // Check if any tunnels need to be cleaned up
94
+ obj.checkTimeout = function () {
95
+ const limit = Date.now() - (1 * 60 * 1000); // This is is 5 minutes before current time
96
+
97
+ // Close any old non-websocket tunnels
98
+ const tunnelToRemove = [];
99
+ for (var i in tunnels) { if ((tunnels[i].lastOperation < limit) && (tunnels[i].isWebSocket !== true)) { tunnelToRemove.push(tunnels[i]); } }
100
+ for (var i in tunnelToRemove) { console.log('session-close-tunnel'); tunnelToRemove[i].close(); }
101
+
102
+ // Close this session if no longer used
103
+ if (obj.lastOperation < limit) {
104
+ var count = 0;
105
+ for (var i in tunnels) { count++; }
106
+ if (count == 0) { console.log('session-close-self'); close(); } // Time limit reached and no tunnels, clean up.
107
+ }
108
+ }
109
+
110
// Handle new HTTP request
111
obj.handleRequest = function (req, res) {
112
pendingRequests.push([req, res]);
@@ -135,7 +152,7 @@ module.exports.CreateMultiWebRelay = function (parent, db, req, args, domain, us
152
obj.closed = true;
153
for (var i in tunnels) { tunnels[i].close(); }
154
tunnels = null;
138
- if (obj.onclose) { obj.onclose(obj.userid + '/' + obj.multiTunnelId); }
155
+ if (obj.onclose) { obj.onclose(obj.userid + '/' + obj.sessionId); }
156
delete obj.userid;
157
delete obj.lastOperation;
158
}
@@ -150,6 +167,7 @@ module.exports.CreateWebRelay = function (parent, db, args, domain) {
167
const WebSocket = require('ws')
168
169
const obj = {};
170
+ obj.lastOperation = Date.now();
171
obj.relayActive = false;
172
obj.closed = false;
173
obj.isWebSocket = false;
@@ -163,6 +181,7 @@ module.exports.CreateWebRelay = function (parent, db, args, domain) {
181
// Process a HTTP request
182
obj.processRequest = function (req, res) {
183
if (obj.relayActive == false) { console.log("ERROR: Attempt to use an unconnected tunnel"); return false; }
184
+ parent.lastOperation = obj.lastOperation = Date.now();
185
186
// Construct the HTTP request
187
var request = req.method + ' ' + req.url + ' HTTP/' + req.httpVersion + '\r\n';
@@ -265,6 +284,7 @@ module.exports.CreateWebRelay = function (parent, db, args, domain) {
284
obj.tls = require('tls').connect(tlsoptions, function () {
285
parent.parent.debug('relay', "Web Relay Secure TLS Connection");
286
obj.relayActive = true;
287
+ parent.lastOperation = obj.lastOperation = Date.now(); // Update time of last opertion performed
288
if (obj.onconnect) { obj.onconnect(obj.tunnelId); } // Event connection
289
});
290
obj.tls.setEncoding('binary');
@@ -275,6 +295,7 @@ module.exports.CreateWebRelay = function (parent, db, args, domain) {
295
} else {
296
// No TLS needed, tunnel is now active
297
obj.relayActive = true;
298
+ parent.lastOperation = obj.lastOperation = Date.now(); // Update time of last opertion performed
299
if (obj.onconnect) { obj.onconnect(obj.tunnelId); } // Event connection
300
}
301
}
@@ -376,7 +397,8 @@ module.exports.CreateWebRelay = function (parent, db, args, domain) {
397
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
398
obj.res.end(data, 'binary'); // Write the data
399
delete obj.res;
379
-
400
+ parent.lastOperation = obj.lastOperation = Date.now(); // Update time of last opertion performed
401
+
402
// Event completion
403
if (obj.oncompleted) { obj.oncompleted(obj.tunnelId); }
404
}
webrelayserver.js
+34
-20
@@ -26,8 +26,9 @@ module.exports.CreateWebRelayServer = function (parent, db, args, certificates,
26
obj.app = obj.express();
27
obj.webRelayServer = null;
28
obj.port = 0;
29
- var nextMultiTunnelId = 1;
30
- var relayMultiTunnels = {} // RelayID --> Web Mutli-Tunnel
29
+ obj.cleanupTimer = null;
30
+ var nextSessionId = 1;
31
+ var relaySessions = {} // RelayID --> Web Mutli-Tunnel
32
const constants = (require('crypto').constants ? require('crypto').constants : require('constants')); // require('constants') is deprecated in Node 11.10, use require('crypto').constants instead.
33
var tlsSessionStore = {}; // Store TLS session information for quick resume.
34
var tlsSessionStoreCount = 0; // Number of cached TLS session information in store.
@@ -115,10 +116,10 @@ module.exports.CreateWebRelayServer = function (parent, db, args, certificates,
116
return next();
117
} else {
118
if ((req.session.userid != null) && (req.session.rid != null)) {
118
- var relayMultiTunnel = relayMultiTunnels[req.session.userid + '/' + req.session.rid];
119
- if (relayMultiTunnel != null) {
119
+ var relaySession = relaySessions[req.session.userid + '/' + req.session.rid];
120
+ if (relaySession != null) {
121
// The multi-tunnel session is valid, use it
121
- relayMultiTunnel.handleRequest(req, res);
122
+ relaySession.handleRequest(req, res);
123
} else {
124
// No multi-tunnel session with this relay identifier, close the HTTP request.
125
res.end();
@@ -149,26 +150,34 @@ module.exports.CreateWebRelayServer = function (parent, db, args, certificates,
150
const appid = parseInt(req.query.appid);
151
152
// Check to see if we already have a multi-relay session that matches exactly this device and port for this user
152
- var relayMultiTunnel = null;
153
- for (var i in relayMultiTunnels) {
154
- const xrelayMultiTunnel = relayMultiTunnels[i];
155
- if ((xrelayMultiTunnel.domain.id == domain.id) && (xrelayMultiTunnel.userid == userid) && (xrelayMultiTunnel.nodeid == nodeid) && (xrelayMultiTunnel.addr == addr) && (xrelayMultiTunnel.port == port) && (xrelayMultiTunnel.appid == appid)) {
156
- relayMultiTunnel = xrelayMultiTunnel; // We found an exact match
153
+ var relaySession = null;
154
+ for (var i in relaySessions) {
155
+ const xrelaySession = relaySessions[i];
156
+ if ((xrelaySession.domain.id == domain.id) && (xrelaySession.userid == userid) && (xrelaySession.nodeid == nodeid) && (xrelaySession.addr == addr) && (xrelaySession.port == port) && (xrelaySession.appid == appid)) {
157
+ relaySession = xrelaySession; // We found an exact match
158
}
159
}
160
160
- if (relayMultiTunnel != null) {
161
+ if (relaySession != null) {
162
// Since we found a match, use it
162
- req.session.rid = relayMultiTunnel.multiTunnelId;
163
+ req.session.rid = relaySession.sessionId;
164
} else {
164
- // Create the multi-tunnel
165
- relayMultiTunnel = require('./apprelays.js').CreateMultiWebRelay(parent, db, req, args, domain, userid, nodeid, addr, port, appid);
166
- relayMultiTunnel.onclose = function (multiTunnelId) { delete obj.relayTunnels[multiTunnelId]; }
167
- relayMultiTunnel.multiTunnelId = nextMultiTunnelId++;
168
-
169
- // Set the tunnel
170
- relayMultiTunnels[userid + '/' + relayMultiTunnel.multiTunnelId] = relayMultiTunnel;
171
- req.session.rid = relayMultiTunnel.multiTunnelId;
165
+ // Create a web relay session
166
+ relaySession = require('./apprelays.js').CreateWebRelaySession(parent, db, req, args, domain, userid, nodeid, addr, port, appid);
167
+ relaySession.onclose = function (sessionId) {
168
+ // Remove the relay session
169
+ delete relaySessions[sessionId];
170
+ // If there are not more relay sessions, clear the cleanup timer
171
+ if ((Object.keys(relaySessions).length == 0) && (obj.cleanupTimer != null)) { clearInterval(obj.cleanupTimer); obj.cleanupTimer = null; }
172
+ }
173
+ relaySession.sessionId = nextSessionId++;
174
+
175
+ // Set the multi-tunnel session
176
+ relaySessions[userid + '/' + relaySession.sessionId] = relaySession;
177
+ req.session.rid = relaySession.sessionId;
178
+
179
+ // Setup the cleanup timer if needed
180
+ if (obj.cleanupTimer == null) { obj.cleanupTimer = setInterval(checkTimeout, 10000); }
181
}
182
183
// Redirect to root
@@ -191,6 +200,11 @@ module.exports.CreateWebRelayServer = function (parent, db, args, certificates,
200
}
201
}
202
203
+ // Check that everything is cleaned up
204
+ function checkTimeout() {
205
+ for (var i in relaySessions) { relaySessions[i].checkTimeout(); }
206
+ }
207
+
208
// Find a free port starting with the specified one and going up.
209
function CheckListenPort(port, addr, func) {
210
var s = obj.net.createServer(function (socket) { });