邻居发现用 ICMPv6 询问下一跳的链路地址。请求是类型 135,通告是类型 136,跳数必须是 255。IPv4 在以太网上做对应的事时用 ARP,请求的操作码是 1。截至 2026 年 10 月,某一隧道实现会不会在自己的虚拟链路上发送这些报文,本文没有测量。
下一跳先选定,再问链路地址
一个可独立引用的定义句:发送单播包时,节点先做下一跳确定,再查邻居缓存里该邻居的链路层信息。目的地在链上时,下一跳就是目的地址;不在链上时,下一跳是一台路由器(RFC 4861 第 5.2 节)。
地址解析问的是这个下一跳,不是「网站最终在哪一台机器上」。目的地在路由器后面时,本机要解析的是路由器的链路地址。
ARP 做的是同一层映射。RFC 826 把协议地址对应到同一段硬件上的硬件地址。请求的操作码是 1,应答是 2。接收方在查看操作码之前,把发送方的协议地址和硬件地址并进表:已有表项会被更新;只有目标在表里还没有这项时才新增。探测把发送方协议地址设成全 0,就是为了避免地址已被占用时写出这一项(RFC 5227)。
IPv6 路由器通告 回答的是默认路由器和前缀从哪来。那是类型 134。类型 135 不提供默认路由。
请求和通告各带什么
被请求节点多播地址由目标地址的低 24 位接在前缀 FF02:0:0:0:0:1:FF00::/104 之后构成,范围从 FF02:0:0:0:0:1:FF00:0000 到 FF02:0:0:0:0:1:FFFF:FFFF(RFC 4291)。需要解析地址时,邻居请求发往这个多播地址;要确认邻居是否仍可达时,发往目标地址本身。
| 邻居请求 | 邻居通告 | ARP 请求 | |
|---|---|---|---|
| 类型 | ICMPv6 类型 135,代码 0 | 类型 136,代码 0 | 操作码 1。应答把操作码改成 2 |
| 跳数 | 255 | 255 | 没有这个字段。它装在以太网帧里 |
| 目标 | 被询问的 IPv6 地址,不得为多播 | 征求来的通告里,等于请求中的目标 | 目标协议地址是被询问的 IPv4 地址 |
| 目的 | 被请求节点多播,或目标单播 | 征求来的通告发给请求的源地址 | RFC 826 允许把目标硬件地址设成该硬件的广播地址 |
源地址是未指定地址时,邻居请求不得带源链路层地址选项;此时目的地址还必须是被请求节点多播。其余情况下,在有链路层地址的链路上,多播请求必须带上源链路层地址,单播请求应当带上。接收方必须静默忽略不认识的选项。
邻居通告里有三位。R 表示发送方是路由器。S 表示这份通告是在回答来自目的地址的邻居请求,用作可达性确认;多播通告和未经请求的单播通告不得置 S。O 表示应当覆盖缓存里已有的链路层地址。对任播地址的征求通告,以及征求来的代理通告,不应当置 O;其他征求通告和未经请求的通告应当置 O。
校验写在 RFC 4861 第 7.1.1 与 7.1.2 节。跳数不是 255、代码不是 0、目标是多播,都必须丢掉。该文件后来另有更新,包括讨论分片扩展头的 RFC 6980。本文没有逐份核对那些更新是否改写这两节的跳数检查。
五态只说明缓存里的可达性
一个可独立引用的定义句:邻居缓存里的可达性有五个值:INCOMPLETE 表示解析还在进行,链路层地址尚未得到;REACHABLE 表示最近已知可达;STALE 表示不再确知可达,但在有流量要发之前不主动探测;DELAY 表示已经有流量发出,先等一小段时间看上层是否给出确认;PROBE 表示正在发送单播邻居请求(RFC 4861 第 5.1 节)。
第 5.1 节写明这些是非正式定义,精确定义在第 7.3.2 节。同一节的常数表给出:REACHABLE_TIME 为 30,000 毫秒,RETRANS_TIMER 为 1,000 毫秒,DELAY_FIRST_PROBE_TIME 为 5 秒,MAX_MULTICAST_SOLICIT 与 MAX_UNICAST_SOLICIT 各为 3 次。
上层若能证明数据仍在前进,例如最近收到新的确认,就可以作为可达性的正面确认。没有这种提示时,节点才向正在发送流量的下一跳发出单播邻居请求。这和 保活 不是同一层。保活看的是隧道或中间映射还在不在。这里的探测看的是某一条链路上的邻居缓存。
RFC 7048 放宽了重传。实现若发出的请求超过上述次数,应当使用指数退避,以免持续重传造成稳定的背景负载。因此「三次就停止」只是 RFC 4861 常数表里的次数,不是 2026 年 10 月对某一系统的测量。本文不复述 RFC 7048 的退避公式。
启用地址之前先探测
RFC 5227 规定:实现该规范的主机,在开始使用一个 IPv4 地址之前,必须广播 ARP 探测。探测是操作码为 1 的 ARP 请求。发送方硬件地址填本接口,发送方协议地址必须全 0,以免地址其实已被占用时污染别人的缓存。目标硬件地址应当全 0。目标协议地址必须是正在探测的地址。
常数表里 PROBE_NUM 是 3,ANNOUNCE_NUM 是 2,ANNOUNCE_INTERVAL 是 2 秒。探测未发现冲突后,主机必须广播 ARP 宣布:发送方和目标的协议地址都设成这个新地址,操作码仍是请求。发出第一份宣布之后就可以使用该地址。这些次数是规范里的常数。某一系统在 2026 年 10 月是否按此探测,本文没有测量。
IPv6 的重复地址检测使用源地址为未指定地址的邻居请求,步骤在 RFC 4862,并被 RFC 7527 更新。本文只保留上面已经写过的两条校验,不复述探测次数。
DHCP 租约 把地址交给客户机。RFC 2131 提到客户机应当用 ARP 做最后检查。检查的报文格式以 RFC 5227 为准,不在租约选项里。
隧道是另一条链路
一个可独立引用的结论句:邻居缓存属于收到报文的那条链路。路由器不得把链路本地源或目的的包转发到其他链路,也不得把多播包转发到超出范围字段所指的范围(RFC 4291)。被请求节点多播的范围是链路本地。家里那条链路上的缓存,不会因为隧道图标变化而搬到虚拟接口上。
跳数为 255 的检查依赖同一事实:路由器转发时会把跳数减一。内层包若被原样封装,外层路由器减的是外层跳数,这和内层有没有被当作「转发」不是同一步。封装格式见 IP-in-IP 与 GRE。
RFC 4861 第 3.2 节写明:点到点链路按多播链路处理,接口可以有链路本地地址。因此规范允许一条点到点隧道自己成为邻居发现的链路。发送方在这条链路上把跳数设为 255,对端看到的仍是 255。这和「把物理局域网上的邻居请求转进隧道」不是同一件事。
TUN 与 TAP 决定虚拟接口上读写的是 IP 包还是以太网帧。TAP 能承载帧,ARP 和邻居发现可以在帧里。TUN 承载的是 IP 包。某一 TUN 实现有没有邻居缓存、会不会发出类型 135,本文没有测量。
局域网发现 用的也是留在本地链路的多播。组播 DNS 和邻居请求的目的地址不同,过不了路由器的理由相同:范围是链路本地。
代理邻居通告是另一项。路由器在有限情形下可以代其他节点应答,表示愿意接收并非明确发给自己的包。征求来的代理通告必须把 O 置 0,这样节点自己还在链路上时,它那份 O 为 1 的通告优先。这是 RFC 4861 第 7.2.8 节的 MAY。本文没有测量任何隧道网关是否代答。
本文不提供打开代理应答、修改邻居缓存或在隧道里转发链路本地多播的步骤。
现象对照
| 现象 | 更可能先看什么 |
|---|---|
类型 135,跳数是 255,目的是 FF02::1:FF 后接 24 位 | 正在解析的邻居请求。目标地址本身不得是多播 |
| 跳数已经小于 255 的类型 135 或 136 | 按第 7.1 节必须丢掉。它不像是这条链路上的邻居发出的 |
| ARP 请求里发送方协议地址全 0 | RFC 5227 的探测。宣布则是发送方和目标协议地址都是新地址 |
| 物理接口上的打印机还在,隧道接口上没有对应邻居 | 两条链路各有缓存。链路本地多播不会被路由器转过去 |
| 点到点隧道两端仍能解析对方的链路本地地址 | 规范把点到点链路按多播链路处理。这不表示物理局域网的缓存被搬走了 |
核对清单
- 先分清问的是哪一跳:在链上的目的,下一跳就是它;否则下一跳是路由器;
- 135 和 ARP 操作码 1 分开:跳数 255 只属于邻居发现。ARP 没有这个字段;
- 未指定源地址有两条限制:目的必须是被请求节点多播,且不得带源链路层地址;
- 三次探测是 RFC 4861 的常数:RFC 7048 允许超出之后指数退避。某一系统怎么做,要另测;
- 缓存属于一条链路:路由器不转发链路本地多播。隧道接口上有没有邻居发现,本文没有测量。