私有 IPv4 地址范围与边界案例
精确梳理 RFC 1918、CGNAT、回环、链路本地、0.0.0.0/8、组播与保留 IPv4 范围,并提供可运行的 Python 边界测试。
作者Yiyao
IPv4 地址常被笼统地称为“私有地址”,但不同地址空间的用途和可见性并不相同。RFC 1918 私有地址只有 10.0.0.0/8、172.16.0.0/12 和 192.168.0.0/16。回环、链路本地、CGNAT 和组播地址属于不同的特殊用途范围。
本文给出精确的范围边界,并测试边界前后的地址。重点不只是“这个 IP 是否 private”,还要回答“它能在哪里使用,以及谁能看到它”。
RFC 1918 的三个范围
10.0.0.0/8 覆盖 10.0.0.0 到 10.255.255.255。172.16.0.0/12 覆盖 172.16.0.0 到 172.31.255.255,因为它实际包含 172.16 到 172.31 这 16 个 /16 网段。192.168.0.0/16 覆盖 192.168.0.0 到 192.168.255.255。
边界必须精确判断:172.15.255.255 在 RFC 1918 之外,172.16.0.0 在范围内,172.31.255.255 在范围内,172.32.0.0 又在范围外。不能因为地址以 172 开头,就直接判定为私有地址。
CGNAT:共享地址空间,不等于 RFC 1918
100.64.0.0/10 覆盖 100.64.0.0 到 100.127.255.255,用于运营商级 NAT。它是用户与运营商 NAT 基础设施之间的共享地址空间,不属于三个 RFC 1918 网段,因此程序中应单独标记。
边界示例是:100.63.255.255 在范围外,100.64.0.0 在范围内,100.127.255.255 在范围内,100.128.0.0 在范围外。不要把 CGNAT 简化成普通的“私网”。
回环、链路本地与 0.0.0.0/8
127.0.0.0/8 是回环地址空间。127.0.0.1 最常见,但整个 127/8 都表示主机访问自己。169.254.0.0/16 是 IPv4 链路本地地址,通常在主机无法通过 DHCP 等方式获得正常地址时自动配置。它只适用于本地链路,不能当作普通 LAN 私网。
0.0.0.0/8 是特殊用途的“本网络”空间。0.0.0.0 还常用于表示未指定地址,例如服务器监听所有本地网卡,或路由表中的默认路由。它不是可以定位普通远端主机的公网地址。
组播与保留范围
224.0.0.0/4 覆盖 224.0.0.0 到 239.255.255.255,用于组播。它表示一组接收者,而不是普通单播主机。240.0.0.0/4 属于保留或特殊用途空间。两者都不能称为 RFC 1918 私有网段。
from ipaddress import ip_address, ip_network
CASES = [
"10.0.0.1",
"172.16.0.1", "172.31.255.254", "172.32.0.1",
"192.168.1.10",
"100.64.0.1", "100.127.255.254", "100.128.0.1",
"127.0.0.1", "169.254.10.20",
"0.0.0.0", "224.0.0.1", "8.8.8.8",
]
SPECIAL = {
"rfc1918_10": ip_network("10.0.0.0/8"),
"rfc1918_172": ip_network("172.16.0.0/12"),
"rfc1918_192": ip_network("192.168.0.0/16"),
"cgnat": ip_network("100.64.0.0/10"),
"loopback": ip_network("127.0.0.0/8"),
"link_local": ip_network("169.254.0.0/16"),
"this_network": ip_network("0.0.0.0/8"),
"multicast": ip_network("224.0.0.0/4"),
}
def classify(value: str) -> dict:
ip = ip_address(value)
labels = [name for name, network in SPECIAL.items() if ip in network]
return {
"input": value,
"normalized": str(ip),
"is_private": ip.is_private,
"is_global": ip.is_global,
"is_loopback": ip.is_loopback,
"is_link_local": ip.is_link_local,
"is_unspecified": ip.is_unspecified,
"is_multicast": ip.is_multicast,
"special_ranges": labels,
}
for value in CASES:
print(classify(value))
典型结果包括:10.0.0.1 的 is_private=True、is_global=False;172.15.255.255 的 is_private=False;100.64.0.1 既不是普通 private,也不是 global;127.0.0.1 的 is_loopback=True;169.254.10.20 的 is_link_local=True;224.0.0.1 的 is_multicast=True。业务规则需要区分类型时,应保留显式的网段标签。
可运行的边界测试
from ipaddress import ip_address, ip_network
PRIVATE = (
ip_network("10.0.0.0/8"),
ip_network("172.16.0.0/12"),
ip_network("192.168.0.0/16"),
)
CGNAT = ip_network("100.64.0.0/10")
def routing_bucket(value: str) -> str:
ip = ip_address(value)
if any(ip in network for network in PRIVATE):
return "RFC1918 private address"
if ip in CGNAT:
return "shared address space / CGNAT (100.64/10)"
if ip.is_loopback:
return "loopback (local host only)"
if ip.is_link_local:
return "link-local (local segment)"
if ip.is_unspecified:
return "unspecified / this network"
if ip.is_multicast:
return "multicast"
if ip.is_global:
return "globally routable candidate"
return "special-purpose or non-global address"
for sample in ["172.15.255.255", "172.16.0.0", "172.31.255.255",
"172.32.0.0", "100.63.255.255", "100.64.0.0",
"100.127.255.255", "100.128.0.0"]:
print(sample, "=>", routing_bucket(sample))
预期边界输出是:172.15.255.255 => globally routable candidate;172.16.0.0 => RFC1918 private address;172.31.255.255 => RFC1918 private address;172.32.0.0 => globally routable candidate;100.63.255.255 => globally routable candidate;100.64.0.0 => shared address space / CGNAT;100.127.255.255 => shared address space / CGNAT;100.128.0.0 => globally routable candidate。
私有、本地、内网与公网可见不是一回事
“私有”通常指 RFC 1918 地址,适合在不同网络中重复使用,不作为公网 Internet 的普通目的地址进行通告。“本地”强调作用范围,例如回环只到本机,链路本地只到本地链路。“内网”是运维语境,可能包括 RFC 1918、CGNAT、VPN 网段和企业自定义网段。
“公网可见”描述的是观察结果,而不是单凭数字得出的类别。浏览器或远端服务器通常看到 NAT 后的公网出口地址,局域网应用看到的可能是本地地址。CGNAT 地址对运营商内部是共享空间,用户无法控制它,公网通常也不能直接向它发起入站连接。仅看 IPv4 分类,无法知道远端网站最终观察到哪个地址。
实际判断建议
防火墙规则应比较明确 CIDR,并记录规则意图。日志应保留原始地址和分类原因。代理或网络诊断应同时记录本机网卡地址与外部观测到的出口地址。不要把所有非 global 地址都写成“私有”,否则会丢失 RFC 1918、CGNAT、回环、链路本地和组播之间的差异。
结语
可靠的流程是:先解析地址,再与明确的特殊用途网段比较,测试边界邻居,最后结合网络上下文解释可见性。RFC 1918 是精确的三个网段;100.64/10、回环、链路本地、0/8 和组播都应该单独标记。
