Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for subnet mask on the "Parse IP range" operation #1896

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
105 changes: 98 additions & 7 deletions src/core/lib/IP.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -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");
}

Expand Down Expand Up @@ -189,6 +189,57 @@ 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} match
* @param {boolean} includeNetworkInfo
* @param {boolean} enumerateAddresses
* @param {boolean} allowLargeList
* @returns {string}
*/
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
while (maskCalculate) {
cidr += maskCalculate & 1;
maskCalculate >>>= 1;
}
if (!validateCidr(cidr)) {
throw new OperationError("IPv4 CIDR must be less than 32");
}

const ip1 = network & mask,
ip2 = ip1 | ~mask;

if (includeNetworkInfo) {
output += "Network: " + ipv4ToStr(network) + "\n";
output += "CIDR: " + cidr + "\n";
output += "Mask: " + ipv4ToStr(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.
Expand All @@ -207,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));
}
Expand Down Expand Up @@ -509,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.";

Expand Down
11 changes: 7 additions & 4 deletions src/core/operations/ParseIPRange.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -22,7 +22,7 @@ class ParseIPRange extends Operation {

this.name = "Parse IP range";
this.module = "Default";
this.description = "Given a CIDR range (e.g. <code>10.0.0.0/24</code>), hyphenated range (e.g. <code>10.0.0.0 - 10.0.1.0</code>), 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.<br><br>IPv6 is supported but will not be enumerated.";
this.description = "This operation provides network information and enumerates all IP addresses in the given range.<br>Supported inputs:<br><ul><li>IP with a CIDR (e.g. <code>10.0.0.0/24</code>)</li><li>IP with a subnet mask (e.g <code>10.0.0.0/255.255.255.0</code>)</li><li>Hyphenated range (e.g. <code>10.0.0.0 - 10.0.1.0</code>). Only one hyphenated range is allowed</li><li>List of IPs and/or CIDR ranges/subnet masks separated by a new line</li></ul><br>IPv6 is supported but will not be enumerated.";
this.infoURL = "https://wikipedia.org/wiki/Subnetwork";
this.inputType = "string";
this.outputType = "string";
Expand Down Expand Up @@ -60,7 +60,8 @@ 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*$/,
ipv4ListRegex = /^\s*(((?:\d{1,3}\.){3}\d{1,3})(\/(\d\d?))?(\n|$)(\n*))+\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?)|(?:\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;
Expand All @@ -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))) {
Expand All @@ -79,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). Only one hyphenated range is allowed\nIPv6 also supported.");
}
}

Expand Down
26 changes: 24 additions & 2 deletions tests/operations/tests/ParseIPRange.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand All @@ -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: [
{
Expand Down Expand Up @@ -107,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",
Expand All @@ -121,7 +143,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). Only one hyphenated range is allowed\nIPv6 also supported.",
recipeConfig: [
{
"op": "Parse IP range",
Expand Down
Loading