From d7a8c650670f609ecdf57f5c32a73bd26c4de926 Mon Sep 17 00:00:00 2001 From: sharevb Date: Wed, 3 Apr 2024 22:45:54 +0200 Subject: [PATCH 1/5] feat: ipv4/6 calculator lib --- src/libs/ip_calculator/ip.js | 406 ++++++++++++++++++++++++++++++ src/libs/ip_calculator/network.js | 268 ++++++++++++++++++++ 2 files changed, 674 insertions(+) create mode 100644 src/libs/ip_calculator/ip.js create mode 100644 src/libs/ip_calculator/network.js diff --git a/src/libs/ip_calculator/ip.js b/src/libs/ip_calculator/ip.js new file mode 100644 index 00000000..14052454 --- /dev/null +++ b/src/libs/ip_calculator/ip.js @@ -0,0 +1,406 @@ +/* eslint-disable no-labels */ +/* eslint-disable no-restricted-syntax */ + +const IPv4MAX = (BigInt(2) ** BigInt(32)) - BigInt(1); +const IPv6MAX = (BigInt(2) ** BigInt(128)) - BigInt(1); + +/** +* Represents a single IP address v4 or v6. +* @class IP +* @param {string} address +* host = new IP("184.170.96.196"); +* @return {object} -> IP{address:"184.170.96.196", version: 4, integer: 0, short: 0} +*/ + +export default class IP { + /** + * @constructor + */ + constructor(address) { + this.integer = 0; + this.short = 0; + this.version = this._checkVersion(address); + this.address = this._checkAddress(address, this.version); + } + + // Public methods + + /** + * toInteger - Converts dotquad or hextet IP to integer + * @return {BigInt} -> 2130706432 + */ + toInteger() { + let bigInt; + if (this.version === 4) { + const splittedAddr = this.address.split('.').reverse(); + bigInt = splittedAddr.reduce((bigInt, octet, index) => { + return (octet * 256 ** index + bigInt + ); + }, 0); + } + else { + const joinedAddr = this.address.split(':').join(''); + bigInt = BigInt(`0x${joinedAddr}`); + } + this.integer = BigInt(bigInt); + return BigInt(bigInt); + } + + /** + * toDottedNotation - Converts big integer IP to full dotquad or hextet representation + * @param {bigint} bigInt + * @return {string} -> "184.170.96.196" + */ + toDottedNotation(bigInt) { + if (this.version === 4) { + return ( + [(bigInt >> BigInt(24) & BigInt(255)), (bigInt >> BigInt(16) & BigInt(255)), + (bigInt >> BigInt(8) & BigInt(255)), + (bigInt & BigInt(255)), + ].join('.') + ); + } + else { + let hex = bigInt.toString(16); + const groups = []; + while (hex.length < 32) { + hex = `0${hex}`; + } + for (let i = 0; i < 8; i++) { + groups.push(hex.slice(i * 4, (i + 1) * 4)); + } + return groups.join(':'); + } + } + + /** + * toBinary - Converts decimal IP to full-length binary representation. + * @return {string} -> 01111111000000000000000000000001 + */ + toBinary() { + if (this.integer === 0) { + this.toInteger(); + } + let binary = this.integer.toString(2); + const v = this.version; + const marks = { 4: 32, 6: 128 }; + + if (binary.length < marks[v]) { + while (binary.length < marks[v]) { + binary = `0${binary}`; + } + } + return binary; + } + + /** + * toHEX - Converts both IP versions to hexadecimal representation. + * @return {string} -> 7f000001 + */ + toHEX() { + if (this.integer === 0) { + this.toInteger(); + } + return this.integer.toString(16); + } + + /** + * toCompressed - Compress an IP address to its shortest possible form. + * IP('127.1.0.0').toCompressed + * @return {string} -> "127.1" + */ + toCompressed(addr, ver) { + if (ver === 4) { + const splittedAddr = addr.split('.'); + const sRange = [[1, 3], [2, 2], [3, 1], [0, 0]]; + + for (let i = splittedAddr.length - 1; i >= 0; i--) { + if (splittedAddr[i] === '0') { + continue; + } + else { + splittedAddr.splice(sRange[i][0], sRange[i][1]); + this.short = splittedAddr.join('.'); + return this.short; + } + } + } + else { + const splitted = addr.split(':'); + // finding longest zero group + const [startOfLongest, longestLength] = _longestZerosGroup(splitted); + // 'N/A' - _longestZerosGroup fn return in case if there is NO + // '0000' blocks in address + if (startOfLongest !== 'N/A' || longestLength !== 'N/A') { + splitted.splice(startOfLongest, longestLength, ''); + if (startOfLongest === 0) { + splitted.unshift(''); + } + if (startOfLongest + longestLength === 8) { + splitted.push(''); + } + } + + // removing single '0000' blocks and leading zeros + for (let i = 0; i < splitted.length; i++) { + if (splitted[i] === '0000') { + splitted.splice(i, 1, '0'); + } + + loopStr: + for (let j = 0; j < splitted[i].length; j++) { + if (splitted[i][j] === '0' && splitted[i] !== '0') { + splitted[i] = splitted[i].substring(j + 1); + j--; + continue; + } + else { + break loopStr; + } + } + } + this.short = splitted.join(':'); + return this.short; + } + } + + // Private methods + + /** + * checkVersion - Determins this IP version. + * @private + * @param {string} addr + * @return {number} -> 4 or 6 + */ + _checkVersion(addr) { + // matches all possible chars in both versions of IP + const reGen = /^[0-9a-f.:]+$/i; + if (reGen.test(addr)) { + // checks if there is .. and more or whole IP is just a dot + const reDots = /\.{2,}|^\.{1}$/; + // checks if there is ::: and more or whole IP is just a colon + const reColon = /:{3,}|^:{1}$/; + // checks if there is only digits in integer IP + const reNum = /^[0-9]+$/; + + if (reNum.test(addr)) { + addr = BigInt(addr); + if (addr > IPv6MAX || addr <= 0) { + throw new Error('Tips: IP address cant be bigger than 2 to the 128-th power or negative number'); + } + else if (addr <= IPv4MAX) { + return 4; + } + else if (addr > IPv4MAX) { + return 6; + } + } + else if (addr.includes('.') && !reDots.test(addr)) { + return 4; + } + else if (addr.includes(':') && !reColon.test(addr)) { + return 6; + } + } + throw new Error('Tips: Please, enter a valid IP address (Like "127.1.0.0", long integer, short or long IPv6)'); + } + + /** + * checkAddress - Validates this IP address. + * @private + * @return {string} as a valid address + */ + _checkAddress(addr, v) { + const reNum = /^[0-9]+$/; + if (reNum.test(addr)) { + this.integer = BigInt(addr); + return this.toDottedNotation(this.integer); + } + + const marks = { + 4: ['.', this._isIPv4, 4], + 6: [':', this._isIPv6, 8], + }; + const splittedAddr = addr.split(marks[v][0]); + + if (v === 6 && splittedAddr.length < 8) { + const dbColon = (addr.match(/::/g) || []).length; + if (dbColon !== 1) { + throw new Error('Tips: Please, enter a valid IP address (Like "127.1.0.0", long integer, short or long IPv6)'); + } + } + + if (marks[v][1].call(this, splittedAddr)) { // TODO: make ifs more readable + if (splittedAddr.length === marks[v][2] && this.short === 0) { + return addr; + } + else { + return this._toRepresentation(splittedAddr); + } + } + else { + throw new Error('Tips: Please, enter a valid IP address (Like "127.1.0.0", long integer, short or long IPv6)'); + } + } + + /** + * _isIPv6 - Validates IPv6. + * @private + * @return {boolean} whether splitted address is valid IPv6 or not + */ + _isIPv6(splittedAddr) { + if (splittedAddr.length <= 8) { + let checked = false; + const [isShort, cleanedAddr] = this._isShort(splittedAddr); + + const regex = /^[0-9a-f]{1,4}$/i; + const isValid = function (hextet) { + return regex.test(hextet); + }; + checked = cleanedAddr.every(isValid); + + if (checked && isShort) { + this.short = splittedAddr.join(':'); + } + return checked; + } + else { + throw new Error('Tips: IPv6 cannot contain more than 8 bytes'); + } + } + + /** + * _isIPv4 - Validates IPv4. + * @private + * @return {boolean} whether splitted address is valid IPv4 or not + */ + _isIPv4(splittedAddr) { + if (splittedAddr.length <= 4) { + if (splittedAddr.length < 4) { + this.short = splittedAddr.join('.'); + } + const isValid = function (octet) { + return (!!((octet <= 255 && octet >= 0))); + }; + return splittedAddr.every(isValid); + } + else { + throw new Error('Tips: IPv4 cannot contain more than 4 bytes'); + } + } + + /** + * _isShort - checks if IPv6 addres was compressed like this "234:f:34:34:1:1:2:2" or like "1234::1234:1234" and removes empty strings for future validation + * @private + * @param {array} splittedAddr + * @return {array} with both results boolean and cleaned array + */ + _isShort(splittedAddr) { + let isShort = false; + const cleanedAddr = [...splittedAddr]; + for (let i = 0; i < cleanedAddr.length; i++) { + if (cleanedAddr[i].length === 0) { + cleanedAddr.splice(i, 1); + isShort = true; + i--; // code chunk similar to toCompressed method + // for addr '::1' can happen that there are 2 empty strings + // together, so by i-- we check every el of array but not next but one + } + else if (cleanedAddr[i].length < 4) { + isShort = true; + } + } + return [isShort, cleanedAddr]; + } + + /** + * toRepresentation - Converts short version to canonical representation of IP. + * IP('::1').address + * @private + * @param {array} splittedAddr + * @return {string} -> "0000:0000:0000:0000:0000:0000:0000:0001" + */ + _toRepresentation(splittedAddr) { + if (this.version === 4) { + for (let i = 0; i <= 4; i++) { + if (splittedAddr[i] === '') { + let missOcts = 5 - splittedAddr.length; + let flag = true; + while (missOcts > 0) { + if (flag) { + splittedAddr.splice(i, 1, '0'); + missOcts--; + flag = false; + } + else { + splittedAddr.splice(i, 0, '0'); + missOcts--; + } + } + } + } + while (splittedAddr.length < 4) { + splittedAddr.push('0'); + } + return splittedAddr.join('.'); + } + else { + for (let i = 0; i <= 8; i++) { + if (splittedAddr[i] === '') { + let missHex = 9 - splittedAddr.length; + let flag = true; + while (missHex > 0) { + if (flag) { + splittedAddr.splice(i, 1, '0000'); + missHex--; + flag = false; + } + else { + splittedAddr.splice(i, 0, '0000'); + missHex--; + } + } + } + } + for (let i = 0; i < splittedAddr.length; i++) { + if (splittedAddr[i].length < 4) { + let missNum = 4 - splittedAddr[i].length; + while (missNum > 0) { + splittedAddr[i] = `0${splittedAddr[i]}`; + missNum--; + } + } + } + } + return splittedAddr.join(':'); + } +}// IP class end + +/** + * longestZerosGroup - Helper fn counting longest consecutive zeros for shortening IPv6 + * "0000:0000:0000:0000:0000:0000:0000:0001" + * @private + * @param {array} zeros + * @return {array} -> [0, 7] + */ +function _longestZerosGroup(splittedAddr) { + let curr = 0; + let currLongest = 0; + let startOfLongest = 0; + let hasZeros = false; + + while (curr < splittedAddr.length - 2) { + const startOfRun = curr; + while (curr < splittedAddr.length && splittedAddr[curr] === '0000') { + hasZeros = true; + curr++; + } + + if ((curr - startOfRun) > currLongest) { + startOfLongest = startOfRun; + currLongest = curr - startOfRun; + } + curr++; + } + return hasZeros ? [startOfLongest, currLongest] : ['N/A', 'N/A']; +} diff --git a/src/libs/ip_calculator/network.js b/src/libs/ip_calculator/network.js new file mode 100644 index 00000000..5f46a61b --- /dev/null +++ b/src/libs/ip_calculator/network.js @@ -0,0 +1,268 @@ +import IP from './ip.js'; + +const IPv4MAX = (BigInt(2) ** BigInt(32)) - BigInt(1); +const IPv6MAX = (BigInt(2) ** BigInt(128)) - BigInt(1); + +// IP range specific information, see IANA allocations. +// http://www.iana.org/assignments/iana-ipv4-special-registry/iana-ipv4-special-registry.xhtml +const _ipv4Registry = new Map([ + ['0.0.0.0', [8, 'This host on this network']], + ['10.0.0.0', [8, 'Private-Use']], + ['100.64.0.0', [10, 'Shared Address Space']], + ['127.0.0.0', [8, 'Loopback']], + ['169.254.0.0', [16, 'Link Local']], + ['172.16.0.0', [12, 'Private-Use']], + ['192.0.0.0', [24, 'IETF Protocol Assignments']], + ['192.0.0.0', [29, 'IPv4 Service Continuity Prefix']], + ['192.0.0.8', [32, 'IPv4 dummy address']], + ['192.0.0.9', [32, 'Port Control Protocol Anycast']], + ['192.0.0.10', [32, 'Traversal Using Relays around NAT Anycast']], + ['192.0.0.170', [32, 'NAT64/DNS64 Discovery']], + ['192.0.0.171', [32, 'NAT64/DNS64 Discovery']], + ['192.0.2.0', [24, 'Documentation (TEST-NET-1)']], + ['192.31.196.0', [24, 'AS112-v4']], + ['192.52.193.0', [24, 'AMT']], + ['192.88.99.0', [24, 'Deprecated (6to4 Relay Anycast)']], + ['192.168.0.0', [16, 'Private Use']], + ['192.175.48.0', [24, 'Direct Delegation AS112 Service']], + ['198.18.0.0', [15, 'Benchmarking']], + ['198.51.100.0', [24, 'Documentation (TEST-NET-2)']], + ['203.0.113.0', [24, 'Documentation (TEST-NET-3)']], + ['240.0.0.0', [4, 'Reserved']], + ['255.255.255.255', [32, 'Limited Broadcast']], +]); + +// https://www.iana.org/assignments/iana-ipv6-special-registry/iana-ipv6-special-registry.xhtml +const _ipv6Registry = new Map([ + ['::1', [128, 'Loopback Address']], + ['::', [128, 'Unspecified Address']], + ['::', [128, 'Unspecified Address']], + ['::ffff:0:0', [98, 'IPv4-mapped Address']], + ['64:ff9b::', [96, 'IPv4-IPv6 Translat.']], + ['64:ff9b:1::', [48, 'IPv4-IPv6 Translat.']], + ['100::', [64, 'Discard-Only Address Block']], + ['2001::', [23, 'IETF Protocol Assignments']], + ['2001::', [32, 'TEREDO']], + ['2001:1::1', [128, 'Port Control Protocol Anycast']], + ['2001:1::2', [128, 'Traversal Using Relays around NAT Anycast']], + ['2001:2::', [48, 'Benchmarking']], + ['2001:3::', [32, 'AMT']], + ['2001:4:112::', [48, 'AS112-v6']], + ['2001:5::', [32, 'EID Space for LISP (Managed by RIPE NCC)']], + ['2001:10::', [28, 'Deprecated (previously ORCHID)']], + ['2001:20::', [28, 'ORCHIDv2']], + ['2001:db8::', [32, 'Documentation']], + ['2002::', [16, '6to4']], + ['2620:4f:8000::', [48, 'Direct Delegation AS112 Service']], + ['fc00::', [7, 'Unique-Local']], + ['fe80::', [10, 'Link-Local Unicast']], +]); + +/** +* Network slice calculations. +* @class Network +* @param {string} address +* @param {integer} prefix +* host = new IP("127.128.99.3",8) +* @return {object} -> IP{address:"127.128.99.3", prefix: 8} +*/ + +export default class Network extends IP { + /** + * Extends IP class. Calls the parent class IP with the parameters passed to Network. + * @constructor + */ + constructor(address, prefix) { + super(address); + this.prefix = this._checkPrefix(prefix); + } + + // Private methods + + /** + * _checkPrefix - Returns this IP prefix and validates it + * @private + * @return {integer} -> prefix: 25n + */ + _checkPrefix(prefix) { + if (this.version === 4) { + if (prefix > 0 && prefix <= 32) { + return BigInt(prefix); + } + } + else { + if (prefix > 0 && prefix <= 128) { + return BigInt(prefix); + } + } + throw new Error('Tips: Invalid prefix'); + } + + // Public methods + + /** + * printInfo - Shows IANA allocation information for the current IP address. + * @return {string} ->LOOPBACK + */ + printInfo() { + const registry = { 4: _ipv4Registry, 6: _ipv6Registry }; + const results = []; + for (const [addr, info] of registry[this.version].entries()) { + const found = this.contains(this.address, addr, info[0]); + if (found) { + results.unshift(info[1]); + } + } + return results.length === 0 ? 'Unknown' : results[0]; + } + + /** + * maskToInteger - Returns network mask as bigInt + * @return {BigInt} -> 4278190080n + */ + maskToInteger() { + if (this.version === 4) { + return (IPv4MAX >> (BigInt(32) - this.prefix)) << (BigInt(32) - this.prefix); + } + else { + return (IPv6MAX >> (BigInt(128) - this.prefix)) << (BigInt(128) - this.prefix); + } + } + + /** + * getMask - Returns mask from the prefix + * @return {string} -> 255.255.0.0 + */ + getMask() { + return this.toDottedNotation(this.maskToInteger()); + } + + /** + * networkToInteger - Returns network as bigInt. + * @return {BigInt} -> 21307064320 + */ + networkToInteger() { + return this.toInteger() & this.maskToInteger(); + } + + /** + * getNetwork - Returns network part of the address + * @return {string} -> 127 + */ + getNetwork() { + return this.toDottedNotation(this.networkToInteger()); + } + + /** + * getBroadcast - Calculates broadcast.IPv6 doesn't have a broadcast + * address, but it's used for other calculations such as Network.hostLast. + * @return {string} -> 127.255.255.255 + */ + getBroadcast() { + return this.version === 4 + ? this.toDottedNotation(this.broadcastToLong()) + : 'IPv6 doesnt have broadcast address'; + } + + /** + * broadcastToLong - Returns broadcast as long. + * @return {BigInt} ->2147483647 + */ + broadcastToLong() { + if (this.version === 4) { + return this.networkToInteger() | (IPv4MAX - this.maskToInteger()); + } + else { + return this.networkToInteger() | (IPv6MAX - this.maskToInteger()); + } + } + + /** + * hostFirst - Calculates first available host in this subnet. + * @return {string} ->127.0.0.1 + */ + hostFirst() { + const isSmall4 = this.version === 4 && this.prefix > BigInt(30); + let first; + + if (this.version === 6) { + first = this.getNetwork(); + } + else if (isSmall4) { + return 'N/A'; + } + else { + first = this.toDottedNotation(this.networkToInteger() + BigInt(1)); + } + return this.toCompressed(first, this.version); + } + + /** + * hostLast - Calculates last available host in this subnet. + * @return {string} ->127.255.255.255 + */ + hostLast() { + const isLast4 = this.version === 4 && this.prefix === BigInt(32); + const isLast6 = this.version === 6 && this.prefix === BigInt(128); + const isPrev4 = this.version === 4 && this.prefix === BigInt(31); + const isPrev6 = this.version === 6 && this.prefix === BigInt(127); + let last; + + if (isLast4 || isLast6 || isPrev4) { + return 'N/A'; + } + else if (isPrev6) { + last = this.address; + } + else if (this.version === 4) { + last = this.toDottedNotation(this.broadcastToLong() - BigInt(1)); + } + else { + last = this.toDottedNotation(this.broadcastToLong()); + } + return this.toCompressed(last, this.version); + } + + /** + * contains - Check if thisIP is part of the network + * @param {string} thisIP + * @param {string} otherIP + * @param {number} prefix + * @return {boolean} + */ + contains(thisIP, otherIP, prefix) { + const other = new Network(otherIP, prefix); + const thisNetwork = this.networkToInteger(); + const otherNetwork = other.networkToInteger(); + const smaller = (thisNetwork <= otherNetwork) + && (otherNetwork <= this.broadcastToLong()); + const bigger = (otherNetwork <= thisNetwork) + && (thisNetwork <= other.broadcastToLong()); + return smaller || bigger; + } + + /** + * hostRange - Generates a range of usable host IP addresses within the network. + * @return {array} -> ['127.0.0.1','127.255.255.255'] + */ + hostRange() { + const range = []; + range.push(this.hostFirst()); + range.push(this.hostLast()); + return range; + } + + /** + * networkSize - Returns number of ips within the network. + * @return {number} -> 16777214 + */ + networkSize() { + const marks = { 4: BigInt(32), 6: BigInt(128) }; + const size = BigInt(2) ** (marks[this.version] - this.prefix); + + if (this.version === 4 && this.prefix < BigInt(30)) { + return size - BigInt(2); + } + return size; + } +} From 1f67ba15e9a8de71290281c5eaf655763a33d617 Mon Sep 17 00:00:00 2001 From: sharevb Date: Wed, 3 Apr 2024 22:47:43 +0200 Subject: [PATCH 2/5] feat(new tool): IPv6 Subnet Calculator IPv6 Subnet Calculator Fix #354 and #924 --- components.d.ts | 12 +- package.json | 1 + pnpm-lock.yaml | 34 ++++- src/libs/ip_calculator/{ip.js => ip.ts} | 71 +++++----- src/libs/ip_calculator/ipv4registry.json | 26 ++++ src/libs/ip_calculator/ipv6registry.json | 24 ++++ .../ip_calculator/{network.js => network.ts} | 79 +++-------- src/tools/index.ts | 11 +- src/tools/ipv6-subnet-calculator/index.ts | 12 ++ .../ipv6-subnet-calculator.vue | 126 ++++++++++++++++++ 10 files changed, 285 insertions(+), 111 deletions(-) rename src/libs/ip_calculator/{ip.js => ip.ts} (86%) create mode 100644 src/libs/ip_calculator/ipv4registry.json create mode 100644 src/libs/ip_calculator/ipv6registry.json rename src/libs/ip_calculator/{network.js => network.ts} (67%) create mode 100644 src/tools/ipv6-subnet-calculator/index.ts create mode 100644 src/tools/ipv6-subnet-calculator/ipv6-subnet-calculator.vue diff --git a/components.d.ts b/components.d.ts index e31119b3..cca87d45 100644 --- a/components.d.ts +++ b/components.d.ts @@ -105,6 +105,7 @@ declare module '@vue/runtime-core' { Ipv4AddressConverter: typeof import('./src/tools/ipv4-address-converter/ipv4-address-converter.vue')['default'] Ipv4RangeExpander: typeof import('./src/tools/ipv4-range-expander/ipv4-range-expander.vue')['default'] Ipv4SubnetCalculator: typeof import('./src/tools/ipv4-subnet-calculator/ipv4-subnet-calculator.vue')['default'] + Ipv6SubnetCalculator: typeof import('./src/tools/ipv6-subnet-calculator/ipv6-subnet-calculator.vue')['default'] Ipv6UlaGenerator: typeof import('./src/tools/ipv6-ula-generator/ipv6-ula-generator.vue')['default'] JsonDiff: typeof import('./src/tools/json-diff/json-diff.vue')['default'] JsonMinify: typeof import('./src/tools/json-minify/json-minify.vue')['default'] @@ -127,24 +128,16 @@ declare module '@vue/runtime-core' { MetaTagGenerator: typeof import('./src/tools/meta-tag-generator/meta-tag-generator.vue')['default'] MimeTypes: typeof import('./src/tools/mime-types/mime-types.vue')['default'] NavbarButtons: typeof import('./src/components/NavbarButtons.vue')['default'] - NCode: typeof import('naive-ui')['NCode'] NCollapseTransition: typeof import('naive-ui')['NCollapseTransition'] NConfigProvider: typeof import('naive-ui')['NConfigProvider'] - NDivider: typeof import('naive-ui')['NDivider'] NEllipsis: typeof import('naive-ui')['NEllipsis'] - NFormItem: typeof import('naive-ui')['NFormItem'] - NGi: typeof import('naive-ui')['NGi'] - NGrid: typeof import('naive-ui')['NGrid'] NH1: typeof import('naive-ui')['NH1'] NH3: typeof import('naive-ui')['NH3'] NIcon: typeof import('naive-ui')['NIcon'] - NInputNumber: typeof import('naive-ui')['NInputNumber'] - NLabel: typeof import('naive-ui')['NLabel'] NLayout: typeof import('naive-ui')['NLayout'] NLayoutSider: typeof import('naive-ui')['NLayoutSider'] NMenu: typeof import('naive-ui')['NMenu'] - NScrollbar: typeof import('naive-ui')['NScrollbar'] - NSpin: typeof import('naive-ui')['NSpin'] + NTable: typeof import('naive-ui')['NTable'] NumeronymGenerator: typeof import('./src/tools/numeronym-generator/numeronym-generator.vue')['default'] OtpCodeGeneratorAndValidator: typeof import('./src/tools/otp-code-generator-and-validator/otp-code-generator-and-validator.vue')['default'] PasswordStrengthAnalyser: typeof import('./src/tools/password-strength-analyser/password-strength-analyser.vue')['default'] @@ -159,6 +152,7 @@ declare module '@vue/runtime-core' { RouterLink: typeof import('vue-router')['RouterLink'] RouterView: typeof import('vue-router')['RouterView'] RsaKeyPairGenerator: typeof import('./src/tools/rsa-key-pair-generator/rsa-key-pair-generator.vue')['default'] + SafelinkDecoder: typeof import('./src/tools/safelink-decoder/safelink-decoder.vue')['default'] SlugifyString: typeof import('./src/tools/slugify-string/slugify-string.vue')['default'] SpanCopyable: typeof import('./src/components/SpanCopyable.vue')['default'] SqlPrettify: typeof import('./src/tools/sql-prettify/sql-prettify.vue')['default'] diff --git a/package.json b/package.json index fd6c02e6..fc00ad79 100644 --- a/package.json +++ b/package.json @@ -64,6 +64,7 @@ "highlight.js": "^11.7.0", "iarna-toml-esm": "^3.0.5", "ibantools": "^4.3.3", + "is-cidr": "^5.0.3", "json5": "^2.2.3", "jwt-decode": "^3.1.2", "libphonenumber-js": "^1.10.28", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index bd6c38c9..cb383786 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -92,6 +92,9 @@ dependencies: ibantools: specifier: ^4.3.3 version: 4.3.3 + is-cidr: + specifier: ^5.0.3 + version: 5.0.3 json5: specifier: ^2.2.3 version: 2.2.3 @@ -3351,7 +3354,7 @@ packages: dependencies: '@unhead/dom': 0.5.1 '@unhead/schema': 0.5.1 - '@vueuse/shared': 10.7.2(vue@3.3.4) + '@vueuse/shared': 10.9.0(vue@3.3.4) unhead: 0.5.1 vue: 3.3.4 transitivePeerDependencies: @@ -3993,10 +3996,10 @@ packages: - vue dev: false - /@vueuse/shared@10.7.2(vue@3.3.4): - resolution: {integrity: sha512-qFbXoxS44pi2FkgFjPvF4h7c9oMDutpyBdcJdMYIMg9XyXli2meFMuaKn+UMgsClo//Th6+beeCgqweT/79BVA==} + /@vueuse/shared@10.9.0(vue@3.3.4): + resolution: {integrity: sha512-Uud2IWncmAfJvRaFYzv5OHDli+FbOzxiVEQdLCKQKLyhz94PIyFC3CHcH7EDMwIn8NPtD06+PNbC/PiO0LGLtw==} dependencies: - vue-demi: 0.14.6(vue@3.3.4) + vue-demi: 0.14.7(vue@3.3.4) transitivePeerDependencies: - '@vue/composition-api' - vue @@ -4463,6 +4466,13 @@ packages: engines: {node: '>=8'} dev: true + /cidr-regex@4.0.3: + resolution: {integrity: sha512-HOwDIy/rhKeMf6uOzxtv7FAbrz8zPjmVKfSpM+U7/bNBXC5rtOyr758jxcptiSx6ZZn5LOhPJT5WWxPAGDV8dw==} + engines: {node: '>=14'} + dependencies: + ip-regex: 5.0.0 + dev: false + /clean-regexp@1.0.0: resolution: {integrity: sha512-GfisEZEJvzKrmGWkvfhgzcz/BllN1USeqD2V6tg14OAOgaCD2Z/PUEuxnAZ/nPvmaHRG7a8y77p1T/IRQ4D1Hw==} engines: {node: '>=4'} @@ -6185,6 +6195,11 @@ packages: jsbn: 1.1.0 dev: false + /ip-regex@5.0.0: + resolution: {integrity: sha512-fOCG6lhoKKakwv+C6KdsOnGvgXnmgfmp0myi3bcNwj3qfwPAxRKWEuFhvEFF7ceYIz6+1jRZ+yguLFAmUNPEfw==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + dev: false + /is-alphabetical@1.0.4: resolution: {integrity: sha512-DwzsA04LQ10FHTZuL0/grVDk4rFoVH1pjAToYwBrHSxcrBIGQuXrQMtD5U1b0U2XVgKZCTLLP8u2Qxqhy3l2Vg==} dev: true @@ -6240,6 +6255,13 @@ packages: engines: {node: '>= 0.4'} dev: true + /is-cidr@5.0.3: + resolution: {integrity: sha512-lKkM0tmz07dAxNsr8Ii9MGreExa9ZR34N9j8mTG5op824kcwBqinZPowNjcVWWc7j+jR8XAMMItOmBkniN0jOA==} + engines: {node: '>=14'} + dependencies: + cidr-regex: 4.0.3 + dev: false + /is-core-module@2.13.0: resolution: {integrity: sha512-Z7dk6Qo8pOCp3l4tsX2C5ZVas4V+UxwQodwZhLopL91TX8UyyHEXafPcyoeeWuLrwzHcr3igO78wNLwHJHsMCQ==} dependencies: @@ -9151,8 +9173,8 @@ packages: vue: 3.3.4 dev: false - /vue-demi@0.14.6(vue@3.3.4): - resolution: {integrity: sha512-8QA7wrYSHKaYgUxDA5ZC24w+eHm3sYCbp0EzcDwKqN3p6HqtTCGR/GVsPyZW92unff4UlcSh++lmqDWN3ZIq4w==} + /vue-demi@0.14.7(vue@3.3.4): + resolution: {integrity: sha512-EOG8KXDQNwkJILkx/gPcoL/7vH+hORoBaKgGe+6W7VFMvCYJfmF2dGbvgDroVnI8LU7/kTu8mbjRZGBU1z9NTA==} engines: {node: '>=12'} hasBin: true requiresBuild: true diff --git a/src/libs/ip_calculator/ip.js b/src/libs/ip_calculator/ip.ts similarity index 86% rename from src/libs/ip_calculator/ip.js rename to src/libs/ip_calculator/ip.ts index 14052454..8e7ec106 100644 --- a/src/libs/ip_calculator/ip.js +++ b/src/libs/ip_calculator/ip.ts @@ -13,12 +13,16 @@ const IPv6MAX = (BigInt(2) ** BigInt(128)) - BigInt(1); */ export default class IP { + integer: bigint; + short: string; + version: number; + address: string; /** * @constructor */ - constructor(address) { - this.integer = 0; - this.short = 0; + constructor(address: string) { + this.integer = 0n; + this.short = ''; this.version = this._checkVersion(address); this.address = this._checkAddress(address, this.version); } @@ -34,7 +38,7 @@ export default class IP { if (this.version === 4) { const splittedAddr = this.address.split('.').reverse(); bigInt = splittedAddr.reduce((bigInt, octet, index) => { - return (octet * 256 ** index + bigInt + return (Number(octet) * 256 ** index + bigInt ); }, 0); } @@ -51,7 +55,7 @@ export default class IP { * @param {bigint} bigInt * @return {string} -> "184.170.96.196" */ - toDottedNotation(bigInt) { + toDottedNotation(bigInt: bigint) { if (this.version === 4) { return ( [(bigInt >> BigInt(24) & BigInt(255)), (bigInt >> BigInt(16) & BigInt(255)), @@ -78,15 +82,14 @@ export default class IP { * @return {string} -> 01111111000000000000000000000001 */ toBinary() { - if (this.integer === 0) { + if (this.integer === 0n) { this.toInteger(); } let binary = this.integer.toString(2); - const v = this.version; - const marks = { 4: 32, 6: 128 }; + const markLen = this.version === 4 ? 32 : 128; - if (binary.length < marks[v]) { - while (binary.length < marks[v]) { + if (binary.length < markLen) { + while (binary.length < markLen) { binary = `0${binary}`; } } @@ -98,7 +101,7 @@ export default class IP { * @return {string} -> 7f000001 */ toHEX() { - if (this.integer === 0) { + if (this.integer === 0n) { this.toInteger(); } return this.integer.toString(16); @@ -109,7 +112,7 @@ export default class IP { * IP('127.1.0.0').toCompressed * @return {string} -> "127.1" */ - toCompressed(addr, ver) { + toCompressed(addr: string, ver: number) { if (ver === 4) { const splittedAddr = addr.split('.'); const sRange = [[1, 3], [2, 2], [3, 1], [0, 0]]; @@ -132,11 +135,11 @@ export default class IP { // 'N/A' - _longestZerosGroup fn return in case if there is NO // '0000' blocks in address if (startOfLongest !== 'N/A' || longestLength !== 'N/A') { - splitted.splice(startOfLongest, longestLength, ''); + splitted.splice(Number(startOfLongest), Number(longestLength), ''); if (startOfLongest === 0) { splitted.unshift(''); } - if (startOfLongest + longestLength === 8) { + if (Number(startOfLongest) + Number(longestLength) === 8) { splitted.push(''); } } @@ -172,7 +175,7 @@ export default class IP { * @param {string} addr * @return {number} -> 4 or 6 */ - _checkVersion(addr) { + _checkVersion(addr: string) { // matches all possible chars in both versions of IP const reGen = /^[0-9a-f.:]+$/i; if (reGen.test(addr)) { @@ -184,14 +187,14 @@ export default class IP { const reNum = /^[0-9]+$/; if (reNum.test(addr)) { - addr = BigInt(addr); - if (addr > IPv6MAX || addr <= 0) { + const parsedAddr = BigInt(addr); + if (parsedAddr > IPv6MAX || parsedAddr <= 0) { throw new Error('Tips: IP address cant be bigger than 2 to the 128-th power or negative number'); } - else if (addr <= IPv4MAX) { + else if (parsedAddr <= IPv4MAX) { return 4; } - else if (addr > IPv4MAX) { + else if (parsedAddr > IPv4MAX) { return 6; } } @@ -210,18 +213,14 @@ export default class IP { * @private * @return {string} as a valid address */ - _checkAddress(addr, v) { + _checkAddress(addr: string, v: number) { const reNum = /^[0-9]+$/; if (reNum.test(addr)) { this.integer = BigInt(addr); return this.toDottedNotation(this.integer); } - const marks = { - 4: ['.', this._isIPv4, 4], - 6: [':', this._isIPv6, 8], - }; - const splittedAddr = addr.split(marks[v][0]); + const splittedAddr = addr.split(v === 4 ? '.' : ':'); if (v === 6 && splittedAddr.length < 8) { const dbColon = (addr.match(/::/g) || []).length; @@ -230,8 +229,8 @@ export default class IP { } } - if (marks[v][1].call(this, splittedAddr)) { // TODO: make ifs more readable - if (splittedAddr.length === marks[v][2] && this.short === 0) { + if ((v === 4 ? this._isIPv4 : this._isIPv6).call(this, splittedAddr)) { // TODO: make ifs more readable + if (splittedAddr.length === (v === 4 ? 4 : 8) && this.short === '') { return addr; } else { @@ -248,16 +247,16 @@ export default class IP { * @private * @return {boolean} whether splitted address is valid IPv6 or not */ - _isIPv6(splittedAddr) { + _isIPv6(splittedAddr: string[]) { if (splittedAddr.length <= 8) { let checked = false; const [isShort, cleanedAddr] = this._isShort(splittedAddr); const regex = /^[0-9a-f]{1,4}$/i; - const isValid = function (hextet) { + const isValid = function (hextet: string) { return regex.test(hextet); }; - checked = cleanedAddr.every(isValid); + checked = (cleanedAddr as string[]).every(isValid); if (checked && isShort) { this.short = splittedAddr.join(':'); @@ -274,13 +273,13 @@ export default class IP { * @private * @return {boolean} whether splitted address is valid IPv4 or not */ - _isIPv4(splittedAddr) { + _isIPv4(splittedAddr: string[]) { if (splittedAddr.length <= 4) { if (splittedAddr.length < 4) { this.short = splittedAddr.join('.'); } - const isValid = function (octet) { - return (!!((octet <= 255 && octet >= 0))); + const isValid = function (octet: string) { + return (!!((Number(octet) <= 255 && Number(octet) >= 0))); }; return splittedAddr.every(isValid); } @@ -295,7 +294,7 @@ export default class IP { * @param {array} splittedAddr * @return {array} with both results boolean and cleaned array */ - _isShort(splittedAddr) { + _isShort(splittedAddr: string[]) { let isShort = false; const cleanedAddr = [...splittedAddr]; for (let i = 0; i < cleanedAddr.length; i++) { @@ -320,7 +319,7 @@ export default class IP { * @param {array} splittedAddr * @return {string} -> "0000:0000:0000:0000:0000:0000:0000:0001" */ - _toRepresentation(splittedAddr) { + _toRepresentation(splittedAddr: string[]) { if (this.version === 4) { for (let i = 0; i <= 4; i++) { if (splittedAddr[i] === '') { @@ -383,7 +382,7 @@ export default class IP { * @param {array} zeros * @return {array} -> [0, 7] */ -function _longestZerosGroup(splittedAddr) { +function _longestZerosGroup(splittedAddr: string[]) { let curr = 0; let currLongest = 0; let startOfLongest = 0; diff --git a/src/libs/ip_calculator/ipv4registry.json b/src/libs/ip_calculator/ipv4registry.json new file mode 100644 index 00000000..91b262c7 --- /dev/null +++ b/src/libs/ip_calculator/ipv4registry.json @@ -0,0 +1,26 @@ +[ + ["0.0.0.0", [8, "This host on this network"]], + ["10.0.0.0", [8, "Private-Use"]], + ["100.64.0.0", [10, "Shared Address Space"]], + ["127.0.0.0", [8, "Loopback"]], + ["169.254.0.0", [16, "Link Local"]], + ["172.16.0.0", [12, "Private-Use"]], + ["192.0.0.0", [24, "IETF Protocol Assignments"]], + ["192.0.0.0", [29, "IPv4 Service Continuity Prefix"]], + ["192.0.0.8", [32, "IPv4 dummy address"]], + ["192.0.0.9", [32, "Port Control Protocol Anycast"]], + ["192.0.0.10", [32, "Traversal Using Relays around NAT Anycast"]], + ["192.0.0.170", [32, "NAT64/DNS64 Discovery"]], + ["192.0.0.171", [32, "NAT64/DNS64 Discovery"]], + ["192.0.2.0", [24, "Documentation (TEST-NET-1)"]], + ["192.31.196.0", [24, "AS112-v4"]], + ["192.52.193.0", [24, "AMT"]], + ["192.88.99.0", [24, "Deprecated (6to4 Relay Anycast)"]], + ["192.168.0.0", [16, "Private Use"]], + ["192.175.48.0", [24, "Direct Delegation AS112 Service"]], + ["198.18.0.0", [15, "Benchmarking"]], + ["198.51.100.0", [24, "Documentation (TEST-NET-2)"]], + ["203.0.113.0", [24, "Documentation (TEST-NET-3)"]], + ["240.0.0.0", [4, "Reserved"]], + ["255.255.255.255", [32, "Limited Broadcast"]] +] \ No newline at end of file diff --git a/src/libs/ip_calculator/ipv6registry.json b/src/libs/ip_calculator/ipv6registry.json new file mode 100644 index 00000000..c419b4d1 --- /dev/null +++ b/src/libs/ip_calculator/ipv6registry.json @@ -0,0 +1,24 @@ +[ + ["::1", [128, "Loopback Address"]], + ["::", [128, "Unspecified Address"]], + ["::", [128, "Unspecified Address"]], + ["::ffff:0:0", [98, "IPv4-mapped Address"]], + ["64:ff9b::", [96, "IPv4-IPv6 Translat."]], + ["64:ff9b:1::", [48, "IPv4-IPv6 Translat."]], + ["100::", [64, "Discard-Only Address Block"]], + ["2001::", [23, "IETF Protocol Assignments"]], + ["2001::", [32, "TEREDO"]], + ["2001:1::1", [128, "Port Control Protocol Anycast"]], + ["2001:1::2", [128, "Traversal Using Relays around NAT Anycast"]], + ["2001:2::", [48, "Benchmarking"]], + ["2001:3::", [32, "AMT"]], + ["2001:4:112::", [48, "AS112-v6"]], + ["2001:5::", [32, "EID Space for LISP (Managed by RIPE NCC)"]], + ["2001:10::", [28, "Deprecated (previously ORCHID)"]], + ["2001:20::", [28, "ORCHIDv2"]], + ["2001:db8::", [32, "Documentation"]], + ["2002::", [16, "6to4"]], + ["2620:4f:8000::", [48, "Direct Delegation AS112 Service"]], + ["fc00::", [7, "Unique-Local"]], + ["fe80::", [10, "Link-Local Unicast"]] + ] \ No newline at end of file diff --git a/src/libs/ip_calculator/network.js b/src/libs/ip_calculator/network.ts similarity index 67% rename from src/libs/ip_calculator/network.js rename to src/libs/ip_calculator/network.ts index 5f46a61b..9ffcdc74 100644 --- a/src/libs/ip_calculator/network.js +++ b/src/libs/ip_calculator/network.ts @@ -1,62 +1,16 @@ -import IP from './ip.js'; +import IP from './ip'; +import ipv4registry from './ipv4registry.json'; +import ipv6registry from './ipv6registry.json'; const IPv4MAX = (BigInt(2) ** BigInt(32)) - BigInt(1); const IPv6MAX = (BigInt(2) ** BigInt(128)) - BigInt(1); // IP range specific information, see IANA allocations. // http://www.iana.org/assignments/iana-ipv4-special-registry/iana-ipv4-special-registry.xhtml -const _ipv4Registry = new Map([ - ['0.0.0.0', [8, 'This host on this network']], - ['10.0.0.0', [8, 'Private-Use']], - ['100.64.0.0', [10, 'Shared Address Space']], - ['127.0.0.0', [8, 'Loopback']], - ['169.254.0.0', [16, 'Link Local']], - ['172.16.0.0', [12, 'Private-Use']], - ['192.0.0.0', [24, 'IETF Protocol Assignments']], - ['192.0.0.0', [29, 'IPv4 Service Continuity Prefix']], - ['192.0.0.8', [32, 'IPv4 dummy address']], - ['192.0.0.9', [32, 'Port Control Protocol Anycast']], - ['192.0.0.10', [32, 'Traversal Using Relays around NAT Anycast']], - ['192.0.0.170', [32, 'NAT64/DNS64 Discovery']], - ['192.0.0.171', [32, 'NAT64/DNS64 Discovery']], - ['192.0.2.0', [24, 'Documentation (TEST-NET-1)']], - ['192.31.196.0', [24, 'AS112-v4']], - ['192.52.193.0', [24, 'AMT']], - ['192.88.99.0', [24, 'Deprecated (6to4 Relay Anycast)']], - ['192.168.0.0', [16, 'Private Use']], - ['192.175.48.0', [24, 'Direct Delegation AS112 Service']], - ['198.18.0.0', [15, 'Benchmarking']], - ['198.51.100.0', [24, 'Documentation (TEST-NET-2)']], - ['203.0.113.0', [24, 'Documentation (TEST-NET-3)']], - ['240.0.0.0', [4, 'Reserved']], - ['255.255.255.255', [32, 'Limited Broadcast']], -]); +const _ipv4Registry = new Map(ipv4registry.map(v => [v[0] as string, v[1]])); // https://www.iana.org/assignments/iana-ipv6-special-registry/iana-ipv6-special-registry.xhtml -const _ipv6Registry = new Map([ - ['::1', [128, 'Loopback Address']], - ['::', [128, 'Unspecified Address']], - ['::', [128, 'Unspecified Address']], - ['::ffff:0:0', [98, 'IPv4-mapped Address']], - ['64:ff9b::', [96, 'IPv4-IPv6 Translat.']], - ['64:ff9b:1::', [48, 'IPv4-IPv6 Translat.']], - ['100::', [64, 'Discard-Only Address Block']], - ['2001::', [23, 'IETF Protocol Assignments']], - ['2001::', [32, 'TEREDO']], - ['2001:1::1', [128, 'Port Control Protocol Anycast']], - ['2001:1::2', [128, 'Traversal Using Relays around NAT Anycast']], - ['2001:2::', [48, 'Benchmarking']], - ['2001:3::', [32, 'AMT']], - ['2001:4:112::', [48, 'AS112-v6']], - ['2001:5::', [32, 'EID Space for LISP (Managed by RIPE NCC)']], - ['2001:10::', [28, 'Deprecated (previously ORCHID)']], - ['2001:20::', [28, 'ORCHIDv2']], - ['2001:db8::', [32, 'Documentation']], - ['2002::', [16, '6to4']], - ['2620:4f:8000::', [48, 'Direct Delegation AS112 Service']], - ['fc00::', [7, 'Unique-Local']], - ['fe80::', [10, 'Link-Local Unicast']], -]); +const _ipv6Registry = new Map(ipv6registry.map(v => [v[0] as string, v[1]])); /** * Network slice calculations. @@ -68,11 +22,12 @@ const _ipv6Registry = new Map([ */ export default class Network extends IP { + prefix: bigint; /** * Extends IP class. Calls the parent class IP with the parameters passed to Network. * @constructor */ - constructor(address, prefix) { + constructor(address: string, prefix: number) { super(address); this.prefix = this._checkPrefix(prefix); } @@ -84,7 +39,7 @@ export default class Network extends IP { * @private * @return {integer} -> prefix: 25n */ - _checkPrefix(prefix) { + _checkPrefix(prefix: number) { if (this.version === 4) { if (prefix > 0 && prefix <= 32) { return BigInt(prefix); @@ -105,10 +60,9 @@ export default class Network extends IP { * @return {string} ->LOOPBACK */ printInfo() { - const registry = { 4: _ipv4Registry, 6: _ipv6Registry }; const results = []; - for (const [addr, info] of registry[this.version].entries()) { - const found = this.contains(this.address, addr, info[0]); + for (const [addr, info] of (this.version === 4 ? _ipv4Registry : _ipv6Registry).entries()) { + const found = this.contains(this.address, addr, Number(info[0])); if (found) { results.unshift(info[1]); } @@ -230,7 +184,7 @@ export default class Network extends IP { * @param {number} prefix * @return {boolean} */ - contains(thisIP, otherIP, prefix) { + contains(thisIP: string, otherIP: string, prefix: number) { const other = new Network(otherIP, prefix); const thisNetwork = this.networkToInteger(); const otherNetwork = other.networkToInteger(); @@ -257,12 +211,19 @@ export default class Network extends IP { * @return {number} -> 16777214 */ networkSize() { - const marks = { 4: BigInt(32), 6: BigInt(128) }; - const size = BigInt(2) ** (marks[this.version] - this.prefix); + const size = BigInt(2) ** ((this.version === 4 ? BigInt(32) : BigInt(128)) - this.prefix); if (this.version === 4 && this.prefix < BigInt(30)) { return size - BigInt(2); } return size; } + + /** + * networkCount - Returns number of network fo the prefix. + * @return {number} -> 16777214 + */ + networkCount() { + return this.prefix <= 64 ? (BigInt(2) ** BigInt(64n - this.prefix)).toString() : ''; + } } diff --git a/src/tools/index.ts b/src/tools/index.ts index aa861c93..05ecc090 100644 --- a/src/tools/index.ts +++ b/src/tools/index.ts @@ -6,6 +6,7 @@ import { tool as asciiTextDrawer } from './ascii-text-drawer'; import { tool as textToUnicode } from './text-to-unicode'; import { tool as safelinkDecoder } from './safelink-decoder'; +import { tool as ipv6SubnetCalculator } from './ipv6-subnet-calculator'; import { tool as pdfSignatureChecker } from './pdf-signature-checker'; import { tool as numeronymGenerator } from './numeronym-generator'; import { tool as macAddressGenerator } from './mac-address-generator'; @@ -152,7 +153,15 @@ export const toolsByCategory: ToolCategory[] = [ }, { name: 'Network', - components: [ipv4SubnetCalculator, ipv4AddressConverter, ipv4RangeExpander, macAddressLookup, macAddressGenerator, ipv6UlaGenerator], + components: [ + ipv4SubnetCalculator, + ipv6SubnetCalculator, + ipv4AddressConverter, + ipv4RangeExpander, + macAddressLookup, + macAddressGenerator, + ipv6UlaGenerator, + ], }, { name: 'Math', diff --git a/src/tools/ipv6-subnet-calculator/index.ts b/src/tools/ipv6-subnet-calculator/index.ts new file mode 100644 index 00000000..16db73bb --- /dev/null +++ b/src/tools/ipv6-subnet-calculator/index.ts @@ -0,0 +1,12 @@ +import { RouterOutlined } from '@vicons/material'; +import { defineTool } from '../tool'; + +export const tool = defineTool({ + name: 'Ipv6 subnet calculator', + path: '/ipv6-subnet-calculator', + description: 'Parse your IPv6 CIDR blocks and get all the info you need about your sub network.', + keywords: ['ipv6', 'subnet', 'calculator', 'mask', 'network', 'cidr', 'netmask', 'bitmask', 'broadcast', 'address'], + component: () => import('./ipv6-subnet-calculator.vue'), + icon: RouterOutlined, + createdAt: new Date('2024-02-25'), +}); diff --git a/src/tools/ipv6-subnet-calculator/ipv6-subnet-calculator.vue b/src/tools/ipv6-subnet-calculator/ipv6-subnet-calculator.vue new file mode 100644 index 00000000..e9f1c660 --- /dev/null +++ b/src/tools/ipv6-subnet-calculator/ipv6-subnet-calculator.vue @@ -0,0 +1,126 @@ + + + From 444ccddaa2444c4ce65978f9e51762c3d28e493b Mon Sep 17 00:00:00 2001 From: ShareVB Date: Sat, 6 Apr 2024 11:56:09 +0200 Subject: [PATCH 3/5] chore(github workflows): ci and e2e tests: try updating to node 20 --- .github/workflows/ci.yml | 2 +- .github/workflows/e2e-tests.yml | 2 +- playwright.config.ts | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d5c67208..4dd9ffb7 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -15,7 +15,7 @@ jobs: - run: corepack enable - uses: actions/setup-node@v3 with: - node-version: 16 + node-version: 20 cache: 'pnpm' - name: Install dependencies diff --git a/.github/workflows/e2e-tests.yml b/.github/workflows/e2e-tests.yml index b5b04096..13b787ef 100644 --- a/.github/workflows/e2e-tests.yml +++ b/.github/workflows/e2e-tests.yml @@ -18,7 +18,7 @@ jobs: - uses: actions/setup-node@v3 with: - node-version: 16 + node-version: 20 cache: 'pnpm' - name: Get Playwright version diff --git a/playwright.config.ts b/playwright.config.ts index 3caa0612..5257c526 100644 --- a/playwright.config.ts +++ b/playwright.config.ts @@ -9,7 +9,7 @@ const useWebServer = process.env.NO_WEB_SERVER !== 'true'; */ export default defineConfig({ testDir: './src', - testMatch: /.*\.e2e\.(spec\.)?ts/, + testMatch: /\.e2e\.(spec\.)?ts$/, /* Run tests in files in parallel */ fullyParallel: true, /* Fail the build on CI if you accidentally left test.only in the source code. */ @@ -57,7 +57,7 @@ export default defineConfig({ && { webServer: { command: 'npm run preview', - url: 'http://127.0.0.1:5050', + url: 'http://localhost:5050', reuseExistingServer: !isCI, }, } From 70ed8fe7ea8aab72e6fee04c7b98e9741de63cd9 Mon Sep 17 00:00:00 2001 From: sharevb Date: Sun, 14 Apr 2024 14:49:07 +0200 Subject: [PATCH 4/5] feat(utils): IPv4/6 Utils Network Type, Subnets and counts, 6to4 prefix, ARPA, IPv4 Mapped Parse IP/Mask/Range as CIDR --- package.json | 6 + src/utils/ip.test.ts | 232 ++++++++++++++++++++++++++++++++++++ src/utils/ip.ts | 147 +++++++++++++++++++++++ src/utils/ipv4registry.json | 27 +++++ src/utils/ipv6registry.json | 24 ++++ 5 files changed, 436 insertions(+) create mode 100644 src/utils/ip.test.ts create mode 100644 src/utils/ip.ts create mode 100644 src/utils/ipv4registry.json create mode 100644 src/utils/ipv6registry.json diff --git a/package.json b/package.json index fc00ad79..659f147a 100644 --- a/package.json +++ b/package.json @@ -49,6 +49,7 @@ "@vueuse/router": "^10.0.0", "bcryptjs": "^2.4.3", "change-case": "^4.1.2", + "cidr-tools": "^7.0.4", "colord": "^2.9.3", "composerize-ts": "^0.6.2", "country-code-lookup": "^0.1.0", @@ -64,7 +65,12 @@ "highlight.js": "^11.7.0", "iarna-toml-esm": "^3.0.5", "ibantools": "^4.3.3", + "ip-address": "^9.0.5", + "ip-bigint": "^8.0.2", + "ip-cidr": "^4.0.0", + "ip-matching": "^2.1.2", "is-cidr": "^5.0.3", + "is-ip": "^5.0.1", "json5": "^2.2.3", "jwt-decode": "^3.1.2", "libphonenumber-js": "^1.10.28", diff --git a/src/utils/ip.test.ts b/src/utils/ip.test.ts new file mode 100644 index 00000000..6fdee458 --- /dev/null +++ b/src/utils/ip.test.ts @@ -0,0 +1,232 @@ +import { describe, expect, it } from 'vitest'; +import { getIPNetworkType, getNetworksCount, getSubnets, parseAsCIDR, to6to4Prefix, toARPA, toIPv4MappedAddress, toIPv4MappedAddressDecimal } from './ip'; + +describe('ipv4/6 util', () => { + describe('parseAsCIDR', () => { + it('returns cidr', () => { + expect(parseAsCIDR('1.1.1.1/6')).to.eql('1.1.1.1/6'); + expect(parseAsCIDR('172.16.2.2/16')).to.eql('172.16.2.2/16'); + expect(parseAsCIDR('1a:b:c::d:e:f/ffff:ffff:f4ff:ffff:ffff:ff5f:ffff:ff00')).to.eql(); + expect(parseAsCIDR('10.1.2.3/255.255.255.252')).to.eql('10.1.2.0/30'); + expect(parseAsCIDR('10.*.0.*')).to.eql(); + expect(parseAsCIDR('10.1.0.*')).to.eql('10.1.0.0/24'); + expect(parseAsCIDR('10.2.*.*')).to.eql('10.2.0.0/16'); + expect(parseAsCIDR('a:b:0:8000::/ffff:ffff:ffff:8000::')).to.eql('a:b:0:8000::/49'); + expect(parseAsCIDR('::/::')).to.eql('::/0'); + expect(parseAsCIDR('10.20.30.64-10.20.30.127')).to.eql('10.20.30.64/26'); + expect(parseAsCIDR('10.0.128.0/255.0.128.0')).to.eql(); + expect(parseAsCIDR('a::bc:1234/128')).to.eql('a::bc:1234/128'); + expect(parseAsCIDR('a::bc:ff00-a::bc:ff0f')).to.eql('a::bc:ff00/124'); + expect(parseAsCIDR('10.0.1.1/255.255.1.0')).to.eql(); + expect(parseAsCIDR('10.0.0.0/255.255.0.0')).to.eql('10.0.0.0/16'); + }); + }); + describe('getSubnets', () => { + it('returns subnets', () => { + expect(getSubnets('1.1.1.1/1')).to.eql([ + '0.0.0.0/1', + '128.0.0.0/1', + ]); + expect(getSubnets('1.1.1.1/6')).to.eql([ + '0.0.0.0/6', + '4.0.0.0/6', + '8.0.0.0/6', + '12.0.0.0/6', + '16.0.0.0/6', + '20.0.0.0/6', + '24.0.0.0/6', + '28.0.0.0/6', + '32.0.0.0/6', + '36.0.0.0/6', + '40.0.0.0/6', + '44.0.0.0/6', + '48.0.0.0/6', + '52.0.0.0/6', + '56.0.0.0/6', + '60.0.0.0/6', + '64.0.0.0/6', + '68.0.0.0/6', + '72.0.0.0/6', + '76.0.0.0/6', + '80.0.0.0/6', + '84.0.0.0/6', + '88.0.0.0/6', + '92.0.0.0/6', + '96.0.0.0/6', + '100.0.0.0/6', + '104.0.0.0/6', + '108.0.0.0/6', + '112.0.0.0/6', + '116.0.0.0/6', + '120.0.0.0/6', + '124.0.0.0/6', + '128.0.0.0/6', + '132.0.0.0/6', + '136.0.0.0/6', + '140.0.0.0/6', + '144.0.0.0/6', + '148.0.0.0/6', + '152.0.0.0/6', + '156.0.0.0/6', + '160.0.0.0/6', + '164.0.0.0/6', + '168.0.0.0/6', + '172.0.0.0/6', + '176.0.0.0/6', + '180.0.0.0/6', + '184.0.0.0/6', + '188.0.0.0/6', + '192.0.0.0/6', + '196.0.0.0/6', + '200.0.0.0/6', + '204.0.0.0/6', + '208.0.0.0/6', + '212.0.0.0/6', + '216.0.0.0/6', + '220.0.0.0/6', + '224.0.0.0/6', + '228.0.0.0/6', + '232.0.0.0/6', + '236.0.0.0/6', + '240.0.0.0/6', + '244.0.0.0/6', + '248.0.0.0/6', + '252.0.0.0/6', + ]); + expect(getSubnets('1.1.1.1/8')).to.eql([]); + expect(getSubnets('1.1.1.1/11')).to.eql([ + '1.0.0.0/11', + '1.32.0.0/11', + '1.64.0.0/11', + '1.96.0.0/11', + '1.128.0.0/11', + '1.160.0.0/11', + '1.192.0.0/11', + '1.224.0.0/11', + ]); + expect(getSubnets('172.16.2.2/16')).to.eql([]); + expect(getSubnets('172.16.2.2/26')).to.eql([ + '172.16.2.0/26', + '172.16.2.64/26', + '172.16.2.128/26', + '172.16.2.192/26', + ]); + expect(getSubnets('172.16.2.2/31').length).to.eql(128); + expect(getSubnets('255.255.255.0/32')).to.eql([]); + expect(getSubnets('2001:db8:0:85a3::ac1f:8001/62')).to.eql([]); + expect(getSubnets('2001:db8:0:85a3:0:0:ac1f:8001/62')).to.eql([]); + expect(getSubnets('2001:db8:0:85a3::ac1f:8001/112')).to.eql([]); + expect(getSubnets('2001:db8:0:85a3:0:0:ac1f:8001/112')).to.eql([]); + }); + }); + describe('getNetworksCount', () => { + it('returns networks count', () => { + expect(getNetworksCount('1.1.1.1/1')).to.eql(2); + expect(getNetworksCount('1.1.1.1/2')).to.eql(4); + expect(getNetworksCount('1.1.1.1/3')).to.eql(8); + expect(getNetworksCount('1.1.1.1/4')).to.eql(16); + expect(getNetworksCount('1.1.1.1/5')).to.eql(32); + expect(getNetworksCount('1.1.1.1/6')).to.eql(64); + expect(getNetworksCount('1.1.1.1/7')).to.eql(128); + expect(getNetworksCount('1.1.1.1/8')).to.eql(0); + expect(getNetworksCount('1.1.1.1/9')).to.eql(2); + expect(getNetworksCount('1.1.1.1/10')).to.eql(4); + expect(getNetworksCount('1.1.1.1/11')).to.eql(8); + expect(getNetworksCount('1.1.1.1/12')).to.eql(16); + expect(getNetworksCount('1.1.1.1/13')).to.eql(32); + expect(getNetworksCount('1.1.1.1/14')).to.eql(64); + expect(getNetworksCount('1.1.1.1/15')).to.eql(128); + expect(getNetworksCount('1.1.1.1/16')).to.eql(0); + expect(getNetworksCount('1.1.1.1/17')).to.eql(2); + expect(getNetworksCount('1.1.1.1/18')).to.eql(4); + expect(getNetworksCount('1.1.1.1/19')).to.eql(8); + expect(getNetworksCount('1.1.1.1/20')).to.eql(16); + expect(getNetworksCount('1.1.1.1/21')).to.eql(32); + expect(getNetworksCount('1.1.1.1/22')).to.eql(64); + expect(getNetworksCount('1.1.1.1/23')).to.eql(128); + expect(getNetworksCount('1.1.1.1/24')).to.eql(0); + expect(getNetworksCount('1.1.1.1/25')).to.eql(2); + expect(getNetworksCount('1.1.1.1/26')).to.eql(4); + expect(getNetworksCount('1.1.1.1/27')).to.eql(8); + expect(getNetworksCount('1.1.1.1/28')).to.eql(16); + expect(getNetworksCount('1.1.1.1/29')).to.eql(32); + expect(getNetworksCount('1.1.1.1/30')).to.eql(64); + expect(getNetworksCount('1.1.1.1/31')).to.eql(128); + expect(getNetworksCount('1.1.1.1/32')).to.eql(0); + expect(getNetworksCount('2001:db8:0:85a3::ac1f:8001/32')).to.eql(4294967296n); + expect(getNetworksCount('2001:db8:0:85a3::ac1f:8001/42')).to.eql(4194304n); + expect(getNetworksCount('2001:db8:0:85a3:0:0:ac1f:8001/62')).to.eql(4n); + expect(getNetworksCount('2001:db8:0:85a3::ac1f:8001/112')).to.eql(-1); + expect(getNetworksCount('2001:db8:0:85a3:0:0:ac1f:8001/122')).to.eql(-1); + }); + }); + describe('getIPNetworkType', () => { + it('returns network type', () => { + expect(getIPNetworkType('1.1.1.1')).to.eql('Public'); + expect(getIPNetworkType('10.10.1.1')).to.eql('Private Use'); + expect(getIPNetworkType('172.16.0.1')).to.eql('Private Use'); + expect(getIPNetworkType('192.168.1.1')).to.eql('Private Use'); + expect(getIPNetworkType('255.255.255.0')).to.eql('Reserved'); + expect(getIPNetworkType('224.0.0.1')).to.eql('Multicast'); + expect(getIPNetworkType('198.51.100.1')).to.eql('Documentation (TEST-NET-2)'); + expect(getIPNetworkType('198.18.0.1')).to.eql('Benchmarking'); + expect(getIPNetworkType('169.254.0.1')).to.eql('Link Local'); + expect(getIPNetworkType('127.0.0.1')).to.eql('Loopback'); + expect(getIPNetworkType('2001:db8:8:4::2')).to.eql('Documentation'); + expect(getIPNetworkType('FF02::2')).to.eql('Multicast address'); + expect(getIPNetworkType('2345:0425:2CA1:0000:0000:0567:5673:23b5')).to.eql('Public'); + expect(getIPNetworkType('fdf8:f53b:82e4::53')).to.eql('Unique-Local'); + expect(getIPNetworkType('::ffff:192.0.2.47')).to.eql('IPv4-mapped Address'); + expect(getIPNetworkType('::ffff:ac12:0a09')).to.eql('IPv4-mapped Address'); + expect(getIPNetworkType('::1')).to.eql('Loopback Address'); + expect(getIPNetworkType('fe80::200:5aee:feaa:20a2')).to.eql('Link-Local Unicast'); + expect(getIPNetworkType('2001:0002::6c::430')).to.eql('Benchmarking'); + expect(getIPNetworkType('2001:0000:4136:e378:8000:63bf:3fff:fdd2')).to.eql('TEREDO'); + expect(getIPNetworkType('2002:cb0a:3cdd:1::1')).to.eql('6to4'); + expect(getIPNetworkType('ff01:0:0:0:0:0:0:2')).to.eql('Multicast address'); + }); + }); + + describe('toARPA', () => { + it('returns ARPA address', () => { + expect(toARPA('1.1.1.1')).to.eql('1.1.1.1.in-addr.arpa'); + expect(toARPA('10.10.1.1')).to.eql('1.1.10.10.in-addr.arpa'); + expect(toARPA('192.168.1.1')).to.eql('1.1.168.192.in-addr.arpa'); + expect(toARPA('255.255.255.0')).to.eql('0.255.255.255.in-addr.arpa'); + expect(toARPA('FF02::2')).to.eql('2.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.2.0.f.f.ip6.arpa.'); + expect(toARPA('2345:0425:2CA1:0000:0000:0567:5673:23b5')).to.eql('5.b.3.2.3.7.6.5.7.6.5.0.0.0.0.0.0.0.0.0.1.a.c.2.5.2.4.0.5.4.3.2.ip6.arpa.'); + expect(toARPA('fdf8:f53b:82e4::53')).to.eql('3.5.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.4.e.2.8.b.3.5.f.8.f.d.f.ip6.arpa.'); + expect(toARPA('::ffff:192.0.2.47')).to.eql('f.2.2.0.0.0.0.c.f.f.f.f.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.ip6.arpa.'); + expect(toARPA('::1')).to.eql('1.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.ip6.arpa.'); + }); + }); + describe('toIPv4MappedAddress', () => { + it('returns IPv4MappedAddress', () => { + expect(toIPv4MappedAddress('1.1.1.1')).to.eql('::ffff:0101:0101'); + expect(toIPv4MappedAddress('10.10.1.1')).to.eql('::ffff:0a0a:0101'); + expect(toIPv4MappedAddress('172.18.10.9')).to.eql('::ffff:ac12:0a09'); + expect(toIPv4MappedAddress('192.168.1.1')).to.eql('::ffff:c0a8:0101'); + expect(toIPv4MappedAddress('255.255.255.0')).to.eql('::ffff:ffff:ff00'); + }); + }); + describe('toIPv4MappedAddressDecimal', () => { + it('returns networks count', () => { + expect(toIPv4MappedAddressDecimal('1.1.1.1')).to.eql('::ffff:1.1.1.1'); + expect(toIPv4MappedAddressDecimal('10.10.1.1')).to.eql('::ffff:10.10.1.1'); + expect(toIPv4MappedAddressDecimal('192.168.1.1')).to.eql('::ffff:192.168.1.1'); + expect(toIPv4MappedAddressDecimal('172.18.10.9')).to.eql('::ffff:172.18.10.9'); + expect(toIPv4MappedAddressDecimal('255.255.255.0')).to.eql('::ffff:255.255.255.0'); + expect(toIPv4MappedAddressDecimal('2001:db8:0:85a3::ac1f:8001')).to.eql(''); + }); + }); + describe('to6to4Prefix', () => { + it('returns networks count', () => { + expect(to6to4Prefix('1.1.1.1')).to.eql('2002:01:0:1:01:01::/48'); + expect(to6to4Prefix('10.10.1.1')).to.eql('2002:0a:0:a:01:01::/48'); + expect(to6to4Prefix('172.18.10.9')).to.eql('2002:ac:1:2:0a:09::/48'); + expect(to6to4Prefix('192.168.1.1')).to.eql('2002:c0:a:8:01:01::/48'); + expect(to6to4Prefix('255.255.255.0')).to.eql('2002:ff:f:f:ff:00::/48'); + expect(to6to4Prefix('2001:db8:0:85a3::ac1f:8001')).to.eql(''); + }); + }); +}); diff --git a/src/utils/ip.ts b/src/utils/ip.ts new file mode 100644 index 00000000..bd1c561e --- /dev/null +++ b/src/utils/ip.ts @@ -0,0 +1,147 @@ +import { isIPv4 } from 'is-ip'; +import { Address4, Address6 } from 'ip-address'; +import { contains as containsCidr } from 'cidr-tools'; +import type { IPMatch } from 'ip-matching'; +import { IPMask, IPSubnetwork, getMatch } from 'ip-matching'; +import isCidr from 'is-cidr'; +import ipv4registry from './ipv4registry.json'; +import ipv6registry from './ipv6registry.json'; + +const IPv4MAX = (BigInt(2) ** BigInt(32)) - BigInt(1); + +// IP range specific information, see IANA allocations. +// http://www.iana.org/assignments/iana-ipv4-special-registry/iana-ipv4-special-registry.xhtml +const _ipv4Registry = new Map(ipv4registry.map(v => [v[0] as string, v[1]])); + +// https://www.iana.org/assignments/iana-ipv6-special-registry/iana-ipv6-special-registry.xhtml +const _ipv6Registry = new Map(ipv6registry.map(v => [v[0] as string, v[1]])); + +export function parseAsCIDR(form: string) { + if (isCidr(form)) { + return form; + } + + const ipMatch: IPMatch = getMatch(form); + if (ipMatch instanceof IPSubnetwork) { + return (ipMatch as IPSubnetwork).toString(); + } + if (ipMatch instanceof IPMask) { + return (ipMatch as IPMask).convertToSubnet()?.toString(); + } + return (ipMatch.convertToMasks() || [])[0]?.convertToSubnet()?.toString(); +} + +export function getSubnets(cidr: string) { + const [address, prefix] = cidr.split('/'); + if (isIPv4(address)) { + const prefix4Int = Number(prefix || '32'); + const getMask = (prefix: number) => (IPv4MAX >> (BigInt(32) - BigInt(prefix))) << (BigInt(32) - BigInt(prefix)); + const bigInt = BigInt((new Address4(address)).bigInteger()); + + const subnets = []; + let startNetwork; + if (prefix4Int < 8) { + startNetwork = 0; + } + if (prefix4Int % 8 === 0) { + return []; + } + startNetwork = bigInt & getMask(prefix4Int); + const increment = BigInt(2) ** BigInt(32 - prefix4Int); + const netCount = getNetworksCount(cidr); + for (let netIndex = 0; netIndex < netCount; netIndex += 1) { + const netAddr = Address4.fromBigInteger(startNetwork.toString()).correctForm(); + subnets.push(`${netAddr}/${prefix4Int}`); + startNetwork += increment; + } + return subnets; + } + + return []; +} + +export function getNetworksCount(cidr: string) { + const [address, prefix] = cidr.split('/'); + if (isIPv4(address)) { + const prefix4Int = Number(prefix || '32'); + + if (prefix4Int % 8 === 0) { + return 0; + } + else if (prefix4Int < 8) { + return 2 ** prefix4Int; + } + else if (prefix4Int < 16) { + return 2 ** (prefix4Int - 8); + } + else if (prefix4Int < 24) { + return 2 ** (prefix4Int - 16); + } + else { + return 2 ** (prefix4Int - 24); + } + } + + const prefix6Int = Number(prefix || '128'); + return prefix6Int <= 64 ? (BigInt(2) ** BigInt(64n - BigInt(prefix6Int))) : -1; +} + +export function getIPNetworkType(address: string) { + const results = []; + for (const [addr, info] of (isIPv4(address) ? _ipv4Registry : _ipv6Registry).entries()) { + const found = containsCidr([`${addr}/${Number(info[0])}`], address); + if (found) { + results.unshift(info[1]); + } + } + return results.length === 0 ? 'Public' : results[0]?.toString(); +} + +export function toARPA(address: string) { + if (isIPv4(address)) { + const bigInt = BigInt((new Address4(address)).bigInteger()); + const reverseIP = ( + [(bigInt & BigInt(255)), (bigInt >> BigInt(8) & BigInt(255)), + (bigInt >> BigInt(16) & BigInt(255)), + (bigInt >> BigInt(24) & BigInt(255)), + ].join('.') + ); + return `${reverseIP}.in-addr.arpa`; + } + + return (new Address6(address)).reverseForm(); +} + +export function toIPv4MappedAddress(address: string) { + if (!isIPv4(address)) { + return ''; + } + + const hexIP = (new Address4(address)).toHex().replace(/:/g, ''); + return `::ffff:${hexIP.substring(0, 4)}:${hexIP.substring(4)}`; +} + +export function toIPv4MappedAddressDecimal(address: string) { + if (!isIPv4(address)) { + return ''; + } + + return `::ffff:${address}`; +} + +export function to6to4Prefix(address: string) { + if (!isIPv4(address)) { + return ''; + } + + const hexIP = (new Address4(address)).toHex(); + return `2002:${hexIP.substring(0, 4)}:${hexIP.substring(4)}::/48`; +} + +export function toMicrosoftTranscription(address: string) { + if (!isIPv4(address)) { + return ''; + } + + return (new Address6(address)).microsoftTranscription(); +} diff --git a/src/utils/ipv4registry.json b/src/utils/ipv4registry.json new file mode 100644 index 00000000..e9e3c2dc --- /dev/null +++ b/src/utils/ipv4registry.json @@ -0,0 +1,27 @@ +[ + ["0.0.0.0", [8, "This host on this network"]], + ["10.0.0.0", [8, "Private Use"]], + ["100.64.0.0", [10, "Shared Address Space"]], + ["127.0.0.0", [8, "Loopback"]], + ["169.254.0.0", [16, "Link Local"]], + ["172.16.0.0", [12, "Private Use"]], + ["192.0.0.0", [24, "IETF Protocol Assignments"]], + ["192.0.0.0", [29, "IPv4 Service Continuity Prefix"]], + ["192.0.0.8", [32, "IPv4 dummy address"]], + ["192.0.0.9", [32, "Port Control Protocol Anycast"]], + ["192.0.0.10", [32, "Traversal Using Relays around NAT Anycast"]], + ["192.0.0.170", [32, "NAT64/DNS64 Discovery"]], + ["192.0.0.171", [32, "NAT64/DNS64 Discovery"]], + ["192.0.2.0", [24, "Documentation (TEST-NET-1)"]], + ["192.31.196.0", [24, "AS112-v4"]], + ["192.52.193.0", [24, "AMT"]], + ["192.88.99.0", [24, "Deprecated (6to4 Relay Anycast)"]], + ["192.168.0.0", [16, "Private Use"]], + ["192.175.48.0", [24, "Direct Delegation AS112 Service"]], + ["198.18.0.0", [15, "Benchmarking"]], + ["198.51.100.0", [24, "Documentation (TEST-NET-2)"]], + ["203.0.113.0", [24, "Documentation (TEST-NET-3)"]], + ["224.0.0.0", [24, "Multicast"]], + ["240.0.0.0", [4, "Reserved"]], + ["255.255.255.255", [32, "Limited Broadcast"]] +] \ No newline at end of file diff --git a/src/utils/ipv6registry.json b/src/utils/ipv6registry.json new file mode 100644 index 00000000..23c18f09 --- /dev/null +++ b/src/utils/ipv6registry.json @@ -0,0 +1,24 @@ +[ + ["::1", [128, "Loopback Address"]], + ["::", [128, "Unspecified Address"]], + ["::ffff::", [96, "IPv4-mapped Address"]], + ["64:ff9b::", [96, "IPv4-IPv6 Translat."]], + ["64:ff9b:1::", [48, "IPv4-IPv6 Translat."]], + ["100::", [64, "Discard-Only Address Block"]], + ["2001::", [23, "IETF Protocol Assignments"]], + ["2001::", [32, "TEREDO"]], + ["2001:1::1", [128, "Port Control Protocol Anycast"]], + ["2001:1::2", [128, "Traversal Using Relays around NAT Anycast"]], + ["2001:2::", [48, "Benchmarking"]], + ["2001:3::", [32, "AMT"]], + ["2001:4:112::", [48, "AS112-v6"]], + ["2001:5::", [32, "EID Space for LISP (Managed by RIPE NCC)"]], + ["2001:10::", [28, "Deprecated (previously ORCHID)"]], + ["2001:20::", [28, "ORCHIDv2"]], + ["2001:db8::", [32, "Documentation"]], + ["2002::", [16, "6to4"]], + ["2620:4f:8000::", [48, "Direct Delegation AS112 Service"]], + ["fc00::", [7, "Unique-Local"]], + ["fe80::", [10, "Link-Local Unicast"]], + ["ff00::", [8, "Multicast address"]] + ] \ No newline at end of file From e0c642faa45e4ee5cd972bef1cd523c1bc70187f Mon Sep 17 00:00:00 2001 From: ShareVB Date: Sat, 20 Apr 2024 21:55:05 +0200 Subject: [PATCH 5/5] fix: refactor using IPv4/6 Utils --- pnpm-lock.yaml | 99 +++++ src/libs/ip_calculator/ip.ts | 405 ------------------ src/libs/ip_calculator/ipv4registry.json | 26 -- src/libs/ip_calculator/ipv6registry.json | 24 -- src/libs/ip_calculator/network.ts | 229 ---------- .../ipv6-subnet-calculator.vue | 96 +++-- src/utils/ip.test.ts | 19 +- 7 files changed, 169 insertions(+), 729 deletions(-) delete mode 100644 src/libs/ip_calculator/ip.ts delete mode 100644 src/libs/ip_calculator/ipv4registry.json delete mode 100644 src/libs/ip_calculator/ipv6registry.json delete mode 100644 src/libs/ip_calculator/network.ts diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index cb383786..71907a62 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -47,6 +47,9 @@ dependencies: change-case: specifier: ^4.1.2 version: 4.1.2 + cidr-tools: + specifier: ^7.0.4 + version: 7.0.7 colord: specifier: ^2.9.3 version: 2.9.3 @@ -92,9 +95,24 @@ dependencies: ibantools: specifier: ^4.3.3 version: 4.3.3 + ip-address: + specifier: ^9.0.5 + version: 9.0.5 + ip-bigint: + specifier: ^8.0.2 + version: 8.0.2 + ip-cidr: + specifier: ^4.0.0 + version: 4.0.0 + ip-matching: + specifier: ^2.1.2 + version: 2.1.2 is-cidr: specifier: ^5.0.3 version: 5.0.3 + is-ip: + specifier: ^5.0.1 + version: 5.0.1 json5: specifier: ^2.2.3 version: 2.2.3 @@ -4473,6 +4491,13 @@ packages: ip-regex: 5.0.0 dev: false + /cidr-tools@7.0.7: + resolution: {integrity: sha512-JeLGxKmaxk59IDRptqYfKa6Pw0bq8EzX7NoBu5XRKLPP9YmUu9mYcqiNliX4h3exjOqMdtzuY6F/rFj43V4Yww==} + engines: {node: '>=18'} + dependencies: + ip-bigint: 8.0.2 + dev: false + /clean-regexp@1.0.0: resolution: {integrity: sha512-GfisEZEJvzKrmGWkvfhgzcz/BllN1USeqD2V6tg14OAOgaCD2Z/PUEuxnAZ/nPvmaHRG7a8y77p1T/IRQ4D1Hw==} engines: {node: '>=4'} @@ -4500,6 +4525,13 @@ packages: wrap-ansi: 6.2.0 dev: false + /clone-regexp@3.0.0: + resolution: {integrity: sha512-ujdnoq2Kxb8s3ItNBtnYeXdm07FcU0u8ARAT1lQ2YdMwQC+cdiXX8KoqMVuglztILivceTtp4ivqGSmEmhBUJw==} + engines: {node: '>=12'} + dependencies: + is-regexp: 3.1.0 + dev: false + /clone@1.0.4: resolution: {integrity: sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==} engines: {node: '>=0.8'} @@ -4604,6 +4636,11 @@ packages: upper-case: 2.0.2 dev: false + /convert-hrtime@5.0.0: + resolution: {integrity: sha512-lOETlkIeYSJWcbbcvjRKGxVMXJR+8+OQb/mTPbA4ObPMytYIsUbuOE0Jzy60hjARYszq1id0j8KgVhC+WGZVTg==} + engines: {node: '>=12'} + dev: false + /convert-source-map@1.9.0: resolution: {integrity: sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==} dev: true @@ -5753,6 +5790,11 @@ packages: resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} dev: true + /function-timeout@0.1.1: + resolution: {integrity: sha512-0NVVC0TaP7dSTvn1yMiy6d6Q8gifzbvQafO46RtLG/kHJUBNd+pVRGOBoK44wNBvtSPUJRfdVvkFdD3p0xvyZg==} + engines: {node: '>=14.16'} + dev: false + /function.prototype.name@1.1.6: resolution: {integrity: sha512-Z5kx79swU5P27WEayXM1tBi5Ze/lbIyiNgU3qyXUOf9b2rgXYyF9Dy9Cx+IQv/Lc8WCG6L82zwUPpSS9hGehIg==} engines: {node: '>= 0.4'} @@ -6187,6 +6229,19 @@ packages: sprintf-js: 1.1.2 dev: false + /ip-address@9.0.5: + resolution: {integrity: sha512-zHtQzGojZXTwZTHQqra+ETKd4Sn3vgi7uBmlPoXVWZqYvuKmtI0l/VZTjqGmJY9x88GGOaZ9+G9ES8hC4T4X8g==} + engines: {node: '>= 12'} + dependencies: + jsbn: 1.1.0 + sprintf-js: 1.1.3 + dev: false + + /ip-bigint@8.0.2: + resolution: {integrity: sha512-UMKHGx7+4O2mD/6jnpNtt4UMA0tRQ3XAiNVYlbLssFU1LegKqKwPqbqtLVW7lQU/c6rCWI1hcxxs4TP96Xa+rQ==} + engines: {node: '>=18'} + dev: false + /ip-cidr@3.1.0: resolution: {integrity: sha512-HUCn4snshEX1P8cja/IyU3qk8FVDW8T5zZcegDFbu4w7NojmAhk5NcOgj3M8+0fmumo1afJTPDtJlzsxLdOjtg==} engines: {node: '>=10.0.0'} @@ -6195,6 +6250,17 @@ packages: jsbn: 1.1.0 dev: false + /ip-cidr@4.0.0: + resolution: {integrity: sha512-i1Jhb9sqm2+PuOHTfya3ekAUi+dadhgcEz+4FKKY1hXemocP4Xf7io8Xflc74/i2ejxe/5fp4z8z3BAsfAZ8sw==} + engines: {node: '>=16.14.0'} + dependencies: + ip-address: 9.0.5 + dev: false + + /ip-matching@2.1.2: + resolution: {integrity: sha512-/ok+VhKMasgR5gvTRViwRFQfc0qYt9Vdowg6TO4/pFlDCob5ZjGPkwuOoQVCd5OrMm20zqh+1vA8KLJZTeWudg==} + dev: false + /ip-regex@5.0.0: resolution: {integrity: sha512-fOCG6lhoKKakwv+C6KdsOnGvgXnmgfmp0myi3bcNwj3qfwPAxRKWEuFhvEFF7ceYIz6+1jRZ+yguLFAmUNPEfw==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} @@ -6327,6 +6393,14 @@ packages: engines: {node: '>=8'} dev: true + /is-ip@5.0.1: + resolution: {integrity: sha512-FCsGHdlrOnZQcp0+XT5a+pYowf33itBalCl+7ovNXC/7o5BhIpG14M3OrpPPdBSIQJCm+0M5+9mO7S9VVTTCFw==} + engines: {node: '>=14.16'} + dependencies: + ip-regex: 5.0.0 + super-regex: 0.2.0 + dev: false + /is-lower-case@1.1.3: resolution: {integrity: sha512-+5A1e/WJpLLXZEDlgz4G//WYSHyQBD32qa4Jd3Lw06qQlv3fJHnp3YIHjTQSGzHMgzmVKz2ZP3rBxTHkPw/lxA==} dependencies: @@ -6392,6 +6466,11 @@ packages: engines: {node: '>=0.10.0'} dev: true + /is-regexp@3.1.0: + resolution: {integrity: sha512-rbku49cWloU5bSMI+zaRaXdQHXnthP6DZ/vLnfdSKyL4zUzuWnomtOEiZZOd+ioQ+avFo/qau3KPTc7Fjy1uPA==} + engines: {node: '>=12'} + dev: false + /is-shared-array-buffer@1.0.2: resolution: {integrity: sha512-sqN2UDu1/0y6uvXyStCOzyhAjCSlHceFoMKJW8W9EU9cvic/QdsZ0kEU93HEy3IUEFZIiH/3w+AH/UQbPHNdhA==} dependencies: @@ -8205,6 +8284,10 @@ packages: resolution: {integrity: sha512-VE0SOVEHCk7Qc8ulkWw3ntAzXuqf7S2lvwQaDLRnUeIEaKNQJzV6BwmLKhOqT61aGhfUMrXeaBk+oDGCzvhcug==} dev: false + /sprintf-js@1.1.3: + resolution: {integrity: sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==} + dev: false + /sql-formatter@13.0.0: resolution: {integrity: sha512-V21cVvge4rhn9Fa7K/fTKcmPM+x1yee6Vhq8ZwgaWh3VPBqApgsaoFB5kLAhiqRo5AmSaRyLU7LIdgnNwH01/w==} hasBin: true @@ -8335,6 +8418,15 @@ packages: dependencies: acorn: 8.11.2 + /super-regex@0.2.0: + resolution: {integrity: sha512-WZzIx3rC1CvbMDloLsVw0lkZVKJWbrkJ0k1ghKFmcnPrW1+jWbgTkTEWVtD9lMdmI4jZEz40+naBxl1dCUhXXw==} + engines: {node: '>=14.16'} + dependencies: + clone-regexp: 3.0.0 + function-timeout: 0.1.1 + time-span: 5.1.0 + dev: false + /supports-color@5.5.0: resolution: {integrity: sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==} engines: {node: '>=4'} @@ -8427,6 +8519,13 @@ packages: resolution: {integrity: sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==} dev: true + /time-span@5.1.0: + resolution: {integrity: sha512-75voc/9G4rDIJleOo4jPvN4/YC4GRZrY8yy1uU4lwrB3XEQbWve8zXoO5No4eFrGcTAMYyoY67p8jRQdtA1HbA==} + engines: {node: '>=12'} + dependencies: + convert-hrtime: 5.0.0 + dev: false + /tiny-emitter@2.1.0: resolution: {integrity: sha512-NB6Dk1A9xgQPMoGqC5CVXn123gWyte215ONT5Pp5a0yt4nlEoO1ZWeCwpncaekPHXO60i47ihFnZPiRPjRMq4Q==} dev: false diff --git a/src/libs/ip_calculator/ip.ts b/src/libs/ip_calculator/ip.ts deleted file mode 100644 index 8e7ec106..00000000 --- a/src/libs/ip_calculator/ip.ts +++ /dev/null @@ -1,405 +0,0 @@ -/* eslint-disable no-labels */ -/* eslint-disable no-restricted-syntax */ - -const IPv4MAX = (BigInt(2) ** BigInt(32)) - BigInt(1); -const IPv6MAX = (BigInt(2) ** BigInt(128)) - BigInt(1); - -/** -* Represents a single IP address v4 or v6. -* @class IP -* @param {string} address -* host = new IP("184.170.96.196"); -* @return {object} -> IP{address:"184.170.96.196", version: 4, integer: 0, short: 0} -*/ - -export default class IP { - integer: bigint; - short: string; - version: number; - address: string; - /** - * @constructor - */ - constructor(address: string) { - this.integer = 0n; - this.short = ''; - this.version = this._checkVersion(address); - this.address = this._checkAddress(address, this.version); - } - - // Public methods - - /** - * toInteger - Converts dotquad or hextet IP to integer - * @return {BigInt} -> 2130706432 - */ - toInteger() { - let bigInt; - if (this.version === 4) { - const splittedAddr = this.address.split('.').reverse(); - bigInt = splittedAddr.reduce((bigInt, octet, index) => { - return (Number(octet) * 256 ** index + bigInt - ); - }, 0); - } - else { - const joinedAddr = this.address.split(':').join(''); - bigInt = BigInt(`0x${joinedAddr}`); - } - this.integer = BigInt(bigInt); - return BigInt(bigInt); - } - - /** - * toDottedNotation - Converts big integer IP to full dotquad or hextet representation - * @param {bigint} bigInt - * @return {string} -> "184.170.96.196" - */ - toDottedNotation(bigInt: bigint) { - if (this.version === 4) { - return ( - [(bigInt >> BigInt(24) & BigInt(255)), (bigInt >> BigInt(16) & BigInt(255)), - (bigInt >> BigInt(8) & BigInt(255)), - (bigInt & BigInt(255)), - ].join('.') - ); - } - else { - let hex = bigInt.toString(16); - const groups = []; - while (hex.length < 32) { - hex = `0${hex}`; - } - for (let i = 0; i < 8; i++) { - groups.push(hex.slice(i * 4, (i + 1) * 4)); - } - return groups.join(':'); - } - } - - /** - * toBinary - Converts decimal IP to full-length binary representation. - * @return {string} -> 01111111000000000000000000000001 - */ - toBinary() { - if (this.integer === 0n) { - this.toInteger(); - } - let binary = this.integer.toString(2); - const markLen = this.version === 4 ? 32 : 128; - - if (binary.length < markLen) { - while (binary.length < markLen) { - binary = `0${binary}`; - } - } - return binary; - } - - /** - * toHEX - Converts both IP versions to hexadecimal representation. - * @return {string} -> 7f000001 - */ - toHEX() { - if (this.integer === 0n) { - this.toInteger(); - } - return this.integer.toString(16); - } - - /** - * toCompressed - Compress an IP address to its shortest possible form. - * IP('127.1.0.0').toCompressed - * @return {string} -> "127.1" - */ - toCompressed(addr: string, ver: number) { - if (ver === 4) { - const splittedAddr = addr.split('.'); - const sRange = [[1, 3], [2, 2], [3, 1], [0, 0]]; - - for (let i = splittedAddr.length - 1; i >= 0; i--) { - if (splittedAddr[i] === '0') { - continue; - } - else { - splittedAddr.splice(sRange[i][0], sRange[i][1]); - this.short = splittedAddr.join('.'); - return this.short; - } - } - } - else { - const splitted = addr.split(':'); - // finding longest zero group - const [startOfLongest, longestLength] = _longestZerosGroup(splitted); - // 'N/A' - _longestZerosGroup fn return in case if there is NO - // '0000' blocks in address - if (startOfLongest !== 'N/A' || longestLength !== 'N/A') { - splitted.splice(Number(startOfLongest), Number(longestLength), ''); - if (startOfLongest === 0) { - splitted.unshift(''); - } - if (Number(startOfLongest) + Number(longestLength) === 8) { - splitted.push(''); - } - } - - // removing single '0000' blocks and leading zeros - for (let i = 0; i < splitted.length; i++) { - if (splitted[i] === '0000') { - splitted.splice(i, 1, '0'); - } - - loopStr: - for (let j = 0; j < splitted[i].length; j++) { - if (splitted[i][j] === '0' && splitted[i] !== '0') { - splitted[i] = splitted[i].substring(j + 1); - j--; - continue; - } - else { - break loopStr; - } - } - } - this.short = splitted.join(':'); - return this.short; - } - } - - // Private methods - - /** - * checkVersion - Determins this IP version. - * @private - * @param {string} addr - * @return {number} -> 4 or 6 - */ - _checkVersion(addr: string) { - // matches all possible chars in both versions of IP - const reGen = /^[0-9a-f.:]+$/i; - if (reGen.test(addr)) { - // checks if there is .. and more or whole IP is just a dot - const reDots = /\.{2,}|^\.{1}$/; - // checks if there is ::: and more or whole IP is just a colon - const reColon = /:{3,}|^:{1}$/; - // checks if there is only digits in integer IP - const reNum = /^[0-9]+$/; - - if (reNum.test(addr)) { - const parsedAddr = BigInt(addr); - if (parsedAddr > IPv6MAX || parsedAddr <= 0) { - throw new Error('Tips: IP address cant be bigger than 2 to the 128-th power or negative number'); - } - else if (parsedAddr <= IPv4MAX) { - return 4; - } - else if (parsedAddr > IPv4MAX) { - return 6; - } - } - else if (addr.includes('.') && !reDots.test(addr)) { - return 4; - } - else if (addr.includes(':') && !reColon.test(addr)) { - return 6; - } - } - throw new Error('Tips: Please, enter a valid IP address (Like "127.1.0.0", long integer, short or long IPv6)'); - } - - /** - * checkAddress - Validates this IP address. - * @private - * @return {string} as a valid address - */ - _checkAddress(addr: string, v: number) { - const reNum = /^[0-9]+$/; - if (reNum.test(addr)) { - this.integer = BigInt(addr); - return this.toDottedNotation(this.integer); - } - - const splittedAddr = addr.split(v === 4 ? '.' : ':'); - - if (v === 6 && splittedAddr.length < 8) { - const dbColon = (addr.match(/::/g) || []).length; - if (dbColon !== 1) { - throw new Error('Tips: Please, enter a valid IP address (Like "127.1.0.0", long integer, short or long IPv6)'); - } - } - - if ((v === 4 ? this._isIPv4 : this._isIPv6).call(this, splittedAddr)) { // TODO: make ifs more readable - if (splittedAddr.length === (v === 4 ? 4 : 8) && this.short === '') { - return addr; - } - else { - return this._toRepresentation(splittedAddr); - } - } - else { - throw new Error('Tips: Please, enter a valid IP address (Like "127.1.0.0", long integer, short or long IPv6)'); - } - } - - /** - * _isIPv6 - Validates IPv6. - * @private - * @return {boolean} whether splitted address is valid IPv6 or not - */ - _isIPv6(splittedAddr: string[]) { - if (splittedAddr.length <= 8) { - let checked = false; - const [isShort, cleanedAddr] = this._isShort(splittedAddr); - - const regex = /^[0-9a-f]{1,4}$/i; - const isValid = function (hextet: string) { - return regex.test(hextet); - }; - checked = (cleanedAddr as string[]).every(isValid); - - if (checked && isShort) { - this.short = splittedAddr.join(':'); - } - return checked; - } - else { - throw new Error('Tips: IPv6 cannot contain more than 8 bytes'); - } - } - - /** - * _isIPv4 - Validates IPv4. - * @private - * @return {boolean} whether splitted address is valid IPv4 or not - */ - _isIPv4(splittedAddr: string[]) { - if (splittedAddr.length <= 4) { - if (splittedAddr.length < 4) { - this.short = splittedAddr.join('.'); - } - const isValid = function (octet: string) { - return (!!((Number(octet) <= 255 && Number(octet) >= 0))); - }; - return splittedAddr.every(isValid); - } - else { - throw new Error('Tips: IPv4 cannot contain more than 4 bytes'); - } - } - - /** - * _isShort - checks if IPv6 addres was compressed like this "234:f:34:34:1:1:2:2" or like "1234::1234:1234" and removes empty strings for future validation - * @private - * @param {array} splittedAddr - * @return {array} with both results boolean and cleaned array - */ - _isShort(splittedAddr: string[]) { - let isShort = false; - const cleanedAddr = [...splittedAddr]; - for (let i = 0; i < cleanedAddr.length; i++) { - if (cleanedAddr[i].length === 0) { - cleanedAddr.splice(i, 1); - isShort = true; - i--; // code chunk similar to toCompressed method - // for addr '::1' can happen that there are 2 empty strings - // together, so by i-- we check every el of array but not next but one - } - else if (cleanedAddr[i].length < 4) { - isShort = true; - } - } - return [isShort, cleanedAddr]; - } - - /** - * toRepresentation - Converts short version to canonical representation of IP. - * IP('::1').address - * @private - * @param {array} splittedAddr - * @return {string} -> "0000:0000:0000:0000:0000:0000:0000:0001" - */ - _toRepresentation(splittedAddr: string[]) { - if (this.version === 4) { - for (let i = 0; i <= 4; i++) { - if (splittedAddr[i] === '') { - let missOcts = 5 - splittedAddr.length; - let flag = true; - while (missOcts > 0) { - if (flag) { - splittedAddr.splice(i, 1, '0'); - missOcts--; - flag = false; - } - else { - splittedAddr.splice(i, 0, '0'); - missOcts--; - } - } - } - } - while (splittedAddr.length < 4) { - splittedAddr.push('0'); - } - return splittedAddr.join('.'); - } - else { - for (let i = 0; i <= 8; i++) { - if (splittedAddr[i] === '') { - let missHex = 9 - splittedAddr.length; - let flag = true; - while (missHex > 0) { - if (flag) { - splittedAddr.splice(i, 1, '0000'); - missHex--; - flag = false; - } - else { - splittedAddr.splice(i, 0, '0000'); - missHex--; - } - } - } - } - for (let i = 0; i < splittedAddr.length; i++) { - if (splittedAddr[i].length < 4) { - let missNum = 4 - splittedAddr[i].length; - while (missNum > 0) { - splittedAddr[i] = `0${splittedAddr[i]}`; - missNum--; - } - } - } - } - return splittedAddr.join(':'); - } -}// IP class end - -/** - * longestZerosGroup - Helper fn counting longest consecutive zeros for shortening IPv6 - * "0000:0000:0000:0000:0000:0000:0000:0001" - * @private - * @param {array} zeros - * @return {array} -> [0, 7] - */ -function _longestZerosGroup(splittedAddr: string[]) { - let curr = 0; - let currLongest = 0; - let startOfLongest = 0; - let hasZeros = false; - - while (curr < splittedAddr.length - 2) { - const startOfRun = curr; - while (curr < splittedAddr.length && splittedAddr[curr] === '0000') { - hasZeros = true; - curr++; - } - - if ((curr - startOfRun) > currLongest) { - startOfLongest = startOfRun; - currLongest = curr - startOfRun; - } - curr++; - } - return hasZeros ? [startOfLongest, currLongest] : ['N/A', 'N/A']; -} diff --git a/src/libs/ip_calculator/ipv4registry.json b/src/libs/ip_calculator/ipv4registry.json deleted file mode 100644 index 91b262c7..00000000 --- a/src/libs/ip_calculator/ipv4registry.json +++ /dev/null @@ -1,26 +0,0 @@ -[ - ["0.0.0.0", [8, "This host on this network"]], - ["10.0.0.0", [8, "Private-Use"]], - ["100.64.0.0", [10, "Shared Address Space"]], - ["127.0.0.0", [8, "Loopback"]], - ["169.254.0.0", [16, "Link Local"]], - ["172.16.0.0", [12, "Private-Use"]], - ["192.0.0.0", [24, "IETF Protocol Assignments"]], - ["192.0.0.0", [29, "IPv4 Service Continuity Prefix"]], - ["192.0.0.8", [32, "IPv4 dummy address"]], - ["192.0.0.9", [32, "Port Control Protocol Anycast"]], - ["192.0.0.10", [32, "Traversal Using Relays around NAT Anycast"]], - ["192.0.0.170", [32, "NAT64/DNS64 Discovery"]], - ["192.0.0.171", [32, "NAT64/DNS64 Discovery"]], - ["192.0.2.0", [24, "Documentation (TEST-NET-1)"]], - ["192.31.196.0", [24, "AS112-v4"]], - ["192.52.193.0", [24, "AMT"]], - ["192.88.99.0", [24, "Deprecated (6to4 Relay Anycast)"]], - ["192.168.0.0", [16, "Private Use"]], - ["192.175.48.0", [24, "Direct Delegation AS112 Service"]], - ["198.18.0.0", [15, "Benchmarking"]], - ["198.51.100.0", [24, "Documentation (TEST-NET-2)"]], - ["203.0.113.0", [24, "Documentation (TEST-NET-3)"]], - ["240.0.0.0", [4, "Reserved"]], - ["255.255.255.255", [32, "Limited Broadcast"]] -] \ No newline at end of file diff --git a/src/libs/ip_calculator/ipv6registry.json b/src/libs/ip_calculator/ipv6registry.json deleted file mode 100644 index c419b4d1..00000000 --- a/src/libs/ip_calculator/ipv6registry.json +++ /dev/null @@ -1,24 +0,0 @@ -[ - ["::1", [128, "Loopback Address"]], - ["::", [128, "Unspecified Address"]], - ["::", [128, "Unspecified Address"]], - ["::ffff:0:0", [98, "IPv4-mapped Address"]], - ["64:ff9b::", [96, "IPv4-IPv6 Translat."]], - ["64:ff9b:1::", [48, "IPv4-IPv6 Translat."]], - ["100::", [64, "Discard-Only Address Block"]], - ["2001::", [23, "IETF Protocol Assignments"]], - ["2001::", [32, "TEREDO"]], - ["2001:1::1", [128, "Port Control Protocol Anycast"]], - ["2001:1::2", [128, "Traversal Using Relays around NAT Anycast"]], - ["2001:2::", [48, "Benchmarking"]], - ["2001:3::", [32, "AMT"]], - ["2001:4:112::", [48, "AS112-v6"]], - ["2001:5::", [32, "EID Space for LISP (Managed by RIPE NCC)"]], - ["2001:10::", [28, "Deprecated (previously ORCHID)"]], - ["2001:20::", [28, "ORCHIDv2"]], - ["2001:db8::", [32, "Documentation"]], - ["2002::", [16, "6to4"]], - ["2620:4f:8000::", [48, "Direct Delegation AS112 Service"]], - ["fc00::", [7, "Unique-Local"]], - ["fe80::", [10, "Link-Local Unicast"]] - ] \ No newline at end of file diff --git a/src/libs/ip_calculator/network.ts b/src/libs/ip_calculator/network.ts deleted file mode 100644 index 9ffcdc74..00000000 --- a/src/libs/ip_calculator/network.ts +++ /dev/null @@ -1,229 +0,0 @@ -import IP from './ip'; -import ipv4registry from './ipv4registry.json'; -import ipv6registry from './ipv6registry.json'; - -const IPv4MAX = (BigInt(2) ** BigInt(32)) - BigInt(1); -const IPv6MAX = (BigInt(2) ** BigInt(128)) - BigInt(1); - -// IP range specific information, see IANA allocations. -// http://www.iana.org/assignments/iana-ipv4-special-registry/iana-ipv4-special-registry.xhtml -const _ipv4Registry = new Map(ipv4registry.map(v => [v[0] as string, v[1]])); - -// https://www.iana.org/assignments/iana-ipv6-special-registry/iana-ipv6-special-registry.xhtml -const _ipv6Registry = new Map(ipv6registry.map(v => [v[0] as string, v[1]])); - -/** -* Network slice calculations. -* @class Network -* @param {string} address -* @param {integer} prefix -* host = new IP("127.128.99.3",8) -* @return {object} -> IP{address:"127.128.99.3", prefix: 8} -*/ - -export default class Network extends IP { - prefix: bigint; - /** - * Extends IP class. Calls the parent class IP with the parameters passed to Network. - * @constructor - */ - constructor(address: string, prefix: number) { - super(address); - this.prefix = this._checkPrefix(prefix); - } - - // Private methods - - /** - * _checkPrefix - Returns this IP prefix and validates it - * @private - * @return {integer} -> prefix: 25n - */ - _checkPrefix(prefix: number) { - if (this.version === 4) { - if (prefix > 0 && prefix <= 32) { - return BigInt(prefix); - } - } - else { - if (prefix > 0 && prefix <= 128) { - return BigInt(prefix); - } - } - throw new Error('Tips: Invalid prefix'); - } - - // Public methods - - /** - * printInfo - Shows IANA allocation information for the current IP address. - * @return {string} ->LOOPBACK - */ - printInfo() { - const results = []; - for (const [addr, info] of (this.version === 4 ? _ipv4Registry : _ipv6Registry).entries()) { - const found = this.contains(this.address, addr, Number(info[0])); - if (found) { - results.unshift(info[1]); - } - } - return results.length === 0 ? 'Unknown' : results[0]; - } - - /** - * maskToInteger - Returns network mask as bigInt - * @return {BigInt} -> 4278190080n - */ - maskToInteger() { - if (this.version === 4) { - return (IPv4MAX >> (BigInt(32) - this.prefix)) << (BigInt(32) - this.prefix); - } - else { - return (IPv6MAX >> (BigInt(128) - this.prefix)) << (BigInt(128) - this.prefix); - } - } - - /** - * getMask - Returns mask from the prefix - * @return {string} -> 255.255.0.0 - */ - getMask() { - return this.toDottedNotation(this.maskToInteger()); - } - - /** - * networkToInteger - Returns network as bigInt. - * @return {BigInt} -> 21307064320 - */ - networkToInteger() { - return this.toInteger() & this.maskToInteger(); - } - - /** - * getNetwork - Returns network part of the address - * @return {string} -> 127 - */ - getNetwork() { - return this.toDottedNotation(this.networkToInteger()); - } - - /** - * getBroadcast - Calculates broadcast.IPv6 doesn't have a broadcast - * address, but it's used for other calculations such as Network.hostLast. - * @return {string} -> 127.255.255.255 - */ - getBroadcast() { - return this.version === 4 - ? this.toDottedNotation(this.broadcastToLong()) - : 'IPv6 doesnt have broadcast address'; - } - - /** - * broadcastToLong - Returns broadcast as long. - * @return {BigInt} ->2147483647 - */ - broadcastToLong() { - if (this.version === 4) { - return this.networkToInteger() | (IPv4MAX - this.maskToInteger()); - } - else { - return this.networkToInteger() | (IPv6MAX - this.maskToInteger()); - } - } - - /** - * hostFirst - Calculates first available host in this subnet. - * @return {string} ->127.0.0.1 - */ - hostFirst() { - const isSmall4 = this.version === 4 && this.prefix > BigInt(30); - let first; - - if (this.version === 6) { - first = this.getNetwork(); - } - else if (isSmall4) { - return 'N/A'; - } - else { - first = this.toDottedNotation(this.networkToInteger() + BigInt(1)); - } - return this.toCompressed(first, this.version); - } - - /** - * hostLast - Calculates last available host in this subnet. - * @return {string} ->127.255.255.255 - */ - hostLast() { - const isLast4 = this.version === 4 && this.prefix === BigInt(32); - const isLast6 = this.version === 6 && this.prefix === BigInt(128); - const isPrev4 = this.version === 4 && this.prefix === BigInt(31); - const isPrev6 = this.version === 6 && this.prefix === BigInt(127); - let last; - - if (isLast4 || isLast6 || isPrev4) { - return 'N/A'; - } - else if (isPrev6) { - last = this.address; - } - else if (this.version === 4) { - last = this.toDottedNotation(this.broadcastToLong() - BigInt(1)); - } - else { - last = this.toDottedNotation(this.broadcastToLong()); - } - return this.toCompressed(last, this.version); - } - - /** - * contains - Check if thisIP is part of the network - * @param {string} thisIP - * @param {string} otherIP - * @param {number} prefix - * @return {boolean} - */ - contains(thisIP: string, otherIP: string, prefix: number) { - const other = new Network(otherIP, prefix); - const thisNetwork = this.networkToInteger(); - const otherNetwork = other.networkToInteger(); - const smaller = (thisNetwork <= otherNetwork) - && (otherNetwork <= this.broadcastToLong()); - const bigger = (otherNetwork <= thisNetwork) - && (thisNetwork <= other.broadcastToLong()); - return smaller || bigger; - } - - /** - * hostRange - Generates a range of usable host IP addresses within the network. - * @return {array} -> ['127.0.0.1','127.255.255.255'] - */ - hostRange() { - const range = []; - range.push(this.hostFirst()); - range.push(this.hostLast()); - return range; - } - - /** - * networkSize - Returns number of ips within the network. - * @return {number} -> 16777214 - */ - networkSize() { - const size = BigInt(2) ** ((this.version === 4 ? BigInt(32) : BigInt(128)) - this.prefix); - - if (this.version === 4 && this.prefix < BigInt(30)) { - return size - BigInt(2); - } - return size; - } - - /** - * networkCount - Returns number of network fo the prefix. - * @return {number} -> 16777214 - */ - networkCount() { - return this.prefix <= 64 ? (BigInt(2) ** BigInt(64n - this.prefix)).toString() : ''; - } -} diff --git a/src/tools/ipv6-subnet-calculator/ipv6-subnet-calculator.vue b/src/tools/ipv6-subnet-calculator/ipv6-subnet-calculator.vue index e9f1c660..df4c79aa 100644 --- a/src/tools/ipv6-subnet-calculator/ipv6-subnet-calculator.vue +++ b/src/tools/ipv6-subnet-calculator/ipv6-subnet-calculator.vue @@ -1,102 +1,116 @@