VPN 环境下的路径 MTU 发现:尺寸与 ICMP 缺口
隧道会缩小路径能承载的报文,而只有反馈返回时发现才有效。与其猜测,不如测量、探测并验证路径。
隧道完成了握手,小请求能返回,而第一次大传输却卡住了。这个模式早在指向吞吐量之前,就先指向路径 MTU。路由上的每一段链路都限制报文大小,而封装又会从这份额度里分走一部分给外层首部,因此内层报文必须比原始路径所暗示的更小。这种故障是静默的,这也正是它难以诊断的原因。
路径 MTU 到底测量什么
路径 MTU 是两台主机之间路由上最小的链路 MTU,而不是你本地接口上印着的 MTU。本地值只是估算的起点,路径决定它必须止于何处。这就是同一个隧道在一个接入网上能工作、在另一个上却卡住的原因:变的是路由,而不是配置。只测一次就到处假设,正是让这个问题长期存在的错误。
对隧道而言,真正要紧的数字是内层 MTU,也就是外层路径的额度减去封装消耗的部分。WireGuard 会增加一个外层 IP 首部和一个 UDP 首部;其他传输方式会加上自己的首部。把内层尺寸设得过高,每个满尺寸报文都需要分片,而这种分片可能永远不会发生。
为什么隧道会缩小可用报文
如果隧道接口沿用物理链路的 MTU,每个大报文都取决于下游是否有人愿意对它分片。IPv4 原则上允许这样做,但被要求不得分片的路由器会改为返回错误。IPv6 路由器在转发途中完全不做分片,因此同一设置在不同地址族下表现不同。无论哪种情况,发送方在有人回报之前都学不到任何东西。
实际后果是:尺寸刚好适合握手,却不适合载荷。TLS ClientHello 或 DNS 查询小到可以原封不动地穿过被缩小的路径,而批量传输不行。因此症状集中在特定操作周围,而不是特定主机周围。
只有反馈到达时,发现才有效
IPv4 的发现依赖 don't-fragment 位:无法转发报文的路由器会返回带下一跳 MTU 的 ICMP fragmentation-needed,发送方随即下调估算。IPv6 则改用 Packet Too Big 消息,因为路由器在转发途中不分片。两种机制都假定该消息能找到回到发送方的路,穿过隧道,并越过返回路径上的任何防火墙。一旦这个假定破裂,发送方就会反复重试路径根本承载不了的尺寸。
允许 echo request 和 echo reply 的防火墙,未必允许了这些错误类型。ICMP 是一个家族,这里要紧的成员是错误消息,而不是那对回显。检查隧道两端以及两者之间路径上的控制流量策略,并遵循平台支持的指引,而不是到处放行所有 ICMP 类型。
同样的过滤也可能位于隧道内部。如果内层路径丢弃了错误,而外层路径却把它带了过去,发送方就可能去适应一个被封装流量其实从未真正看到的限制。
去探测路径,而不是猜一个数字
固定目的地、接入网和隧道,只改变报文尺寸。带 MTU 提示而失败的 don't-fragment 探测会给出下一跳的限制;重复直到某个尺寸通过,并同时记录尺寸和地址族。
ping -M do -s 1400 10.60.20.10
ping -M do -s 1300 10.60.20.10
ping -6 -M do -s 1300 2001:db8::10
能通过的 ping 并不能证明真实应用的满尺寸分段也能通过,所以要用实际失败的那次传输重做测试。把最大的可用探测与你配置的隧道 MTU 作比较,并在笔记里把这两个数字分开保存。如果两者相差大约一个首部,你找到的是封装开销,而不是故障。
RFC 4821 描述的 PLPMTUD 向上探测,并使用传输层确认,而不是信任被送回的差错。它在过滤 ICMP 的路径上有帮助,但支持程度因协议栈和应用而异。TCP 流可能自行恢复,而固定数据报尺寸的 UDP 应用会持续失败。
选定一个值并记录理由
一旦你了解了路径,调低隧道 MTU 既是诊断手段,也是正当的修复。它不能取代对“是哪条路径、哪种反馈机制在起作用”的了解,因为为移动链路调好的值,在光纤连接上会小得没有必要。记录所选尺寸、证明它的探测以及回滚步骤。
IPv4 机制在 RFC 1191 中描述,IPv6 的分组化指引在 RFC 8201 中;把两者都点出来,能让下一位工程师不必重新推导这个假定。如果传输在你选定的尺寸上仍然卡住,就别再往下调数字,而应去看下一层。在任何尺寸上都失败的路径,不是 MTU 问题。