为什么应该尽量避免使用 FakeIP¶
省流
FakeIP 带来的污染是持久且隐蔽的,大部分情况下,FakeIP 带来的坏处 > 收益。
FakeIP 的技术原理¶
首先我们来回顾下 FakeIP 的技术原理,工作过程大概分为两个阶段,第一阶段是欺骗操作系统并返回假的 IP 地址(注意此阶段没有在互联网进行任何 DNS 查询):
- 浏览器发起了 DNS 查询:“
github.com的 IP 是多少?“。 - 操作系统将查询发送给 Tun 网卡(或者被劫持的 DNS 端口),相关代理核心(比如 sing-box)捕获该查询。
- 代理核心查看内存中的映射表,如果之前没有访问过
github.com,则从保留网段(比如198.18.0.0/15)中分配一个闲置 IP,假设是 198.18.0.1。然后代理核心在表中建立映射:198.18.0.1 <----> github.com。 - 代理核心立即构造一个 DNS 响应返回给操作系统:
github.com的 IP 地址是198.18.0.1,TTL 一般极短,比如 1s(TTL 用于决定 DNS 结果应该被缓存多久)。
浏览器获得 IP 后,进入第二阶段,向目标地址建立连接:
- 浏览器向 IP
198.18.0.1发起 TCP 握手。 - IP 属于保留网段,再次被路由到 Tun 网卡,被代理核心捕获。
- 代理核心看到
198.18.0.1后去查找内存映射表,得知要连接github.com。 - 此时代理核心根据分流规则,决定流量走直连还是代理。如果走代理,则将
github.com封装进代理协议,发给服务端,服务端负责进行 dns 解析。(这也就是为什么 FakeIP 解决了 DNS 污染,因为服务端往往在没有污染的区域,但是后面会讲到,FakeIP 本身又带来了新的污染)如果是直连,则在本地进行 DNS 解析拿到真实 IP 然后建立连接。
FakeIP 的问题:生命周期的错配¶
而我之所以不推荐使用 FakeIP,是因为 FakeIP 与原生的 DNS 查询机制存在不可弥合的矛盾:FakeIP 试图用一个临时的、依赖代理进程状态的虚假映射,去欺骗那些习惯了持久的、全局有效的真实映射的操作系统和应用软件。
正常情况下,操作系统拿到域名后发起 DNS 查询得到的 IP 地址,在 TTL 过期前,该地址在任何时间点、任何网络接口都是有效的。
但设置了 FakeIP 后,代理返回的是虚假的 IP 地址,只是一张互联网临时入场券(只在代理软件运行时有效)。然而,操作系统、浏览器甚至本机各种需要联网的服务并不知道这只是一个冒牌货,它们只会按照既定逻辑缓存代理核心返回的 DNS 结果。
一旦代理软件关闭,映射表消失,但应用层的缓存还在,混乱就开始了,下面讲一下我本人的经历。
FakeIP 对浏览器的污染¶
我的 PC 使用环境是 ArchLinux,因为一些事情临时从 Firefox 迁移到 Chrome,而过去我也用过一段时间 Chrome,当时是习惯开启 sing-box 的 Tun + FakeIP 的,但是我在 Firefox 上一直使用的是 socks 代理,所以到 Chrome 上安装了 ZeroOmega 继续使用 socks 代理,此时我完全没有发现任何 DNS 污染问题。(因为浏览器在设置 socks 代理后,浏览器会改变连接策略:不再本地查询 github.com 的 IP 是多少,而是直接向 socks 代理发送指令————“帮我连接到github.com”。此时 DNS 解析的责任完全移交到代理核心,浏览器无视了我本地存在的 FakeIP 污染)
然后某天上网偶尔发现了 Linux 专属的高性能透明代理 dae,所以就开始了折腾之旅。dae 的配置文件相当清晰,sing-box 的配置虽然也很清晰但是 json 本身的嵌套结构比较多,而 dae 不使用 json 所以没有这个问题。(写配置时推荐安装 vscode 扩展)。
接着发现 dae 只是按照原代理协议进行实现和支持,这很合理,但是我也想使用一些其他代理核心的子协议或者 dae 还没支持的协议,解决方法很简单,只需要让 dae 作为透明代理然后设置 socks 代理,而其他代理核心(sing-box 或者 xray)设置一个 socks 入站,代理节点出站即可。
一切大功告成,启动服务后完美运行,接着去到浏览器禁用 ZeroOmega 扩展,进行谷歌搜索,正常访问,但是弹出了 google.com 想要访问我的本地网络权限申请,我好奇中点击了拒绝,接着我输入 https://github.com,进入网站,再次弹出申请,我点击拒绝,Github 不像 Google,拒绝后完全无法正常工作,只能加载出 HTML 骨架,所有 CSS 和 JS 都挂掉。
但是谷歌搜索和 Github 官网是不可能想要访问我的本地网络的,这有问题,我就重置了权限,提前打开 f12 看网络请求,再次进入有问题的页面,点击拒绝授权,结果发现了发起的各种请求因为我的拒绝失败了,但是这些请求的域名都是外部的网络域名,不是我的本地网络。我马上怀疑到 FakeIP 上,因为 FakeIP 的原理就是映射到私有地址,首先我去查看了 Chrome 142 的关于收紧网络权限的更新说明,其中列举了受影响的所有网段,就包括我们常用的 FakeIP 段:198.18.0.0/15 和 fc00::/18:
// sing-box 常见的 FakeIP 配置:
{
"tag": "remote",
"type": "fakeip",
"inet4_range": "198.18.0.0/15",
"inet6_range": "fc00::/18"
}
chrome://net-internals/#dns 中清除本地 DNS 缓存,点击 Clear host cache,输入 chrome://restart 重启浏览器测试,发现无效,非无痕模式下拒绝内网访问后 Github 依旧无法加载;我又去 chrome://net-internals/#sockets 然后 Flush socket pools,当然还是无效,我又去操作系统层面重启 NetworkManager,重启系统,还回归浏览器清问题网站的 cookie,清 cache,清 application data,结果问题依旧。
到这里我真的抓狂了,因为还有更诡异的事情,明明我的电脑环境中已经不存在任何 FakeIP 设置了,但是当我允许 Github 访问内网时,Github 奇迹的正常显示了,这一度让我认为我错怪 FakeIP 了。直到解决问题好几天之后我才反应过来,是 sniff 在帮我擦屁股,浏览器因为缓存了错误的 IP,依旧朝着 FakeIP 发起连接。但透明代理在收到连接后,通过读取 SNI 等方式嗅探到了真实域名,然后直接按域名分流、建立连接,完全无视了被污染的 IP。所以网页“莫名其妙”的正常加载了。
最后我是怎么解决问题的呢?我直接清除了浏览器数据,问题解决后我马上拿起手机打开代理软件关闭了 FakeIP 选项,我的手机系统是 LineageOS,刷了 gapp 默认浏览器是 Chrome,但是 Chrome 并没有受到影响,因为我打开了代理软件中的 Append HTTP Proxy to VPN 所以我的 Chrome 一直是通过 HTTP 代理上网的,和上文提到的设置了 socks 代理类似,所以避免了 FakeIP 污染,但是我手机上的其他浏览器就没这么好运了,比如 Cromite,打开 Github 后出现了和 PC 上完全一致的行为,拒绝内网访问就无法加载网页,而无痕模式正常,也在手机上尝试了 Clear host cache 等方法,自然也是无效,最后也是清除应用数据解决的。
为什么只能清除应用数据才能解决问题?经过后续查阅资料和分析,问题的根源大概在于 Service Worker 的持久化缓存机制。像 Github 这种典型的 PWA 应用会在浏览器内安装一个 Service Worker(后文使用 SW 指代),SW 会充当浏览器和网络之间的代理,当网页发起请求时,SW 可以拦截这些请求,并决定如何响应。而 SW 的生命周期独立于网页,甚至你重启浏览器,SW 还活着,而在无痕模式下,浏览器禁止使用已有的 SW,并在新的、隔离的环境中启动,所以无痕模式不受影响。而我上面各种 flush dns 不奏效的原因就是只清空操作系统和 Chrome 网络服务的 DNS 缓存,完全触碰不到 SW 的 Cache Storage。
你可能还会好奇,为什么 FakeIP 极短的 TTL 救不了我,因为 DNS 的 TTL 只是“传输层”的过期时间,而 SW 是工作在“应用层”的。当 SW 将一次请求的结果(或上下文)存入 Cache Storage 时,它是持久化存储的。它根本不关心当初那个 DNS 记录是不是 1 秒后就作废。只要 SW 不卸载、缓存不清理,浏览器就会根据 SW 的逻辑,执着地尝试复用之前建立的连接或安全上下文,从而陷入死循环。这就是为什么必须“清除浏览器数据”——这不仅是清缓存,更是为了杀掉这个“记忆错乱”的 SW。
总结:拒绝“叠罗汉”式的网络架构¶
在软件中每多引入一层,就多一层的复杂度,也就多了一层的不确定性。让我们看看为了这个所谓的“DNS 零延迟”引入了多少层复杂度:
- DNS 层:引入 FakeIP 映射表。
- 存储层:导致浏览器和 OS 缓存了错误的 IP。
- 安全层:触发 Chrome LNA 保护,被迫点击“允许”。
- 补救层:依赖代理软件的 Sniffing 来修正错误的 IP 路由。
这是一个摇摇欲坠的“叠罗汉”。 只要中间任何一个环节出问题,整个网络访问就会瞬间崩塌。
更何况由于初学者的不当配置,很多人根本没有享受到 FakeIP 带来的“DNS 零延迟”,比如浏览器开启了外部 secure dns 导致浏览器依旧进行 DNS 解析,或者没配置 IPv6 FakeIP 段引入了额外的延迟...就算所有的坑你都避开了,最后你还得配置白名单让相当一部分域名进行真实的 IP 解析,这简直自讨苦吃。
然后对于开发者,开启 FakeIP 更是多害少利,F12 想看一下连接 IP 发现全是内网地址,ping 一下发现全是 1ms,trace 路径完全错误...
最后我想说,实际上由于 DNS 缓存没有 FakeIP 体验并不差,大多数 CDN 分发的静态资源域名 TTL 都在数分钟甚至数小时...,实际查询一次后面都是 0 延迟,完全没必要为了 FakeIP 那一丁点收益引入各种复杂性。Real-IP 模式配合现代的分流策略(国内直连 DNS + 国外远端 DNS),在绝大多数网络环境下已经足够快且极其稳定。
一句话
FakeIP 为了解决“DNS 延迟”和“污染”问题,结果引入了远远超过问题本身的复杂度。
补充说明
FakeIP 对不可嗅探协议几乎没有任何实际价值(SSH,ICMP 等其他私有未知协议)。
一直开着 FakeIP 的情况下,如果 DNS 分流规则不够全面浏览器仍会弹窗。