终止开关在流量开始前就挡住 VPN 服务器
终止开关究竟卡在哪一步:端点解析、外层路由、回程路径与过期的对端例外,用计数器而不是猜测来判断。
终止开关失败的方向往往出人意料:它的设计目的是在隧道断开时阻断流量,而用来建立隧道的那些数据包本身就是流量。这就是问题的全部。一套把不经隧道外出的流量统统丢弃的规则,也会把本该把隧道拉起来的握手丢掉,于是客户端循环重试,错误被归到最先检查的那一步。扩大防火墙之前,先弄清三个阶段中哪一个被拒绝:名称解析、发往对端的外层数据包,还是返回的应答。
隧道与它自身的传输如何相互依赖
把 VPN 客户端看作共享同一条套接字路径的两种网络状态。隧道还不存在时,客户端在物理网卡上打开套接字,把加密帧发往对端地址和端口。只有这次交换成功之后,它才创建隧道设备并安装路由。终止开关是针对第二种状态的政策,却写得宽到两种状态都管。
基于接口的规则是最常见的形式:放行从tun0或wg0出去的流量,丢弃其余出站路径。这种写法无法豁免客户端自己到服务器的连接,所以正确的实现会显式加上这条例外,手写规则时你也该这样做。Windows 与 macOS 上按应用匹配的规则用进程代替接口,能避开一部分问题,但进程同样必须在隧道存在之前先出去。
三个阶段,按发生顺序
第一阶段是解析。当配置保存的是主机名而不是字面地址时,客户端必须先拿到 DNS 应答才能发送任何东西。把 DNS 指向隧道内解析器的配置会造成死锁:解析器只能经由本应由这次查询打开的隧道访问。在隧道之外解析,或把端点固定成一个地址,就能消除这种含糊。
第二阶段是外层数据包,它必须先过路由、再过防火墙,顺序如此。对 WireGuard 而言,wireguard-tools 承担了其中路由的那一半:它标记自己的数据包并添加一条策略规则,让这些包绕过刚刚安装的默认路由;wg-quick 手册页描述了那条规则以及它使用的表。手动重建这套路由,或者让第二个工具重写规则,被标记的数据包就可能被送回它们本该建立的隧道里。
第三阶段是回程路径。没有应答的握手并不能证明出站被丢弃,它同样符合服务器入站规则或忘了映射的 NAT 设备。动客户端之前先把这两种情况分开。
| What you see | Stage | Where to look |
|---|---|---|
| The client log repeats a resolve step and no packets appear on the wire | 1, name lookup | Resolver path while the tunnel is down |
| The send counter on the physical adapter stays flat | 2, routing | Peer route and policy rules |
| The send counter rises and a firewall drop counter rises | 2, firewall | Rule order, mark, and interface match |
| Packets leave and nothing comes back | 3, return path | Server 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 和按应用路由,各自都会造成隧道已连接但流量不通,而且每种的第一个检查都不一样。