摘要

  • 1981 年的 IPv4 ICMP Parameter Problem 用一个 8 位指针标出头部中检测到错误的字节;丢弃报文和解释丢弃原因是两件独立的事。
  • ICMPv6 把指针扩为 32 位,并区分错误字段、无法识别的 Next Header 与未知选项,使偏移能够沿扩展头链继续向后。
  • 指针可以合法地落在 ICMPv6 返回引用之外。精确位置不等于完整证据,更不等于认证、根因证明或安全的内存索引。

一次失败终于有了坐标

网络中大量丢包没有解释。发送方看到超时,却不知道报文是在排队中消失、被策略过滤,还是根本无法解析。RFC 792 在 1981 年定义的 Parameter Problem 针对的是一种更具体的失败:IPv4 网关或主机发现头部参数有问题,以至于无法继续处理。原报文必须丢弃,但处理者可以向源地址返回 ICMP Type 12。

这个报文最特别的部分不是一段诊断文字,而是一个 8 位 Pointer。Code 0 表示指针有效,数值就是原 IPv4 头部中检测到错误的字节位置。偏移 1 可以指向当时的 Type of Service 字节;如果存在选项,偏移 20 可以指向第一个选项的类型。错误甚至可能落在某个选项的中间。

这使“报文不工作”变成了“解析器在这个坐标停下”。源端可以拿坐标去对照自己的序列化代码、配置和抓包。丢弃权仍在接收方,修正自己输出的责任则回到发送方。

RFC 792 还要求带回原 IP 头部及最初 64 位载荷。对早期 TCP、UDP 来说,这八个字节往往足以包含端口,从而把错误交给相关进程。引用负责关联上下文,指针负责定位;两者不能互相替代。

但 ICMP 从来不是可靠交付层。标准明确说控制消息只是反馈,并不保证每次失败都有回音。ICMP 错误不会无限递归地产生新的 ICMP 错误,分片也有额外限制。因此,没有 Parameter Problem 不能反推报文已经被接受。

从格式设计变成主机职责

RFC 1122 在 1989 年把这种反馈纳入主机行为。实现应当接收 Parameter Problem,并在能够识别原始进程时把信息向上传递。对 TCP 而言,它应被报告,而不是天然成为终止整个连接的命令。一个错误报文提供事实材料,具体反应仍由了解状态的上层决定。

这份主机要求还讨论了诊断的成本。日志应保存足够的异常头部信息,却不能让大量无害异常耗尽内存、磁盘或处理能力。后来线上的报文引用也遵循同样思路:提供有用证据,但不给解释别人的坏输入开一张无限资源支票。

RFC 1812 在 1995 年把要求落到 IPv4 路由器。无效报文必须被丢弃;在仍能安全读取部分字段的情况下,路由器可以返回 Parameter Problem,让指针落在 Internet Header Length 或 Total Length 上。一个看似错误的长度可能来自链路截断、传输损坏、别的 IP 版本,或者源端生成了非法头部。偏移只证明检查在何处失败,并不能单独裁决是哪段历史造成了这些字节。

路由器要求允许尽可能多地引用原报文,但整个 ICMP 报文不能超过 IPv4 的 576 字节最小重组缓冲。相比 RFC 792 最初的八个载荷字节,诊断上下文变长了;边界仍然存在,避免错误反馈成为放大器。

IPv6 让坐标沿头链前进

IPv4 头部连同选项最多 60 字节,8 位指针足够覆盖。IPv6 的可选信息却放在 40 字节基本头之后的一串扩展头中。解析者必须按顺序沿 Next Header 关系前进,错误位置可能离开基本头很远。

1998 年的 RFC 2463 为 ICMPv6 Parameter Problem 定义 Type 4 和 32 位 Pointer。指针成为相对于触发报文开头的字节偏移。Code 0 表示错误的头字段,Code 1 表示无法识别的 Next Header,Code 2 表示无法识别的 IPv6 选项。2006 年的 RFC 4443 保留了这套结构。

标准中的例子很直观:Code 1 加 Pointer 40,表示基本 IPv6 头之后的那个 Next Header 值无法识别。40 不是跳数、协议号或严重等级,而是解析链中的坐标。

RFC 8200 说明,扩展头必须按出现顺序处理。目的节点不能越过一个无法理解的过渡,先去寻找后面熟悉的协议。当它必须继续而当前 Next Header 未知时,应丢弃并把指针指向这个值。IPv6 选项类型的高位还可能要求 Code 2;那套选项动作位已有独立历史,本文只把它视为同一定位机制的一种使用场景。

因此,Pointer 记录的是理解边界,而不是整个报文的健康证明。解析器只说明自己到达了哪里;它没有检查到的后续字节不因沉默而获得背书。

坐标可能超出带回来的字节

ICMPv6 错误需要尽可能引用触发报文,同时不得让响应超过 IPv6 最小 MTU。长扩展头链带来一个不舒服的问题:真正出错的字段可能已经落在能够返回的引用之后。

RFC 4443 没有用假坐标掩盖这个矛盾。32 位指针可以超过 ICMPv6 报文中实际附带的触发报文字节。接收者得到“报告者停在偏移 N”这一精确信息,却没有在本次响应中得到偏移 N 对应的那个字节。

这是一种诚实的证据边界。若把指针压到引用末尾,就会诬指另一个字段;若无限扩大响应,就会破坏资源上限。协议同时保留了确定信息和缺失信息:位置已知,这份引用不完整。

软件因此不能把 Pointer 直接当数组下标。协议坐标有效,不代表本地缓冲区拥有该位置。RFC 8883 后来把这条安全规则写得很明确:用指针读取引用内容前,必须先确认它小于实际返回数据的长度。

引用过短时,上层协议类型也可能无法识别。RFC 4443 允许在完成 IPv6 层处理后丢掉这类错误,因为系统无法找到应被通知的进程。网络层定位精确,不保证应用一定收到。

“格式错误”扩展为“本机处理到此为止”

RFC 8883 在 2020 年增加 Code 5 到 10,描述中间节点遇到未知 Next Header,以及扩展头过大、头链过长、扩展头过多、选项过多或单个选项过大。不同代码让指针落在未知值、第一个越过字节上限的字节、第一个超出数量上限的头或选项。

此时,报文不一定违反语法。它可能完全按格式构造,却超过某个节点愿意投入的解析资源。代码说明是哪类本地边界,指针说明在哪里跨过了这条边界。

这种区分避免把一台设备的容量包装成全网真理。别的节点可能接受同一条扩展链;同样,格式合法也不意味着每个转发设备必须提供无限解析能力。互操作需要让拒绝可见,同时把“谁的限制”说清楚。

精确证据仍不拥有自动权威

普通 ICMP 错误默认没有认证。RFC 4443 讨论了伪造来源、篡改字段、拒绝服务和诱导上层错误反应的风险。上层在行动前应核对引用中的地址、协议、端口及近期实际发送状态。核对能提高可信度,却不能把一条返回报文变成完整路径记录。

错误生成还必须限速。攻击者或故障源可以连续发来坏报文,如果每个都得到响应,诊断本身会消耗带宽和计算。组播规则、源地址规则、中间盒过滤和普通丢包也会制造沉默。因此,一条 Parameter Problem 能证明某个节点形成了这项报告;没有报告不能证明所有节点都顺利解析。

这段历史真正标准化的,不是一种万能纠错,而是四个分开的事实:动作是丢弃,原因由代码描述,位置由指针给出,上下文只返回到资源边界。把它们合成“网络坏了”会损失修复线索;把它们抬高成认证真相则会越过证据。

来源