为什么你的公网 Linux 服务反而从 VPN 回包
本地服务可以在 eth0 上接收请求,却通过全隧道 VPN 回包。带完整 WAN 表的源规则能把这类回包留在原路径上。
套接字里没有任何东西记得请求是从哪个接口进来的,因此全隧道 VPN 的默认路由可能截走本该走 WAN 的回包。服务并没有忽略入站接口;Linux 是按目的地和源策略来路由本地生成的回包的。基于源的规则可以只把服务的回包改由完整的 WAN 表发出,让普通出站流量继续走隧道。回包从错误路径离开时端口仍然开着,所以症状看起来是单向的。
误解:回包会沿它进来的接口返回
数据包从 eth0 进来就从 eth0 出去,听上去很合理。无状态包过滤可以这么写,入站侧的 tcpdump 也确实在预期位置看到了请求。但对主机自己产生的流量,路由并不是这样工作的。
服务一旦接受连接,它的回包就是一个新生成的本地数据包。内核按回包的目的地和源地址去查路由规则,而 VPN 的默认路由是完全合法的答案。套接字里没有任何东西记得请求是从哪个接口进来的。
这就是症状看起来单向的原因:请求进得来,回包却消失了。这也是为入站端口加防火墙规则毫无效果的原因。数据包并不是在进方向被挡住,而是在出方向被路由到了别处。用带源地址的查询跟踪回包,绕路就显现出来了。
内核真正路由的是什么:源地址
需要的抓手是服务用于回包的本地地址。在示例 eth0 地址为 192.0.2.10/24、网关为 192.0.2.1 的主机上,该地址就挂在接口上。在云 VM 上,公网地址可能在上游被转换,客户机里并不存在,所以要使用主机真正持有的地址。
在预留表号或规则优先级之前,先检查当前状态。已有的策略规则可能盖住新规则,同号的旧表也可能已经带着不相干的路由:
ip -4 addr show dev eth0
ip -4 rule show
ip -4 route show table all
然后按源选择表。该规则匹配已经带有 192.0.2.10 的回包,正是你想豁免的本地终结流量。转发流量和其他本地地址的数据包不会匹配。
要建完整的返回表,而不是半个默认路由
只有默认路由的表还不够。内核仍要解析网关,而这个解析需要同一张表里的直连前缀。缺少该前缀的表,即使默认路由看起来正确,也会在网关解析处失败。两者都要加,然后把源规则指向这张表:
ip -4 route add table 100 192.0.2.0/24 dev eth0 src 192.0.2.10
ip -4 route add table 100 default via 192.0.2.1 dev eth0
ip -4 rule add priority 1000 from 192.0.2.10/32 lookup 100
使用真实的前缀和网关,不要照抄示例。以 /32 加 on-link 网关配置的云接口需要另一条直连路由,用错误前缀建的表会在第一跳就失败。src 属性只声明该路由的首选源地址,并不替代源规则。
顺序同样重要。优先级数字更小的宽规则会先被评估,因此必须先命中源规则,再轮到任何会把这份流量送回主表的规则。添加之后用 ip rule show 检查优先级,而不是只在启动时看一次。
用带源地址的查询验证
让内核从服务地址出发去路由一个测试目的地。答案应当给出 eth0 和 WAN 网关,而不是隧道:
ip -4 route get 198.51.100.25 from 192.0.2.10
然后建立一条新的已授权连接,观察双向。若查询选中了正确的表,而实连仍然失败,问题就在反向路径过滤或防火墙规则上,而不在表上。在全局关闭任何东西之前,先检查这些控制点。
把最终路由和规则放到拥有该接口的网络管理器下面。地址变更、DHCP 续租和 VPN 重连都会重写内核状态,手动添加的规则在下一次事件后就会消失,或者指向已经过期的网关。
这个例外的边界在哪里
该规则只豁免来自一个源地址的本地终结流量。它不覆盖 Docker 发布的端口、DNAT,也不覆盖经主机转发的服务,因为这些包按转发流量路由,从来不会以同样的方式带上主机的本地源地址。这类设计通常需要在入方向打标记,并加一条匹配该标记的规则。
它也不会让主机的公网地址出现在接口上。当上游 NAT 持有该地址时,回包仍必须从该 NAT 期望的路径离开,所以 WAN 表要照搬原来的网关和前缀,而不是猜一个公网段。
把例外连同表号、规则优先级和确切的源地址一起记录下来。下一次改动 VPN 或接口就可能悄悄弄坏它,而写明这三个值的备忘,比从客户报告里重新找症状要快得多。
如果带源地址的查询通过了,服务却仍然从错误路径回包,就把实际套接字的源地址与规则里的地址对比。绑定在通配地址上的服务可能选择与测试地址不同的源,这个不一致就是剩下的缺口。