VPN 路由还是防火墙规则:究竟是谁把数据包丢了?
路由选择路径,过滤决定是否放行。扩大防火墙规则之前,先按同一源、目的和方向把两者都查一遍。
同一个数据包会遇到两个彼此独立的判断,其中任何一个都可能让请求无声地结束。路由查找选定出接口和下一跳,随后包过滤决定这一组合是否放行。超时只告诉你两者之一拒绝了,而不是哪一个。按顺序逐段排查,修复通常就不再靠猜。
两个判断,一个数据包,两个负责人
路由查找先查策略规则,再查规则指向的表,并选出与目的地址匹配的最具体前缀。安装了隧道路由的客户端拥有这个判断,防火墙规则里的任何内容都无法替代它。当查找指向意料之外的位置时,后面每一段都在处理错误的数据包。
过滤是另一回事。在 Linux 主机上,netfilter 钩子按数据包的归属分流:出站离开本机,入站终结于本机,转发穿过本机。接受 VPN 客户端并把它们路由到 LAN 的网关需要转发路径,而这正是大多数默认配置留空的那条链。
源地址选择属于同一个判断,大多数不一致就藏在这里。除非路由指定,内核会从出接口选一个源地址,因此为 LAN 子网写的过滤规则,永远不会匹配带着隧道地址出去的数据包。
读懂查找实际选了什么
把判断打印出来,而不是靠推断。下面的命令解析内核为该目的地址会用的路由,包括接口和它会选择的源地址:
ip route get 10.8.0.5 ip route get 10.8.0.5 from 192.0.2.44 ip rule show ip route show table all | sed -n '1,40p'
VPN 客户端经常把策略规则装在主表以下的优先级,所以主表干净本身什么都证明不了。把规则的优先级与它们选择的表对照,并检查隧道路由能否在重连后保留,而不是只手工添加一次。
基于策略的 IPsec 增加了路由不覆盖的第三层匹配:strongSwan 的转发说明描述了隧道与传输两种配置。安全策略按前缀和协议选流量,路由只决定未受保护的包会往哪里走。被路由但没有匹配选择器的子网,要么保持不受保护,要么按配置被丢弃,两种结果都不像防火墙问题。打印策略,并把它选择器与预期隧道承载的前缀对照。
过滤发生在钩子上,而不是路由上
允许 VPN 监听的规则是关于发往网关本身的流量的。它对网关后面主机的客户端只字未提,因为被转发的流量走的是另一条链。把规则集当成一张平铺列表来读,正是看起来正确的规则从不触发的原因。
计数器能很快定论。包计数为零的规则从未被命中,指向错误的链、错误的接口或更早的丢弃。丢弃策略下计数仍在增长的规则,就是你要找的边界,数据包是被丢弃而不是被错误路由。
用客户端实际走的路径发包探测
在网关本机跑的测试走的是出站和入站,用的是网关自己的源地址。那和客户端被转发的请求是不同路径,所以本地 curl 成功能证明的东西比看上去少。从失败的那台机器测,或者显式固定接口和源地址。
nc -vz -w 3 10.8.0.5 443 ping -c 3 -W 1 10.8.0.5 tcpdump -ni any host 10.8.0.5 and port 443 iptables -vnL FORWARD --line-numbers nft list ruleset | sed -n '1,60p'
两端同时抓包能分开剩下的情况。SYN 离开客户端却从未到达,说明是路由、加密或上游访问列表。SYN 到达却没有回应,说明是服务或其主机过滤,隧道从来不是问题。
回程流量值得单独看。非对称路由加上反向路径过滤,会丢掉从错误接口回来的响应,表现为单向连接而不是端口被挡。先查 sysctl 值,再怪防火墙造成了它没造成的症状。
日志是第二意见,不是第一步。转发链上的临时日志会写出命中的接口和地址,事后也容易删除。长期留着,你得到的不是答案,而是日志量问题。
把每处改动放在重连后仍然有效的地方
用 ip route add 添加的路由,接口一倒就消失。改为写进连接配置:NetworkManager 接受连接上的额外路由,netplan 让它们跨重启保留。客户端防火墙例外同理,下次重载后就没了。
服务端规则属于服务启动时加载的配置文件,而不是你关掉就结束的交互会话。写完规则、重载,并确认计数器动了,因为语法被拒的重载会悄悄把旧规则留在原处。
然后从头到尾重跑原来的应用请求,记下哪一层变了。需要同时改过滤的路由修复仍会失败,而知道第一个问题已消失之后,第二次失败读起来容易得多。把日期和客户端版本记在结果旁边,因为路由和过滤的默认值都会随版本变化。把这条记录留在工单上,让下一个人从你停下的地方开始,而不是把两段排查再走一遍。