页面能打开,一个小 API 调用能返回,然后文件上传一直挂到客户端放弃。服务器日志里没有任何解释,因为数据包是在路径中途被丢弃的,没有有用的回应被送回来。这种模式就是 MTU 黑洞:链路比发送端以为的更窄,同时又沉默地拒绝说明这一点。失败的大小可以复现,这才使它能够被诊断。

是什么让洞变成黑洞

每条链路都有一个最大传输单元,大于下一跳上限的数据包要么被分片,要么被一条说明原因的 ICMP 报文拒绝。路径 MTU 发现依赖这条报文能够到达并被遵守。过滤或忽略它,发送端就会不断重试一个永远放不下的数据包。

封装让这在 VPN 周围很常见,因为隧道会给本就按物理链路定好大小的数据包再加一层外层报头。内层数据包在 1500 字节时合法,封装后就不合法了,于是它在边界处死掉,而小数据包照常通过。握手完成、登录页能打开,只能证明小数据包可用。应用试图发送的最大数据包才是关键。

找出开始断裂的大小

用禁止分片的测试二分查找载荷大小,从隧道一侧的主机发到另一侧的主机。能通过的最大载荷,就是可用的路径 MTU 减去报头开销。

ping -M do -s 1400 -c 2 192.0.2.20
ping -M do -s 1200 -c 2 192.0.2.20
ping -f -l 1372 192.0.2.20

在干净的 1500 字节以太网路径上,加上 IPv4 和 ICMP 报头的 28 字节后,1472 字节的载荷可以通过。如果隧道建立后同样的测试只有更小的载荷才能通过,差值就是封装开销,而隧道边界正是本应产生拒绝报文的地方。把连接前后的两个数字都记下来,因为这一对就是证据。应用使用 IPv6 时也要重复测试,因为报头更大,失败阈值会移动。

在责怪隧道之前先读证据

远端应用报错、拥塞和普通丢包同样会让上传卡住,所以一次传输失败并不能认定是黑洞。区分它们的是大小关系:1200 字节可以重复成功,1400 字节可以重复卡住,方向相同,端点相同。

两端同时抓包会看到大数据包发出后从未到达,中间也没有拒绝。这时该看的是边界设备,而不是服务器,因为服务器对每个真正收到的请求都作了回应。拥塞也会卡住,但它会在不同时间卡住不同大小,那是另一种特征。

在两种传输上都验证修复

受支持的修复是让边界报告真实上限,或者在那里钳制 TCP 的最大分段大小,使过大的分段根本不会被尝试。把全网每个 MTU 都调低,或者关掉防火墙,只会掩盖症状,留下一份以后谁也说不清原因的配置。

ip link show dev tun0
sudo nft list ruleset | grep -i -E 'mtu|mss'
sysctl net.ipv4.tcp_mtu_probing
ping -M do -s 1400 -c 2 192.0.2.20

用同样的尺寸测试验证,再用出问题的应用验证,并检查第二种协议,因为 UDP 应用永远收不到 TCP 的分段大小修正。把原始数值留在工单里,因为无法回滚的修复就是第二个等着发生的问题。如果尺寸测试通过而上传仍然卡住,那黑洞就不是原因,调查应当继续,而不是扩大改动。

相关文章