摘要

  • IPv4 的后续分片也能建立重组上下文、写入数据并占用内存;最后一片甚至可以先到,让接收端知道完整数据报应当在哪里结束。
  • RFC 1122 要求计时器到期时必须丢弃未完成的数据报;只有已经收到零号分片时,才必须向源端发送 ICMP Time Exceeded Code 1。
  • 因此,“没有看见 Code 1”不能推出“没有发生重组超时”。零号分片缺失、ICMP 限速或过滤、回程丢包和实现差异都可能制造同样的沉默。

零不是顺序,而是位置

“零号分片”很容易被误读成第一张到达的票。IPv4 说的不是到达次序,而是 Fragment Offset 为零。网络可以乱序,末片可以早来,中间片可以率先进入接收队列,真正承载数据报开头的片段则可能永远没有抵达。

RFC 791 用源地址、目的地址、上层协议和 Identification 四项来选择重组缓冲区。只要某个分片具有这组身份,接收端就能为它分配资源,把载荷放进偏移量指定的位置,并记录哪些八字节块已经出现。MF 位为零的分片给出尾端,偏移量为零的分片给出开头。完整数据报必须同时具备首部、尾端和中间连续字节。

这意味着一个看似反常的状态完全合法:接收端已经掌握数据报的尾端,缓冲区里也有许多真实字节,却仍不知道原始开头。它不是“什么都没收到”,也不是“几乎成功”这种可以凭比例判断的状态。缺少任意必要区间都会让整个数据报无法交给上层。

RFC 791 的示例程序还把首部单独处理。只有当 FO = 0 时,首部才会写入 header buffer。后来到达的片段可以填洞,却不能代替原始数据报开头。零号分片因此承担两个职能:提供起始字节,也提供失败时可以原样引用的起始语境。

最早的等待策略借用了 TTL

1981 年的重组示例给每个上下文配置计时器。初始下界建议为 15 秒;后续分片到达时,计时器取当前值与该分片剩余 TTL 的较大者。较小的 TTL 不会缩短等待,较大的 TTL 可以把等待向后延长。

这种设计延续了 TTL 名称中“时间”的想象,但它同时暴露了重组的经济学。规范直接写出一个近似关系:数据速率乘以计时值,就是需要准备的缓冲容量。等待不是抽象的耐心。每多等一段时间,接收端就让不完整数据继续占据有限资源。

代价的另一端是误杀。等待过短,原本只是迟到的分片会被当作永远缺席;等待过长,无法完成的上下文会拖住内存,还会延长 Identification 组合必须保持不混淆的时间。计时器从一开始就是损失、延迟与资源之间的政策,而不只是实现常量。

问题在于,经过路由器后的 TTL 越来越像跳数预算,而不是精确流逝的秒数。同一剩余数值无法可靠说明分片已经在网络里待了多久。把接收端的内存寿命交给这个字段,等于让路径长度替本地资源管理作决定。

ICMP 要引用的不是“任意一片”

RFC 792 把 Time Exceeded 定为 Type 11:Code 0 表示转发过程中 TTL 用尽,Code 1 表示分片重组时间用尽。返回消息包含原始 IP 首部以及原始数据报最前面的 64 位数据。当时的假设是,上层端口等信息通常能从这段开头帮助源主机关联到相应进程。

这里的“原始”非常重要。后续分片也有自己的 IPv4 首部,也携带源地址、目的地址、协议和 Identification;否则接收端根本无法把它归入某个缓冲区。零号规则不是因为后续片段完全无名,而是因为 ICMP 承诺引用原始数据报的开头,不能把偏移量更后的片段伪装成这段证据。

RFC 792 因而规定,ICMP 错误只针对处理零号分片时发生的错误。重组在时限内未完成时,主机可以发送 Time Exceeded;如果零号分片不可用,则完全不必发送。接收端仍然知道本地状态失败了,也仍然可以找到源地址,但它没有被要求拼造一份证据强度更低的回告。

这条边界把“知道回信寄到哪里”和“有材料支撑回信内容”分开。地址回答去向,零号分片回答引用对象。两者不是同一件事。

填洞算法解决了效率,没有创造缺失的开头

RFC 815 提出了一种精巧的重组方法:不必为每个已收到区块设置庞大位图,只记录还存在的洞。新分片到来时,一个洞可能缩短、裂成两个或完全消失;当洞的清单为空,数据报才算完整。

这种算法天然允许乱序,也清楚指出了 IPv4 options 带来的麻烦。有些 option 会复制到每个分片,有些只留在第一片。在零号分片出现之前,接收端甚至不能最终确定原始首部应有多长。缓冲布局可以预留或搬移,算法可以优化,但缺少的协议语境不会由 bookkeeping 自动生成。

