/** * Утилиты для работы с 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)); }