VPN 一连接,域名解析立刻中断,而向公网地址发 ping 仍有回应,防火墙状态也看不出任何异常。策略本身什么都没变;变的是出现了一个新接口,而默认拒绝从第一个数据包起就把它挡住了。UFW 按方向和接口做判断,所以为无线链路写的规则对离开隧道的流量毫无说明。修复办法是几条按特定顺序应用的窄规则。

症状只在隧道起来之后才出现

默认拒绝策略正在按吩咐行事:凡是没匹配规则的流量,在每一个存在的接口上都被拒绝。规则写就时隧道还不存在,所以没有一条能匹配它,而一直存在的拒绝现在落在了域名解析上。发往字面地址的直接流量,在有更宽泛的放行规则覆盖时仍能工作,这就是为什么 ping 公网解析服务器成功、查名字却失败。缺口在于:策略从没见过这个接口,因而没有针对它的规则。

这同时解释了故障为何能干净地反转:拆掉隧道,域名又能解析,因为查询回到了有规则的接口上。只在隧道起来时失败的策略不是 DNS 问题,在这里去调解析器本身最浪费时间。先读接口列表,再读解析器配置。

三个方向,三套不同的规则

一次会话会触及不止一个方向的规则,而隧道几乎给每个方向都加了第二个接口。先按方向和接口把流量归类,剩下的就是机械工作,因为下面每一行都需要在自己位置上的一条规则。示例使用 tun0 和 10.8.0.0/24 地址池,请把名称和范围换成客户端自己输出里的值。

DirectionExample packetRule that admits itWritten on
Client to the pushed resolverA DNS query to 10.8.0.1Destination port 53 over the tunnelThe client's tun0
Client to the VPN serverA handshake to the server's public addressServer address and port on the physical linkThe client's physical interface
Server to a serviceSSH from the pool to port 22Source pool, destination port, tunnel interfaceThe server's tun0
Anyone to the listenerThe VPN listener itself1194/udp on the public sideThe server's public interface

有两行经常被混淆,因为两行都提到了隧道。解析器那条规则放行客户端送进隧道的流量,而 SSH 那条放行服务器从隧道收到的流量;它们住在不同的机器上,各自独立失败。最常被忘掉的是第二行,因为在客户端发往服务器公网地址的流量被放行之前,隧道根本建不起来。从上往下逐条处理,每次失败就只有一个候选原因。

被推送的解析器需要自己的规则

隧道是否承载 DNS,取决于服务器推送了什么。推送解析器选项的服务器会在客户端配置里装一个私有地址,而那个地址只能经隧道路由。如果服务器什么都不推,客户端就保留原有的解析器,下面这些规则也就没有可放行的对象。动手之前先检查推送状态和当前查询结果:

dig +short @10.8.0.1 example.com
resolvectl status tun0
sudo ufw status numbered

状态输出会给出正在使用的解析器和它绑定的链路,它背后的路由查询则显示哪个接口会把查询送往那个地址。当解析器位于另一条链路上时,问题比防火墙更早,任何规则都帮不上忙。当两者都指向隧道时,就需要两条规则,因为大多数查询走 UDP,而应答被截断时由 TCP 接手。把两个值都记下来,因为规则要引用的正是它们:

sudo ufw allow out on tun0 to 10.8.0.1 port 53 proto udp comment 'pushed resolver'
sudo ufw allow out on tun0 to 10.8.0.1 port 53 proto tcp comment 'truncated answers'

先开外面的路,再开里面的路

客户端必须在隧道存在之前就能到达服务器的公网地址,所以这条规则落在物理链路上,必须先就位。只放行解析器却忘了服务器的策略,会让客户端根本建不起隧道,而这种症状通常被归咎于 kill switch。然后再放行你真正想经隧道使用的服务,一次一条窄规则,而不是把整个接口敞开。窄规则正是默认拒绝还能留在原地的原因。

sudo ufw allow out to 198.51.100.10 port 1194 proto udp comment 'vpn server'
sudo ufw allow out on tun0 to any port 443 proto tcp comment 'web through tunnel'

测试时顺序很重要,因为正是第一条规则让隧道能够起来。加上它、连上、确认隧道已起来,再加服务规则,因为两件事一起做的话,失败就无法告诉你哪一半是错的。把上面的文档地址换成客户端实际配置的端点。

故意断开连接,把客户端这一侧收尾。拒绝仍应拦住你刻意排除的流量,因为隧道起来时会应答的解析器,只覆盖了有疑即拒策略的一半。

连上的客户端还没有被放行

服务器有它自己的方向要考虑,完成握手的客户端在服务器放行之前仍然到不了服务器上运行的任何东西。公网监听那条规则只允许隧道存在,并不放隧道的流量进来。在服务器上,这是两条规则加上一次检查,确认服务确实在监听:

sudo ufw allow 1194/udp comment 'openvpn listener'
sudo ufw limit in on tun0 from 10.8.0.0/24 to any port 22 proto tcp comment 'admin over tunnel'
ss -ltn | grep ':22'

规则里的地址池必须等于服务器分配的网段,隧道编号也必须与服务器创建的一致,因为针对另一个接口的规则和无规则无法区分。limit 对管理端口是有意为之,因为它限制重复连接,而不是无限制地放行。监听检查把关闭的端口和被丢弃的数据包分开,这两者从客户端看完全一样。带编号的规则也不是全貌:UFW 的 before 规则和任何手写 nftables 行都会更早匹配,仍可能拒绝列表看起来放行的数据包。

相关文章