RFC 1122 推荐 Clark 的算法时特意加上一项修正:与 RFC 815 的描述不同,第一分片的首部必须保存,以便可能放入 ICMP 重组超时消息。成功路径需要首部来交付完整数据报;失败路径同样需要它来解释为何没有交付。效率与可问责性在这里相遇。

1989 年把两只时钟拆开

RFC 1122 要求 IPv4 主机必须实现重组,也必须有重组超时。这个计时值应该固定,而不应由剩余 TTL 设置;文档建议 60 到 120 秒。理由很直接:路由器通常把 TTL 当作跳数,接收端若继续把它当秒表,会得到不可靠的等待期限。

规范随后给出两个不同强度的动作:超时后,部分重组的数据报必须丢弃;如果已经收到零号分片,则必须向源端发送 ICMP Time Exceeded。

括号里的条件决定了本文的主题。资源释放不依赖对外发言条件。即便零号分片永远缺席,接收端也不能把不完整状态永久保留。反过来,消耗过内存也不会自动产生一条可以发送的诊断证据。删除是本地安全义务,回告是有证据前提的远程陈述。

60 到 120 秒是 1989 年规范给出的建议,不是今天所有系统的默认值,更不是本文测得的部署结果。它的历史意义在于明确责任归属:重组等待属于接收端的固定资源政策,不再借用一路经过多少跳的 TTL 来决定。

路由器何时才成为重组者

“网络分片”还会诱发另一种误会,好像沿途每台路由器都应当拼回完整数据报。RFC 1812 划得更窄:路由器转发普通过境流量时不靠重组完成转发;只有分片的目的地址就是这台路由器、它为自己重组时,才以 Internet host 的角色遵守主机重组要求。

同一文档延续了不得针对非首片发送 ICMP 错误的约束,而且这些禁发条件优先于其他发送要求。由此可见,诊断不能只问“路径上是否有路由器”,还必须问“哪个节点真正拥有目的端重组上下文”。

较长的寿命也延长了身份占用

重组缓冲区靠有限的 Identification 字段区分数据报。RFC 6864 后来把最大数据报寿命、重组超时和 ID 唯一性窗口联系起来。旧分片仍可能存在时,同一源、目的、协议与 ID 组合被重新使用,会增加不同数据报片段混在一起的风险。

这不是另一篇 IPv4 ID 通史。与本主题直接相关的只有一个结论:延长计时器不仅保存更多字节,也让一个有限命名空间里的旧身份存活更久。资源政策与证据政策并不能完全分开。

有回告,只能证明一件较窄的事

源端收到 Code 1 时,可以合理地说:某个目的端重组者保留了不完整状态直到超时,并且收到了足以引用原始开头的零号分片。它不能据此确定哪一片在何处丢失,不能证明某个组织有过错,也不能证明下一次仍会失败。

源端没有收到 Code 1 时,信息更少。零号分片可能没有抵达;目的端可能限制 ICMP;错误可能在回程被过滤或丢弃;实现可能不同;观测点也可能漏记。最危险的做法,是把沉默填成一个肯定答案。

互联网的失败证据常常具有选择偏差:只有保住关键语境的失败才更容易留下可解释回告。若监控只统计回告,就会系统性忽略那些恰好丢了开头的重组失败。零号分片越关键,它的丢失越可能同时删除故障和故障说明之间的联系。

末片也不能替代这份语境。MF 为零可以封住预期长度,让接收端知道洞落在哪个范围,却没有提供 ICMP 所承诺引用的原始开头。监控必须把“已知末端”和“收到零号片”设成两个字段,而不是折叠成“接近完成”。Code 1 本身同样不携带 MTU 数值或丢包地点;从一次超时推断窄链路,还需要发送包、路径变化和重复试验。

这种克制不会削弱诊断,反而避免把目的端的一次局部陈述升级成对整条路径的判决。标准能证明的是一个具体重组上下文在期限内没有补齐,并非某个中间节点必然删除了哪一片。

测试窗口还必须长于被评估的 timeout。若采集过早结束,仍在等待的上下文会像已经消失一样,结果会人为偏向短计时。比较前后应固定数据报大小、流量类别,并标记隧道与路径变化,才能区分真正释放了内存,还是仅把故障推迟到观测窗口之外。此外还要记录每个上下文真正关闭的时刻,不能把采集结束时间误作清退时间,也不能用展示窗口替代协议状态。

来源与证据边界

这些一手规范能够证明协议规则、算法和设计者写下的取舍,不能证明当前操作系统默认值、实际分片比例、Code 1 到达率、过滤强度或厂商合规性。本文不从标准文字推断任何具体生产网络的现状。