feat(new tool): IPv6 Subnet Calculator

IPv6 Subnet Calculator
Fix #354 and #924
This commit is contained in:
sharevb 2024-04-03 22:47:43 +02:00 committed by ShareVB
parent d7a8c65067
commit 1f67ba15e9
10 changed files with 285 additions and 111 deletions

12
components.d.ts vendored
View file

@ -105,6 +105,7 @@ declare module '@vue/runtime-core' {
Ipv4AddressConverter: typeof import('./src/tools/ipv4-address-converter/ipv4-address-converter.vue')['default'] 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'] 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'] 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'] Ipv6UlaGenerator: typeof import('./src/tools/ipv6-ula-generator/ipv6-ula-generator.vue')['default']
JsonDiff: typeof import('./src/tools/json-diff/json-diff.vue')['default'] JsonDiff: typeof import('./src/tools/json-diff/json-diff.vue')['default']
JsonMinify: typeof import('./src/tools/json-minify/json-minify.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'] MetaTagGenerator: typeof import('./src/tools/meta-tag-generator/meta-tag-generator.vue')['default']
MimeTypes: typeof import('./src/tools/mime-types/mime-types.vue')['default'] MimeTypes: typeof import('./src/tools/mime-types/mime-types.vue')['default']
NavbarButtons: typeof import('./src/components/NavbarButtons.vue')['default'] NavbarButtons: typeof import('./src/components/NavbarButtons.vue')['default']
NCode: typeof import('naive-ui')['NCode']
NCollapseTransition: typeof import('naive-ui')['NCollapseTransition'] NCollapseTransition: typeof import('naive-ui')['NCollapseTransition']
NConfigProvider: typeof import('naive-ui')['NConfigProvider'] NConfigProvider: typeof import('naive-ui')['NConfigProvider']
NDivider: typeof import('naive-ui')['NDivider']
NEllipsis: typeof import('naive-ui')['NEllipsis'] 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'] NH1: typeof import('naive-ui')['NH1']
NH3: typeof import('naive-ui')['NH3'] NH3: typeof import('naive-ui')['NH3']
NIcon: typeof import('naive-ui')['NIcon'] NIcon: typeof import('naive-ui')['NIcon']
NInputNumber: typeof import('naive-ui')['NInputNumber']
NLabel: typeof import('naive-ui')['NLabel']
NLayout: typeof import('naive-ui')['NLayout'] NLayout: typeof import('naive-ui')['NLayout']
NLayoutSider: typeof import('naive-ui')['NLayoutSider'] NLayoutSider: typeof import('naive-ui')['NLayoutSider']
NMenu: typeof import('naive-ui')['NMenu'] NMenu: typeof import('naive-ui')['NMenu']
NScrollbar: typeof import('naive-ui')['NScrollbar'] NTable: typeof import('naive-ui')['NTable']
NSpin: typeof import('naive-ui')['NSpin']
NumeronymGenerator: typeof import('./src/tools/numeronym-generator/numeronym-generator.vue')['default'] 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'] 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'] 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'] RouterLink: typeof import('vue-router')['RouterLink']
RouterView: typeof import('vue-router')['RouterView'] RouterView: typeof import('vue-router')['RouterView']
RsaKeyPairGenerator: typeof import('./src/tools/rsa-key-pair-generator/rsa-key-pair-generator.vue')['default'] 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'] SlugifyString: typeof import('./src/tools/slugify-string/slugify-string.vue')['default']
SpanCopyable: typeof import('./src/components/SpanCopyable.vue')['default'] SpanCopyable: typeof import('./src/components/SpanCopyable.vue')['default']
SqlPrettify: typeof import('./src/tools/sql-prettify/sql-prettify.vue')['default'] SqlPrettify: typeof import('./src/tools/sql-prettify/sql-prettify.vue')['default']

View file

