短请求可以返回,稍长的请求却一直等待。这个现象能否证明链路存在 MTU 黑洞?如果在发送端看到了一个大包,而接收端没有看到它,又能否立即认定中间路由器按 MTU 丢弃了报文?

第 07 篇解释了下一跳选择,第 08 篇区分了 IPv4 与 IPv6 的邻居发现和分片职责。本篇在路径中加入一个更小的出口,沿着报文长度、错误反馈与应用结果逐项判断。需要证明的不是“大包失败”这句话,而是哪个长度超过哪个限制、在哪里停止,以及发送者得到了什么信息。

先统一长度的口径

链路 MTU 限制的是该链路承载的网络层报文大小。在本篇的普通以太网/IP 实验中,接口配置的 MTU 与 IP 包长比较,不把以太网首部或帧校验序列加到 IP 长度里。应用写入的字节数也不是 IP 包长,还要加上传输层与 IP 首部。

设 IPv4 没有选项,UDP 首部为 8 字节,应用载荷为 D 字节,则 IP 总长度为 20 + 8 + D。IPv6 没有扩展首部时,对应长度为 40 + 8 + D。这两式的条件决定了计算结果;增加首部后不能继续套用原来的载荷上限。

中间出口 MTU IP 条件 UDP 载荷上限 再多一字节的 IP 包长
1280 IPv4,20 字节首部 1252 1281
1280 IPv6,40 字节首部,无扩展首部 1232 1281

这里的上限指无需 IP 分片即可放入该出口的载荷,不表示任意网络都能交付这个长度。丢包、过滤、路由和接收服务仍可能使小于上限的报文失败。MTU 条件满足只是路径交付的一个条件。

MTU 与 MSS 回答不同的问题

MSS 是 TCP 的概念,计数范围是 TCP 数据,不包括 IP 和 TCP 首部。它不是 UDP 应用载荷上限,也不是整条路径上的最小 MTU。对端通告的接收限制不能直接说明中间每条链路的承载能力。

RFC 6691 §2区分了通告 MSS 与实际发送长度:计算通告值时只扣固定 IP、TCP 首部;发送含选项的报文时,发送端仍须为实际选项减少数据长度。把通告值和每个报文的可用数据空间当成同一个量,容易漏掉选项开销。

例如 MTU 为 1500,IPv4 和 TCP 的固定首部各为 20 字节,得到基准 1460。若本次还携带 12 字节 TCP 选项,且没有其他首部变化,留给 TCP 数据的空间为 1448。这只是长度手算,不是本篇实验测得的 TCP 分段结果;真实 TCP 连接及协商留到后续篇章。

超过出口限制后谁负责处理

IPv4 报文的 DF 位决定是否允许分片。按照 RFC 1191 §4,路由器无法转发设置 DF 的过大报文时,丢弃该报文并以 ICMP Destination Unreachable 的 code 4 反馈,扩展格式携带下一跳 MTU。发送者据此降低路径 MTU 估计,后续构造更小的报文。

DF 为零允许分片,但不能据此保证分片一定成功到达,也不能把“应用最终收到了数据”当成 PMTUD 已经完成。IPv4 分片、重组和基于错误反馈调整包长,是不同的过程。

RFC 8200 §4.5规定 IPv6 中途路由器不执行分片。RFC 8201 §3描述过大报文的 Packet Too Big(PTB)反馈及源端对后续包长的处理。源端可以有自己的分片行为,因而“路由器不分片”不等于“线上永远没有 IPv6 分片”。本篇通过固定报文格式隔离超出中间 MTU 的情况,不展开所有扩展首部组合。

一份 ICMP 错误也不是没有条件的权威事实。发送者需要核对错误引用是否与自己发送的报文相关,错误可能丢失或被过滤。分析抓包时,也要关联 ICMP 引用的源地址、目的地址、协议和端口。无关 ICMP 不能解释本次请求。

没有反馈时怎样解释超时

经典 PMTUD 依靠网络层错误帮助发送端得知报文过大。若小包可以交付,过大的报文在中途被丢弃,而有关错误又未到达发送端,应用可能只看到等待超时。这是需要调查的黑洞现象,但“小包成功、大包超时”本身不能排除按长度过滤、随机丢失、服务端处理分支或返回路径问题。

RFC 8899描述数据报传输的 DPLPMTUD,通过分包层探测及确认判断可用大小,不要求完全依赖 ICMP。判断一个实现是否具备这种能力,需要检查它怎样构造探测、确认成功、处理丢失并维护状态。发出两个不同大小的 UDP 包并比较回显,不能被称为完整的 DPLPMTUD 实现。

实验还要区分线上失败与本地拒绝。发送端已经缓存较小 PMTU 时,后续一次大数据报调用可能直接得到 EMSGSIZE,根本没有再次经过路由器。如果沿用旧状态,又把本地错误解释为中途再次丢包,证据位置就错了。每组对比应保留初始路由和接口状态,使用独立环境或明确控制已有状态。

隔离实验的证据链

实验仅在自建 Linux 命名空间中降低出口 MTU。路径依次经过 A、R、B:A 发往 R 的入口允许 1500 字节,R 发往 B 的出口限制为 1280。A 与 B 都记录原始帧。B 对收到的数据返回短确认,避免把原样大回显的返回路径同时加入故障原因。

1
2
3
A ── MTU 1500 ── R ── MTU 1280 ── B
发送记录 出口判定 接收记录
↑────── ICMP 错误或短应用确认 ──────┘

