/** * IP Range utility functions for network scanning */ /** * Validates an IPv4 address */ export function isValidIp(ip: string): boolean { const parts = ip.split('.'); if (parts.length !== 4) return false; return parts.every((part) => { const num = parseInt(part, 10); return !isNaN(num) && num >= 0 && num <= 255 && part === num.toString(); }); } /** * Converts an IPv4 address to a 32-bit number */ export function ipToNumber(ip: string): number { const parts = ip.split('.').map((p) => parseInt(p, 10)); return ((parts[0] << 24) | (parts[1] << 16) | (parts[2] << 8) | parts[3]) >>> 0; } /** * Converts a 32-bit number to an IPv4 address */ export function numberToIp(num: number): string { return [ (num >>> 24) & 255, (num >>> 16) & 255, (num >>> 8) & 255, num & 255, ].join('.'); } /** * Generates an array of IP addresses from a start to end range * Excludes network address (.0) and broadcast address (.255) for /24 ranges */ export function ipRangeToIps(startIp: string, endIp: string): string[] { if (!isValidIp(startIp) || !isValidIp(endIp)) { throw new Error(`Invalid IP address: ${!isValidIp(startIp) ? startIp : endIp}`); } const start = ipToNumber(startIp); const end = ipToNumber(endIp); if (start > end) { throw new Error(`Start IP (${startIp}) must be less than or equal to end IP (${endIp})`); } const ips: string[] = []; for (let i = start; i <= end; i++) { ips.push(numberToIp(i)); } return ips; } /** * Parses CIDR notation and returns an array of usable host IPs * Excludes network address and broadcast address * Example: "192.168.1.0/24" returns 192.168.1.1 through 192.168.1.254 */ export function cidrToIps(cidr: string): string[] { const match = cidr.match(/^(\d+\.\d+\.\d+\.\d+)\/(\d+)$/); if (!match) { throw new Error(`Invalid CIDR notation: ${cidr}`); } const [, networkIp, prefixStr] = match; const prefix = parseInt(prefixStr, 10); if (!isValidIp(networkIp)) { throw new Error(`Invalid network address: ${networkIp}`); } if (prefix < 0 || prefix > 32) { throw new Error(`Invalid prefix length: ${prefix}`); } // For /32, just return the single IP if (prefix === 32) { return [networkIp]; } // For /31 (point-to-point), return both IPs if (prefix === 31) { const networkNum = ipToNumber(networkIp); const mask = (0xffffffff << (32 - prefix)) >>> 0; const network = (networkNum & mask) >>> 0; return [numberToIp(network), numberToIp(network + 1)]; } // Calculate network and broadcast addresses const networkNum = ipToNumber(networkIp); const mask = (0xffffffff << (32 - prefix)) >>> 0; const network = (networkNum & mask) >>> 0; const broadcast = (network | (~mask >>> 0)) >>> 0; // Generate usable host IPs (exclude network and broadcast) const ips: string[] = []; for (let i = network + 1; i < broadcast; i++) { ips.push(numberToIp(i)); } return ips; } /** * Gets the local network interfaces and returns the first non-loopback IPv4 subnet * Returns CIDR notation (e.g., "192.168.1.0/24") */ export function getLocalSubnet(): string | null { try { const os = require('os'); const interfaces = os.networkInterfaces(); for (const name of Object.keys(interfaces)) { const iface = interfaces[name]; if (!iface) continue; for (const info of iface) { // Skip loopback and non-IPv4 if (info.family !== 'IPv4' || info.internal) continue; // Calculate the network address from IP and netmask const ip = ipToNumber(info.address); const mask = ipToNumber(info.netmask); const network = (ip & mask) >>> 0; // Calculate prefix length from netmask const maskBits = info.netmask.split('.').reduce((acc: number, octet: string) => { const byte = parseInt(octet, 10); let bits = 0; for (let i = 7; i >= 0; i--) { if ((byte >> i) & 1) bits++; else break; } return acc + bits; }, 0); return `${numberToIp(network)}/${maskBits}`; } } } catch { // os module might not be available } return null; } /** * Counts the number of IPs in a CIDR range (excluding network and broadcast) */ export function countIpsInCidr(cidr: string): number { const match = cidr.match(/^(\d+\.\d+\.\d+\.\d+)\/(\d+)$/); if (!match) { throw new Error(`Invalid CIDR notation: ${cidr}`); } const prefix = parseInt(match[2], 10); if (prefix === 32) return 1; if (prefix === 31) return 2; // 2^(32-prefix) - 2 (minus network and broadcast) return Math.pow(2, 32 - prefix) - 2; }