UNPKG

@simplyappdevs/cidr-calculator

Version:

Classless Inter-Domain Routing (CIDR) Notation Calculator

593 lines (592 loc) 20 kB
/** * Node imports */ /** * SimplyAppDevs imports */ /** * 3rd party imports */ import { default as BitSet } from 'bitset'; /** * Return defalt or cloned Octects instance * @param clone Octect instance to clonef from * @returns Return default or cloned instance */ function defaultOctects(clone) { if (clone) { return { first: clone.first, second: clone.second, third: clone.third, fourth: clone.fourth, add: (num) => { return defaultOctects(OctectsImpl.add(clone, num)); }, substract: (num) => { return defaultOctects(OctectsImpl.substract(clone, num)); }, toArray: () => { return clone.toArray(); }, toBinary: () => { return clone.toBinary(); }, toString: () => { return clone.toString(); } }; } else { return { first: 0, second: 0, third: 0, fourth: 0, add: (num) => { return defaultOctects(OctectsImpl.add(OCTECTSMIN, num)); }, substract: (num) => { return defaultOctects(OctectsImpl.substract(OCTECTSMIN, num)); }, toArray: () => { return [0, 0, 0, 0]; }, toBinary: () => { return '00000000000000000000000000000000'; }, toString: () => { return '0.0.0.0'; } }; } } /** * Octects implementation */ class OctectsImpl { static MAXOCTECTSNUM = 0xFFFFFFFF >>> 0; static IPREGEX = /^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/; static SHIFTVALS = [24, 16, 8, 0]; static LENVALS = [8, 16, 24, 32]; static ANDVALS = [0xFF000000, 0xFF0000, 0xFF00, 0xFF]; // private members _first = 0; _second = 0; _third = 0; _fourth = 0; /** * Creates new OCTECTS instance * @param ip IP in either IP string, OCTECTS, or array of number */ constructor(ip) { if (ip) { if (typeof ip === 'string') { const tmpOct = OctectsImpl.octectsFromString(ip); this.setOctects(tmpOct.first, tmpOct.second, tmpOct.third, tmpOct.fourth); } else if (typeof ip === 'number') { const tmpOct = OctectsImpl.numberToOctects(ip); this.setOctects(tmpOct.first, tmpOct.second, tmpOct.third, tmpOct.fourth); } else if (Array.isArray(ip)) { this.setOctects(ip[0], ip[1], ip[2], ip[3]); } else { // OCTECTS this.setOctects(ip.first, ip.second, ip.third, ip.fourth); } } } /** * Sets octects * @param f First octect * @param s Second octect * @param t Third octect * @param h Forth octect */ setOctects(f, s, t, h) { // validate before we set if ([f, s, t, h].some((octect) => { return ((octect < 0) || (octect > 255)); })) { throw new Error('One or more octect values are invalid'); } // set this._first = f; this._second = s; this._third = t; this._fourth = h; } /** * Gets first octect */ get first() { return this._first; } /** * Gets second octect */ get second() { return this._second; } /** * Gets third octect */ get third() { return this._third; } /** * Gets fourth octect */ get fourth() { return this._fourth; } /** * Adds number of IPs to current IP * @param num Number of IP to add * @returns New resulting OCTECTS */ add(num) { return OctectsImpl.add(this, num); } /** * Subtract number of IPs to currrent IP * @param num Number of IP to substract * @returns New resulting OCTECTS */ substract(num) { return OctectsImpl.substract(this, num); } /** * Returns octects as array of number * @returns Octects as [first, second, third, forth] */ toArray() { return [this._first, this._second, this._third, this._fourth]; } /** * Returns octects as IP string * @returns Octects as IP string */ toString() { return `${this._first}.${this._second}.${this._third}.${this._fourth}`; } /** * Returns octects as binary string * @returns Octects as binary string */ toBinary() { // return `${this.formatBinary(this._first)}${this.formatBinary(this._second)}${this.formatBinary(this._third)}${this.formatBinary(this._fourth)}`; return `${OctectsImpl.formatBinary(OctectsImpl.octectsToNumber(this))}`; } /** * Converts from IP string to OCTECTS * @param ip IP in string format * @returns IP as OCTECTS */ static octectsFromString(ip) { // validate ip = ip.trim(); if (!OctectsImpl.IPREGEX.test(ip)) { throw new Error('Invalid IP format'); } // extract let octString = OctectsImpl.IPREGEX.exec(ip); // remove first item octString.splice(0, 1); let octs = octString.map((oct) => { return parseInt(oct); }); return new OctectsImpl([octs[0], octs[1], octs[2], octs[3]]); } /** * Returns number for the octects * @param octects Octects to convert to decimal * @returns Decimal value for octects */ static octectsToNumber(octects) { // get array const octs = octects.toArray(); return octs.reduce((acc, oct, ind) => { return acc | ((oct << OctectsImpl.SHIFTVALS[ind]) >>> 0); }, 0) >>> 0; } /** * Returns OCTECTS representation of a number * @param num Number to convert to OCTECTS * @returns OCTECTS for the number */ static numberToOctects(num) { // validate if (num > OctectsImpl.MAXOCTECTSNUM) { throw new Error(`Number ${num} is greater than maximum octects value ${OctectsImpl.MAXOCTECTSNUM}`); } // AND the number by masks and right shift by the octect bits const octs = OctectsImpl.ANDVALS.map((val, ind) => { return (num & val) >>> OctectsImpl.SHIFTVALS[ind]; }); // return return new OctectsImpl([octs[0], octs[1], octs[2], octs[3]]); } /** * Formats octect in binary string * @param octect Octect to format in binary * @param separator Optional seperator to use * @returns Returns octect in binary string (8 bits) */ static formatBinary(octect, separator) { // convert to binary string const binString = octect.toString(2); const binLen = binString.length; // determine the max string length let padCount = 0; OctectsImpl.LENVALS.forEach((val) => { if ((binLen <= val) && (padCount === 0)) { padCount = val; } }); // return const fullBinString = binString.padStart(padCount, '0'); const fillBinStrLen = fullBinString.length; separator = separator || ''; const seps = [separator, separator, separator, '']; let startInd = 0; let endInd = 0; return OctectsImpl.LENVALS.reduce((acc, val, ind) => { endInd += 8; acc = `${acc}${fullBinString.substring(startInd, endInd)}${seps[ind]}`; startInd = endInd; return acc; }, ''); } /** * Adds number of IPs to an IP * @param octects Octects to add to * @param num Number of IP to add * @returns New resulting OCTECTS */ static add(octects, num) { // validation const curVal = octects.toString(); if (curVal === '255.255.255.255') { throw new Error(`Cannot add to \'${curVal}\'`); } if (num < 0) { throw new Error(`Invalid number \'${num}\' to add`); } if (num === 0) { return new OctectsImpl(octects); } // convert octects to number let octNum = OctectsImpl.octectsToNumber(octects); // add it octNum += num; // validate if (octNum > OctectsImpl.MAXOCTECTSNUM) { throw new Error(`Resulting value \'${octNum}\' is greater than maximum octects value \'${OctectsImpl.MAXOCTECTSNUM}\'`); } return new OctectsImpl(OctectsImpl.numberToOctects(octNum)); } /** * Subtract number of IPs to an IP * @param octects Octects to substract from * @param num Number of IP to substract * @returns New resulting OCTECTS */ static substract(octects, num) { // validation const curVal = octects.toString(); if (curVal === '0.0.0.0') { throw new Error(`Cannot substract from \'${curVal}\'`); } if (num < 0) { throw new Error(`Invalid number \'${num}\' to substract`); } if (num === 0) { return new OctectsImpl(octects); } // covert octects to number let octNum = OctectsImpl.octectsToNumber(octects); octNum -= num; return new OctectsImpl(OctectsImpl.numberToOctects(octNum)); } } // MAX & MIN octects export const OCTECTSMIN = defaultOctects(); export const OCTECTSMAX = defaultOctects(new OctectsImpl('255.255.255.255')); /** * Returns default or clone CIDRInformation instance * @returns Default CIDRInformation instance */ function defaultCIDRInformation() { return { networkPrefix: '0.0.0.0', cidrBlock: 0, subnetMask: '0.0.0.0', wilcardMask: '0.0.0.0', broadcastIP: '0.0.0.0', minIP: '0.0.0.0', maxIP: '0.0.0.0', totalIPs: 0 }; } /** * Returns default CIDR instance * @param clone CIDR instance to clone from * @returns Default CIDR instance */ function defaultCIDR(clone) { return { inputIP: clone.inputIP, inputCIDR: clone.inputCIDR, ipOctects: defaultOctects(clone.ipOctects), cidrInformation: clone.cidrInformation, maxSubnetCount: clone.maxSubnetCount, calculateCIDR: (cidr) => { return clone.calculateCIDR(cidr); }, splitCIDR: (subnetCount) => { return clone.splitCIDR(subnetCount); }, toString: () => { return clone.toString(); } }; } /** * CIDR implementation */ class CIDRImpl { // private _octects = new OctectsImpl(); _inputIP = this._octects.toString(); _inputCIDR = 0; _cidrInfo = defaultCIDRInformation(); _maxSubnetCount = 0; _networkBitCount = 0; _hostBitCount = 0; /** * Creates new CIDR instance * @param ip IP in either IP string, OCTECTS, or array of number * @param cidr CIDR block */ constructor(ip, cidr) { if (ip) { this._octects = new OctectsImpl(ip); // validation of IP happens in here this._inputIP = this._octects.toString(); } if (cidr) { if (typeof cidr === 'string') { const cidrNum = parseInt(cidr); if (isNaN(cidrNum)) { throw new Error(`\'${cidr}\' is an invalid CIDR block value`); } this._inputCIDR = cidrNum; } else { this._inputCIDR = cidr; } } // if we have IP, we need CIDR if ((this._inputIP !== '0.0.0.0') && (this._inputCIDR <= 0)) { throw new Error(`Must specify CIDR block value for IP \'${this._inputIP}\'`); } // calculate CIDR if ((this._octects.toString() !== '0.0.0.0') && (this._inputCIDR > 0)) { this.calculateCIDR(this._inputCIDR); } } /** * Converts BitSet to Octects * @param bs Bitset to convert * @returns Returns Octects representation of the BitSet */ bitsetToIP(bs) { // grab every 8bits const frh = +bs.slice(0, 7).toString(10); const thd = +bs.slice(8, 15).toString(10); const sec = +bs.slice(16, 23).toString(10); const fst = +bs.slice(24, 31).toString(10); return new OctectsImpl([fst, sec, thd, frh]); } /** * Gets IP that was used to initialize this instance */ get inputIP() { return this._inputIP; } ; /** * Gets IP that was used to initialize this instance */ get inputCIDR() { return this._inputCIDR; } ; /** * Gets octects */ get ipOctects() { return this._octects; } /** * Gets CIDRInformation (last successful calculation) */ get cidrInformation() { return this._cidrInfo; } /** * Gets the maximum number of subnet that this CIDR can be split into */ get maxSubnetCount() { return this._maxSubnetCount; } /** * Calculate CIDR information based on CIDR block * @param cidr CIDR block */ calculateCIDR(cidr) { // CIDR is between 1-32 if ((cidr < 1) || (cidr > 32)) { throw new Error(`CIDR block value must be between 1 and 32`); } if (this._octects.first === 0) { throw new Error(`Cannot calculate CIDR for \'${this.inputIP}\'`); } // IP consists of 4x 8bits octects // each octect range is 0 - 255 // CIDR block value indicates which top N bits to "lock" (unchangble) based on the input IP // for example: // IP := 172.90.0.2 and CIDR := 8 - that means 172 is locked while the last 3 octects (24bits) can change // bitset for CIDR block const cidrBS = new BitSet('0xffffffff'); // calculate the lower bits that can change let varBits = 32 - cidr; this._networkBitCount = cidr; this._hostBitCount = varBits; if (varBits > 0) { varBits -= 1; // 0 based array index cidrBS.setRange(0, varBits, 0); } // bitset for input IP const inputIPBS = new BitSet(`${this.ipOctects.toBinary()}`); // netmask - AND inputIP and CIDR block const lockedCIDR = inputIPBS.and(cidrBS); // minimum IP is just the locked CIDR block (this is also the network prefix) const minIP = this.bitsetToIP(lockedCIDR).toString(); // maximum IP is lockedCIDR block but with the lower 32-cidr bits flipped const maxIPBS = lockedCIDR.clone(); maxIPBS.flip(0, 31 - cidr); const maxIP = this.bitsetToIP(maxIPBS).toString(); // maximum # of IP 2^N where N is the # of lower bits that can change const maxIPCount = Math.pow(2, 32 - cidr); // wildcard address is NOT of netmask const wilcardMask = cidrBS.clone().not(); // save result this._inputCIDR = cidr; this._maxSubnetCount = Math.pow(2, this._hostBitCount) - 2; this._cidrInfo = { networkPrefix: minIP, cidrBlock: this.inputCIDR, subnetMask: `${this.bitsetToIP(cidrBS).toString()}`, wilcardMask: `${this.bitsetToIP(wilcardMask).toString()}`, broadcastIP: maxIP, minIP: minIP, maxIP: maxIP, totalIPs: maxIPCount }; return this._cidrInfo; } /** * Returns subnet segments for the current CIDR * @param subnetCount Number of subnets to split from the current CIDR */ splitCIDR(subnetCount) { // validate if ((this._inputIP === '0.0.0.0') || (this._inputCIDR === 0)) { throw new Error('Cannot split CIDR because it has not been defined.'); } if (subnetCount <= 1) { return [this._cidrInfo]; } if (this._hostBitCount === 0) { return []; } if (subnetCount > this.maxSubnetCount) { throw new Error(`CIDR can only be split into maximum of \'${this.maxSubnetCount}\' subnets.`); } // subnetting takes the available host bits and lock N high order bits // to use as network bits (in addtional to the CIDR network bits) // so we need to find the max bits we need to reserve for the subnetting // formula: 2^N >= subnetCount - find N that result to subnetCount or the next // one greater than subnetCount let realSubnetCount = 0; let n = 0; while (realSubnetCount === 0) { const subCount = Math.pow(2, n); if (subCount >= subnetCount) { realSubnetCount = subCount; } else { ++n; } } // new CIDR let newCIDR = this._cidrInfo.cidrBlock + n; // loop to calculate subnets let subnets = []; let startIP = this._cidrInfo.minIP; let startIPOctects = new OctectsImpl(startIP); for (let i = 0; i < realSubnetCount; i++) { // subnet using startIP but new CIDR subnets[i] = new CIDRImpl(startIP, newCIDR).cidrInformation; // next start IP is current subnet maxIP + 1 startIPOctects = new OctectsImpl(subnets[i].maxIP); startIPOctects = startIPOctects.add(1); startIP = startIPOctects.toString(); } return subnets; } /** * Return string representation of this instance * @returns String representation of this CIDR instance */ toString() { return `${this._inputIP}/${this.inputCIDR}`; } /** * Returns CIDR block based on subnet mask * @param mask Subnet mask */ static cidrBlockFromSubnetMask(mask) { // convert to octects const maskOctects = new OctectsImpl(mask); // bitset const maskBS = new BitSet(maskOctects.toBinary()); if (maskBS.toString(10) === '0') { throw new Error(`\'${mask}\' is not a valid subnet mask value`); } // subnet mask cannot have 0 in between 1s const bits = maskBS.toString(2); if (/01/.test(bits)) { throw new Error(`\'${mask}\' is not a valid subnet mask format`); } // get the last 0s const zeroBits = /(0+)/.exec(bits); if (!zeroBits) { // all 1s return 32; } else { // regex match - first item is the full matched text and subsequent items are the groups // in this case it has to be a match and the first item is all the 0s return 32 - zeroBits[0].length; } } } /** * CIDR module */ export const CIDRModule = { parseCIDR: (ip, cidr) => { return defaultCIDR(new CIDRImpl(ip, cidr)); }, parseOctects: (ip) => { return defaultOctects(new OctectsImpl(ip)); }, cidrFromSubnetMask: (mask) => { return CIDRImpl.cidrBlockFromSubnetMask(mask); }, octectsFromIPString: (ip) => { return defaultOctects(OctectsImpl.octectsFromString(ip)); }, octectsToNumber: (octs) => { return OctectsImpl.octectsToNumber(octs); }, numberToBinary: (num, sep) => { return OctectsImpl.formatBinary(num, sep); }, addToIP: (ip, num) => { const octs = new OctectsImpl(ip); return defaultOctects(octs.add(num)); }, substractFromIP: (ip, num) => { const octs = new OctectsImpl(ip); return defaultOctects(octs.substract(num)); } };