172 lines
4.6 KiB
TypeScript
172 lines
4.6 KiB
TypeScript
/**
|
|
* 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;
|
|
}
|