两个测试页面结果不一致:一个报告的是隧道对端的运营商地址,另一个报告的是你家里的前缀,于是显而易见的结论是 VPN 在泄漏。这个结论跳过了一步:两份报告触发的是两个不同的路由决策,而其中只有一个本该进入隧道。缺口通常是真实的,它属于覆盖问题,而不是隧道坏了。三个问题能定论:究竟是哪条流出去了、内核为它选了哪条路由、客户端本意是什么行为。

按这个顺序来。顺序颠倒就会变成一场关于客户端是否有缺陷的长争论,而真正的答案是从未安装过任何 IPv6 路由。

究竟是哪条流出去了,走的是哪块网卡

先做一个写明所用地址族的测试,然后记下应用、接入网络、当前生效的配置文件,以及这条连接到底是否提供 IPv6。这四项事实才能让两次运行可比,而第四项最常被忘记:没有 v6 前缀的网络不可能泄漏 v6,而后来加上前缀的网络会改变答案。

按地址族各发一个请求,而不是加载一个页面再读它的结果。单个页面可能混用 v6 与 v4 资源,后台进程的行为可能与眼前的标签页不同,缓存中的回答也可能比你要验证的网络变更活得更久。固定被测目标并记录下来,让重复运行测量的是同一件事。

读取内核选中的路由

路由表回答的是浏览器答不了的问题。向系统查询某个具体目的地会走向哪里,并把策略规则和可见的默认路由都算进去,因为两者都可能把它覆盖掉:

ip -6 route get 2606:4700:4700::1111
ip -6 rule show
ip -6 route show table all

更具体的前缀是某个目的地留在隧道之外、其余全在隧道之内的常见原因,而策略规则能以高于任何路由的优先级做到同样的事。也要读度量值,因为同一前缀的两条记录是按度量值而不是按屏幕上的顺序决胜的。在 Windows 上等价的列表是 Get-NetRoute -AddressFamily IPv6,按度量值排序以对应决策顺序。在表里看着正确的规则,仍可能输给编号更小的策略规则,所以规则列表与路由列表要在同一次呼吸里读完。

客户端的 IPv6 策略从哪里来

客户端可以被做成把 v6 送进隧道,也可以被做成拒绝它,或允许某个具名例外;这三种意图会产生三种不同的正确结果。在把某个观察称作泄漏之前,先读配置文件和客户端自己的文档,因为一个在操作系统仍在物理链路上发送邻居发现时封锁 v6 的客户端,正是按设计在行为。

被有意排除在隧道之外的应用,与隧道从未承载过的地址族是两回事。前者是对一个程序的决策,后者是覆盖上的缺口,修法也相应不同。在去找一条本不该存在的路由之前,先读客户端的排除程序清单。

窄口径观测:当路由表互相矛盾时

当路由查询结果含糊时,主机上的一次接口观测就能定论,但前提是它瞄准的是那个问题。发往 VPN 端点自身的流量按设计会走物理网卡;真正要紧的是被指向完全别处的应用流是否也出现在那里。把抓包过滤到被测目的地,而不是全都收下来。

宽口径抓包暴露的个人流量远超这一个问题所需。它给出的答案也更不清楚,因为你关心的那条流是埋在一堆无关连接里到达的。过滤到被测目的地,抓几个包就停,然后删掉抓包文件。

补上缺口的三种做法

把地址族送进隧道;当隧道承载不了时在客户端封锁它;或者记录一条带负责人和复核日期的显式例外。三种都站得住,唯一站不住的是把选择留在隐含状态,因为泄漏的本质就是一个隐含的选择。如果隧道根本没有 v6 出口,在客户端拒绝 v6 既诚实又可验证。要紧的是把这个选择写在下一个接手的人能找到的地方,而不是半年后从行为里反推。

在重新连接之后、以及在网络之间移动之后重做检查,因为为上一段链路装上的路由可能熬过这次变更。然后把目的地、选中的路由、客户端策略和测试所用的地址族都记下来,让下一个人读到的是一个决定,而不是重新推导一遍。RFC 7359 把双栈泄漏记为设计问题而非缺陷,当有人声称一条能用的 v4 隧道就把所有情况都覆盖了时,该引用的正是它。

相关文章