回应从局域网发出,而不是走隧道:原因与对策
从隧道进来的请求并不意味着回应会沿原路返回。Linux 会查目的地,而本地子网通常胜出。
通过网关发布的服务从互联网的大部分位置都能访问,却在一个办公室访问失败。有人检查隧道,看到请求到达,就断定回应一定沿着同一条路径返回。Linux 并不是这样工作的,再怎么检查隧道也不会改变结果。回应是按目的地选路的,而那个唯一失败的调用方所在子网,主机本来就能直接到达。
真正决定出向数据包的是什么
内核会为目的地址查找路由,按优先级顺序检查策略规则,然后查看匹配规则所选的表。数据包从哪块网卡进来并不参与决定,除非专门写了规则或标记让它参与。这是本机的路由事实,不是隧道的故障。
因此,从隧道收到的请求可能产生一个从物理网卡发出的回应,流量在没有任何设备报错的情况下变成单向。在就隧道下结论之前,请先检查两个方向。走错方向的回应会从隧道计数器中消失,这正是两端对“是否发出过数据”各执一词的原因。动任何规则之前,先确认是哪一个计数器在变化。
为什么本地子网会胜出
在本地网络上有地址的主机,其主表里有一条该前缀的直连路由,而直连路由就是直接匹配。当调用方来自同一子网时,路由查找在考虑隧道之前就已经命中了本地链路。
正是这种不对称让诊断变得混乱。来自更广互联网的调用方能通,是因为它们不匹配任何本地路由;而 唯一共享办公室前缀的那个调用方失败,偏偏有人就用它来测试。
对端覆盖范围与已安装路由不是一回事
在隧道一侧,对端的允许范围描述的是可以向隧道发送什么、接受来自隧道的哪些源地址。它并不说明本地路由表会把数据包送到哪里。
如果隧道工具在主机上安装了默认路由,它可能把无关流量当作附带损害拖进隧道。在应当由策略规则掌握决定的地方,工具提供的“不安装路由”选项正是为了防止这种情况,而地址、密钥和端点设置保持原样。为图方便而加的默认路由,是主机丢掉自己本地网络的常见原因。
按源地址选择路由表
基于源地址的规则把来自服务地址的回应送进一张私有表,该表的默认路由是隧道,其余流量继续使用主表。请选择本地未使用的表和优先级编号,并在插入任何内容之前先读取现有规则。要读当前生效的规则,而不是保存下来的文件,因为重启后两者可能不同。
ip -6 route add default dev wg0 table 200
ip -6 rule add priority 32000 from 2001:db8:aa::20/128 lookup 200
ip -6 rule add priority 32001 from 2001:db8:aa::20/128 unreachable最后一行让缺失的路由显式失败,而不是回落到本地链路。源地址必须确实分配给了本机,并且常规的 local 与 main 查找必须保持在更靠前的位置。规则是按源地址选表,并不会跨表比较前缀长度。
先确认路径,再确认服务
不要从抓包推断,而要直接问内核对这个源地址会怎么做,把服务地址用作源选择器。这一步答对了,就能省掉整条抓包追踪。
ip -6 route get 2001:db8:bb::30 from 2001:db8:aa::20输出里应该出现隧道的名字。然后测试一个没有改动的源,确认它仍然走本地链路,从而证明改动只影响了一个地址。用更宽的选择器什么也证明不了,反而偏离了最初的抱怨。
这解决不了什么
网关上的转发、防火墙规则和 ICMPv6 行为都保持原样,因为按源地址选路并不替代其中任何一项。缺少 Packet Too Big 消息,无论哪张表在查找中胜出,大响应依然会被破坏。要在添加路由规则之前先修好它,否则症状会在改动之后继续存在。
在动手之前,先确认发布模型。反向代理自己开后端连接时,呈现的是它自己的源地址,不会保留调用方,所以这种故障在该架构下根本不会发生。在那里套用规则,只会增加一条解决不了任何问题、反而掩盖真实原因的路由例外。
最后,把规则编号和表号写进主机的配置管理里。手工添加的策略规则会在下一次重建时消失,办公室的调用方又会带着同样的抱怨回来。