it-tools/src/utils/ip.ts
sharevb 94f0b9ef93 feat(utils): IPv4/6 Utils
Network Type, Subnets and counts, 6to4 prefix, ARPA, IPv4 Mapped
Parse IP/Mask/Range as CIDR
2024-04-20 20:18:36 +02:00

148 lines
4.3 KiB
TypeScript

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();
}