为了省一次 DNS 查询的 RTT,我参考 Sukka 的 DNS 方案用上了 FakeIP,本地 DNS 先返回一个假地址,代理再把它映射回域名,不必先等真实 IP 的解析结果。但在生产环境里延迟不是唯一的问题。
FakeIP 常用的类似 198.18.0.0/15 是基准测试专用地址。应用为了防止 SSRF,会拒绝访问私网、回环或特殊用途地址,于是正常的外部请求也可能被拦截。这个副作用在生产环境里不可接受。我也不想为了代理关掉或者更改应用的 SSRF 防护配置,所以开始找另一类地址,检查库把它当作普通公网地址,但与真实业务冲突的机会尽量小。
最初看中了美国国防部的几个 IPv4 /8,以为它们基本不路由。但是 RIPEstat 的查询结果推翻了这个印象,不少网段已经由 AS749 或 AS721 宣告。
下面是 2026-10-09 的查询结果。子段数指观测到的更具体前缀数量,可见率指能看到该前缀的 RIS 观测点数量。
| 段 | 宣告 AS | 子段数 | 可见率 |
|---|---|---|---|
| 28/8 | AS749 | 26 | 325/327 |
| 11/8、22/8 | AS749 | ≥50 | 325/327 |
| 21/8 | AS749 | 4 | 326/327 |
| 26/8 | AS749 | 1 | 326/327 |
| 29/8 | AS749 | 24 | 325/327 |
| 30/8 | AS749 | 0 | 326/327 |
| 33/8 | AS749 | 0 | 326/327 |
| 55/8、214/8 | AS721 | ≥50 | 325/327 |
| 215/8 | /8 本身没人宣告 | ≥50 | 0/327 |
其中 30.0.0.0/8 和 33.0.0.0/8,在那次查询中只有覆盖整段的宣告,没有观测到更具体的子段。我把 30/8 当作候选,虽然没有子段不代表没有真实主机,更不代表这块地址可以随便占用,但是我不管。
IPv6 则考虑 2d00::/8。它位于 2000::/3 全球单播空间内,但 IANA 登记表明确将它标为 RESERVED,尚未分配。某些检查库可能把它归类为普通 unicast 而不是「它不是保留地址」。不过能否通过检查必须以应用实际使用的库和版本为准。
IPv4 /16、IPv6 /112,各有 65536 个地址,感觉已经十分够用。如果旧映射被回收,应用却还缓存着旧 FakeIP,就有连错目标的风险。地址池不能只按「平时解析几个域名」来估算。
可以在候选段里随机选一个小池子,避免大家都挤在同一处:
import ipaddress
import secrets
v4 = int(ipaddress.ip_address("30.0.0.0")) | (secrets.randbits(8) << 16)
v6 = int(ipaddress.ip_address("2d00::")) | (secrets.randbits(104) << 16)
print(ipaddress.ip_network((v4, 16)))
print(ipaddress.ip_network((v6, 112)))IPv4 的结果是 30.x.0.0/16,只有 8 位随机空间。IPv6 固定的是前 8 位,所以结果可以从 2d00:… 到 2dff:…,不一定以 2d00: 开头。两个池子的主机位都保持为零。
使用边界
这只适合自己控制的 FakeIP DNS 与 TUN 环境。假地址必须由代理接管,不能泄漏到公网,也不能让不经过代理的应用拿到它。用了 30/8 的子段,就意味着这条路径无法再把该子段当作真实目的地址访问。
选段前可以查 RIPEstat,之后也应复查:
curl -fsS 'https://stat.ripe.net/data/routing-status/data.json?resource=30.0.0.0/8' \
| jq '.data | {origins, visibility, more_specifics}'IPv6 同样可以查询 2d00::/8,具体子池也要查。BGP 观测只是某个时间、某些观测点的结果,不是未来不会宣告的承诺。
最后还有一个安全边界,FakeIP 隐藏了真实 IP,应用原来的 IP 检查就不再等价于检查最终目的地址。代理出口仍需限制对内网、回环等敏感目标的访问,包括真实解析结果。省下一个 RTT,不值得换来真正的 SSRF 漏洞。