双端记录分别回答不同问题:A 是否真正发出了指定长度的报文,是否收到了相关错误;B 是否收到请求,是否发出确认。两端均未看到回包时,仍需结合明确的故障配置,不能只凭“接收端没有包”定位丢失点。短确认只证明这个隔离测试服务处理了本次载荷,不代表生产应用成功。

本次实测结果

附件 lab.py 可在已有 Python、iproute2 的专用 Linux 环境中复跑。完整原始帧及每组应用结果保存在 run.jsonl。脚本只创建随机前缀的命名空间和 veth,结束时停止自己创建的子进程、删除本组对象并核对清理结果。

1
sudo python3 source/_posts/2026-09-19-计算机网络09-小包能通而大包失败/lab.py

当次环境是 Linux 6.18.10-200.fc43.aarch64、Python 3.14.3、iproute2 6.14.0。两族都显式使用 Linux 的 PMTU discovery DO 模式,避免把允许分片的情况混入阈值比较。常量来自当次核查的 Linux IPv4 UAPIIPv6 UAPI;这是 Linux 专用脚本,不保证相同数字适用于其他操作系统。

每个场景重新创建三个命名空间,因此本组没有前一组留下的 PMTU 缓存。服务端先绑定并启动捕获,再通过就绪信号允许客户端发送。读取等待上限为一秒;这个设定仅用于结束有界实验,不是测得的网络性能指标。

场景 IPv4 载荷 / IPv6 载荷 A 应用结果 双端报文证据
阈值以内 1252 / 1232 收到三字节 ACK IP 长度均为 1280;B 收到对应 UDP
比阈值多一字节 1253 / 1233 发送成功,随后 recv 得到 EMSGSIZE A 发出 1281 字节 IP 包;收到 MTU=1280 的相关错误;B 未见请求
A 没有默认路由 100 / 100 connect 得到 ENETUNREACH,发送字节数为零 A 未见目标 UDP,B 等待超时
R 过滤相关 MTU 错误 1253 / 1233 发送成功,随后 recv 超时 A 发出超限包但未收到相关错误;B 未见请求;过滤计数命中

IPv4 超限包的 DF 为 1,错误为 ICMP type 3、code 4。IPv6 对应 ICMPv6 type 2、code 0。两者都记录了反馈 MTU;原始错误中引用的源地址、目的地址与 UDP 首部也和发送包相符,而不只按错误消息名称分类。表中 EMSGSIZE 出现在已完成发送之后的接收阶段,因此不能把这次现象写成“大包从未离开发送端”。

过滤对照使用虚拟机已有的 nft,仅在 R 自建命名空间的 output 链丢弃对应 MTU 错误,并保留规则计数。没有对宿主防火墙或默认路由动手。脚本若找不到 nft,会明确输出跳过该场景,不安装新工具,也不会把跳过写成通过。

验证边界

抓包脚本没有实现捕获丢弃计数或独立校验和验证。“未见请求”限定为本次捕获与服务端等待窗口,不是对任意时间范围的全量证明。

八组结果来自虚拟机内的 veth 路径,未验证物理链路、公网、生产防火墙或其他内核版本。解码器针对本次普通以太网与无扩展首部的已知报文,不是通用抓包工具;没有把原始记录扩展成丢包率或性能统计。

实验展示了大小阈值、错误反馈和过滤条件的对应关系,没有实现根据反馈自动重分包并持续恢复的应用协议,也没有实现 DPLPMTUD。TCP MSS 只做规范核查和手算,没有运行 TCP 协商、MSS 改写或 TCP 黑洞恢复实验。无路由组是本地选路失败,未覆盖中途路由器返回普通不可达的全部分支。

从哪一步失败开始判断

相同的错误名可以出现在不同阶段,日志应记录 connectsendrecv 中哪一步返回。应用等待结束只说明在设定时间内没有获得所需结果;它不说明此前发生了什么。

证据组合 可以支持的判断 仍不能代替的证据
本地无目标路由,调用失败,A 未见目标 UDP 本地选路阶段未形成这次发送 中途路由器产生 ICMP 不可达
A 发出超限 IP 包,随后收到相关 MTU 错误,B 未见请求 本次发送遇到中间 MTU 限制 完整 PMTUD 恢复与长期路径变化处理
A 发出超限包,过滤规则计数增加,B 未见请求,应用超时 在该受控配置中错误反馈被抑制 任意公网超时都由 ICMP 过滤导致
B 收到请求并发出短确认,A 收到确认 本次隔离应用往返完成 公网吞吐、真实业务处理或所有包长可达

无路由对照若通过删除 A 自建命名空间里的默认路由实现,证明的是本地不可达。它与“路由器接收包后返回不可达”不是同一个实验,不能借同一个错误名称把两者合并。对超限组也要区分首次发包后的错误反馈和之后的本地包长检查。

练习

第一题:MTU 为 1280 的链路上,IPv4 UDP 载荷为 1252 字节,但 IP 首部另外有 4 字节选项。这个报文还能在不分片的条件下通过吗?

校验:IP 总长度为 24 + 8 + 1252 = 1284,超过限制。若其他条件不变,载荷应减至 1248 字节。不能只看应用字节数,也不能对 UDP 套用 TCP MSS。

第二题:同一目的地先收到 PTB,紧接着再次发送大数据报,调用返回 EMSGSIZE,同时 A 的抓包没有新数据报。能否写成“路由器又丢了一次包”?

校验:不能。现有证据支持本地发送拒绝,未证明第二次报文到达路由器。需要分开记录第一次错误反馈、之后的本地状态以及第二次是否上网线。

参考资料