有人告诉你 VPN“因为 MTU 在丢包”,你把隧道设成 1400,传输又恢复了。这个结果感觉像一次诊断。它通常只是算术上的巧合:你挑了一个足够小的数字,刚好落在你从未确认过的上限之下,而在下一个网络、另一份预算里,你还会遇到同一个上限。

这背后大多是两个迷思。第一,隧道接口的 MTU 就是路径 MTU。第二,封装只花“一个包头”,而 PPPoE 没有什么值得计算的开销。两者都错在:你选的那个数字看起来随意,实际上却是由这些开销决定的。

迷思一:隧道 MTU 就是路径 MTU

你在隧道接口上设置的值,只是一个本地承诺,说明实现最多会向下交付多大的内层包。路径的真实容量是端到端每一条链路的属性,它会随路由变化、移动连接切换、流量走不同对等而改变。设置成 1500 只说明你的主机会构造什么,不说明网络能承载什么。

路径 MTU 发现正是为了调和这两者,而它依赖反馈。在 IPv4 中,无法转发满尺寸包的路由器应当发送 ICMP“fragmentation needed”,发送方据此下调估计值。在 IPv6 中对应的是 Packet Too Big 消息,而路由器在转发中根本不做分片。一旦这类反馈被过滤,发送方就会继续制造路径承载不了的包,却没人告诉它原因。

迷思二:封装只花一个包头

老老实实数一数外层。外层 IP 头在 IPv4 中是 20 字节,IPv6 中是 40 字节。用于穿透 NAT 的 UDP 封装再加 8 字节。隧道协议还要加自己的头、初始化向量或 nonce、按分组或对齐边界补齐的填充、尾部以及完整性校验值。视协议与算法而定,在内层包开始之前,合计轻松就有几十字节。

接着再减去内层包自己的头。隧道内的 TCP 段要带自己的 20 字节 IP 头和 20 字节 TCP 头,而 TCP 的 MSS 是从 MTU 推导出来的,不是从隧道的名义载荷来的。这就是为什么用 ping 发小载荷能通,而文件传输会卡住:两种工具测的不是一回事,只有其中一种真的在使用满尺寸 TCP 段。

ip link show tun0
ping -M do -s 1472 192.0.2.10
ping -M do -s 1400 192.0.2.10

第一条命令报告设置的隧道 MTU。两个探测分别发送 1472 字节载荷,加上 IPv4 与 ICMP 的 28 字节头正好是 1500 字节的包,然后再发一个更小的。如果大探测彻底失败、小探测成功,那就是依赖尺寸的故障,而不是一般的路由问题。

迷思三:PPPoE 仍然给你干净的 1500

传统 PPPoE 接入链路并不承载 1500 字节的 IP 包。PPPoE 头加上 PPP 协议字段在以太网帧内占去 8 字节,只给 IP 包留下 1492。这就是 RFC 2516 描述的传统上限。如果你在这种链路上跑 VPN,隧道的开销是从那 1492 里出的,不是从 1500 里出的。

RFC 4638 定义了在接入集中器和用户设备都支持时协商更大 PPP 载荷的方法,所以单凭“这条连接用 PPPoE”并不能证明某个具体数字。本地设置无法强迫中间设备接受它从未被配置过的包,因此有用的问题是链路实际协商了什么,而不是标签上写了什么。

为什么故障看起来是选择性的

小请求能过、大传输卡住,是路径 MTU 黑洞的经典特征。握手、DNS 查询和 API 调用都放得下;批量传输产生的满尺寸段在某一跳死掉,而本该报信的 ICMP 消息永远到不了。连接看起来正常,下载却挂住,于是人们去查服务器而不是查路径。

第二个相关错误是把三个数字混为一谈:接口 MTU、TCP 的 MSS,以及协议自身的分片或填充设置。在路由器上钳制 MSS 只会改写通告的 MSS,让发送方构造更小的段;它不改变隧道 MTU,也帮不了 UDP 流量。因为它们看起来相关就把三者设成同一个数,正是好意的改动弄坏可用配置的方式。

改什么,按什么顺序改

先取证。用上面的探测确认故障依赖尺寸,然后找出能通过的最小尺寸和会失败的最大尺寸。在责怪隧道之前,先确认接入链路的真实 MTU;PPPoE 链路在 VPN 碰到它之前就已经压低了预算。把原始值记下来,以便回滚。

然后采用客户端文档里最小的那项调整。许多客户端同时提供隧道 MTU 和 MSS 修正选项,而 MSS 选项往往是更好的杠杆,因为它能在不缩小每个包的情况下抵达发送方。用同一目的地、同一种传输重测,并记录你最终定下的数字。在某个接入网络上有效的隧道值不是普适常数,下一个站点会有自己的预算。

相关文章