问大多数人 VPN 客户端对 DNS 做了什么,他们会说它设置了解析器,所以查询走在隧道里。推送是真的;但推送会变成查询,这个假设并不成立。从被推送的选项到线上的数据包之间隔着三个阶段,而每个阶段都会独立失败,失败的样子都像一个坏掉的 DNS 服务器。这就是为什么泄漏测试页面和能正常工作的浏览器,会在同一时刻对同一台机器给出相反的结论。

推送的选项不等于查询

第一阶段是服务器给出设置,例如推送一个指向隧道内解析器的 DNS 选项。第二阶段是客户端把这些设置应用到某个接口上。第三阶段是应用程序真的向那个地址发出查询。第二阶段失败时,选项仍会留在连接日志里,而解析器列表毫无变化;第三阶段失败时,一份完全正确的解析器列表就白白搁着不用。

大多数排障把这三件事揉成一件,然后责怪 VPN 服务器,而它通常是唯一能证明自己做了本职的一方。不如去读客户端真实的解析器状态,再观察一次查询离开本机。这两步观察把配置、路由和策略区分开来,花费大约五分钟。把抓包留着,因为下一位工程师想要的是前后对比,而不是一段描述。

检查接口真正持有什么

在 Linux 上,系统解析器可能是普通文件、NetworkManager 或 systemd-resolved,究竟谁拥有它,决定了被推送的服务器落在哪里。命令行启动的 OpenVPN 进程不会自己更新系统解析器,所以文档里的集成方式是 up/down 脚本,或者一个本来就懂得应用这些设置的连接管理器。两者同时跑,是制造竞态的高效办法。

resolvectl status
cat /etc/resolv.conf

在 Windows 上解析器列表是按适配器分开的,所以隧道适配器可以持有内网服务器,而物理适配器保留本地服务器,并在某些名字上胜出。要比较两个适配器,而不是假设中的那个活动适配器。

跟着一次查询走出本机

同时抓两个接口,然后解析一个本应使用内网解析器的名字。这一对抓包回答了任何配置输出都回答不了的问题。

sudo tcpdump -ni tun0 port 53
sudo tcpdump -ni any port 53

查询出现在物理接口上却从不出现在隧道里,说明客户端选错了解析器。两边都看不到查询,说明应用从缓存里回答了,或者问了监听在 loopback 上的桩,或者用了加密解析器,其流量根本不会以 53 端口出现。既要抓查询也要抓应答,因为查询从隧道出去、却收到来自公网地址的应答,与根本没有应答是两种不同的故障。

只接受自己 DNS 的防火墙

商业客户端有时会装上自己的过滤链。一种有文档的模式允许 DNS 发往客户端自己的解析器,并丢弃所有试图离开非 loopback 接口的其余 UDP 53 端口流量,包括用户故意引到别处的流量。守护进程的查询无声消失,于是被读成解析器故障,而不是过滤器。

sudo iptables -S OUTPUT
sudo iptables -S | grep -i -E 'dns|53'

如果丢弃规则排在你的例外之前被求值,后来追加的规则就永远到不了,所以要插到第一位,并把目的地限定为你真正想要的那个解析器。

sudo iptables -I OUTPUT 1 -p udp --dport 53 -d 172.20.8.1 -j ACCEPT

那个例外是诊断,不是策略。如果厂商规则是有意为之,受支持的修法是把客户端配置成允许该解析器,并把决定记录下来,因为插进去的规则撑不过下一次客户端升级。

为什么泄漏页面证明的东西比看上去少

泄漏测试页面显示的是与测试运营方通话的那个解析器,而不是你机器查询的那个。向外递归的本地转发器会把自己的出口地址写到页面上,而加密查询可能让页面完全空白。两种结果都被误读,一个当成泄漏,一个当成保证,而这两种解读都不来自观察。

站得住的证据是抓包:接口、目的地址、应答和时间戳。在隧道起来之前抓一份,起来之后再抓一份,下次某个名字失败时拿来对比。这一对还能告诉下一个人,故障出在解析、路由,还是解析器自己的访问策略上。

相关文章