← 中文资源

简体中文版本

IPv4 CIDR 网段匹配

把 IPv4 转为整数并按 CIDR 掩码判断归属,适用于访问白名单、代理链与限流规则。

阅读英文原文

功能说明

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;
}