IPv6 NDP Relay 原理与 odhcpd 修复解析
坑边闲话:笔者位于国内某高校,OpenWrt 路由器的 WAN 口能拿到公网 IPv6 地址,但校园网只发
/64、不给 PD,内网设备的 IPv6 因此长期缺席。多年前笔者就试过用 NDP Relay 补救,可惜当时不稳定、用一阵就断,身边也有好几个人遇到同样的问题。最近升级到 OpenWrt 25.12,同样的配置却突然稳定可用了。顺着odhcpd的提交历史,笔者找到一个只有四行的 patch(commit f0d8553),它修掉的逻辑死锁,看起来正是当年的元凶。然而当笔者试图坐实这条因果时,故事变得复杂了:笔者专门搭了对照中继,从 23.05.5 一直退到当年那个版本时代的 22.03.0(都不含该 patch),结果它们在今天的校园网里照样跑得稳稳当当,连把代理表项全删光都能在一两秒内反应式自愈。版本这条线既已基本排除,最干净的那个对照实验其实已经做不成了:真正变了、又无从还原的,是当年校园网上游的行为。这篇文章因此有两层:一是讲清 NDP Relay 的原理与 f0d8553 修掉的那个真实 bug;二是诚实记录这场调查,并在结尾说明,今天的「能用」未必是这个 commit 的功劳,更可能是环境本身早已改变(园区路由器升级、配置优化等)。
1. 问题背景·
1.1 ISP 不下发 PD 时的困境·
IPv6 的设计初衷是消灭 NAT,让每台设备都持有全球可路由地址。在实践中,家庭和宿舍网络通常通过 DHCPv6 前缀委托(Prefix Delegation,PD)拿到一段前缀(通常是 /56 或 /48),路由器再把这段前缀分割成多个 /64 下发给内网。
但部分 ISP(尤其是高校校园网)只为路由器的 WAN 口分配一个 /64 地址(通过 SLAAC),而不提供 PD。由于 /64 是 SLAAC 的最小单元,路由器无法从中再「切割」出子前缀分配给 LAN. 内网设备因此只能拿到 ULA(Unique Local Address,类似 IPv4 私有地址)或干脆没有 IPv6。
1.2 NDP Relay 的思路·
在这一约束下,一种可行的做法是让路由器充当 NDP 中继(NDP Relay):路由器本身是上游 /64 内的一个节点,同时代理内网设备在这同一个 /64 内「占用」地址。上游路由器在 NDP 层面看到的就好像这些内网设备都直接接在校园网链路上。
OpenWrt 的 odhcpd 内置了这一功能,通过 UCI 配置即可启用。然而在 OpenWrt 25.12 之前,这套配置在一些环境下会静默失效:内网设备拿不到全局 IPv6,或偶尔能用随后又断。odhcpd 中确实存在一个逻辑缺陷,直到 2025 年 10 月的 commit f0d8553 才被修复。不过这个缺陷是否就是某个具体环境失效的根因,并不总能下定论,第 7 章会专门回到这个问题。
要理解这个 bug,需要先从 NDP 协议本身讲起。
2. IPv6 邻居发现协议 NDP·
2.1 NS 与 NA:地址解析的基本单元·
IPv4 使用 ARP 解决「IP 地址对应哪个 MAC 地址」的问题。IPv6 废弃了 ARP, 以 NDP(Neighbor Discovery Protocol,RFC 4861)取而代之。NDP 的核心报文有两种:
- Neighbor Solicitation(NS,ICMPv6 type 135):「谁持有地址 X?请告诉我你的 MAC.」
- Neighbor Advertisement(NA,ICMPv6 type 136):「我持有地址 X,我的 MAC 是 Y.」
下图展示一次完整的邻居解析过程:
sequenceDiagram
participant S as Sender
participant T as Target (addr: 2400::abc)
S->>T: NS (ICMPv6 type 135)<br/>src: 2400::111, dst: ff02::1:ff00:abc<br/>Target: 2400::abc
T->>S: NA (ICMPv6 type 136)<br/>src: 2400::abc, dst: 2400::111<br/>Target: 2400::abc, Link-layer addr: aa:bb:cc:dd:ee:ff
Note over S: Neighbor cache updated:<br/>2400::abc -> aa:bb:cc:dd:ee:ff
2.2 请求节点组播地址·
NS 并非广播,而是发往请求节点组播地址(solicited-node multicast address):ff02::1:ff 加上目标地址的低 24 位。例如,查询 2400::a1b2:c3d4 时,NS 发往 ff02::1:ffb2:c3d4。只有低 24 位匹配的节点才会加入该组播组并处理 NS,大幅减少了广播开销。
这一细节在后面理解 NDP Relay 的代理行为时会用到。
3. NDP Relay: odhcpd 的中间人机制·
3.1 跨链路的邻居代理·
正常的 NDP 只在同一链路 link 内有效。路由器的 WAN 口和 LAN 口是两条不同的链路,上游路由器发出的 NS 到了 WAN 口就停下来了,内网设备根本收不到。
NDP Relay 的核心思路是:让 odhcpd 站在 WAN 和 LAN 之间,把上游的 NS 转发给 LAN,再把 LAN 的 NA 转回给上游,同时在内核中创建代理邻居表项(proxy neighbor entry),使内核知道「某个 /64 地址在 LAN 侧」。
网络拓扑大致如下:
1 | Campus Network (/64: 2400:dd01:103a:4008::/64) |
3.2 正常工作流程·
当 NDP Relay 正常工作时,流程如下:
sequenceDiagram
participant UR as Upstream Router
participant OW as OpenWrt (odhcpd)
participant LD as LAN Device (2400::pc1)
UR->>OW: NS: who has 2400::pc1?
Note over OW: relay mode: forward NS to LAN
OW->>LD: NS (relayed): who has 2400::pc1?
LD->>OW: NA: I have it, MAC: xx:xx:xx
Note over OW: add proxy neighbor entry in kernel:<br/>2400::pc1 reachable via br-lan
OW->>UR: NA (relayed): 2400::pc1 is here
Note over UR: Neighbor cache: 2400::pc1 -> OpenWrt WAN MAC
UR->>OW: Traffic to 2400::pc1
OW->>LD: forwarded
整个过程的关键是上游路由器主动发来了 NS,odhcpd 才有机会转发并建立代理表项。然而这是一条「被动等待」的路径:它的前提是上游路由器已经有理由发 NS(即已知道该地址存在)。全新 SLAAC 设备上线时,这一前提并不成立。第 4 章将专门讨论这个引导问题。
3.3 出向流量·
上面展示的是「上游路由器来问,odhcpd 中继回答」的流程。实际使用中,更常见的起点是内网设备主动发起对外的 IPv6 连接。
出向流程本身并不复杂:PC1 需要先解析默认网关的 MAC,然后把数据包交给 OpenWrt,由 OpenWrt 从 WAN 口转发给 ISP 路由器。
sequenceDiagram
participant LD as LAN Device (PC1, 2400::pc1)
participant OW as OpenWrt
participant UR as ISP Router
participant INT as Internet Server
LD->>OW: NS on br-lan: who has fe80::1 (gateway)?
OW->>LD: NA: fe80::1 is here, MAC = OpenWrt LAN MAC
LD->>OW: IPv6 pkt: src=2400::pc1, dst=server (to OpenWrt LAN MAC)
Note over OW: L3 routing: forward via eth1
OW->>UR: IPv6 pkt: src=2400::pc1 (Eth src: OpenWrt WAN MAC)
UR->>INT: forwarded upstream
出向路径本身畅通。值得注意的是,ISP 路由器在收到这个包时,同时看到了以太帧的源 MAC(OpenWrt WAN MAC)以及 IPv6 的源地址(2400::pc1)。是否利用这一信息更新邻居缓存,取决于 ISP 路由器的具体实现,RFC 4861 对此没有强制规定。这一点将在下一节中进一步说明。
3.4 回程流量·
proxy neighbor entry 建立后,回程流量的投递过程值得细看,其中有一个细节容易被忽略:内核回 NA 时用的是 OpenWrt 自己的 WAN MAC,而不是 PC1 的 MAC. ISP 路由器从始至终不知道 PC1 的真实 MAC: 在它眼里,2400::pc1 就是 OpenWrt 这台机器持有的地址。
sequenceDiagram
participant INT as Internet Server
participant UR as ISP Router
participant OW as OpenWrt
participant LD as PC1 (2400::pc1)
LD->>OW: outbound pkt (src: 2400::pc1)
OW->>UR: forwarded (Eth src: OpenWrt WAN MAC)
UR->>INT: upstream
INT->>UR: return pkt (dst: 2400::pc1)
Note over UR: neighbor cache miss for 2400::pc1
UR->>OW: NS: who has 2400::pc1?
Note over OW: kernel NDP proxy: entry found
OW->>UR: NA: 2400::pc1 is here, MAC = OpenWrt WAN MAC
Note over UR: NOT PC1's real MAC.<br/>ISP router only knows OpenWrt.
UR->>OW: frame to OpenWrt WAN MAC (L2 delivery done)
Note over OW: L3 route lookup: 2400::pc1 via br-lan
OW->>LD: forward to PC1 (L2 on br-lan)
4. 旧版 odhcpd 的静默死锁·
4.1 问题的结构性原因·
proxy entry 缺失导致的连通性问题,有时是立即失效,有时是延迟失效,取决于 ISP 路由器的实现。
如果 ISP 路由器在收到出向数据包时,从其源地址中主动学习了邻居条目(某些实现会这样做),那么回程初期的 NDP 查询可能命中这条条目,连通性短暂成立。但该条目未经 NS/NA 机制确认,处于 STALE 状态。NDP 规范要求路由器通过 NUD(Neighbor Unreachability Detection)周期性地重新确认邻居可达性:条目从 STALE 经过 DELAY 阶段后,会进入 PROBE 阶段,向 2400::pc1 发出 NS。
这一步是问题的根本所在:NUD 探测是无法绕过的。只要 proxy entry 未建立,OpenWrt 对这个 NS 就无从响应,条目进入 FAILED, 回程流量中断。
如果 ISP 路由器不做上述学习,则初始回程就直接触发 NS, 立即失败。
两种情形的共同结果是:只要 proxy entry 缺失,NUD 探测必然在某个时刻失败,连通性无法长期维持。
sequenceDiagram
participant UR as ISP Router
participant OW as OpenWrt (no proxy entry)
participant LD as LAN Device (2400::pc1)
LD->>OW: outbound traffic (src: 2400::pc1)
OW->>UR: forwarded (Eth src: OpenWrt WAN MAC)
Note over UR: May or may not learn 2400::pc1<br/>from source address (impl-dependent)
Note over UR: NUD: entry goes STALE, then PROBE
UR->>OW: NS: is 2400::pc1 still reachable?
Note over OW: No proxy entry. Cannot respond.
Note over UR: No reply. Entry -> FAILED.
Note over UR: Return traffic dropped.
4.2 ping6 为何能临时有效·
许多用户发现,在内网设备上执行 ping6 <上游网关> 之后,IPv6 连通性会短暂恢复。这个现象看似奇怪,实际上有清晰的解释。
当内网设备发出 ping6 时,需要先通过 NDP 解析上游网关的 MAC 地址。这会产生一个 NS 报文,其源地址就是内网设备的全局 IPv6 地址(2400::pc1). odhcpd 将这个 NS 从 LAN 中继到 WAN,上游路由器收到后:
- 解析了 NS 的内容,知道谁在问自己
- 更重要的是,它看到了源地址
2400::pc1,一个来自本链路的数据包
上游路由器随即将 2400::pc1 写入自己的邻居缓存。RFC 4861 §7.2.3 规定,节点收到 NS 时应(SHOULD)为其源地址创建邻居条目,初始状态为 STALE: 即「有记录但未经可达性确认」。处于 STALE 状态的条目仍可用于转发,因此回程流量此时能够正常送达。
然而邻居缓存条目是有生命周期的。STALE 条目一旦有流量触发,会经过 DELAY(5 秒)进入 PROBE 阶段,路由器在此阶段发出 NS 重新确认可达性。若此时没有 proxy entry, OpenWrt 无从响应,条目最终变为 FAILED, 连通性中断,直到下一次手动 ping6.
这个现象本质上是在用手工方式替代 odhcpd 本应自动完成的工作:向上游路由器「通报」内网设备地址的存在。
5. commit f0d8553 解析·
5.1 问题的触发链·
先看 handle_solicit() 的核心逻辑:收到 NS 后,对每个「另一侧」的 relay 接口调用 ping6(),用 ICMPv6 echo 探测目标地址是否存在于对侧:
1 | /* src/ndp.c — handle_solicit() */ |
注意:ping6() 发的是 ICMPv6 echo request,不是 NS。
触发链从 PC1 做 SLAAC 时的 DAD(Duplicate Address Detection,重复地址检测) 开始。PC1 配置好新地址后,必须先发一条源地址为 :: 的 NS 确认地址无冲突:这就是 DAD NS。
odhcpd 在 LAN 侧收到这条 DAD NS,因为 ns_is_dad = true,会调用 ping6(2400::pc1, WAN口),尝试在 WAN 侧探测该地址是否已存在。
问题就出在这里:内核要把这个 ping 发出 WAN 口,首先得知道 2400::pc1 在 WAN 链路上的 MAC 地址。由于 WAN 口开启了 proxy_ndp = 1,内核会在 WAN 口上生成一条 NS 来做邻居解析。这条 NS 随即被 odhcpd 的 AF_PACKET socket 捕获,其源 MAC 是 WAN 口自己的 MAC,odhcpd 认定为「自己发的包」。
旧代码在此处直接 return:
1 | /* src/ndp.c, before f0d8553 */ |
探测链就此断掉。odhcpd 从未向 LAN 发出 ping,PC1 没有机会回复,proxy entry 无从建立。
完整的因果链如下:
sequenceDiagram
participant PC1 as PC1
participant OW as odhcpd
participant KRN as Linux Kernel
participant WAN as WAN
PC1->>OW: DAD NS (src=::, target=2400::pc1)<br/>on br-lan
Note over OW: ns_is_dad=true<br/>DAD 必须覆盖整个 /64,包括 WAN 侧
OW->>KRN: ping6(2400::pc1, WAN口)<br/>relay DAD to upstream segment
Note over KRN: 发 ping 前需解析 2400::pc1 的 MAC<br/>WAN 口开启了 proxy_ndp=1
KRN->>WAN: NS: who has 2400::pc1?
WAN->>OW: 同一条 NS 回环 (AF_PACKET)<br/>源 MAC = WAN 口自身 MAC
Note over OW: handle_solicit()<br/>is_self_sent=YES, iface->master=YES
rect rgb(255, 200, 200)
Note over OW,PC1: [OLD] return<br/>链路断开,LAN 侧从未被探测<br/>proxy entry 无从建立
end
OW->>PC1: [NEW] ping6(2400::pc1, LAN口)
PC1->>OW: echo reply
Note over OW: NEIGH6_ADD 事件触发<br/>proxy entry 建立在 WAN 口
5.2 修复的逻辑·
commit f0d8553 的改动极为精简,4 行插入、2 行删除:
1 | --- a/src/ndp.c |
修改分两处:
第一处:引入 is_self_sent 变量,将原先「自己发的就直接返回」改为「自己发的且不是 master 接口才返回」。这一条件使得从 WAN(master 接口)发出并回环的 NS 得以继续向下执行,进而触发对 LAN 侧设备的探测。
第二处:在生成 NA 响应之前追加 || is_self_sent 条件。这是必要的防护:如果 odhcpd 对自己发出的 NS 作出 NA 响应,会在邻居缓存中写入一条虚假表项(将自己的地址指向自己),产生错误的路由行为。
两处修改合在一起,使 handle_solicit() 在 self-sent + master 这条路径上的行为发生了质变,如下图所示:
sequenceDiagram
participant OW as odhcpd
participant WAN as eth1 (WAN)
participant LAN as br-lan
OW->>WAN: NS, target: 2400::pc1
WAN->>OW: same NS loops back (AF_PACKET)
Note over OW: handle_solicit():<br/>is_self_sent=YES, iface->master=YES
rect rgb(255, 200, 200)
Note over OW,LAN: [OLD] return immediately — LAN never probed, proxy entry never built
end
OW->>LAN: [NEW] NS: who has 2400::pc1?
LAN->>OW: NA: I have it, MAC: xx:xx:xx
Note over OW: ip neigh add proxy 2400::pc1 dev eth1
5.3 修复后的工作流·
修复后,odhcpd 能够主动打破死锁:
sequenceDiagram
participant UR as Upstream Router
participant OW as OpenWrt (odhcpd, new)
participant LD as LAN Device (2400::pc1)
LD->>LD: SLAAC: configured 2400::pc1
Note over OW: Probe: send NS on WAN for 2400::pc1
OW->>OW: NS loops back (self-sent, master iface)
Note over OW: is_self_sent=true, iface->master=true<br/>Continue processing (do NOT return)
OW->>LD: NS on LAN: who has 2400::pc1?
LD->>OW: NA: I have it
Note over OW: Proxy neighbor entry created:<br/>2400::pc1 reachable via br-lan
UR->>OW: NS: who has 2400::pc1?
OW->>UR: NA: 2400::pc1 is here (proxy)
Note over UR: Neighbor cache established. Traffic flows.
odhcpd 不再被动等待上游发来 NS,而是主动探测、主动建立代理表项,上游路由器一旦有流量就能立刻得到响应。
6. OpenWrt 25.12+ 配置方法·
commit f0d8553 于 2025 年 10 月合入,OpenWrt 25.12 是首个包含该修复的正式版本。在此之前的版本(包括 22.03、23.05、24.10)均不包含该修复,缺少「主动探测」路径,只能依赖被动反应路径;在没有上游主动发 NS 的环境下,中继无法自行完成引导。第 7 章会用实测重新审视「这是否就是某个具体环境失效的决定性原因」。
在 OpenWrt 25.12+ 上启用 NDP Relay 的配置如下。如果路由器存在 wan6 对应的 DHCP 配置(通常有 ignore='1' 需先删除),或者需要新建:
1 | # 如果已有 wan6 条目且带 ignore,先删掉 ignore: |
配置生效后,LAN 设备会通过中继的 RA(Router Advertisement)得到 WAN 侧的 /64 前缀,进而 SLAAC 生成全局 IPv6 地址。可以用以下命令确认:
1 | # 在 LAN 设备上 |
几点注意:
- 此配置要求 ISP 给路由器分配了全局
/64(SLAAC),WAN 口有全局 IPv6 地址。如果 WAN 口只有 ULA 或 link-local,则无效。 - LAN 设备获得的是与路由器 WAN 口同一个
/64内的地址,从上游视角看它们「直接在校园网链路上」,无 NAT。OpenWrt 的 fw4 防火墙默认对 WAN 区域的forward执行 REJECT,可阻止外部主动连入 LAN 设备。 - 升级到 OpenWrt 25.12 之前的版本缺少主动探测路径;在没有上游主动 NS 的网络中,此配置可能静默失效,原因正是本文描述的引导死锁,而非配置错误。是否真的触发,取决于具体环境,第 7 章会用一台 23.05.5 的对照中继说明这一点。
7. 一次迟来的对照实验·
第 5、6 章把 commit f0d8553 讲成「连通性恢复的原因」。这条因果听起来顺理成章,笔者却一直没能直接验证它。本章记录一次迟来的对照实验:笔者最终搭出一台纯 23.05.5 的中继来做对照,而它给出的结果,把上面这条因果推翻了一半。
7.1 搭对照中继:23.05.5 与 22.03.0·
最初做不成对照,是因为手头两台路由器都已是 OpenWrt 25.12.4(含补丁),没有备机可降级,降级现网设备又会中断服务。后来笔者专门准备了一台独立测试机补上这一环:
- 一台运行原版 OpenWrt 23.05.5(r24106)的虚拟机充当中继,WAN 口接入校园
/64,另一网口接一台测试设备(下称 PC),中继配置与第 6 章一致。 - 翻阅
odhcpd提交历史可确认:23.05.5 携带的src/ndp.c与 f0d8553 之前的代码在功能上完全等同(中间只隔着一个与逻辑无关的拼写修正提交),即这台中继不含第 5 章所述的「主动探测」路径。 - 为把版本变量探到底,笔者随后又把这台测试机换成更老的 OpenWrt 22.03.0(同样不含 f0d8553,配置同为标准 relay)。22.03.0 正落在笔者当年真正使用的版本时代,是最贴近「案发现场」的一档。
启用后,PC 通过中继转发的 RA 拿到了一个校园 /64 内的全局地址。这里有一个必须先排除的陷阱:PC 本身还有另一条独立的 IPv6 出口,若不约束,测试流量可能从那条路走掉、根本不经过被测中继。笔者通过绑定出口接口、并关掉另一条出口,确保所有测试流量都老老实实穿过这台 23.05.5 中继。
7.2 它就这么通了·
第一项测试很直接:让 PC 经这台 23.05.5 中继访问公网 IPv6。结果是 0% 丢包、RTT 稳定,仅首包有一次引导延迟。23.05.5 的中继,能用。
真正决定性的是「冷态重建」实验。笔者先在中继上把 PC 地址的 proxy 表项、/128 路由、LAN 侧邻居全部删除,让中继侧彻底回到一无所知的冷态;随后从一台位于另一家宽带(不同 ISP)的外部主机,向 PC 的全局地址发起 ping。事件链如下:
1 | Time(relative) Iface Frame |
几点是确凿的:
- 删除之后,中继对入向包回了一条 ICMPv6 Destination unreachable。这既证明它当时确实是冷的,也反证流量的确打到了这台中继,没有从别处漏走。
- 大约 2 秒后连通恢复,靠的全是反应式机制:入向数据触发中继内核去解析 PC,LAN 侧 PC 应答、
NEIGH6_ADD事件让odhcpd重建 proxy;LAN 侧那条id=0的 echo 正是odhcpd的ping6()探测签名。全程没有 f0d8553 的主动探测路径参与。
也就是说:一台不含 f0d8553 的 23.05.5 中继,被人为删光状态后,仍能在约 2 秒内纯靠反应式路径自愈。
把测试机换成 22.03.0(标准 relay 配置)再做同一组操作,逐帧过程完全一致:入向数据触发内核解析、odhcpd 的 ping6() 探测(LAN 侧 seq 0 的 echo 就是签名)重建表项、随后对上游与同段中继代答 NA,约半秒、丢一个包即恢复。把三个横跨 f0d8553 前后的版本放在一起对照:
| 版本 | 含 f0d8553 | 正常连通 | 删光后冷态自愈 |
|---|---|---|---|
| 22.03.0(笔者当年的版本时代) | 否 | 0% 丢包 | 约 0.5 秒,丢 1 个包 |
| 23.05.5 | 否 | 0% 丢包 | 约 2 秒,丢 1 至 2 个包 |
| 25.12.4 | 是 | 0% 丢包 | 即时 |
三个版本在今天的校园网里全部能用、都能从冷态自愈。含补丁的 25.12.4 因为多了主动探测路径,重建几乎无感;但「能不能用」这件事,三者并无区别。
7.3 人为注入劣化·
既然正常能用,笔者又试着把它压垮,看能不能逼出记忆中那种「不稳定」。
空闲老化。 停掉对 PC 的流量,用 ip monitor 盯邻居表。PC 的 LAN 侧邻居在 REACHABLE、STALE、PROBE 之间循环,但始终没有被回收:该中继的邻居表项数远低于内核回收阈值 gc_thresh1(128),低于阈值时内核根本不回收 stale 表项,于是 proxy 表项全程都在,没有出现「表项老化掉、再重建」的抖动。
人为劣化 WAN。 用 tc 的 netem 在 WAN 口注入延迟与丢包:
| WAN 注入劣化 | 实测影响 | proxy 表项 |
|---|---|---|
| 延迟 3s / 6s / 15s | 丢包约 0,RTT 等量增大 | 一直在 |
| 丢包 50% / 80% / 95% | 实测丢包约等于注入值,无放大 | 一直在 |
哪怕单向延迟拉到 15 秒、丢包拉到 95%,连通性也只是成比例地变差,没有出现「NDP 解析崩塌、整段黑洞」式的放大效应,proxy 表项纹丝不动。
根因很清楚:proxy 表项是静态的(NTF_PROXY),且由 LAN 侧的邻居事件维护,这条链路完全不受 WAN 质量影响。 内核收到上游 NS 当场本地应答;延迟只让报文「晚到」而非「不到」;WAN 再烂,也只是按链路质量成比例丢数据,动不了中继机制本身。
7.4 最干净的对照已无法完成·
把上面这些放在一起,可以给出一个更准确也更克制的图景。odhcpd 有两条独立的建表路径:
- 被动反应路径:上游(或同段其它中继)主动发来针对某地址的 NS,
odhcpd借机探测、建表。这条路径在所有版本都有,前提是「上游有理由发 NS」。 - 主动探测路径:
odhcpd由自己发出又回环的 NS 驱动,主动去 LAN 侧探测、建表。这条路径正是f0d8553才打通的,价值在于不依赖上游主动发 NS。
第 4、5 章对失效机理的分析(NUD 探测无法绕过、DAD 自探测回环死锁)在逻辑上依然成立,f0d8553 修掉的 bug 也是真实的。但本章的实测补上了一个关键限定:第 4 章那张「proxy 缺失、上游来探测、无从应答、条目进入 FAILED」的图景,在繁忙的共享段里往往并不致命:那条到来的 NS 本身就会触发 odhcpd 去重建 proxy(正是 7.2 的冷态重建),于是结局是丢几个包后恢复,而非永久中断。在本文这种校园 /64(上游网关与同段多台中继持续 solicit 全段地址)上,被动反应路径单独就足以把中继喂活,f0d8553 的主动探测路径在这里并不承重。
那么当年的失效又是怎么回事?笔者必须诚实承认:最干净的那个对照实验,已经做不成了。 它需要同时还原两样东西,而如今只剩一样还能还原:
- 版本这条线,笔者已经探到底:23.05.5 与 22.03.0(后者正落在当年使用的版本时代)都直接测过,配置标准、删光状态也照样在一两秒内反应式自愈。
f0d8553前后的多个版本今天都能用,「版本太老」这条基本可以排除。 - 真正还原不了的,是上游环境。校园网的 H3C 设备这些年完全可能改过策略,园区路由器也可能升级、配置也可能被优化过。当年那套上游行为,今天无从复刻。
正因为版本这条线已被排除,能解释「当年失效、如今可用」的,几乎只剩上游环境的变化。因此最老实的说法是:今天的「能用」,未必是 f0d8553 的功劳,更可能是上游环境早已不同。 笔者以及当年同样碰壁的若干人所经历的失效是真实的,但它的根因如今已落在时间的另一侧,无法干净地复现与归因。本文能确定的,是 NDP Relay 的机制、那个真实存在的 bug、以及 f0d8553 对它的修复;本文不能断言的,是这个 commit 就是某一个具体环境在今天得以正常工作的决定性原因。










