From 0ea53fdf9677ed463e80e275af5910c0b5dcad49 Mon Sep 17 00:00:00 2001 From: Dorian Niemiec Date: Sun, 25 Aug 2024 19:17:19 +0200 Subject: [PATCH] Lint out the codebase, add SVRJSFork() function, networking-related code, and ipSubnetUtils.js utility functions file and corresponding unit tests --- src/index.js | 176 +++++++++++++++++++++++++++++- src/utils/ipSubnetUtils.js | 50 +++++++++ tests/utils/ipSubnetUtils.test.js | 49 +++++++++ 3 files changed, 270 insertions(+), 5 deletions(-) create mode 100644 src/utils/ipSubnetUtils.js create mode 100644 tests/utils/ipSubnetUtils.test.js diff --git a/src/index.js b/src/index.js index 6b3c766..91a1153 100644 --- a/src/index.js +++ b/src/index.js @@ -207,6 +207,10 @@ if (!fs.existsSync(process.dirname + "/temp")) const cluster = require("./utils/clusterBunShim.js"); // Cluster module with shim for Bun const legacyModWrapper = require("./utils/legacyModWrapper.js"); const generateErrorStack = require("./utils/generateErrorStack.js"); +const { + calculateNetworkIPv4FromCidr, + calculateBroadcastIPv4FromCidr, +} = require("./utils/ipSubnetUtils.js"); //const serverHTTPErrorDescs = require("../res/httpErrorDescriptions.js"); //const getOS = require("./utils/getOS.js"); //const parseURL = require("./utils/urlParser.js"); @@ -344,7 +348,7 @@ if (typeof process.serverConfig.port === "string") { if (process.serverConfig.port.match(/^[0-9]+$/)) { process.serverConfig.port = parseInt(process.serverConfig.port); } else { - const portLMatch = port.match( + const portLMatch = process.serverConfig.port.match( /^(\[[^ \]@\/\\]+\]|[^ \]\[:@\/\\]+):([0-9]+)$/, ); if (portLMatch) { @@ -357,7 +361,7 @@ if (typeof process.serverConfig.port === "string") { } if (typeof process.serverConfig.sport === "string") { if (process.serverConfig.sport.match(/^[0-9]+$/)) { - process.serverConfig.sport = parseInt(sport); + process.serverConfig.sport = parseInt(process.serverConfig.sport); } else { const sportLMatch = process.serverConfig.sport.match( /^(\[[^ \]@\/\\]+\]|[^ \]\[:@\/\\]+):([0-9]+)$/, @@ -402,6 +406,58 @@ try { wwwrootError = err; } +// IP and network inteface-related +let ifaces = {}; +let ifaceEx = null; +try { + ifaces = os.networkInterfaces(); +} catch (err) { + ifaceEx = err; +} +var ips = []; +const brdIPs = ["255.255.255.255", "127.255.255.255", "0.255.255.255"]; +const netIPs = ["127.0.0.0"]; + +Object.keys(ifaces).forEach((ifname) => { + let alias = 0; + ifaces[ifname].forEach((iface) => { + if (iface.family !== "IPv4" || iface.internal !== false) { + return; + } + if (alias >= 1) { + ips.push(ifname + ":" + alias, iface.address); + } else { + ips.push(ifname, iface.address); + } + brdIPs.push(calculateBroadcastIPv4FromCidr(iface.cidr)); + netIPs.push(calculateNetworkIPv4FromCidr(iface.cidr)); + alias++; + }); +}); + +if (ips.length == 0) { + Object.keys(ifaces).forEach((ifname) => { + let alias = 0; + ifaces[ifname].forEach((iface) => { + if (iface.family !== "IPv6" || iface.internal !== false) { + return; + } + if (alias >= 1) { + ips.push(ifname + ":" + alias, iface.address); + } else { + ips.push(ifname, iface.address); + } + alias++; + }); + }); +} + +// Server IP address +var host = ips[(ips.length) - 1]; +if (!host) host = "[offline]"; + +// TODO: Public IP address-related + // SSL-related let key = ""; let cert = ""; @@ -931,6 +987,108 @@ middleware.forEach((middlewareO) => { } }); +// SVR.JS worker spawn-related +let SVRJSInitialized = false; +let crashed = false; +let threadLimitWarned = false; + +// SVR.JS worker forking function +function SVRJSFork() { + // Log + if (SVRJSInitialized) + serverconsole.locmessage( + "Starting next thread, because previous one hung up/crashed...", + ); + // Fork new worker + var newWorker = {}; + try { + if ( + !threadLimitWarned && + cluster.__shimmed__ && + process.isBun && + process.versions.bun && + process.versions.bun[0] != "0" + ) { + threadLimitWarned = true; + serverconsole.locwarnmessage( + "SVR.JS limited the number of workers to one, because of startup problems in Bun 1.0 and newer with shimmed (not native) clustering module. Reliability may suffer.", + ); + } + if ( + !( + cluster.__shimmed__ && + process.isBun && + process.versions.bun && + process.versions.bun[0] != "0" && + Object.keys(cluster.workers) > 0 + ) + ) { + newWorker = cluster.fork(); + } else { + if (SVRJSInitialized) + serverconsole.locwarnmessage( + "SVR.JS limited the number of workers to one, because of startup problems in Bun 1.0 and newer with shimmed (not native) clustering module. Reliability may suffer.", + ); + } + } catch (err) { + if (err.name == "NotImplementedError") { + // If cluster.fork throws a NotImplementedError, shim cluster module + cluster.bunShim(); + if ( + !threadLimitWarned && + cluster.__shimmed__ && + process.isBun && + process.versions.bun && + process.versions.bun[0] != "0" + ) { + threadLimitWarned = true; + serverconsole.locwarnmessage( + "SVR.JS limited the number of workers to one, because of startup problems in Bun 1.0 and newer with shimmed (not native) clustering module. Reliability may suffer.", + ); + } + if ( + !( + cluster.__shimmed__ && + process.isBun && + process.versions.bun && + process.versions.bun[0] != "0" && + Object.keys(cluster.workers) > 0 + ) + ) { + newWorker = cluster.fork(); + } else { + if (SVRJSInitialized) + serverconsole.locwarnmessage( + "SVR.JS limited the number of workers to one, because of startup problems in Bun 1.0 and newer with shimmed (not native) clustering module. Reliability may suffer.", + ); + } + } else { + throw err; + } + } + + // Add event listeners + if (newWorker.on) { + newWorker.on("error", function (err) { + if (!exiting) + serverconsole.locwarnmessage( + "There was a problem when handling SVR.JS worker! (from master process side) Reason: " + + err.message, + ); + }); + newWorker.on("exit", function () { + if (!exiting && Object.keys(cluster.workers).length == 0) { + crashed = true; + SVRJSFork(); + } + }); + // TODO: add listeners to workers + // newWorker.on("message", bruteForceListenerWrapper(newWorker)); + // newWorker.on("message", listenConnListener); + } +} + +// Starting function function start(init) { init = Boolean(init); if (cluster.isPrimary || cluster.isPrimary === undefined) { @@ -970,7 +1128,7 @@ function start(init) { /^(?:0\.|1\.0\.|1\.1\.[0-9](?![0-9])|1\.1\.1[0-2](?![0-9]))/, ) ) && - users.some(function (entry) { + process.serverConfig.users.some(function (entry) { return entry.pbkdf2; }) ) @@ -1161,7 +1319,12 @@ function start(init) { } // Print server startup information - if (!(process.serverConfig.secure && process.serverConfig.disableNonEncryptedServer)) + if ( + !( + process.serverConfig.secure && + process.serverConfig.disableNonEncryptedServer + ) + ) serverconsole.locmessage( "Starting HTTP server at " + (typeof process.serverConfig.port == "number" @@ -1213,7 +1376,10 @@ function start(init) { } catch (err) { if (err.code != "ERR_SERVER_ALREADY_LISTEN") throw err; } - if (process.serverConfig.secure && !process.serverConfig.disableNonEncryptedServer) { + if ( + process.serverConfig.secure && + !process.serverConfig.disableNonEncryptedServer + ) { try { if (typeof process.serverConfig.port == "number" && listenAddress) { server2.listen(process.serverConfig.port, listenAddress); diff --git a/src/utils/ipSubnetUtils.js b/src/utils/ipSubnetUtils.js new file mode 100644 index 0000000..8c11eeb --- /dev/null +++ b/src/utils/ipSubnetUtils.js @@ -0,0 +1,50 @@ +function calculateBroadcastIPv4FromCidr(ipWithCidr) { + // Check if CIDR notation is valid, if it's not, return null + if (!ipWithCidr) return null; + const ipCA = ipWithCidr.match(/^((?:(?:25[0-5]|(?:2[0-4]|1\d|[1-9]|)\d)\.?\b){4})\/([0-2][0-9]|3[0-2]|[0-9])$/); + if (!ipCA) return null; + + // Extract IP and mask (numeric format) + const ip = ipCA[1]; + const mask = parseInt(ipCA[2]); + + return ip + .split(".") + .map((num, index) => { + // Calculate resulting 8-bit + var power = Math.max(Math.min(mask - index * 8, 8), 0); + return ( + (parseInt(num) & ((Math.pow(2, power) - 1) << (8 - power))) | + (Math.pow(2, 8 - power) - 1) + ).toString(); + }) + .join("."); +} + +function calculateNetworkIPv4FromCidr(ipWithCidr) { + // Check if CIDR notation is valid, if it's not, return null + if (!ipWithCidr) return null; + const ipCA = ipWithCidr.match(/^((?:(?:25[0-5]|(?:2[0-4]|1\d|[1-9]|)\d)\.?\b){4})\/([0-2][0-9]|3[0-2]|[0-9])$/); + if (!ipCA) return null; + + // Extract IP and mask (numeric format) + const ip = ipCA[1]; + const mask = parseInt(ipCA[2]); + + return ip + .split(".") + .map((num, index) => { + // Calculate resulting 8-bit + var power = Math.max(Math.min(mask - index * 8, 8), 0); + return ( + parseInt(num) & + ((Math.pow(2, power) - 1) << (8 - power)) + ).toString(); + }) + .join("."); +} + +module.exports = { + calculateBroadcastIPv4FromCidr: calculateBroadcastIPv4FromCidr, + calculateNetworkIPv4FromCidr: calculateNetworkIPv4FromCidr, +}; diff --git a/tests/utils/ipSubnetUtils.test.js b/tests/utils/ipSubnetUtils.test.js new file mode 100644 index 0000000..a92cc29 --- /dev/null +++ b/tests/utils/ipSubnetUtils.test.js @@ -0,0 +1,49 @@ +const { + calculateBroadcastIPv4FromCidr, + calculateNetworkIPv4FromCidr, + } = require('../../src/utils/ipSubnetUtils'); + + describe('IPv4 subnet utilties', () => { + describe('calculateBroadcastIPv4FromCidr', () => { + test('should return the broadcast address for a given CIDR', () => { + expect(calculateBroadcastIPv4FromCidr('192.168.1.0/24')).toBe('192.168.1.255'); + expect(calculateBroadcastIPv4FromCidr('192.168.1.0/25')).toBe('192.168.1.127'); + expect(calculateBroadcastIPv4FromCidr('192.168.1.0/26')).toBe('192.168.1.63'); + expect(calculateBroadcastIPv4FromCidr('192.168.1.0/27')).toBe('192.168.1.31'); + expect(calculateBroadcastIPv4FromCidr('192.168.1.0/28')).toBe('192.168.1.15'); + expect(calculateBroadcastIPv4FromCidr('192.168.1.0/29')).toBe('192.168.1.7'); + expect(calculateBroadcastIPv4FromCidr('192.168.1.0/30')).toBe('192.168.1.3'); + expect(calculateBroadcastIPv4FromCidr('192.168.1.0/31')).toBe('192.168.1.1'); + expect(calculateBroadcastIPv4FromCidr('192.168.1.0/32')).toBe('192.168.1.0'); + }); + + test('should return null for invalid CIDR notation', () => { + expect(calculateBroadcastIPv4FromCidr(null)).toBe(null); + expect(calculateBroadcastIPv4FromCidr('192.168.1.0')).toBe(null); + expect(calculateBroadcastIPv4FromCidr('192.168.1.0/')).toBe(null); + expect(calculateBroadcastIPv4FromCidr('192.168.1.0/abc')).toBe(null); + }); + }); + + describe('calculateNetworkIPv4FromCidr', () => { + test('should return the network address for a given CIDR', () => { + expect(calculateNetworkIPv4FromCidr('192.168.1.0/24')).toBe('192.168.1.0'); + expect(calculateNetworkIPv4FromCidr('192.168.1.0/25')).toBe('192.168.1.0'); + expect(calculateNetworkIPv4FromCidr('192.168.1.0/26')).toBe('192.168.1.0'); + expect(calculateNetworkIPv4FromCidr('192.168.1.0/27')).toBe('192.168.1.0'); + expect(calculateNetworkIPv4FromCidr('192.168.1.0/28')).toBe('192.168.1.0'); + expect(calculateNetworkIPv4FromCidr('192.168.1.0/29')).toBe('192.168.1.0'); + expect(calculateNetworkIPv4FromCidr('192.168.1.0/30')).toBe('192.168.1.0'); + expect(calculateNetworkIPv4FromCidr('192.168.1.0/31')).toBe('192.168.1.0'); + expect(calculateNetworkIPv4FromCidr('192.168.1.0/32')).toBe('192.168.1.0'); + }); + + test('should return null for invalid CIDR notation', () => { + expect(calculateNetworkIPv4FromCidr(null)).toBe(null); + expect(calculateNetworkIPv4FromCidr('192.168.1.0')).toBe(null); + expect(calculateNetworkIPv4FromCidr('192.168.1.0/')).toBe(null); + expect(calculateNetworkIPv4FromCidr('192.168.1.0/abc')).toBe(null); + }); + }); + }); + \ No newline at end of file