From c4ed768c11fb82e56d76e48aded238bb342bb79d Mon Sep 17 00:00:00 2001 From: anonion Date: Mon, 9 Sep 2024 16:14:56 +0000 Subject: [PATCH 1/5] initial subnet mask additions --- src/core/lib/IP.mjs | 49 ++++++++++++++++++++++++++++ src/core/operations/ParseIPRange.mjs | 5 ++- 2 files changed, 53 insertions(+), 1 deletion(-) diff --git a/src/core/lib/IP.mjs b/src/core/lib/IP.mjs index c97f87ab5..41bada93f 100644 --- a/src/core/lib/IP.mjs +++ b/src/core/lib/IP.mjs @@ -189,6 +189,55 @@ export function ipv6HyphenatedRange(range, includeNetworkInfo) { return output; } +/** + * Parses an IPv4 subnet mask (e.g. 192.168.0.0/255.255.255.0) and displays information about it. + * + * @param {RegExp} subnetMask + * @param {boolean} includeNetworkInfo + * @param {boolean} enumerateAddresses + * @param {boolean} allowLargeList + * @returns {string} + */ +export function ipv4SubnetMask(subnetMask, includeNetworkInfo, enumerateAddresses, allowLargeList) { + const network = strToIpv4(subnetMask[1]), + mask = subnetMask[2]; + let output = ""; + + // Calculate the CIDR + const octets = mask.split(".").map(Number); + let cidr = 0; + for (let i = 0; i < octets.length; i++) { + let octet = octets[i]; + while (octet) { + cidr += (octet & 1); // Check if the last bit is 1 + octet >>= 1; // Shift bits to the right + } + } + if (cidr < 0 || cidr > 31) { + throw new OperationError("IPv4 CIDR must be less than 32"); + } + + const ip1 = network & strToIpv4(mask), + ip2 = ip1 | ~strToIpv4(mask); + + if (includeNetworkInfo) { + output += "Network: " + ipv4ToStr(network) + "\n"; + output += "CIDR: " + cidr + "\n"; + output += "Mask: " + mask + "\n"; + output += "Range: " + ipv4ToStr(ip1) + " - " + ipv4ToStr(ip2) + "\n"; + output += "Total addresses in range: " + (((ip2 - ip1) >>> 0) + 1) + "\n\n"; + } + + if (enumerateAddresses) { + if (cidr >= 16 || allowLargeList) { + output += generateIpv4Range(ip1, ip2).join("\n"); + } else { + output += _LARGE_RANGE_ERROR; + } + } + return output; +} + /** * Parses a list of IPv4 addresses separated by a new line (\n) and displays information * about it. diff --git a/src/core/operations/ParseIPRange.mjs b/src/core/operations/ParseIPRange.mjs index 2c59c0150..5a2496e3a 100644 --- a/src/core/operations/ParseIPRange.mjs +++ b/src/core/operations/ParseIPRange.mjs @@ -7,7 +7,7 @@ import Operation from "../Operation.mjs"; import OperationError from "../errors/OperationError.mjs"; -import {ipv4CidrRange, ipv4HyphenatedRange, ipv4ListedRange, ipv6CidrRange, ipv6HyphenatedRange, ipv6ListedRange} from "../lib/IP.mjs"; +import {ipv4CidrRange, ipv4HyphenatedRange, ipv4SubnetMask, ipv4ListedRange, ipv6CidrRange, ipv6HyphenatedRange, ipv6ListedRange} from "../lib/IP.mjs"; /** * Parse IP range operation @@ -60,6 +60,7 @@ class ParseIPRange extends Operation { // Check what type of input we are looking at const ipv4CidrRegex = /^\s*((?:\d{1,3}\.){3}\d{1,3})\/(\d\d?)\s*$/, ipv4RangeRegex = /^\s*((?:\d{1,3}\.){3}\d{1,3})\s*-\s*((?:\d{1,3}\.){3}\d{1,3})\s*$/, + ipv4SubnetMaskRegex = /^\s*((?:\d{1,3}\.){3}\d{1,3})\/((?:\d{1,3}\.){3}\d{1,3})\s*$/, ipv4ListRegex = /^\s*(((?:\d{1,3}\.){3}\d{1,3})(\/(\d\d?))?(\n|$)(\n*))+\s*$/, ipv6CidrRegex = /^\s*(((?=.*::)(?!.*::.+::)(::)?([\dA-F]{1,4}:(:|\b)|){5}|([\dA-F]{1,4}:){6})((([\dA-F]{1,4}((?!\4)::|:\b|(?![\dA-F])))|(?!\3\4)){2}|(((2[0-4]|1\d|[1-9])?\d|25[0-5])\.?\b){4}))\/(\d\d?\d?)\s*$/i, ipv6RangeRegex = /^\s*(((?=.*::)(?!.*::[^-]+::)(::)?([\dA-F]{1,4}:(:|\b)|){5}|([\dA-F]{1,4}:){6})((([\dA-F]{1,4}((?!\4)::|:\b|(?![\dA-F])))|(?!\3\4)){2}|(((2[0-4]|1\d|[1-9])?\d|25[0-5])\.?\b){4}))\s*-\s*(((?=.*::)(?!.*::.+::)(::)?([\dA-F]{1,4}:(:|\b)|){5}|([\dA-F]{1,4}:){6})((([\dA-F]{1,4}((?!\17)::|:\b|(?![\dA-F])))|(?!\16\17)){2}|(((2[0-4]|1\d|[1-9])?\d|25[0-5])\.?\b){4}))\s*$/i, @@ -70,6 +71,8 @@ class ParseIPRange extends Operation { return ipv4CidrRange(match, includeNetworkInfo, enumerateAddresses, allowLargeList); } else if ((match = ipv4RangeRegex.exec(input))) { return ipv4HyphenatedRange(match, includeNetworkInfo, enumerateAddresses, allowLargeList); + } else if ((match = ipv4SubnetMaskRegex.exec(input))) { + return ipv4SubnetMask(match, includeNetworkInfo, enumerateAddresses, allowLargeList); } else if ((match = ipv4ListRegex.exec(input))) { return ipv4ListedRange(match, includeNetworkInfo, enumerateAddresses, allowLargeList); } else if ((match = ipv6CidrRegex.exec(input))) { From b58cc6b21984ecac03f8c8618074516bf30df09b Mon Sep 17 00:00:00 2001 From: anonion Date: Mon, 9 Sep 2024 20:23:51 +0000 Subject: [PATCH 2/5] finalize subnet mask changes and small code cleanup --- src/core/lib/IP.mjs | 90 ++++++++++++++++++++-------- src/core/operations/ParseIPRange.mjs | 6 +- 2 files changed, 69 insertions(+), 27 deletions(-) diff --git a/src/core/lib/IP.mjs b/src/core/lib/IP.mjs index 41bada93f..02d46d422 100644 --- a/src/core/lib/IP.mjs +++ b/src/core/lib/IP.mjs @@ -25,7 +25,7 @@ export function ipv4CidrRange(cidr, includeNetworkInfo, enumerateAddresses, allo cidrRange = parseInt(cidr[2], 10); let output = ""; - if (cidrRange < 0 || cidrRange > 31) { + if (!validateCidr(cidrRange)) { throw new OperationError("IPv4 CIDR must be less than 32"); } @@ -192,38 +192,40 @@ export function ipv6HyphenatedRange(range, includeNetworkInfo) { /** * Parses an IPv4 subnet mask (e.g. 192.168.0.0/255.255.255.0) and displays information about it. * - * @param {RegExp} subnetMask + * @param {RegExp} match * @param {boolean} includeNetworkInfo * @param {boolean} enumerateAddresses * @param {boolean} allowLargeList * @returns {string} */ -export function ipv4SubnetMask(subnetMask, includeNetworkInfo, enumerateAddresses, allowLargeList) { - const network = strToIpv4(subnetMask[1]), - mask = subnetMask[2]; - let output = ""; +export function ipv4SubnetMask(match, includeNetworkInfo, enumerateAddresses, allowLargeList) { + const network = strToIpv4(match[1]), + mask = strToIpv4(match[2]); + let output = "", + maskCalculate = mask, + cidr = 0; + + // Validate the subnet mask + if (!validateSubnetMask(mask)) { + throw new OperationError("Invalid subnet mask"); + } // Calculate the CIDR - const octets = mask.split(".").map(Number); - let cidr = 0; - for (let i = 0; i < octets.length; i++) { - let octet = octets[i]; - while (octet) { - cidr += (octet & 1); // Check if the last bit is 1 - octet >>= 1; // Shift bits to the right - } + while (maskCalculate) { + cidr += maskCalculate & 1; + maskCalculate >>>= 1; } - if (cidr < 0 || cidr > 31) { + if (!validateCidr(cidr)) { throw new OperationError("IPv4 CIDR must be less than 32"); } - const ip1 = network & strToIpv4(mask), - ip2 = ip1 | ~strToIpv4(mask); + const ip1 = network & mask, + ip2 = ip1 | ~mask; if (includeNetworkInfo) { output += "Network: " + ipv4ToStr(network) + "\n"; output += "CIDR: " + cidr + "\n"; - output += "Mask: " + mask + "\n"; + output += "Mask: " + ipv4ToStr(mask) + "\n"; output += "Range: " + ipv4ToStr(ip1) + " - " + ipv4ToStr(ip2) + "\n"; output += "Total addresses in range: " + (((ip2 - ip1) >>> 0) + 1) + "\n\n"; } @@ -256,15 +258,27 @@ export function ipv4ListedRange(match, includeNetworkInfo, enumerateAddresses, a const ipv4CidrList = ipv4List.filter(function(a) { return a.includes("/"); }); + const cidrCheck = /^\d\d?$/; for (let i = 0; i < ipv4CidrList.length; i++) { const network = strToIpv4(ipv4CidrList[i].split("/")[0]); - const cidrRange = parseInt(ipv4CidrList[i].split("/")[1], 10); - if (cidrRange < 0 || cidrRange > 31) { - throw new OperationError("IPv4 CIDR must be less than 32"); + const cidr = ipv4CidrList[i].split("/")[1]; + let mask; + + // Check if this is a CIDR or subnet mask + if (cidrCheck.exec(cidr)) { + const cidrRange = parseInt(cidr, 10); + if (!validateCidr(cidrRange)) { + throw new OperationError("IPv4 CIDR must be less than 32"); + } + mask = ~(0xFFFFFFFF >>> cidrRange); + } else { + mask = strToIpv4(cidr); + if (!validateSubnetMask(mask)) { + throw new OperationError("Invalid subnet mask"); + } } - const mask = ~(0xFFFFFFFF >>> cidrRange), - cidrIp1 = network & mask, - cidrIp2 = cidrIp1 | ~mask; + const cidrIp1 = network & mask; + const cidrIp2 = cidrIp1 | ~mask; ipv4List.splice(ipv4List.indexOf(ipv4CidrList[i]), 1); ipv4List.push(ipv4ToStr(cidrIp1), ipv4ToStr(cidrIp2)); } @@ -558,6 +572,34 @@ export function ipv6Compare(a, b) { } return 0; } +/** + * Validates a given subnet mask + * + * @param {string} mask + * @returns {boolean} + */ +export function validateSubnetMask (mask) { + for (; mask !== 0; mask <<= 1) { + if ((mask & (1<<31)) === 0) { + return false; + } + } + return true; +} + +/** + * Validates a given CIDR + * + * @param {string} cidr + * @returns {boolean} + */ +export function validateCidr (cidr) { + if (cidr < 0 || cidr > 31) { + return false; + } else { + return true; + } +} const _LARGE_RANGE_ERROR = "The specified range contains more than 65,536 addresses. Running this query could crash your browser. If you want to run it, select the \"Allow large queries\" option. You are advised to turn off \"Auto Bake\" whilst editing large ranges."; diff --git a/src/core/operations/ParseIPRange.mjs b/src/core/operations/ParseIPRange.mjs index 5a2496e3a..fcb3b0061 100644 --- a/src/core/operations/ParseIPRange.mjs +++ b/src/core/operations/ParseIPRange.mjs @@ -22,7 +22,7 @@ class ParseIPRange extends Operation { this.name = "Parse IP range"; this.module = "Default"; - this.description = "Given a CIDR range (e.g. 10.0.0.0/24), hyphenated range (e.g. 10.0.0.0 - 10.0.1.0), or a list of IPs and/or CIDR ranges (separated by a new line), this operation provides network information and enumerates all IP addresses in the range.

IPv6 is supported but will not be enumerated."; + this.description = "Given a CIDR range (e.g. 10.0.0.0/24), IP and subnet mask (e.g 10.0.0.0/255.255.255.0), hyphenated range (e.g. 10.0.0.0 - 10.0.1.0), or a list of IPs and/or CIDR ranges/subnet masks (separated by a new line), this operation provides network information and enumerates all IP addresses in the range.

IPv6 is supported but will not be enumerated."; this.infoURL = "https://wikipedia.org/wiki/Subnetwork"; this.inputType = "string"; this.outputType = "string"; @@ -61,7 +61,7 @@ class ParseIPRange extends Operation { const ipv4CidrRegex = /^\s*((?:\d{1,3}\.){3}\d{1,3})\/(\d\d?)\s*$/, ipv4RangeRegex = /^\s*((?:\d{1,3}\.){3}\d{1,3})\s*-\s*((?:\d{1,3}\.){3}\d{1,3})\s*$/, ipv4SubnetMaskRegex = /^\s*((?:\d{1,3}\.){3}\d{1,3})\/((?:\d{1,3}\.){3}\d{1,3})\s*$/, - ipv4ListRegex = /^\s*(((?:\d{1,3}\.){3}\d{1,3})(\/(\d\d?))?(\n|$)(\n*))+\s*$/, + ipv4ListRegex = /^\s*(((?:\d{1,3}\.){3}\d{1,3})(\/((?:\d\d?)|(?:\d{1,3}\.){3}\d{1,3}))?(\n|$)(\n*))+\s*$/, ipv6CidrRegex = /^\s*(((?=.*::)(?!.*::.+::)(::)?([\dA-F]{1,4}:(:|\b)|){5}|([\dA-F]{1,4}:){6})((([\dA-F]{1,4}((?!\4)::|:\b|(?![\dA-F])))|(?!\3\4)){2}|(((2[0-4]|1\d|[1-9])?\d|25[0-5])\.?\b){4}))\/(\d\d?\d?)\s*$/i, ipv6RangeRegex = /^\s*(((?=.*::)(?!.*::[^-]+::)(::)?([\dA-F]{1,4}:(:|\b)|){5}|([\dA-F]{1,4}:){6})((([\dA-F]{1,4}((?!\4)::|:\b|(?![\dA-F])))|(?!\3\4)){2}|(((2[0-4]|1\d|[1-9])?\d|25[0-5])\.?\b){4}))\s*-\s*(((?=.*::)(?!.*::.+::)(::)?([\dA-F]{1,4}:(:|\b)|){5}|([\dA-F]{1,4}:){6})((([\dA-F]{1,4}((?!\17)::|:\b|(?![\dA-F])))|(?!\16\17)){2}|(((2[0-4]|1\d|[1-9])?\d|25[0-5])\.?\b){4}))\s*$/i, ipv6ListRegex = /^\s*((((?=.*::)(?!.*::.+::)(::)?([\dA-F]{1,4}:(:|\b)|){5}|([\dA-F]{1,4}:){6})((([\dA-F]{1,4}((?!\4)::|:\b|(?![\dA-F])))|(?!\3\4)){2}|(((2[0-4]|1\d|[1-9])?\d|25[0-5])\.?\b){4}))(\/(\d\d?\d?))?(\n|$)(\n*))+\s*$/i; @@ -82,7 +82,7 @@ class ParseIPRange extends Operation { } else if ((match = ipv6ListRegex.exec(input))) { return ipv6ListedRange(match, includeNetworkInfo); } else { - throw new OperationError("Invalid input.\n\nEnter either a CIDR range (e.g. 10.0.0.0/24) or a hyphenated range (e.g. 10.0.0.0 - 10.0.1.0). IPv6 also supported."); + throw new OperationError("Invalid input.\n\nThe following input strings are supported:\nCIDR range (e.g. 10.0.0.0/24)\nSubnet mask (e.g. 10.0.0.0/255.255.255.0)\nHyphenated range (e.g. 10.0.0.0 - 10.0.1.0)\nIPv6 also supported."); } } From aba4f4373f5c6d2536398aed7d844f410a758ec7 Mon Sep 17 00:00:00 2001 From: anonion Date: Mon, 9 Sep 2024 20:36:00 +0000 Subject: [PATCH 3/5] add subnet mask test --- tests/operations/tests/ParseIPRange.mjs | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/tests/operations/tests/ParseIPRange.mjs b/tests/operations/tests/ParseIPRange.mjs index d7ef0b524..e4b6c1dba 100644 --- a/tests/operations/tests/ParseIPRange.mjs +++ b/tests/operations/tests/ParseIPRange.mjs @@ -19,6 +19,17 @@ TestRegister.addTests([ }, ], }, + { + name: "Parse IPv4 subnet mask", + input: "10.0.0.0/255.255.255.252", + expectedOutput: "Network: 10.0.0.0\nCIDR: 30\nMask: 255.255.255.252\nRange: 10.0.0.0 - 10.0.0.3\nTotal addresses in range: 4\n\n10.0.0.0\n10.0.0.1\n10.0.0.2\n10.0.0.3", + recipeConfig: [ + { + "op": "Parse IP range", + "args": [true, true, false] + }, + ], + }, { name: "Parse IPv4 hyphenated", input: "10.0.0.0 - 10.0.0.3", @@ -32,7 +43,7 @@ TestRegister.addTests([ }, { name: "Parse IPv4 list", - input: "10.0.0.8\n10.0.0.5/30\n10.0.0.1\n10.0.0.3", + input: "10.0.0.8\n10.0.0.5/30\n10.0.0.1\n10.0.0.5/255.255.255.252\n10.0.0.3\n10.0.0.6/255.255.255.252", expectedOutput: "Minimum subnet required to hold this range:\n\tNetwork: 10.0.0.0\n\tCIDR: 28\n\tMask: 255.255.255.240\n\tSubnet range: 10.0.0.0 - 10.0.0.15\n\tTotal addresses in subnet: 16\n\nRange: 10.0.0.1 - 10.0.0.8\nTotal addresses in range: 8\n\n10.0.0.1\n10.0.0.2\n10.0.0.3\n10.0.0.4\n10.0.0.5\n10.0.0.6\n10.0.0.7\n10.0.0.8", recipeConfig: [ { @@ -121,7 +132,7 @@ TestRegister.addTests([ { name: "invalid IPv6 address error", input: "2404:6800:4001:/12", - expectedOutput: "Invalid input.\n\nEnter either a CIDR range (e.g. 10.0.0.0/24) or a hyphenated range (e.g. 10.0.0.0 - 10.0.1.0). IPv6 also supported.", + expectedOutput: "Invalid input.\n\nThe following input strings are supported:\nCIDR range (e.g. 10.0.0.0/24)\nSubnet mask (e.g. 10.0.0.0/255.255.255.0)\nHyphenated range (e.g. 10.0.0.0 - 10.0.1.0)\nIPv6 also supported.", recipeConfig: [ { "op": "Parse IP range", From a37beea1d25d6d2b386b40ca3381d2b3d82b04a7 Mon Sep 17 00:00:00 2001 From: anonion Date: Mon, 9 Sep 2024 21:06:12 +0000 Subject: [PATCH 4/5] better description --- src/core/operations/ParseIPRange.mjs | 4 ++-- tests/operations/tests/ParseIPRange.mjs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/core/operations/ParseIPRange.mjs b/src/core/operations/ParseIPRange.mjs index fcb3b0061..ec4a7fc82 100644 --- a/src/core/operations/ParseIPRange.mjs +++ b/src/core/operations/ParseIPRange.mjs @@ -22,7 +22,7 @@ class ParseIPRange extends Operation { this.name = "Parse IP range"; this.module = "Default"; - this.description = "Given a CIDR range (e.g. 10.0.0.0/24), IP and subnet mask (e.g 10.0.0.0/255.255.255.0), hyphenated range (e.g. 10.0.0.0 - 10.0.1.0), or a list of IPs and/or CIDR ranges/subnet masks (separated by a new line), this operation provides network information and enumerates all IP addresses in the range.

IPv6 is supported but will not be enumerated."; + this.description = "This operation provides network information and enumerates all IP addresses in the given range.
Supported inputs:
  • IP with a CIDR (e.g. 10.0.0.0/24)
  • IP with a subnet mask (e.g 10.0.0.0/255.255.255.0)
  • Hyphenated range (e.g. 10.0.0.0 - 10.0.1.0). Only one hyphenated range is allowed
  • List of IPs and/or CIDR ranges/subnet masks separated by a new line

IPv6 is supported but will not be enumerated."; this.infoURL = "https://wikipedia.org/wiki/Subnetwork"; this.inputType = "string"; this.outputType = "string"; @@ -82,7 +82,7 @@ class ParseIPRange extends Operation { } else if ((match = ipv6ListRegex.exec(input))) { return ipv6ListedRange(match, includeNetworkInfo); } else { - throw new OperationError("Invalid input.\n\nThe following input strings are supported:\nCIDR range (e.g. 10.0.0.0/24)\nSubnet mask (e.g. 10.0.0.0/255.255.255.0)\nHyphenated range (e.g. 10.0.0.0 - 10.0.1.0)\nIPv6 also supported."); + throw new OperationError("Invalid input.\n\nThe following input strings are supported:\nCIDR range (e.g. 10.0.0.0/24)\nSubnet mask (e.g. 10.0.0.0/255.255.255.0)\nHyphenated range (e.g. 10.0.0.0 - 10.0.1.0). Only one hyphenated range is allowed\nIPv6 also supported."); } } diff --git a/tests/operations/tests/ParseIPRange.mjs b/tests/operations/tests/ParseIPRange.mjs index e4b6c1dba..428fa96bd 100644 --- a/tests/operations/tests/ParseIPRange.mjs +++ b/tests/operations/tests/ParseIPRange.mjs @@ -132,7 +132,7 @@ TestRegister.addTests([ { name: "invalid IPv6 address error", input: "2404:6800:4001:/12", - expectedOutput: "Invalid input.\n\nThe following input strings are supported:\nCIDR range (e.g. 10.0.0.0/24)\nSubnet mask (e.g. 10.0.0.0/255.255.255.0)\nHyphenated range (e.g. 10.0.0.0 - 10.0.1.0)\nIPv6 also supported.", + expectedOutput: "Invalid input.\n\nThe following input strings are supported:\nCIDR range (e.g. 10.0.0.0/24)\nSubnet mask (e.g. 10.0.0.0/255.255.255.0)\nHyphenated range (e.g. 10.0.0.0 - 10.0.1.0). Only one hyphenated range is allowed\nIPv6 also supported.", recipeConfig: [ { "op": "Parse IP range", From 601915aa615035d5c5da3bbe48ea973db5e09078 Mon Sep 17 00:00:00 2001 From: anonion Date: Mon, 9 Sep 2024 21:06:40 +0000 Subject: [PATCH 5/5] add invalid ipv4 subnet test --- tests/operations/tests/ParseIPRange.mjs | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/tests/operations/tests/ParseIPRange.mjs b/tests/operations/tests/ParseIPRange.mjs index 428fa96bd..f6e4a6165 100644 --- a/tests/operations/tests/ParseIPRange.mjs +++ b/tests/operations/tests/ParseIPRange.mjs @@ -118,6 +118,17 @@ TestRegister.addTests([ }, ], }, + { + name: "Invalid IPv4 subnet mask error", + input: "192.168.0.0/255.255.253.0", + expectedOutput: "Invalid subnet mask", + recipeConfig: [ + { + "op": "Parse IP range", + "args": [true, true, false] + }, + ], + }, { name: "IPv6 subnet out of range error", input: "2404:6800:4001::/129",