功能说明
ipv4ToInt 把点分十进制地址转换为无符号三十二位整数,parseCidr 解析网段地址与前缀长度并计算掩码与网络号,isIpInCidr 判断地址是否落在网段内。适合反向代理可信来源校验、管理后台 IP 白名单、按网段限流以及日志分析。与字符串前缀比较相比,按位掩码判断不会把 10.0.0.1 与 10.0.0.10 混淆。
关键代码
转换时逐段校验数字格式、前导零与 255 上限,用乘二百五十六累加而不是左移,避免符号位问题,最后无符号化。前缀长度限制在零到三十二,掩码通过左移构造,前缀零单独处理,因为 JavaScript 位移位数按三十二取模。网段号由地址与掩码按位与得到。匹配时对目标地址应用同一掩码再与网络号比较。
使用注意
该模块只支持 IPv4,IPv6 需要一百二十八位整数或分段比较。代理头中的地址可能被伪造,只有在可信代理链上剥离并校验后,最右侧地址才有意义。前导零被拒绝是安全优先策略,某些历史系统可能接受。CIDR 中的主机位会被掩码清零,传入 192.168.1.5/24 会得到 192.168.1.0/24,调用方应知晓。
验证方式
测试 0.0.0.0、255.255.255.255、10.0.0.1、192.168.1.5 与边界地址的整数转换。验证 /0、/32、/24、/12 的网段匹配,包含网络号、广播地址与网段外一位。测试非法地址、前导零、越界八位组、缺少斜杠、负前缀与大于三十二的前缀会抛错,并确认 CIDR 主机位被正确清零。
代码示例
export interface ParsedCidr {
network: number;
mask: number;
prefix: number;
}
export function ipv4ToInt(address: string): number {
const parts = address.split(".");
if (parts.length !== 4) throw new TypeError("invalid IPv4 address: " + address);
let value = 0;
for (const part of parts) {
if (!/^\d{1,3}$/.test(part)) throw new TypeError("invalid IPv4 octet: " + part);
if (part.length > 1 && part.startsWith("0")) throw new RangeError("leading zeros are not allowed");
const octet = Number(part);
if (octet > 255) throw new RangeError("IPv4 octet out of range: " + part);
value = value * 256 + octet;
}
return value >>> 0;
}
export function parseCidr(cidr: string): ParsedCidr {
const parts = cidr.split("/");
if (parts.length !== 2) throw new SyntaxError("CIDR must contain one slash");
const address = parts[0]!;
const prefix = Number(parts[1]!);
if (!Number.isInteger(prefix) || prefix < 0 || prefix > 32) {
throw new RangeError("CIDR prefix must be an integer between 0 and 32");
}
const mask = prefix === 0 ? 0 : (0xffffffff << (32 - prefix)) >>> 0;
return { network: (ipv4ToInt(address) & mask) >>> 0, mask, prefix };
}
export function isIpInCidr(address: string, cidr: string): boolean {
const { network, mask } = parseCidr(cidr);
return ((ipv4ToInt(address) & mask) >>> 0) === network;
}