Files
router-lists-ui/frontend/src/utils/ipUtils.js
T

212 lines
4.9 KiB
JavaScript

/**
* Утилиты для работы с IP адресами и CIDR
* Вынесено из IPRangesManager для переиспользования
*/
/**
* Парсинг маски из CIDR
*/
export function parseMask(cidr) {
const parts = String(cidr || '').split('/');
const mask = Number(parts[1]);
return Number.isFinite(mask) ? mask : NaN;
}
/**
* Подсчет адресов в CIDR
*/
export function countAddresses(mask) {
if (!Number.isFinite(mask) || mask < 0 || mask > 32) return 0n;
return 1n << BigInt(32 - mask);
}
/**
* Парсинг октетов IPv4
*/
export function parseOctets(ip) {
const [a, b, c, d] = String(ip || '').split('.').map(x => Number(x));
return [a, b, c, d];
}
/**
* Проверка приватного IPv4
*/
export function isPrivateIPv4(ip) {
const [a, b] = parseOctets(ip);
if (a === 10) return true; // 10.0.0.0/8
if (a === 192 && b === 168) return true; // 192.168.0.0/16
if (a === 172 && b >= 16 && b <= 31) return true; // 172.16.0.0/12
return false;
}
/**
* Проверка CGNAT IPv4
*/
export function isCGNATIPv4(ip) {
const [a, b] = parseOctets(ip);
return a === 100 && b >= 64 && b <= 127; // 100.64.0.0/10
}
/**
* Получение категории IP
*/
export function getIpCategory(cidr) {
const baseIp = String(cidr || '').split('/')[0];
if (isCGNATIPv4(baseIp)) return 'cgnat';
if (isPrivateIPv4(baseIp)) return 'private';
return 'public';
}
/**
* Форматирование BigInt с разделителями тысяч
*/
export function formatBigInt(n) {
try {
const s = (n ?? 0n).toString();
return s.replace(/\B(?=(\d{3})+(?!\d))/g, ' ');
} catch {
return String(n);
}
}
/**
* Конвертация IP в BigInt
*/
export function ipToInt(ip) {
const [a, b, c, d] = parseOctets(ip);
return (BigInt(a) << 24n) | (BigInt(b) << 16n) | (BigInt(c) << 8n) | BigInt(d);
}
/**
* Конвертация BigInt в IP
*/
export function intToIp(num) {
const a = Number((num >> 24n) & 255n);
const b = Number((num >> 16n) & 255n);
const c = Number((num >> 8n) & 255n);
const d = Number(num & 255n);
return `${a}.${b}.${c}.${d}`;
}
/**
* Конвертация CIDR в диапазон
*/
export function cidrToRange(cidr) {
const [ip, maskStr] = String(cidr).split('/');
const mask = Number(maskStr);
const ipNum = ipToInt(ip);
const hostBits = 32 - mask;
const start = ipNum & (~0n << BigInt(hostBits));
const end = start | ((1n << BigInt(hostBits)) - 1n);
return [start, end];
}
/**
* Подсчет trailing zeros для 32-битного числа
*/
export function countTrailingZeros32(n) {
if (n === 0n) return 32;
let zeros = 0;
for (let i = 0; i < 32; i++) {
if ((n & (1n << BigInt(i))) !== 0n) {
zeros = i;
break;
}
}
return zeros;
}
/**
* Floor log2 для BigInt
*/
export function floorLog2(n) {
let p = -1;
while (n > 0n) {
n >>= 1n;
p++;
}
return p < 0 ? 0 : p;
}
/**
* Конвертация диапазона в CIDR блоки
*/
export function rangeToCidrs(start, end) {
const res = [];
let cur = start;
while (cur <= end) {
const maxAlignPow = countTrailingZeros32(cur);
const remaining = end - cur + 1n;
const remainingPow = floorLog2(remaining);
const blockPow = Math.min(maxAlignPow, remainingPow);
const mask = 32 - blockPow;
res.push(`${intToIp(cur)}/${mask}`);
cur += 1n << BigInt(blockPow);
}
return res;
}
/**
* Слияние интервалов
*/
export function mergeIntervals(intervals) {
if (intervals.length === 0) return [];
intervals.sort((a, b) => (a[0] < b[0] ? -1 : a[0] > b[0] ? 1 : 0));
const merged = [];
let [cs, ce] = intervals[0];
for (let i = 1; i < intervals.length; i++) {
const [s, e] = intervals[i];
if (s <= ce + 1n) {
if (e > ce) ce = e;
} else {
merged.push([cs, ce]);
cs = s;
ce = e;
}
}
merged.push([cs, ce]);
return merged;
}
/**
* Суммаризация CIDR блоков
*/
export function summarizeCidrs(cidrs) {
const ranges = cidrs.map(cidrToRange);
const merged = mergeIntervals(ranges);
const out = [];
for (const [s, e] of merged) {
out.push(...rangeToCidrs(s, e));
}
return out;
}
/**
* Получение базового /16 для CIDR
*/
export function get16BaseCidr(cidr) {
const ip = String(cidr).split('/')[0];
const ipNum = ipToInt(ip);
const start = ipNum & (~0n << 16n);
return `${intToIp(start)}/16`;
}
/**
* Применение агрессивной агрегации /16
*/
export function applySupernet16(cidrs) {
const buckets = new Map();
for (const c of cidrs) {
const base16 = get16BaseCidr(c);
if (!buckets.has(base16)) buckets.set(base16, []);
buckets.get(base16).push(c);
}
const out = [];
for (const [base16, list] of buckets) {
if (list.length > 2) out.push(base16);
else out.push(...list);
}
return Array.from(new Set(out));
}