From 2908763346462821b18c3a63572d17c968321c0f Mon Sep 17 00:00:00 2001 From: sharevb Date: Sat, 3 Feb 2024 12:30:27 +0100 Subject: [PATCH 1/7] feat(new tool): IPv4/6 CIDR to IP Range IPv4/6 CIDR to IP Range Reverse fof #802 --- components.d.ts | 1 + package.json | 6 + pnpm-lock.yaml | 120 +++++++++++++++++- src/tools/index.ts | 11 +- src/tools/ip-cidr-to-range/index.ts | 12 ++ .../ip-cidr-to-range/ip-cidr-to-range.vue | 56 ++++++++ 6 files changed, 202 insertions(+), 4 deletions(-) create mode 100644 src/tools/ip-cidr-to-range/index.ts create mode 100644 src/tools/ip-cidr-to-range/ip-cidr-to-range.vue diff --git a/components.d.ts b/components.d.ts index fabbe793..b2a2ec72 100644 --- a/components.d.ts +++ b/components.d.ts @@ -112,6 +112,7 @@ declare module '@vue/runtime-core' { IconMdiVideo: typeof import('~icons/mdi/video')['default'] InputCopyable: typeof import('./src/components/InputCopyable.vue')['default'] IntegerBaseConverter: typeof import('./src/tools/integer-base-converter/integer-base-converter.vue')['default'] + IpCidrToRange: typeof import('./src/tools/ip-cidr-to-range/ip-cidr-to-range.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'] Ipv4SubnetCalculator: typeof import('./src/tools/ipv4-subnet-calculator/ipv4-subnet-calculator.vue')['default'] diff --git a/package.json b/package.json index e0148f87..f116ae1f 100644 --- a/package.json +++ b/package.json @@ -48,6 +48,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", @@ -62,6 +63,11 @@ "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", + "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/pnpm-lock.yaml b/pnpm-lock.yaml index dfacabd4..4310a699 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -44,6 +44,9 @@ dependencies: change-case: specifier: ^4.1.2 version: 4.1.2 + cidr-tools: + specifier: ^7.0.4 + version: 7.0.4 colord: specifier: ^2.9.3 version: 2.9.3 @@ -86,6 +89,21 @@ 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 + 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 @@ -3374,7 +3392,7 @@ packages: dependencies: '@unhead/dom': 0.5.1 '@unhead/schema': 0.5.1 - '@vueuse/shared': 10.6.1(vue@3.3.4) + '@vueuse/shared': 10.7.2(vue@3.3.4) unhead: 0.5.1 vue: 3.3.4 transitivePeerDependencies: @@ -4016,8 +4034,8 @@ packages: - vue dev: false - /@vueuse/shared@10.6.1(vue@3.3.4): - resolution: {integrity: sha512-TECVDTIedFlL0NUfHWncf3zF9Gc4VfdxfQc8JFwoVZQmxpONhLxFrlm0eHQeidHj4rdTPL3KXJa0TZCk1wnc5Q==} + /@vueuse/shared@10.7.2(vue@3.3.4): + resolution: {integrity: sha512-qFbXoxS44pi2FkgFjPvF4h7c9oMDutpyBdcJdMYIMg9XyXli2meFMuaKn+UMgsClo//Th6+beeCgqweT/79BVA==} dependencies: vue-demi: 0.14.6(vue@3.3.4) transitivePeerDependencies: @@ -4486,6 +4504,20 @@ 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 + + /cidr-tools@7.0.4: + resolution: {integrity: sha512-bKd6xC01ObuVKvJPGdV9Rz02KFO3mtHwMe/QTlcVuFAmU5n3RN/F3FgppHZaQjM+c/1i9YB9rgKNH/5iVqwCoA==} + 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'} @@ -4513,6 +4545,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'} @@ -4617,6 +4656,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 @@ -5761,6 +5805,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'} @@ -6195,6 +6244,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'} @@ -6203,6 +6265,18 @@ 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-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 @@ -6258,6 +6332,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: @@ -6323,6 +6404,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: @@ -6388,6 +6477,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: @@ -8225,6 +8319,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 @@ -8355,6 +8453,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'} @@ -8447,6 +8554,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/tools/index.ts b/src/tools/index.ts index 2a477ed2..77ed0fed 100644 --- a/src/tools/index.ts +++ b/src/tools/index.ts @@ -77,6 +77,7 @@ import { tool as uuidGenerator } from './uuid-generator'; import { tool as macAddressLookup } from './mac-address-lookup'; import { tool as xmlFormatter } from './xml-formatter'; import { tool as yamlViewer } from './yaml-viewer'; +import { tool as ipCidrToRange } from './ip-cidr-to-range'; export const toolsByCategory: ToolCategory[] = [ { @@ -147,7 +148,15 @@ export const toolsByCategory: ToolCategory[] = [ }, { name: 'Network', - components: [ipv4SubnetCalculator, ipv4AddressConverter, ipv4RangeExpander, macAddressLookup, macAddressGenerator, ipv6UlaGenerator], + components: [ + ipv4SubnetCalculator, + ipv4AddressConverter, + ipv4RangeExpander, + ipCidrToRange, + macAddressLookup, + macAddressGenerator, + ipv6UlaGenerator, + ], }, { name: 'Math', diff --git a/src/tools/ip-cidr-to-range/index.ts b/src/tools/ip-cidr-to-range/index.ts new file mode 100644 index 00000000..157c5cd4 --- /dev/null +++ b/src/tools/ip-cidr-to-range/index.ts @@ -0,0 +1,12 @@ +import { Binary } from '@vicons/tabler'; +import { defineTool } from '../tool'; + +export const tool = defineTool({ + name: 'Ipv4/6 CIDR to IP Range Calculator', + path: '/ip-cidr-to-range', + description: 'Calculate IP Range from a CIDR (IPv4/6)', + keywords: ['ipv4', 'ipv6', 'cidr'], + component: () => import('./ip-cidr-to-range.vue'), + icon: Binary, + createdAt: new Date('2024-01-10'), +}); diff --git a/src/tools/ip-cidr-to-range/ip-cidr-to-range.vue b/src/tools/ip-cidr-to-range/ip-cidr-to-range.vue new file mode 100644 index 00000000..2af54a60 --- /dev/null +++ b/src/tools/ip-cidr-to-range/ip-cidr-to-range.vue @@ -0,0 +1,56 @@ + + + From 591cdcda7ef471b5f91b622beac1506f1b3dff6e Mon Sep 17 00:00:00 2001 From: sharevb Date: Sun, 14 Apr 2024 14:49:07 +0200 Subject: [PATCH 2/7] feat(utils): IPv4/6 Utils Network Type, Subnets and counts, 6to4 prefix, ARPA, IPv4 Mapped Parse IP/Mask/Range as CIDR --- package.json | 1 + src/utils/ip.test.ts | 232 ++++++++++++++++++++++++++++++++++++ src/utils/ip.ts | 147 +++++++++++++++++++++++ src/utils/ipv4registry.json | 27 +++++ src/utils/ipv6registry.json | 24 ++++ 5 files changed, 431 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 f116ae1f..67b5c5b6 100644 --- a/package.json +++ b/package.json @@ -66,6 +66,7 @@ "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", 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 6a9fb02193f3c71ea4c4a01061dc1a0316419929 Mon Sep 17 00:00:00 2001 From: ShareVB Date: Sat, 20 Apr 2024 22:06:29 +0200 Subject: [PATCH 3/7] fix: refactor using IPv4/6 Utils --- pnpm-lock.yaml | 19 ++++++--- .../ip-cidr-to-range/ip-cidr-to-range.vue | 41 ++++++++++++++++--- src/utils/ip.test.ts | 19 +++++++-- 3 files changed, 63 insertions(+), 16 deletions(-) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 4310a699..ba8ba8b0 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -98,6 +98,9 @@ dependencies: 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 @@ -3392,7 +3395,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: @@ -4034,10 +4037,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 @@ -6272,6 +6275,10 @@ packages: 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} @@ -9314,8 +9321,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/tools/ip-cidr-to-range/ip-cidr-to-range.vue b/src/tools/ip-cidr-to-range/ip-cidr-to-range.vue index 2af54a60..8f084806 100644 --- a/src/tools/ip-cidr-to-range/ip-cidr-to-range.vue +++ b/src/tools/ip-cidr-to-range/ip-cidr-to-range.vue @@ -1,22 +1,29 @@