From b6e3b51be24e3fd0a7b54038c526c80f04798fb3 Mon Sep 17 00:00:00 2001 From: Ylian Saint-Hilaire Date: Wed, 5 May 2021 02:24:23 -0700 Subject: [PATCH] Started work on traffic accounting. --- meshrelay.js | 35 ++++++++++++++++++++++++++++++++++- meshuser.js | 9 ++++++++- webserver.js | 15 +++++++++++++++ 3 files changed, 57 insertions(+), 2 deletions(-) diff --git a/meshrelay.js b/meshrelay.js index 4a1d6320..ba0890de 100644 --- a/meshrelay.js +++ b/meshrelay.js @@ -966,6 +966,10 @@ function CreateLocalRelayEx(parent, ws, req, domain, user, cookie) { obj.ws = ws; obj.user = user; + // Check the protocol in use + var protocolInUse = parseInt(req.query.p); + if (typeof protocolInUse != 'number') { protocolInUse = 0; } + // If there is no authentication, drop this connection if (obj.user == null) { try { ws.close(); parent.parent.debug('relay', 'Relay: Connection with no authentication'); } catch (e) { console.log(e); } return; } @@ -997,6 +1001,8 @@ function CreateLocalRelayEx(parent, ws, req, domain, user, cookie) { // Hold traffic until we connect to the target ws._socket.pause(); + ws._socket.bytesReadEx = 0; + ws._socket.bytesWrittenEx = 0; // Mesh Rights const MESHRIGHT_EDITMESH = 1; @@ -1020,11 +1026,28 @@ function CreateLocalRelayEx(parent, ws, req, domain, user, cookie) { // Clean a IPv6 address that encodes a IPv4 address function cleanRemoteAddr(addr) { if (addr.startsWith('::ffff:')) { return addr.substring(7); } else { return addr; } } + // Perform data accounting + function dataAccounting() { + const datain = ((obj.client.bytesRead - obj.client.bytesReadEx) + (ws._socket.bytesRead - ws._socket.bytesReadEx)); + const dataout = ((obj.client.bytesWritten - obj.client.bytesWrittenEx) + (ws._socket.bytesWritten - ws._socket.bytesWrittenEx)); + obj.client.bytesReadEx = obj.client.bytesRead; + obj.client.bytesWrittenEx = obj.client.bytesWritten; + ws._socket.bytesReadEx = ws._socket.bytesRead; + ws._socket.bytesWrittenEx = ws._socket.bytesWritten; + + // Add to counters + if (parent.trafficStats.localRelayIn[protocolInUse]) { parent.trafficStats.localRelayIn[protocolInUse] += datain; } else { parent.trafficStats.localRelayIn[protocolInUse] = datain; } + if (parent.trafficStats.localRelayOut[protocolInUse]) { parent.trafficStats.localRelayOut[protocolInUse] += dataout; } else { parent.trafficStats.localRelayOut[protocolInUse] = dataout; } + } + // Disconnect obj.close = function (arg) { // If the web socket is already closed, stop here. if (obj.ws == null) return; + // Perform data accounting + dataAccounting(); + // Collect how many raw bytes where received and sent. // We sum both the websocket and TCP client in this case. var inTraffc = obj.ws._socket.bytesRead, outTraffc = obj.ws._socket.bytesWritten; @@ -1083,17 +1106,27 @@ function CreateLocalRelayEx(parent, ws, req, domain, user, cookie) { // Setup TCP client obj.client = new net.Socket(); + obj.client.bytesReadEx = 0; + obj.client.bytesWrittenEx = 0; obj.client.connect(obj.tcpport, node.host, function () { // Log the start of the connection obj.time = Date.now(); var event = { etype: 'relay', action: 'relaylog', domain: domain.id, userid: obj.user._id, username: obj.user.name, msgid: 13, msgArgs: [obj.id, obj.req.clientIp, obj.host], msg: 'Started relay session \"' + obj.id + '\" from ' + obj.req.clientIp + ' to ' + obj.host, nodeid: req.query.nodeid, protocol: req.query.p }; parent.parent.DispatchEvent(['*', obj.user._id, obj.meshid, obj.nodeid], obj, event); + // Count the session + if (parent.trafficStats.localRelayCount[protocolInUse]) { parent.trafficStats.localRelayCount[protocolInUse] += 1; } else { parent.trafficStats.localRelayCount[protocolInUse] = 1; } + // Start the session ws.send('c'); ws._socket.resume(); }); - obj.client.on('data', function (data) { try { this.pause(); ws.send(data, this.clientResume); } catch (ex) { console.log(ex); } }); // Perform relay + obj.client.on('data', function (data) { + // Perform data accounting + dataAccounting(); + // Perform relay + try { this.pause(); ws.send(data, this.clientResume); } catch (ex) { console.log(ex); } + }); obj.client.on('close', function () { obj.close(); }); obj.client.on('error', function (err) { obj.close(); }); obj.client.clientResume = function () { try { obj.client.resume(); } catch (ex) { console.log(ex); } }; diff --git a/meshuser.js b/meshuser.js index b9a178fc..9a8be6de 100644 --- a/meshuser.js +++ b/meshuser.js @@ -905,7 +905,7 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use switch (cmd) { case 'help': { - var fin = '', f = '', availcommands = 'help,maintenance,info,versions,resetserver,usersessions,closeusersessions,tasklimiter,setmaxtasks,cores,migrationagents,agentstats,agentissues,webstats,mpsstats,swarmstats,acceleratorsstats,updatecheck,serverupdate,nodeconfig,heapdump,relays,autobackup,backupconfig,dupagents,dispatchtable,badlogins,showpaths,le,lecheck,leevents,dbstats,dbcounters,sms,amtacm,certhashes,watchdog,amtmanager,amtpasswords,certexpire'; + var fin = '', f = '', availcommands = 'help,maintenance,info,versions,resetserver,usersessions,closeusersessions,tasklimiter,setmaxtasks,cores,migrationagents,agentstats,agentissues,webstats,trafficstats,mpsstats,swarmstats,acceleratorsstats,updatecheck,serverupdate,nodeconfig,heapdump,relays,autobackup,backupconfig,dupagents,dispatchtable,badlogins,showpaths,le,lecheck,leevents,dbstats,dbcounters,sms,amtacm,certhashes,watchdog,amtmanager,amtpasswords,certexpire'; if (parent.parent.config.settings.heapdump === true) { availcommands += ',heapdump'; } availcommands = availcommands.split(',').sort(); while (availcommands.length > 0) { if (f.length > 80) { fin += (f + ',\r\n'); f = ''; } f += (((f != '') ? ', ' : ' ') + availcommands.shift()); } @@ -1113,6 +1113,13 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use } break; } + case 'trafficstats': { + var stats = parent.getTrafficStats(); + for (var i in stats) { + if (typeof stats[i] == 'object') { r += (i + ': ' + JSON.stringify(stats[i]) + '\r\n'); } else { r += (i + ': ' + stats[i] + '\r\n'); } + } + break; + } case 'watchdog': { if (parent.parent.watchdog == null) { r = 'Server watchdog not active.'; diff --git a/webserver.js b/webserver.js index f874fe3f..374293df 100644 --- a/webserver.js +++ b/webserver.js @@ -342,6 +342,18 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) { } obj.getAgentStats = function () { return obj.agentStats; } + // Traffic counters + obj.trafficStats = { + httpRequestCount: 0, + relayCount: {}, + relayIn: {}, + relayOut: {}, + localRelayCount: {}, + localRelayIn: {}, + localRelayOut: {} + } + obj.getTrafficStats = function () { return obj.trafficStats; } + // Keep a record of the last agent issues. obj.getAgentIssues = function () { return obj.agentIssues; } obj.setAgentIssue = function (agent, issue) { obj.agentIssues.push([new Date().toLocaleTimeString(), agent.remoteaddrport, issue]); while (obj.setAgentIssue.length > 50) { obj.agentIssues.shift(); } } @@ -5327,6 +5339,9 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) { // Useful for debugging reverse proxy issues parent.debug('httpheaders', req.method, req.url, req.headers); + // Count the HTTP request + obj.trafficStats.httpRequestCount++; + // Set the real IP address of the request // If a trusted reverse-proxy is sending us the remote IP address, use it. var ipex = '0.0.0.0', xforwardedhost = req.headers.host;