让一个应用走 WireGuard:绑定不等于路由
把应用绑定到 wg0 只设置了源地址。内核仍然需要一张装有隧道路由的表,以及一条把该源地址送进这张表的规则。
这个设置看起来像是按应用区分的开关,所以人们把客户端绑定到 WireGuard 接口,期望只有该客户端使用隧道。绑定实际做的只是选择一个源地址。随后内核为目的地查路由,找到普通的默认路由,把隧道地址挂上去,再从物理链路把包发出去。只有为这个源地址选中的路由才实现了本意。
机制有两半:一张只该给隧道流量使用的路由表,以及一条把匹配的源地址送进那张表的规则。
绑定只设置源地址,仅此而已
Linux 先让策略规则有机会改写这次查找,然后在路由表里查目的地来决定出口接口。规则可以按源地址、防火墙标记、入口接口、UID,甚至端口来选;每一种都是在说这部分流量而不是全部流量。VPN 客户端的配置选择装有隧道路由的那张表,规则选择哪些流量可以查这张表。只做绑定,两样都没有。
这就是为什么这个故障看起来如此奇怪。应用确实在用隧道地址,隧道确实起来了,流量也确实从错误的接口出去了。每句话都对,合起来却描述了一个没有任何东西把地址和路由连起来的系统。
给隧道一张自己的表
wg-quick 会把路由装到配置文件指定的地方。在 [Interface] 下加 Table = 40 会把路由挪进 40 号表,而不是主表。拉起接口并确认它们到位:ip -4 route ls table 40 应该显示隧道的路由,ip -4 route get 10.66.127.142 则显示内核当前如何解析其中一条。路由进了自己的表,主表就继续为其余一切工作。
IPv6 也这样做,不要去匹配一个很宽的前缀。ip -6 rule add from fd00::2/128 lookup 40 固定一个地址;fc00::/8 可能和主机上的其他接口冲突,生成一条比预期匹配更宽的规则。
加上源规则,并观察计数器
然后把两半接起来:ip -4 rule add from 10.66.127.142/32 lookup 40,同一个地址挂在 WireGuard 接口上。把两条规则都放进 PostUp,重连时才能恢复。用真实请求而不是设置来验证:ip route get 1.1.1.1 from 10.66.127.142 应当指出隧道设备,curl --interface wg0 ifconfig.me 应当报告隧道的地址而不是家里的。
还是失败时,读一下 ip -s rule。规则存在却从未匹配,计数器会显示为零,这就把写错规则和根本没有规则区分开。这一条命令省掉了大量关于应用是否忽略了接口设置的猜测。
反向情形:把 SSH 留在隧道外
相反的问题同样常见:wg-quick 占着默认路由,而 到服务器的 SSH 会话中途断掉。它的规则排在较低的优先级号上,所以例外必须比它们更早被求值。ip rule add dport 22 ipproto tcp table main priority 123 在较新的 iproute2 上能做到;在没有选择器的地方,就在 nftables 里标记流量,改为匹配标记。
回程也要检查。建立后随即沉默的 SSH 会话通常是路由不对称:一个方向走隧道,另一个方向走物理接口。对服务器地址执行 ip route get 能告诉你回包会走哪边。
把表号、源规则和例外都记在配置文件本身里。三者属于同一个设计,会一起被修改。