mirror of
https://github.com/CorentinTh/it-tools.git
synced 2025-04-22 07:46:15 -04:00
Merge f4ec4da68b
into 07eea0f484
This commit is contained in:
commit
18e922bd40
15 changed files with 6983 additions and 7948 deletions
|
@ -286,6 +286,9 @@
|
|||
"watchTriggerable": true,
|
||||
"watchWithFilter": true,
|
||||
"whenever": true,
|
||||
"toValue": true
|
||||
"toValue": true,
|
||||
"injectLocal": true,
|
||||
"provideLocal": true,
|
||||
"useClipboardItems": true
|
||||
}
|
||||
}
|
||||
|
|
9
auto-imports.d.ts
vendored
9
auto-imports.d.ts
vendored
|
@ -36,6 +36,7 @@ declare global {
|
|||
const h: typeof import('vue')['h']
|
||||
const ignorableWatch: typeof import('@vueuse/core')['ignorableWatch']
|
||||
const inject: typeof import('vue')['inject']
|
||||
const injectLocal: typeof import('@vueuse/core')['injectLocal']
|
||||
const isDefined: typeof import('@vueuse/core')['isDefined']
|
||||
const isProxy: typeof import('vue')['isProxy']
|
||||
const isReactive: typeof import('vue')['isReactive']
|
||||
|
@ -65,6 +66,7 @@ declare global {
|
|||
const onUpdated: typeof import('vue')['onUpdated']
|
||||
const pausableWatch: typeof import('@vueuse/core')['pausableWatch']
|
||||
const provide: typeof import('vue')['provide']
|
||||
const provideLocal: typeof import('@vueuse/core')['provideLocal']
|
||||
const reactify: typeof import('@vueuse/core')['reactify']
|
||||
const reactifyObject: typeof import('@vueuse/core')['reactifyObject']
|
||||
const reactive: typeof import('vue')['reactive']
|
||||
|
@ -128,6 +130,7 @@ declare global {
|
|||
const useBrowserLocation: typeof import('@vueuse/core')['useBrowserLocation']
|
||||
const useCached: typeof import('@vueuse/core')['useCached']
|
||||
const useClipboard: typeof import('@vueuse/core')['useClipboard']
|
||||
const useClipboardItems: typeof import('@vueuse/core')['useClipboardItems']
|
||||
const useCloned: typeof import('@vueuse/core')['useCloned']
|
||||
const useColorMode: typeof import('@vueuse/core')['useColorMode']
|
||||
const useConfirmDialog: typeof import('@vueuse/core')['useConfirmDialog']
|
||||
|
@ -326,6 +329,7 @@ declare module 'vue' {
|
|||
readonly h: UnwrapRef<typeof import('vue')['h']>
|
||||
readonly ignorableWatch: UnwrapRef<typeof import('@vueuse/core')['ignorableWatch']>
|
||||
readonly inject: UnwrapRef<typeof import('vue')['inject']>
|
||||
readonly injectLocal: UnwrapRef<typeof import('@vueuse/core')['injectLocal']>
|
||||
readonly isDefined: UnwrapRef<typeof import('@vueuse/core')['isDefined']>
|
||||
readonly isProxy: UnwrapRef<typeof import('vue')['isProxy']>
|
||||
readonly isReactive: UnwrapRef<typeof import('vue')['isReactive']>
|
||||
|
@ -355,6 +359,7 @@ declare module 'vue' {
|
|||
readonly onUpdated: UnwrapRef<typeof import('vue')['onUpdated']>
|
||||
readonly pausableWatch: UnwrapRef<typeof import('@vueuse/core')['pausableWatch']>
|
||||
readonly provide: UnwrapRef<typeof import('vue')['provide']>
|
||||
readonly provideLocal: UnwrapRef<typeof import('@vueuse/core')['provideLocal']>
|
||||
readonly reactify: UnwrapRef<typeof import('@vueuse/core')['reactify']>
|
||||
readonly reactifyObject: UnwrapRef<typeof import('@vueuse/core')['reactifyObject']>
|
||||
readonly reactive: UnwrapRef<typeof import('vue')['reactive']>
|
||||
|
@ -418,6 +423,7 @@ declare module 'vue' {
|
|||
readonly useBrowserLocation: UnwrapRef<typeof import('@vueuse/core')['useBrowserLocation']>
|
||||
readonly useCached: UnwrapRef<typeof import('@vueuse/core')['useCached']>
|
||||
readonly useClipboard: UnwrapRef<typeof import('@vueuse/core')['useClipboard']>
|
||||
readonly useClipboardItems: UnwrapRef<typeof import('@vueuse/core')['useClipboardItems']>
|
||||
readonly useCloned: UnwrapRef<typeof import('@vueuse/core')['useCloned']>
|
||||
readonly useColorMode: UnwrapRef<typeof import('@vueuse/core')['useColorMode']>
|
||||
readonly useConfirmDialog: UnwrapRef<typeof import('@vueuse/core')['useConfirmDialog']>
|
||||
|
@ -610,6 +616,7 @@ declare module '@vue/runtime-core' {
|
|||
readonly h: UnwrapRef<typeof import('vue')['h']>
|
||||
readonly ignorableWatch: UnwrapRef<typeof import('@vueuse/core')['ignorableWatch']>
|
||||
readonly inject: UnwrapRef<typeof import('vue')['inject']>
|
||||
readonly injectLocal: UnwrapRef<typeof import('@vueuse/core')['injectLocal']>
|
||||
readonly isDefined: UnwrapRef<typeof import('@vueuse/core')['isDefined']>
|
||||
readonly isProxy: UnwrapRef<typeof import('vue')['isProxy']>
|
||||
readonly isReactive: UnwrapRef<typeof import('vue')['isReactive']>
|
||||
|
@ -639,6 +646,7 @@ declare module '@vue/runtime-core' {
|
|||
readonly onUpdated: UnwrapRef<typeof import('vue')['onUpdated']>
|
||||
readonly pausableWatch: UnwrapRef<typeof import('@vueuse/core')['pausableWatch']>
|
||||
readonly provide: UnwrapRef<typeof import('vue')['provide']>
|
||||
readonly provideLocal: UnwrapRef<typeof import('@vueuse/core')['provideLocal']>
|
||||
readonly reactify: UnwrapRef<typeof import('@vueuse/core')['reactify']>
|
||||
readonly reactifyObject: UnwrapRef<typeof import('@vueuse/core')['reactifyObject']>
|
||||
readonly reactive: UnwrapRef<typeof import('vue')['reactive']>
|
||||
|
@ -702,6 +710,7 @@ declare module '@vue/runtime-core' {
|
|||
readonly useBrowserLocation: UnwrapRef<typeof import('@vueuse/core')['useBrowserLocation']>
|
||||
readonly useCached: UnwrapRef<typeof import('@vueuse/core')['useCached']>
|
||||
readonly useClipboard: UnwrapRef<typeof import('@vueuse/core')['useClipboard']>
|
||||
readonly useClipboardItems: UnwrapRef<typeof import('@vueuse/core')['useClipboardItems']>
|
||||
readonly useCloned: UnwrapRef<typeof import('@vueuse/core')['useCloned']>
|
||||
readonly useColorMode: UnwrapRef<typeof import('@vueuse/core')['useColorMode']>
|
||||
readonly useConfirmDialog: UnwrapRef<typeof import('@vueuse/core')['useConfirmDialog']>
|
||||
|
|
1
components.d.ts
vendored
1
components.d.ts
vendored
|
@ -103,6 +103,7 @@ declare module '@vue/runtime-core' {
|
|||
IconMdiTriangleDown: typeof import('~icons/mdi/triangle-down')['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']
|
||||
|
|
|
@ -54,6 +54,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",
|
||||
|
@ -71,6 +72,12 @@
|
|||
"iarna-toml-esm": "^3.0.5",
|
||||
"ibantools": "^4.3.3",
|
||||
"js-base64": "^3.7.6",
|
||||
"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",
|
||||
|
|
14365
pnpm-lock.yaml
generated
14365
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load diff
|
@ -128,7 +128,7 @@ function activateOption(option: PaletteOption) {
|
|||
<c-input-text ref="inputRef" v-model:value="searchPrompt" raw-text placeholder="Type to search a tool or a command..." autofocus clearable />
|
||||
|
||||
<div v-for="(options, category) in filteredSearchResult" :key="category">
|
||||
<div ml-3 mt-3 text-sm font-bold text-primary op-60>
|
||||
<div ml-3 mt-3 text-sm text-primary font-bold op-60>
|
||||
{{ category }}
|
||||
</div>
|
||||
<command-palette-option v-for="option in options" :key="option.name" :option="option" :selected="selectedOptionIndex === getOptionIndex(option)" @activated="activateOption" />
|
||||
|
|
|
@ -87,6 +87,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[] = [
|
||||
{
|
||||
|
@ -164,7 +165,15 @@ export const toolsByCategory: ToolCategory[] = [
|
|||
},
|
||||
{
|
||||
name: 'Network',
|
||||
components: [ipv4SubnetCalculator, ipv4AddressConverter, ipv4RangeExpander, macAddressLookup, macAddressGenerator, ipv6UlaGenerator],
|
||||
components: [
|
||||
ipv4SubnetCalculator,
|
||||
ipv4AddressConverter,
|
||||
ipv4RangeExpander,
|
||||
ipCidrToRange,
|
||||
macAddressLookup,
|
||||
macAddressGenerator,
|
||||
ipv6UlaGenerator,
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'Math',
|
||||
|
|
12
src/tools/ip-cidr-to-range/index.ts
Normal file
12
src/tools/ip-cidr-to-range/index.ts
Normal file
|
@ -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'),
|
||||
});
|
85
src/tools/ip-cidr-to-range/ip-cidr-to-range.vue
Normal file
85
src/tools/ip-cidr-to-range/ip-cidr-to-range.vue
Normal file
|
@ -0,0 +1,85 @@
|
|||
<script setup lang="ts">
|
||||
import isCidr from 'is-cidr';
|
||||
import { expand } from 'cidr-tools';
|
||||
import { getIPNetworkType, parseAsCIDR } from '@/utils/ip';
|
||||
import { useValidation } from '@/composable/validation';
|
||||
|
||||
const rawCIDR = useStorage('ip-cidr-to-range:cidr', '192.168.1.0/24'); // NOSONAR
|
||||
|
||||
const result = computed(() => {
|
||||
const parsedCIDR = parseAsCIDR(rawCIDR.value) || rawCIDR.value;
|
||||
const ips = expand(parsedCIDR);
|
||||
if (!ips || !ips.length) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return {
|
||||
startIpAddress: ips.slice(0, 1)[0],
|
||||
endIpAddress: ips.slice(-1)[0],
|
||||
parsedCIDR,
|
||||
networkType: getIPNetworkType(ips.slice(0, 1)[0]) || 'Public',
|
||||
};
|
||||
});
|
||||
|
||||
const cidrValidation = useValidation({
|
||||
source: rawCIDR,
|
||||
rules: [{ message: 'Invalid ipv4/6 CIDR', validator: cidr => isCidr(parseAsCIDR(cidr) || cidr) }],
|
||||
});
|
||||
|
||||
const showResult = computed(() => cidrValidation.isValid && result.value !== undefined);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<c-input-text
|
||||
v-model:value="rawCIDR"
|
||||
label="IPv4/6 CIDR (ie, 1.0.0.0/23 or 1.1.1.1/255.255.252.0 or 1.1.1.1-2.2.2.2 or 10.0.0.*)"
|
||||
placeholder="IPv4/6 CIDR (ie, 1.0.0.0/23 or 1.1.1.1/255.255.252.0 or 1.1.1.1-2.2.2.2 or 10.0.0.*)"
|
||||
:validation="cidrValidation"
|
||||
clearable
|
||||
/>
|
||||
|
||||
<c-card v-if="showResult" title="Resulting CIDR" mt-4>
|
||||
<input-copyable
|
||||
label="CIDR"
|
||||
label-position="left"
|
||||
label-width="150px"
|
||||
label-align="right"
|
||||
|
||||
:value="result?.parsedCIDR"
|
||||
disabled mb-2
|
||||
/>
|
||||
</c-card>
|
||||
|
||||
<c-card v-if="showResult" title="IPv4/6 range" mt-4>
|
||||
<input-copyable
|
||||
label="Start IP Address"
|
||||
label-position="left"
|
||||
label-width="150px"
|
||||
label-align="right"
|
||||
|
||||
:value="result?.startIpAddress"
|
||||
disabled mb-2
|
||||
/>
|
||||
<input-copyable
|
||||
label="End IP Address"
|
||||
label-position="left"
|
||||
label-width="150px"
|
||||
label-align="right"
|
||||
|
||||
:value="result?.endIpAddress"
|
||||
disabled mb-2
|
||||
/>
|
||||
|
||||
<input-copyable
|
||||
label="Network type"
|
||||
label-position="left"
|
||||
label-width="150px"
|
||||
label-align="right"
|
||||
|
||||
:value="result?.networkType"
|
||||
disabled mb-2
|
||||
/>
|
||||
</c-card>
|
||||
</div>
|
||||
</template>
|
|
@ -151,7 +151,7 @@ function onSearchInput() {
|
|||
>
|
||||
<div flex-1 truncate>
|
||||
<slot name="displayed-value">
|
||||
<input v-if="searchable && isOpen" ref="searchInputRef" v-model="searchQuery" type="text" placeholder="Search..." class="search-input" w-full lh-normal color-current @input="onSearchInput">
|
||||
<input v-if="searchable && isOpen" ref="searchInputRef" v-model="searchQuery" type="text" placeholder="Search..." class="search-input" w-full color-current lh-normal @input="onSearchInput">
|
||||
<span v-else-if="selectedOption" lh-normal>
|
||||
{{ selectedOption.label }}
|
||||
</span>
|
||||
|
|
|
@ -39,7 +39,7 @@ const headers = computed(() => {
|
|||
<template>
|
||||
<div class="relative overflow-x-auto rounded">
|
||||
<table class="w-full border-collapse text-left text-sm text-gray-500 dark:text-gray-400" role="table" :aria-label="description">
|
||||
<thead v-if="!hideHeaders" class="bg-#ffffff uppercase text-gray-700 dark:bg-#333333 dark:text-gray-400" border-b="1px solid dark:transparent #efeff5">
|
||||
<thead v-if="!hideHeaders" class="bg-#ffffff text-gray-700 uppercase dark:bg-#333333 dark:text-gray-400" border-b="1px solid dark:transparent #efeff5">
|
||||
<tr>
|
||||
<th v-for="header in headers" :key="header.key" scope="col" class="px-6 py-3 text-xs">
|
||||
{{ header.label }}
|
||||
|
|
232
src/utils/ip.test.ts
Normal file
232
src/utils/ip.test.ts
Normal file
|
@ -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'); // NOSONAR
|
||||
expect(parseAsCIDR('172.16.2.2/16')).to.eql('172.16.2.2/16'); // NOSONAR
|
||||
expect(parseAsCIDR('1a:b:c::d:e:f/ffff:ffff:f4ff:ffff:ffff:ff5f:ffff:ff00')).to.eql(undefined); // NOSONAR
|
||||
expect(parseAsCIDR('10.1.2.3/255.255.255.252')).to.eql('10.1.2.0/30'); // NOSONAR
|
||||
expect(parseAsCIDR('10.*.0.*')).to.eql(undefined); // NOSONAR
|
||||
expect(parseAsCIDR('10.1.0.*')).to.eql('10.1.0.0/24'); // NOSONAR
|
||||
expect(parseAsCIDR('10.2.*.*')).to.eql('10.2.0.0/16'); // NOSONAR
|
||||
expect(parseAsCIDR('a:b:0:8000::/ffff:ffff:ffff:8000::')).to.eql('a:b:0:8000::/49'); // NOSONAR
|
||||
expect(parseAsCIDR('::/::')).to.eql('::/0'); // NOSONAR
|
||||
expect(parseAsCIDR('10.20.30.64-10.20.30.127')).to.eql('10.20.30.64/26'); // NOSONAR
|
||||
expect(parseAsCIDR('10.0.128.0/255.0.128.0')).to.eql(undefined); // NOSONAR
|
||||
expect(parseAsCIDR('a::bc:1234/128')).to.eql('a::bc:1234/128'); // NOSONAR
|
||||
expect(parseAsCIDR('a::bc:ff00-a::bc:ff0f')).to.eql('a::bc:ff00/124'); // NOSONAR
|
||||
expect(parseAsCIDR('10.0.1.1/255.255.1.0')).to.eql(undefined); // NOSONAR
|
||||
expect(parseAsCIDR('10.0.0.0/255.255.0.0')).to.eql('10.0.0.0/16'); // NOSONAR
|
||||
});
|
||||
}); // NOSONAR
|
||||
describe('getSubnets', () => {
|
||||
it('returns subnets', () => {
|
||||
expect(getSubnets('1.1.1.1/1')).to.eql([ // NOSONAR
|
||||
'0.0.0.0/1', // NOSONAR
|
||||
'128.0.0.0/1', // NOSONAR
|
||||
]); // NOSONAR
|
||||
expect(getSubnets('1.1.1.1/6')).to.eql([ // NOSONAR
|
||||
'0.0.0.0/6', // NOSONAR
|
||||
'4.0.0.0/6', // NOSONAR
|
||||
'8.0.0.0/6', // NOSONAR
|
||||
'12.0.0.0/6', // NOSONAR
|
||||
'16.0.0.0/6', // NOSONAR
|
||||
'20.0.0.0/6', // NOSONAR
|
||||
'24.0.0.0/6', // NOSONAR
|
||||
'28.0.0.0/6', // NOSONAR
|
||||
'32.0.0.0/6', // NOSONAR
|
||||
'36.0.0.0/6', // NOSONAR
|
||||
'40.0.0.0/6', // NOSONAR
|
||||
'44.0.0.0/6', // NOSONAR
|
||||
'48.0.0.0/6', // NOSONAR
|
||||
'52.0.0.0/6', // NOSONAR
|
||||
'56.0.0.0/6', // NOSONAR
|
||||
'60.0.0.0/6', // NOSONAR
|
||||
'64.0.0.0/6', // NOSONAR
|
||||
'68.0.0.0/6', // NOSONAR
|
||||
'72.0.0.0/6', // NOSONAR
|
||||
'76.0.0.0/6', // NOSONAR
|
||||
'80.0.0.0/6', // NOSONAR
|
||||
'84.0.0.0/6', // NOSONAR
|
||||
'88.0.0.0/6', // NOSONAR
|
||||
'92.0.0.0/6', // NOSONAR
|
||||
'96.0.0.0/6', // NOSONAR
|
||||
'100.0.0.0/6', // NOSONAR
|
||||
'104.0.0.0/6', // NOSONAR
|
||||
'108.0.0.0/6', // NOSONAR
|
||||
'112.0.0.0/6', // NOSONAR
|
||||
'116.0.0.0/6', // NOSONAR
|
||||
'120.0.0.0/6', // NOSONAR
|
||||
'124.0.0.0/6', // NOSONAR
|
||||
'128.0.0.0/6', // NOSONAR
|
||||
'132.0.0.0/6', // NOSONAR
|
||||
'136.0.0.0/6', // NOSONAR
|
||||
'140.0.0.0/6', // NOSONAR
|
||||
'144.0.0.0/6', // NOSONAR
|
||||
'148.0.0.0/6', // NOSONAR
|
||||
'152.0.0.0/6', // NOSONAR
|
||||
'156.0.0.0/6', // NOSONAR
|
||||
'160.0.0.0/6', // NOSONAR
|
||||
'164.0.0.0/6', // NOSONAR
|
||||
'168.0.0.0/6', // NOSONAR
|
||||
'172.0.0.0/6', // NOSONAR
|
||||
'176.0.0.0/6', // NOSONAR
|
||||
'180.0.0.0/6', // NOSONAR
|
||||
'184.0.0.0/6', // NOSONAR
|
||||
'188.0.0.0/6', // NOSONAR
|
||||
'192.0.0.0/6', // NOSONAR
|
||||
'196.0.0.0/6', // NOSONAR
|
||||
'200.0.0.0/6', // NOSONAR
|
||||
'204.0.0.0/6', // NOSONAR
|
||||
'208.0.0.0/6', // NOSONAR
|
||||
'212.0.0.0/6', // NOSONAR
|
||||
'216.0.0.0/6', // NOSONAR
|
||||
'220.0.0.0/6', // NOSONAR
|
||||
'224.0.0.0/6', // NOSONAR
|
||||
'228.0.0.0/6', // NOSONAR
|
||||
'232.0.0.0/6', // NOSONAR
|
||||
'236.0.0.0/6', // NOSONAR
|
||||
'240.0.0.0/6', // NOSONAR
|
||||
'244.0.0.0/6', // NOSONAR
|
||||
'248.0.0.0/6', // NOSONAR
|
||||
'252.0.0.0/6', // NOSONAR
|
||||
]); // NOSONAR
|
||||
expect(getSubnets('1.1.1.1/8')).to.eql([]); // NOSONAR
|
||||
expect(getSubnets('1.1.1.1/11')).to.eql([ // NOSONAR
|
||||
'1.0.0.0/11', // NOSONAR
|
||||
'1.32.0.0/11', // NOSONAR
|
||||
'1.64.0.0/11', // NOSONAR
|
||||
'1.96.0.0/11', // NOSONAR
|
||||
'1.128.0.0/11', // NOSONAR
|
||||
'1.160.0.0/11', // NOSONAR
|
||||
'1.192.0.0/11', // NOSONAR
|
||||
'1.224.0.0/11', // NOSONAR
|
||||
]); // NOSONAR
|
||||
expect(getSubnets('172.16.2.2/16')).to.eql([]); // NOSONAR
|
||||
expect(getSubnets('172.16.2.2/26')).to.eql([ // NOSONAR
|
||||
'172.16.2.0/26', // NOSONAR
|
||||
'172.16.2.64/26', // NOSONAR
|
||||
'172.16.2.128/26', // NOSONAR
|
||||
'172.16.2.192/26', // NOSONAR
|
||||
]); // NOSONAR
|
||||
expect(getSubnets('172.16.2.2/31').length).to.eql(128); // NOSONAR
|
||||
expect(getSubnets('255.255.255.0/32')).to.eql([]); // NOSONAR
|
||||
expect(getSubnets('2001:db8:0:85a3::ac1f:8001/62')).to.eql([]); // NOSONAR
|
||||
expect(getSubnets('2001:db8:0:85a3:0:0:ac1f:8001/62')).to.eql([]); // NOSONAR
|
||||
expect(getSubnets('2001:db8:0:85a3::ac1f:8001/112')).to.eql([]); // NOSONAR
|
||||
expect(getSubnets('2001:db8:0:85a3:0:0:ac1f:8001/112')).to.eql([]); // NOSONAR
|
||||
}); // NOSONAR
|
||||
}); // NOSONAR
|
||||
describe('getNetworksCount', () => {
|
||||
it('returns networks count', () => {
|
||||
expect(getNetworksCount('1.1.1.1/1')).to.eql(2); // NOSONAR
|
||||
expect(getNetworksCount('1.1.1.1/2')).to.eql(4); // NOSONAR
|
||||
expect(getNetworksCount('1.1.1.1/3')).to.eql(8); // NOSONAR
|
||||
expect(getNetworksCount('1.1.1.1/4')).to.eql(16); // NOSONAR
|
||||
expect(getNetworksCount('1.1.1.1/5')).to.eql(32); // NOSONAR
|
||||
expect(getNetworksCount('1.1.1.1/6')).to.eql(64); // NOSONAR
|
||||
expect(getNetworksCount('1.1.1.1/7')).to.eql(128); // NOSONAR
|
||||
expect(getNetworksCount('1.1.1.1/8')).to.eql(0); // NOSONAR
|
||||
expect(getNetworksCount('1.1.1.1/9')).to.eql(2); // NOSONAR
|
||||
expect(getNetworksCount('1.1.1.1/10')).to.eql(4); // NOSONAR
|
||||
expect(getNetworksCount('1.1.1.1/11')).to.eql(8); // NOSONAR
|
||||
expect(getNetworksCount('1.1.1.1/12')).to.eql(16); // NOSONAR
|
||||
expect(getNetworksCount('1.1.1.1/13')).to.eql(32); // NOSONAR
|
||||
expect(getNetworksCount('1.1.1.1/14')).to.eql(64); // NOSONAR
|
||||
expect(getNetworksCount('1.1.1.1/15')).to.eql(128); // NOSONAR
|
||||
expect(getNetworksCount('1.1.1.1/16')).to.eql(0); // NOSONAR
|
||||
expect(getNetworksCount('1.1.1.1/17')).to.eql(2); // NOSONAR
|
||||
expect(getNetworksCount('1.1.1.1/18')).to.eql(4); // NOSONAR
|
||||
expect(getNetworksCount('1.1.1.1/19')).to.eql(8); // NOSONAR
|
||||
expect(getNetworksCount('1.1.1.1/20')).to.eql(16); // NOSONAR
|
||||
expect(getNetworksCount('1.1.1.1/21')).to.eql(32); // NOSONAR
|
||||
expect(getNetworksCount('1.1.1.1/22')).to.eql(64); // NOSONAR
|
||||
expect(getNetworksCount('1.1.1.1/23')).to.eql(128); // NOSONAR
|
||||
expect(getNetworksCount('1.1.1.1/24')).to.eql(0); // NOSONAR
|
||||
expect(getNetworksCount('1.1.1.1/25')).to.eql(2); // NOSONAR
|
||||
expect(getNetworksCount('1.1.1.1/26')).to.eql(4); // NOSONAR
|
||||
expect(getNetworksCount('1.1.1.1/27')).to.eql(8); // NOSONAR
|
||||
expect(getNetworksCount('1.1.1.1/28')).to.eql(16); // NOSONAR
|
||||
expect(getNetworksCount('1.1.1.1/29')).to.eql(32); // NOSONAR
|
||||
expect(getNetworksCount('1.1.1.1/30')).to.eql(64); // NOSONAR
|
||||
expect(getNetworksCount('1.1.1.1/31')).to.eql(128); // NOSONAR
|
||||
expect(getNetworksCount('1.1.1.1/32')).to.eql(0); // NOSONAR
|
||||
expect(getNetworksCount('2001:db8:0:85a3::ac1f:8001/32')).to.eql(4294967296n); // NOSONAR
|
||||
expect(getNetworksCount('2001:db8:0:85a3::ac1f:8001/42')).to.eql(4194304n); // NOSONAR
|
||||
expect(getNetworksCount('2001:db8:0:85a3:0:0:ac1f:8001/62')).to.eql(4n); // NOSONAR
|
||||
expect(getNetworksCount('2001:db8:0:85a3::ac1f:8001/112')).to.eql(-1); // NOSONAR
|
||||
expect(getNetworksCount('2001:db8:0:85a3:0:0:ac1f:8001/122')).to.eql(-1); // NOSONAR
|
||||
}); // NOSONAR
|
||||
}); // NOSONAR
|
||||
describe('getIPNetworkType', () => {
|
||||
it('returns network type', () => {
|
||||
expect(getIPNetworkType('1.1.1.1')).to.eql('Public'); // NOSONAR
|
||||
expect(getIPNetworkType('10.10.1.1')).to.eql('Private Use'); // NOSONAR
|
||||
expect(getIPNetworkType('172.16.0.1')).to.eql('Private Use'); // NOSONAR
|
||||
expect(getIPNetworkType('192.168.1.1')).to.eql('Private Use'); // NOSONAR
|
||||
expect(getIPNetworkType('255.255.255.0')).to.eql('Reserved'); // NOSONAR
|
||||
expect(getIPNetworkType('224.0.0.1')).to.eql('Multicast'); // NOSONAR
|
||||
expect(getIPNetworkType('198.51.100.1')).to.eql('Documentation (TEST-NET-2)'); // NOSONAR
|
||||
expect(getIPNetworkType('198.18.0.1')).to.eql('Benchmarking'); // NOSONAR
|
||||
expect(getIPNetworkType('169.254.0.1')).to.eql('Link Local'); // NOSONAR
|
||||
expect(getIPNetworkType('127.0.0.1')).to.eql('Loopback'); // NOSONAR
|
||||
expect(getIPNetworkType('2001:db8:8:4::2')).to.eql('Documentation'); // NOSONAR
|
||||
expect(getIPNetworkType('FF02::2')).to.eql('Multicast address'); // NOSONAR
|
||||
expect(getIPNetworkType('2345:0425:2CA1:0000:0000:0567:5673:23b5')).to.eql('Public'); // NOSONAR
|
||||
expect(getIPNetworkType('fdf8:f53b:82e4::53')).to.eql('Unique-Local'); // NOSONAR
|
||||
expect(getIPNetworkType('::ffff:192.0.2.47')).to.eql('IPv4-mapped Address'); // NOSONAR
|
||||
expect(getIPNetworkType('::ffff:ac12:0a09')).to.eql('IPv4-mapped Address'); // NOSONAR
|
||||
expect(getIPNetworkType('::1')).to.eql('Loopback Address'); // NOSONAR
|
||||
expect(getIPNetworkType('fe80::200:5aee:feaa:20a2')).to.eql('Link-Local Unicast'); // NOSONAR
|
||||
expect(getIPNetworkType('2001:0002::6c::430')).to.eql('Benchmarking'); // NOSONAR
|
||||
expect(getIPNetworkType('2001:0000:4136:e378:8000:63bf:3fff:fdd2')).to.eql('TEREDO'); // NOSONAR
|
||||
expect(getIPNetworkType('2002:cb0a:3cdd:1::1')).to.eql('6to4'); // NOSONAR
|
||||
expect(getIPNetworkType('ff01:0:0:0:0:0:0:2')).to.eql('Multicast address'); // NOSONAR
|
||||
}); // NOSONAR
|
||||
}); // NOSONAR
|
||||
|
||||
describe('toARPA', () => {
|
||||
it('returns ARPA address', () => {
|
||||
expect(toARPA('1.1.1.1')).to.eql('1.1.1.1.in-addr.arpa'); // NOSONAR
|
||||
expect(toARPA('10.10.1.1')).to.eql('1.1.10.10.in-addr.arpa'); // NOSONAR
|
||||
expect(toARPA('192.168.1.1')).to.eql('1.1.168.192.in-addr.arpa'); // NOSONAR
|
||||
expect(toARPA('255.255.255.0')).to.eql('0.255.255.255.in-addr.arpa'); // NOSONAR
|
||||
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.'); // NOSONAR
|
||||
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.'); // NOSONAR
|
||||
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.'); // NOSONAR
|
||||
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.'); // NOSONAR
|
||||
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.'); // NOSONAR
|
||||
}); // NOSONAR
|
||||
}); // NOSONAR
|
||||
describe('toIPv4MappedAddress', () => {
|
||||
it('returns IPv4MappedAddress', () => {
|
||||
expect(toIPv4MappedAddress('1.1.1.1')).to.eql('::ffff:0101:0101'); // NOSONAR
|
||||
expect(toIPv4MappedAddress('10.10.1.1')).to.eql('::ffff:0a0a:0101'); // NOSONAR
|
||||
expect(toIPv4MappedAddress('172.18.10.9')).to.eql('::ffff:ac12:0a09'); // NOSONAR
|
||||
expect(toIPv4MappedAddress('192.168.1.1')).to.eql('::ffff:c0a8:0101'); // NOSONAR
|
||||
expect(toIPv4MappedAddress('255.255.255.0')).to.eql('::ffff:ffff:ff00'); // NOSONAR
|
||||
}); // NOSONAR
|
||||
}); // NOSONAR
|
||||
describe('toIPv4MappedAddressDecimal', () => {
|
||||
it('returns networks count', () => {
|
||||
expect(toIPv4MappedAddressDecimal('1.1.1.1')).to.eql('::ffff:1.1.1.1'); // NOSONAR
|
||||
expect(toIPv4MappedAddressDecimal('10.10.1.1')).to.eql('::ffff:10.10.1.1'); // NOSONAR
|
||||
expect(toIPv4MappedAddressDecimal('192.168.1.1')).to.eql('::ffff:192.168.1.1'); // NOSONAR
|
||||
expect(toIPv4MappedAddressDecimal('172.18.10.9')).to.eql('::ffff:172.18.10.9'); // NOSONAR
|
||||
expect(toIPv4MappedAddressDecimal('255.255.255.0')).to.eql('::ffff:255.255.255.0'); // NOSONAR
|
||||
expect(toIPv4MappedAddressDecimal('2001:db8:0:85a3::ac1f:8001')).to.eql(''); // NOSONAR
|
||||
}); // NOSONAR
|
||||
}); // NOSONAR
|
||||
describe('to6to4Prefix', () => {
|
||||
it('returns networks count', () => {
|
||||
expect(to6to4Prefix('1.1.1.1')).to.eql('2002:01:0:1:01:01::/48'); // NOSONAR
|
||||
expect(to6to4Prefix('10.10.1.1')).to.eql('2002:0a:0:a:01:01::/48'); // NOSONAR
|
||||
expect(to6to4Prefix('172.18.10.9')).to.eql('2002:ac:1:2:0a:09::/48'); // NOSONAR
|
||||
expect(to6to4Prefix('192.168.1.1')).to.eql('2002:c0:a:8:01:01::/48'); // NOSONAR
|
||||
expect(to6to4Prefix('255.255.255.0')).to.eql('2002:ff:f:f:ff:00::/48'); // NOSONAR
|
||||
expect(to6to4Prefix('2001:db8:0:85a3::ac1f:8001')).to.eql(''); // NOSONAR
|
||||
}); // NOSONAR
|
||||
}); // NOSONAR
|
||||
}); // NOSONAR
|
147
src/utils/ip.ts
Normal file
147
src/utils/ip.ts
Normal file
|
@ -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();
|
||||
}
|
27
src/utils/ipv4registry.json
Normal file
27
src/utils/ipv4registry.json
Normal file
|
@ -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"]]
|
||||
]
|
24
src/utils/ipv6registry.json
Normal file
24
src/utils/ipv6registry.json
Normal file
|
@ -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"]]
|
||||
]
|
Loading…
Add table
Add a link
Reference in a new issue