终止开关失败的方向往往出人意料:它的设计目的是在隧道断开时阻断流量,而用来建立隧道的那些数据包本身就是流量。这就是问题的全部。一套把不经隧道外出的流量统统丢弃的规则,也会把本该把隧道拉起来的握手丢掉,于是客户端循环重试,错误被归到最先检查的那一步。扩大防火墙之前,先弄清三个阶段中哪一个被拒绝:名称解析、发往对端的外层数据包,还是返回的应答。

隧道与它自身的传输如何相互依赖

把 VPN 客户端看作共享同一条套接字路径的两种网络状态。隧道还不存在时,客户端在物理网卡上打开套接字,把加密帧发往对端地址和端口。只有这次交换成功之后,它才创建隧道设备并安装路由。终止开关是针对第二种状态的政策,却写得宽到两种状态都管。

基于接口的规则是最常见的形式:放行从tun0或wg0出去的流量,丢弃其余出站路径。这种写法无法豁免客户端自己到服务器的连接,所以正确的实现会显式加上这条例外,手写规则时你也该这样做。Windows 与 macOS 上按应用匹配的规则用进程代替接口,能避开一部分问题,但进程同样必须在隧道存在之前先出去。

三个阶段,按发生顺序

第一阶段是解析。当配置保存的是主机名而不是字面地址时,客户端必须先拿到 DNS 应答才能发送任何东西。把 DNS 指向隧道内解析器的配置会造成死锁:解析器只能经由本应由这次查询打开的隧道访问。在隧道之外解析,或把端点固定成一个地址,就能消除这种含糊。

第二阶段是外层数据包,它必须先过路由、再过防火墙,顺序如此。对 WireGuard 而言,wireguard-tools 承担了其中路由的那一半:它标记自己的数据包并添加一条策略规则,让这些包绕过刚刚安装的默认路由;wg-quick 手册页描述了那条规则以及它使用的表。手动重建这套路由,或者让第二个工具重写规则,被标记的数据包就可能被送回它们本该建立的隧道里。

第三阶段是回程路径。没有应答的握手并不能证明出站被丢弃,它同样符合服务器入站规则或忘了映射的 NAT 设备。动客户端之前先把这两种情况分开。

What you seeStageWhere to look
The client log repeats a resolve step and no packets appear on the wire1, name lookupResolver path while the tunnel is down
The send counter on the physical adapter stays flat2, routingPeer route and policy rules
The send counter rises and a firewall drop counter rises2, firewallRule order, mark, and interface match
Packets leave and nothing comes back3, return pathServer input rules, NAT, listening port

客户端记录的是它检测到的症状,而不是失败的那一步,所以对照表通常比日志快。

计数器比日志更快给出结论

在 Linux 上,nft list ruleset或iptables -vnL OUTPUT会按规则打印数据包计数器。连接一次、让它失败,再读一遍计数器。计数器动了的那条规则,就是握手撞上的规则;如果哪儿都没动,包根本没到防火墙。我见过一套规则把它自己的握手丢掉,而说出这点的只有计数器。

要区分「从未到防火墙」和「从未离开主机」,盯住接口本身。ip -s link show dev wlan0报告已发送的数据包,而tcpdump -ni wlan0 udp port 51820显示帧。如果客户端反复重试时它一直平着不动,故障就在 netfilter 之上:缺少对端路由、策略规则指向尚不存在的设备,或者客户端吞掉了某个套接字错误。

用文档地址测试例外,例如203.0.113.10,这样一次失误不会去探测别人的主机。

过期的端点例外会被读成服务器故障

最容易误导人的情形出现在对端地址变化之后:动态地址服务把它挪了、主机被重建,或者你网络出口地址不同了。例外里写的还是旧地址,于是发往新地址的包撞上丢弃规则。同一套配置昨天还好用,所以人们先从服务器查起。

防火墙规则里写主机名救不了你。在 nftables 里,名称是在加载规则集时解析的,所以它把当时存在的地址钉死,随后和字面地址一样过期。持久的做法是在端点变化时更新例外,并让它保持很窄:一个对端地址、一个传输端口。宽到能扛住地址变化的范围,也宽到能放无关流量出去。

当终止开关跑在服务器主机上

同样的依赖在另一端也存在,而且更容易被忽略。如果充当 VPN 服务器的那台机器同时跑着带终止开关的桌面客户端,或用了从笔记本抄来的防火墙配置,它的丢弃规则就会作用在终结隧道的服务上。客户端握手的应答被丢弃,于是每个客户端都连不上,而服务器看起来一切正常,隧道接口空着没人用。

那台主机在丢弃之前需要三条很窄的放行:物理接口上的监听端口、用于解密后流量的隧道接口,以及从隧道转发到客户端需要到达的地方。用nft list ruleset、iptables -S或pfctl -sr读一下已安装的规则集,然后再动手改,并记住保存下来的规则集会熬过重启,所以那里的错误比做出它的进程活得更久。

让机器保持关闭状态的恢复顺序

从配置往外做。读出客户端使用的对端地址和端口,确认不依赖隧道也能解析主机名,然后只在物理网卡上为那个地址和端口加一条例外。重新连接并观察发送计数器;握手完成就是第二阶段修好的信号。

测试期间让其余的丢弃策略留在原处。一个无害的检查是尝试连接一个无关的文档地址,确认它仍然超时,这说明终止开关在工作,而不是被关掉了。隧道稳定后再把例外收紧,不要图方便就一直给客户端程序留一条永久全放行。

如果隧道起来了而应用仍然失败,剩下的阶段就不是这一步:拆分 DNS、MTU 和按应用路由,各自都会造成隧道已连接但流量不通,而且每种的第一个检查都不一样。

相关文章