为了修 VPN 泄漏就关掉 IPv6?不如先把策略定下来
链路本地地址不是泄漏,全局 IPv6 路由也不是。先决定是阻断、承载还是不动。
测试页面上出现了一个 IPv6 网段的地址,隧道显然没有承载它,而网上最流行的建议是把 IPv6 到处关掉。那条命令确实能让面板安静下来。但它同时移除了接入网络可能依赖的协议,也让你无从判断这个地址究竟是真正的泄漏、链路本地接口地址,还是解析器在正常工作。决定正确做法的是三个条件,而只有其中一个通向阻断。
先搞清楚工具到底测到了什么
最常见的误报是链路本地地址,它以 fe80: 开头,存在于每一个支持 IPv6 的接口上,无论这台机器能否通过 IPv6 访问互联网。它会出现在接口列表和某些应用诊断里,与你的流量路径毫无关系。
第二种误报是 fc00::/7 范围内的唯一本地地址,这是网络内部使用的私有网段。把所有主机地址都打印出来的工具会乐于把它列在公网地址旁边。读得快的人会把它叫作泄漏,然后在一个并不存在的问题上花掉一小时。
第三种根本不是误报。能在隧道之外到达互联网的全局地址才是真正的泄漏,值得在做出任何决定之前先测一测:
ip -6 addr show scope global
ip -6 route show default
curl -6 -sS --max-time 6 https://ifconfig.co
如果第一条命令打印出全局地址,而第三条返回了一个不属于隧道的公网 IPv6 地址,那么你确实有一条绕开隧道的路径。如果第一条只打印出链路本地条目,那就从来没有需要修的泄漏。
三个条件决定该用哪种控制
决定不是「IPv6 好」或「IPv6 坏」,而是你处在三种情况中的哪一种,每种都有不同的正确做法,做错时的代价也不同。
| Situation | Correct control | Cost if you disable IPv6 instead |
|---|---|---|
| Exit has no IPv6, host does | Block IPv6 deliberately in the tunnel policy | Same visible result, no documentation of intent |
| Exit carries IPv6, host leaks | Route IPv6 into the tunnel and verify | You lose IPv6 reachability entirely |
| Access network is IPv6-only | Change nothing; use the tunnel's IPv6 | Machine loses connectivity completely |
第一行是人们通常所指的情况,也是合理的。出口不支持 IPv6 的客户端不该悄悄放主机自带的 IPv6 流量出去,因为那正是有据可查的双栈泄漏。要点在于把它作为明示的策略来阻断,而不是在内核里关掉一个协议。两者可见的结果相同,而只有一种日后还能被审查。
第三行才是一刀切建议真正有害的地方。移动运营商和部分家庭宽带运行的是 IPv6 单栈网络,对 IPv4 目的地做转换。在那里去掉 IPv6,就等于去掉网络提供的唯一传输方式,造成的故障比你正在排查的那个严重得多。
如果出口没有 IPv6,就有意地阻断它
优先用客户端自身的机制,因为它在隧道建立期间才生效,隧道断开时自动撤销。OpenVPN为此提供了选项,其他客户端则可以用 iptables 的等价规则:
block-ipv6
ip6tables -A OUTPUT -o eth0 -j REJECT --reject-with ipv6-port-unreachable
指令应该写在配置文件里,规则应该写在你能审查和删除的防火墙里。两者都是失败即关闭,这正是你要的,而且两者都能在配置差异里看到;埋在启动脚本里的内核 sysctl 做不到这一点。
记下它为什么在那儿。没有注释的阻断规则会被下一个需要 IPv6 做别的事的人删掉,然后泄漏就悄悄回来了。
如果出口承载 IPv6,就为它做路由,并把两个协议族都测一遍
服务器支持时,承载 IPv6 是更好的答案,而且难度并不高多少。要求是隧道接口在该协议族里有一个地址,并且它的默认路由指向隧道内部,而不是走物理接口出去:
ip -6 route get 2606:4700:4700::1111
ip -6 route show default
curl -6 -sS --max-time 6 https://ifconfig.co
curl -4 -sS --max-time 6 https://ifconfig.co
路由查询才是决定性的测试:如果它点名了隧道接口,说明 IPv6 流量正在被承载。随后两次 curl 调用确认两个协议族各自看到什么,把它们放在一起比较,才能证明两条路径现在走的是同一个出口。
别停在连接成功上。重连隧道再查一次,因为服务器只在初始配置里下发时,推送的 IPv6 路由可能在重新建立后丢失。接着再查一个被排除的目的地,因为分流隧道的规则正是两个协议族最容易不一致的地方。
如果接入网络只有 IPv6,就不要动它
有些网络只提供 IPv6,并指望设备通过运营商侧的转换机制访问 IPv4 服务。在这种机器上关掉 IPv6 不是安全改进,而是砍掉了通往任何地方的唯一路径。这是为了一个谁都不需要的测试结果而自找的故障。
这里做错的症状很特别也很难受:隧道看起来建立了,域名解析也正常,但什么都加载不出来。如果改动 IPv6 之后出现这种情况,立刻恢复设置,换个地方继续排查。
无论你选哪一种,都要把决定明确写进配置文件或防火墙,把它绑在出口真正支持的能力上,并记下RFC 7359 关于双栈的讨论所描述的双栈行为。明示的阻断是一种策略。被关掉的协议是一种没人能审计的绕行办法,而从测试页面看,两者一模一样。