@ -64,6 +64,7 @@
"highlight.js": "^11.7.0", "highlight.js": "^11.7.0",
"iarna-toml-esm": "^3.0.5", "iarna-toml-esm": "^3.0.5",
"ibantools": "^4.3.3", "ibantools": "^4.3.3",
"is-cidr": "^5.0.3",
"json5": "^2.2.3", "json5": "^2.2.3",
"jwt-decode": "^3.1.2", "jwt-decode": "^3.1.2",
"libphonenumber-js": "^1.10.28", "libphonenumber-js": "^1.10.28",

34
pnpm-lock.yaml generated
View file

@ -92,6 +92,9 @@ dependencies:
ibantools: ibantools:
specifier: ^4.3.3 specifier: ^4.3.3
version: 4.3.3 version: 4.3.3
is-cidr:
specifier: ^5.0.3
version: 5.0.3
json5: json5:
specifier: ^2.2.3 specifier: ^2.2.3
version: 2.2.3 version: 2.2.3
@ -3351,7 +3354,7 @@ packages:
dependencies: dependencies:
'@unhead/dom': 0.5.1 '@unhead/dom': 0.5.1
'@unhead/schema': 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 unhead: 0.5.1
vue: 3.3.4 vue: 3.3.4
transitivePeerDependencies: transitivePeerDependencies:
@ -3993,10 +3996,10 @@ packages:
- vue - vue
dev: false dev: false
/@vueuse/shared@10.7.2(vue@3.3.4): /@vueuse/shared@10.9.0(vue@3.3.4):
resolution: {integrity: sha512-qFbXoxS44pi2FkgFjPvF4h7c9oMDutpyBdcJdMYIMg9XyXli2meFMuaKn+UMgsClo//Th6+beeCgqweT/79BVA==} resolution: {integrity: sha512-Uud2IWncmAfJvRaFYzv5OHDli+FbOzxiVEQdLCKQKLyhz94PIyFC3CHcH7EDMwIn8NPtD06+PNbC/PiO0LGLtw==}
dependencies: dependencies:
vue-demi: 0.14.6(vue@3.3.4) vue-demi: 0.14.7(vue@3.3.4)
transitivePeerDependencies: transitivePeerDependencies:
- '@vue/composition-api' - '@vue/composition-api'
- vue - vue
@ -4463,6 +4466,13 @@ packages:
engines: {node: '>=8'} engines: {node: '>=8'}
dev: true 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: /clean-regexp@1.0.0:
resolution: {integrity: sha512-GfisEZEJvzKrmGWkvfhgzcz/BllN1USeqD2V6tg14OAOgaCD2Z/PUEuxnAZ/nPvmaHRG7a8y77p1T/IRQ4D1Hw==} resolution: {integrity: sha512-GfisEZEJvzKrmGWkvfhgzcz/BllN1USeqD2V6tg14OAOgaCD2Z/PUEuxnAZ/nPvmaHRG7a8y77p1T/IRQ4D1Hw==}
engines: {node: '>=4'} engines: {node: '>=4'}
@ -6185,6 +6195,11 @@ packages:
jsbn: 1.1.0 jsbn: 1.1.0
dev: false 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: /is-alphabetical@1.0.4:
resolution: {integrity: sha512-DwzsA04LQ10FHTZuL0/grVDk4rFoVH1pjAToYwBrHSxcrBIGQuXrQMtD5U1b0U2XVgKZCTLLP8u2Qxqhy3l2Vg==} resolution: {integrity: sha512-DwzsA04LQ10FHTZuL0/grVDk4rFoVH1pjAToYwBrHSxcrBIGQuXrQMtD5U1b0U2XVgKZCTLLP8u2Qxqhy3l2Vg==}
dev: true dev: true
@ -6240,6 +6255,13 @@ packages:
engines: {node: '>= 0.4'} engines: {node: '>= 0.4'}
dev: true 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: /is-core-module@2.13.0:
resolution: {integrity: sha512-Z7dk6Qo8pOCp3l4tsX2C5ZVas4V+UxwQodwZhLopL91TX8UyyHEXafPcyoeeWuLrwzHcr3igO78wNLwHJHsMCQ==} resolution: {integrity: sha512-Z7dk6Qo8pOCp3l4tsX2C5ZVas4V+UxwQodwZhLopL91TX8UyyHEXafPcyoeeWuLrwzHcr3igO78wNLwHJHsMCQ==}
dependencies: dependencies:
@ -9151,8 +9173,8 @@ packages:
vue: 3.3.4 vue: 3.3.4
dev: false dev: false
/vue-demi@0.14.6(vue@3.3.4): /vue-demi@0.14.7(vue@3.3.4):
resolution: {integrity: sha512-8QA7wrYSHKaYgUxDA5ZC24w+eHm3sYCbp0EzcDwKqN3p6HqtTCGR/GVsPyZW92unff4UlcSh++lmqDWN3ZIq4w==} resolution: {integrity: sha512-EOG8KXDQNwkJILkx/gPcoL/7vH+hORoBaKgGe+6W7VFMvCYJfmF2dGbvgDroVnI8LU7/kTu8mbjRZGBU1z9NTA==}
engines: {node: '>=12'} engines: {node: '>=12'}
hasBin: true hasBin: true
requiresBuild: true requiresBuild: true

View file

@ -13,12 +13,16 @@ const IPv6MAX = (BigInt(2) ** BigInt(128)) - BigInt(1);
*/ */
export default class IP { export default class IP {
integer: bigint;
short: string;
version: number;
address: string;
/** /**
* @constructor * @constructor
*/ */
constructor(address) { constructor(address: string) {
this.integer = 0; this.integer = 0n;
this.short = 0; this.short = '';
this.version = this._checkVersion(address); this.version = this._checkVersion(address);
this.address = this._checkAddress(address, this.version); this.address = this._checkAddress(address, this.version);
} }
@ -34,7 +38,7 @@ export default class IP {
if (this.version === 4) { if (this.version === 4) {
const splittedAddr = this.address.split('.').reverse(); const splittedAddr = this.address.split('.').reverse();
bigInt = splittedAddr.reduce((bigInt, octet, index) => { bigInt = splittedAddr.reduce((bigInt, octet, index) => {
return (octet * 256 ** index + bigInt return (Number(octet) * 256 ** index + bigInt
); );
}, 0); }, 0);
} }
@ -51,7 +55,7 @@ export default class IP {
* @param {bigint} bigInt * @param {bigint} bigInt
* @return {string} -> "184.170.96.196" * @return {string} -> "184.170.96.196"
*/ */
toDottedNotation(bigInt) { toDottedNotation(bigInt: bigint) {
if (this.version === 4) { if (this.version === 4) {
return ( return (
[(bigInt >> BigInt(24) & BigInt(255)), (bigInt >> BigInt(16) & BigInt(255)), [(bigInt >> BigInt(24) & BigInt(255)), (bigInt >> BigInt(16) & BigInt(255)),
@ -78,15 +82,14 @@ export default class IP {
* @return {string} -> 01111111000000000000000000000001 * @return {string} -> 01111111000000000000000000000001
*/ */
toBinary() { toBinary() {
if (this.integer === 0) { if (this.integer === 0n) {
this.toInteger(); this.toInteger();
} }
let binary = this.integer.toString(2); let binary = this.integer.toString(2);
const v = this.version; const markLen = this.version === 4 ? 32 : 128;
const marks = { 4: 32, 6: 128 };
if (binary.length < marks[v]) { if (binary.length < markLen) {
while (binary.length < marks[v]) { while (binary.length < markLen) {
binary = `0${binary}`; binary = `0${binary}`;
} }
} }
@ -98,7 +101,7 @@ export default class IP {
* @return {string} -> 7f000001 * @return {string} -> 7f000001
*/ */
toHEX() { toHEX() {
if (this.integer === 0) { if (this.integer === 0n) {
this.toInteger(); this.toInteger();
} }
return this.integer.toString(16); return this.integer.toString(16);
@ -109,7 +112,7 @@ export default class IP {
* IP('127.1.0.0').toCompressed * IP('127.1.0.0').toCompressed
* @return {string} -> "127.1" * @return {string} -> "127.1"
*/ */
toCompressed(addr, ver) { toCompressed(addr: string, ver: number) {
if (ver === 4) { if (ver === 4) {
const splittedAddr = addr.split('.'); const splittedAddr = addr.split('.');
const sRange = [[1, 3], [2, 2], [3, 1], [0, 0]]; 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 // 'N/A' - _longestZerosGroup fn return in case if there is NO
// '0000' blocks in address // '0000' blocks in address
if (startOfLongest !== 'N/A' || longestLength !== 'N/A') { if (startOfLongest !== 'N/A' || longestLength !== 'N/A') {
splitted.splice(startOfLongest, longestLength, ''); splitted.splice(Number(startOfLongest), Number(longestLength), '');
if (startOfLongest === 0) { if (startOfLongest === 0) {
splitted.unshift(''); splitted.unshift('');
} }
if (startOfLongest + longestLength === 8) { if (Number(startOfLongest) + Number(longestLength) === 8) {
splitted.push(''); splitted.push('');
} }
} }
@ -172,7 +175,7 @@ export default class IP {
* @param {string} addr * @param {string} addr
* @return {number} -> 4 or 6 * @return {number} -> 4 or 6
*/ */
_checkVersion(addr) { _checkVersion(addr: string) {
// matches all possible chars in both versions of IP // matches all possible chars in both versions of IP
const reGen = /^[0-9a-f.:]+$/i; const reGen = /^[0-9a-f.:]+$/i;
if (reGen.test(addr)) { if (reGen.test(addr)) {
@ -184,14 +187,14 @@ export default class IP {
const reNum = /^[0-9]+$/; const reNum = /^[0-9]+$/;
if (reNum.test(addr)) { if (reNum.test(addr)) {
addr = BigInt(addr); const parsedAddr = BigInt(addr);
if (addr > IPv6MAX || addr <= 0) { if (parsedAddr > IPv6MAX || parsedAddr <= 0) {
throw new Error('Tips: IP address cant be bigger than 2 to the 128-th power or negative number'); 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; return 4;
} }
else if (addr > IPv4MAX) { else if (parsedAddr > IPv4MAX) {
return 6; return 6;
} }
} }
@ -210,18 +213,14 @@ export default class IP {
* @private * @private
* @return {string} as a valid address * @return {string} as a valid address
*/ */
_checkAddress(addr, v) { _checkAddress(addr: string, v: number) {
const reNum = /^[0-9]+$/; const reNum = /^[0-9]+$/;
if (reNum.test(addr)) { if (reNum.test(addr)) {
this.integer = BigInt(addr); this.integer = BigInt(addr);
return this.toDottedNotation(this.integer); return this.toDottedNotation(this.integer);
} }
const marks = { const splittedAddr = addr.split(v === 4 ? '.' : ':');
4: ['.', this._isIPv4, 4],
6: [':', this._isIPv6, 8],
};
const splittedAddr = addr.split(marks[v][0]);
if (v === 6 && splittedAddr.length < 8) { if (v === 6 && splittedAddr.length < 8) {
const dbColon = (addr.match(/::/g) || []).length; 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 ((v === 4 ? this._isIPv4 : this._isIPv6).call(this, splittedAddr)) { // TODO: make ifs more readable
if (splittedAddr.length === marks[v][2] && this.short === 0) { if (splittedAddr.length === (v === 4 ? 4 : 8) && this.short === '') {
return addr; return addr;
} }
else { else {
@ -248,16 +247,16 @@ export default class IP {
* @private * @private
* @return {boolean} whether splitted address is valid IPv6 or not * @return {boolean} whether splitted address is valid IPv6 or not
*/ */
_isIPv6(splittedAddr) { _isIPv6(splittedAddr: string[]) {
if (splittedAddr.length <= 8) { if (splittedAddr.length <= 8) {
let checked = false; let checked = false;
const [isShort, cleanedAddr] = this._isShort(splittedAddr); const [isShort, cleanedAddr] = this._isShort(splittedAddr);
const regex = /^[0-9a-f]{1,4}$/i; const regex = /^[0-9a-f]{1,4}$/i;
const isValid = function (hextet) { const isValid = function (hextet: string) {
return regex.test(hextet); return regex.test(hextet);
}; };
checked = cleanedAddr.every(isValid); checked = (cleanedAddr as string[]).every(isValid);
if (checked && isShort) { if (checked && isShort) {
this.short = splittedAddr.join(':'); this.short = splittedAddr.join(':');
@ -274,13 +273,13 @@ export default class IP {
* @private * @private
* @return {boolean} whether splitted address is valid IPv4 or not * @return {boolean} whether splitted address is valid IPv4 or not
*/ */
_isIPv4(splittedAddr) { _isIPv4(splittedAddr: string[]) {
if (splittedAddr.length <= 4) { if (splittedAddr.length <= 4) {
if (splittedAddr.length < 4) { if (splittedAddr.length < 4) {
this.short = splittedAddr.join('.'); this.short = splittedAddr.join('.');
} }
const isValid = function (octet) { const isValid = function (octet: string) {
return (!!((octet <= 255 && octet >= 0))); return (!!((Number(octet) <= 255 && Number(octet) >= 0)));
}; };
return splittedAddr.every(isValid); return splittedAddr.every(isValid);
} }
@ -295,7 +294,7 @@ export default class IP {
* @param {array} splittedAddr * @param {array} splittedAddr
* @return {array} with both results boolean and cleaned array * @return {array} with both results boolean and cleaned array
*/ */
_isShort(splittedAddr) { _isShort(splittedAddr: string[]) {
let isShort = false; let isShort = false;
const cleanedAddr = [...splittedAddr]; const cleanedAddr = [...splittedAddr];
for (let i = 0; i < cleanedAddr.length; i++) { for (let i = 0; i < cleanedAddr.length; i++) {
@ -320,7 +319,7 @@ export default class IP {
* @param {array} splittedAddr * @param {array} splittedAddr
* @return {string} -> "0000:0000:0000:0000:0000:0000:0000:0001" * @return {string} -> "0000:0000:0000:0000:0000:0000:0000:0001"
*/ */
_toRepresentation(splittedAddr) { _toRepresentation(splittedAddr: string[]) {
if (this.version === 4) { if (this.version === 4) {
for (let i = 0; i <= 4; i++) { for (let i = 0; i <= 4; i++) {
if (splittedAddr[i] === '') { if (splittedAddr[i] === '') {
@ -383,7 +382,7 @@ export default class IP {
* @param {array} zeros * @param {array} zeros
* @return {array} -> [0, 7] * @return {array} -> [0, 7]
*/ */
function _longestZerosGroup(splittedAddr) { function _longestZerosGroup(splittedAddr: string[]) {
let curr = 0; let curr = 0;
let currLongest = 0; let currLongest = 0;
let startOfLongest = 0; let startOfLongest = 0;

View file

@ -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"]]
]

View file

@ -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"]]
]

View file

@ -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 IPv4MAX = (BigInt(2) ** BigInt(32)) - BigInt(1);
const IPv6MAX = (BigInt(2) ** BigInt(128)) - BigInt(1); const IPv6MAX = (BigInt(2) ** BigInt(128)) - BigInt(1);
// IP range specific information, see IANA allocations. // IP range specific information, see IANA allocations.
// http://www.iana.org/assignments/iana-ipv4-special-registry/iana-ipv4-special-registry.xhtml // http://www.iana.org/assignments/iana-ipv4-special-registry/iana-ipv4-special-registry.xhtml
const _ipv4Registry = new Map([ const _ipv4Registry = new Map(ipv4registry.map(v => [v[0] as string, v[1]]));
['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 // https://www.iana.org/assignments/iana-ipv6-special-registry/iana-ipv6-special-registry.xhtml
const _ipv6Registry = new Map([ const _ipv6Registry = new Map(ipv6registry.map(v => [v[0] as string, v[1]]));
['::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. * Network slice calculations.
@ -68,11 +22,12 @@ const _ipv6Registry = new Map([
*/ */
export default class Network extends IP { export default class Network extends IP {
prefix: bigint;
/** /**
* Extends IP class. Calls the parent class IP with the parameters passed to Network. * Extends IP class. Calls the parent class IP with the parameters passed to Network.
* @constructor * @constructor
*/ */
constructor(address, prefix) { constructor(address: string, prefix: number) {
super(address); super(address);
this.prefix = this._checkPrefix(prefix); this.prefix = this._checkPrefix(prefix);
} }
@ -84,7 +39,7 @@ export default class Network extends IP {
* @private * @private
* @return {integer} -> prefix: 25n * @return {integer} -> prefix: 25n
*/ */
_checkPrefix(prefix) { _checkPrefix(prefix: number) {
if (this.version === 4) { if (this.version === 4) {
if (prefix > 0 && prefix <= 32) { if (prefix > 0 && prefix <= 32) {
return BigInt(prefix); return BigInt(prefix);
@ -105,10 +60,9 @@ export default class Network extends IP {
* @return {string} ->LOOPBACK * @return {string} ->LOOPBACK
*/ */
printInfo() { printInfo() {
const registry = { 4: _ipv4Registry, 6: _ipv6Registry };
const results = []; const results = [];
for (const [addr, info] of registry[this.version].entries()) { for (const [addr, info] of (this.version === 4 ? _ipv4Registry : _ipv6Registry).entries()) {
const found = this.contains(this.address, addr, info[0]); const found = this.contains(this.address, addr, Number(info[0]));
if (found) { if (found) {
results.unshift(info[1]); results.unshift(info[1]);
} }
@ -230,7 +184,7 @@ export default class Network extends IP {
* @param {number} prefix * @param {number} prefix
* @return {boolean} * @return {boolean}
*/ */
contains(thisIP, otherIP, prefix) { contains(thisIP: string, otherIP: string, prefix: number) {
const other = new Network(otherIP, prefix); const other = new Network(otherIP, prefix);
const thisNetwork = this.networkToInteger(); const thisNetwork = this.networkToInteger();
const otherNetwork = other.networkToInteger(); const otherNetwork = other.networkToInteger();
@ -257,12 +211,19 @@ export default class Network extends IP {
* @return {number} -> 16777214 * @return {number} -> 16777214
*/ */
networkSize() { networkSize() {
const marks = { 4: BigInt(32), 6: BigInt(128) }; const size = BigInt(2) ** ((this.version === 4 ? BigInt(32) : BigInt(128)) - this.prefix);
const size = BigInt(2) ** (marks[this.version] - this.prefix);
if (this.version === 4 && this.prefix < BigInt(30)) { if (this.version === 4 && this.prefix < BigInt(30)) {
return size - BigInt(2); return size - BigInt(2);
} }
return size; 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() : '';
}
} }

View file

@ -6,6 +6,7 @@ import { tool as asciiTextDrawer } from './ascii-text-drawer';
import { tool as textToUnicode } from './text-to-unicode'; import { tool as textToUnicode } from './text-to-unicode';
import { tool as safelinkDecoder } from './safelink-decoder'; 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 pdfSignatureChecker } from './pdf-signature-checker';
import { tool as numeronymGenerator } from './numeronym-generator'; import { tool as numeronymGenerator } from './numeronym-generator';
import { tool as macAddressGenerator } from './mac-address-generator'; import { tool as macAddressGenerator } from './mac-address-generator';
@ -152,7 +153,15 @@ export const toolsByCategory: ToolCategory[] = [
}, },
{ {
name: 'Network', name: 'Network',
components: [ipv4SubnetCalculator, ipv4AddressConverter, ipv4RangeExpander, macAddressLookup, macAddressGenerator, ipv6UlaGenerator], components: [
ipv4SubnetCalculator,
ipv6SubnetCalculator,
ipv4AddressConverter,
ipv4RangeExpander,
macAddressLookup,
macAddressGenerator,
ipv6UlaGenerator,
],
}, },
{ {
name: 'Math', name: 'Math',

View file

@ -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'),
});

View file

@ -0,0 +1,126 @@
<script setup lang="ts">
import isCidr from 'is-cidr';
import { useStorage } from '@vueuse/core';
import Network from '@/libs/ip_calculator/network';
import { withDefaultOnError } from '@/utils/defaults';
import SpanCopyable from '@/components/SpanCopyable.vue';
const ip = useStorage('ipv6-subnet-calculator:ip', '2001:db8:0:85a3:0:0:ac1f:8001/32');
function getNetworkInfo(address: string) {
const [ip, mask] = address.split('/');
return new Network(ip.trim(), Number(mask?.trim() || '32'));
}
const networkInfo = computed(() => withDefaultOnError(() =>
isCidr(ip.value.trim()) === 6 ? getNetworkInfo(ip.value) : undefined, undefined));
const ipValidationRules = [
{
message: 'We cannot parse this address, check the format',
validator: (value: string) => isCidr(value.trim()) === 6,
},
];
const sections: {
label: string
getValue: (net: Network) => string | undefined
undefinedFallback?: string
}[] = [
{
label: 'Full address',
getValue: net => net.address.toString(),
},
{
label: 'Short address',
getValue: net => net.toCompressed(net.address || '', net.version || 4)?.toString(),
},
{
label: 'Address as binary',
getValue: (net) => {
const binary = net.toBinary();
const hBin = binary.length / 2;
return `${binary.substring(0, hBin)} ${
binary.substring(hBin, binary.length)}`;
},
},
{
label: 'Address as integer',
getValue: net => net.toInteger().toString(),
},
{
label: 'Address as hex',
getValue: net => net.toHEX().toString(),
},
{
label: 'Network address',
getValue: net => net.getNetwork().toString(),
},
{
label: 'Network address as integer',
getValue: net => net.networkToInteger().toString(),
},
{
label: 'Network mask size',
getValue: net => net.prefix.toString(),
},
{
label: 'Network mask',
getValue: net => net.getMask().toString(),
},
{
label: 'Network mask as integer',
getValue: net => net.maskToInteger().toString(),
},
{
label: 'Network size',
getValue: net => net.networkSize().toString(),
},
{
label: 'Networks count',
getValue: net => net.networkCount().toString(),
},
{
label: 'First address',
getValue: net => net.hostRange()[0],
},
{
label: 'Last address',
getValue: net => net.hostRange()[1],
},
{
label: 'Type',
getValue: net => net.printInfo()?.toString(),
},
];
</script>
<template>
<div>
<c-input-text
v-model:value="ip"
label="An IPv6 address with or without mask"
placeholder="The ipv6 address..."
:validation-rules="ipValidationRules"
mb-4
/>
<div v-if="networkInfo">
<n-table>
<tbody>
<tr v-for="{ getValue, label, undefinedFallback } in sections" :key="label">
<td font-bold>
{{ label }}
</td>
<td>
<SpanCopyable v-if="networkInfo" :value="getValue(networkInfo)" />
<span v-else op-70>
{{ undefinedFallback }}
</span>
</td>
</tr>
</tbody>
</n-table>
</div>
</div>
</template>