绑定到 VPN 地址的应用仍然走错路由
绑定到 VPN 地址的套接字走的是普通默认路由。添加策略规则之前,先核对地址归属和基于源的查找。
应用绑定到 VPN 的地址,连接却仍然从普通默认路由出去。绑定没有失败,它只是做了绑定该做的事:把一个套接字绑定到本地地址,只是选择内核写入报文头的源地址。它不会安装路由,也不会创建把携带该源的报文送进某条隧道的策略规则。
本文剩下的部分是我使用的检查顺序,以及产生每个观察结果的命令。
绑定实际决定了什么
绑定为套接字分配一个本地地址。随后内核照旧执行同样的路由查找——除非某条策略规则改变了这次查找读取哪张表。
这就是报文可以带着 VPN 源地址从物理接口出去的原因。对端收到来自一个该路径从未见过的私有地址段的报文,回程流量没有返回路由,连接就会挂起或重置。
Linux IP 套接字手册记录了绑定的语义。路由那一半在 ip-rule 和 ip-route 里,它们分成两本手册是有原因的。
确认地址属于这个命名空间
能绑定的地址必须存在于应用所在网络命名空间内的某个接口上。对端地址、网关地址,或只存在于另一个命名空间里的地址都无法绑定,调用会以 EADDRNOTAVAIL 失败。
ip -br addr show
ip netns identify <pid>
ss -tanp | grep <port>把这三条命令放在与应用相同的上下文中运行:同一个命名空间,能拿到的话也用同一个用户。旧的 net30 示意图不能证明当前拓扑给客户端分配了哪个地址。请读真正处于启用状态的接口。
读取该源和该目的地的查找结果
一条命令就能回答绑定没有回答的问题:
ip route get 203.0.113.10 from 10.8.0.2
ip rule show第一行打印从该源到该目的地的报文将走的路由,包括出口接口和首选源。如果它指向物理接口,说明绑定和路由相互矛盾,报文会带着无法返回的源地址出去。
接着 ip rule show 会告诉你是否有规则会选择另一张表。这里的优先级很关键:第一条匹配的规则胜出,而位于你的规则之上的一条兜底规则会让你的规则失效。
只有当这就是设计时才安装策略规则
基于源的路由是一种刻意的设计,而不是对某个应用的修补。如果策略所有者希望隧道子网的流量走隧道,规则应属于他的配置,而不是你笔记本上的一次性命令。
可行的形态是一张专用表,带有经隧道出去的默认路由,再加一条把 VPN 源地址段送进该表的规则。还要补上设计所需的例外,因为只含隧道默认路由的表会把本该留在本地或经物理路径到达的目的地也一并截获。
ip route add default dev tun0 table 100
ip rule add from 10.8.0.2 table 100 priority 100
ip route get 203.0.113.10 from 10.8.0.2用 ip rule del 和 ip route flush table 100 回退。这样添加的内容会在重启后消失,除非由配置管理器写入;这既是安全属性,也是不要把唯一副本留在 shell 历史里的理由。
用三项观察来验证
记录应用请求的源、查找选中的路由,以及服务器实际看到的源。
这三者可能不一致,而不一致的地方各自指向不同的环节。在隧道接口上抓包可以证明报文带着正确的源出去;在物理接口上抓包则证明它走错了路。只有服务端视角才能证明整条路径。
如果应用提供绑定到设备的选项或按应用路由模式,请把它当作另一种机制,约束也不同。看起来相似的设置并不会都落到同一个内核决定上。