这是《OpenWrt IPv6 中继间歇断流排查》的后续。
上一篇解决的是 IPv6 relay 架构中的一个具体故障:Apple TV 发出的 Router Lifetime=0 RA 经 odhcpd 中继后换成了 OpenWrt 的身份,最终让下游客户端撤销真正的 IPv6 默认路由。精确过滤能够止血,但 relay 仍然是一个不得已的结构。
后来我们发现,运营商其实已经给了主路由一个完整的 /60。真正阻止二级 OpenWrt 获得独立 /64 的,不是运营商,也不是二级 OpenWrt,而是小米 AX6000 原厂配置中的这一项:
network.lan.ip6assign='64'
把它改为 60 后,小米原本已经存在的 odhcpd 就能够继续向下游委派 /64。网络也由“把同一个 /64 勉强中继到二级 LAN”,变成了标准的分层路由式 IPv6。
不过事情没有到此结束:小米会在部分重拨流程里把该值重新写回 64;即使脚本修回 60 并重启网络,odhcpd 的下游 PD 池也可能没有同步重建。最终方案因此包含两个动作:恢复 ip6assign=60,并在网络稳定后重启 odhcpd。
本文只讨论 IPv6 前缀委派、RA 和路由结构,不涉及任何代理软件或分流策略。文中的公网 IPv6 均使用文档地址替换,设备 MAC、链路本地地址和账户信息也已隐去。
省流
完整结论如下:
运营商通过 DHCPv6-PD 给小米 AX6000 一个 /60
↓
小米的 network.lan.ip6assign 却是 64
↓
主 LAN 只获得一个 /64,无法再从该 assignment 中分出下游 PD
↓
二级 OpenWrt 请求 IA_PD /64
↓
小米回复空 IA_PD 或 NoPrefixAvail
↓
二级 OpenWrt 只能退回 RA/DHCPv6/NDP relay
修复后:
运营商 /60
↓
小米 LAN assignment /60
├── 第一个 /64:小米自己的 LAN
└── 其余空间:odhcpd 向下游路由器委派 /64
↓
二级 OpenWrt
↓
独立 LAN /64
一、两种架构:没有 PD 与拿到 PD 以后
1. 没有 PD 时:只能把上游 /64 中继下来
最初的网络是:
光猫桥接
↓
小米 AX6000 PPPoE 拨号
↓ 同一个上游 LAN /64
二级 OpenWrt:RA relay + DHCPv6 relay + NDP relay
↓
客户端
这种结构能让二级 LAN 获得公网 IPv6,却没有真正的子网边界。二级 OpenWrt 没有属于自己的 delegated prefix,只能在两个 routed、non-bridged 接口之间转发 RA、DHCPv6 和 NDP(邻居发现协议)。
OpenWrt 的 odhcpd 本来就提供 relay 功能,官方也明确说明它用于“没有 delegated prefix 时”的场景。因此 relay 不是错误配置,但它有天然代价:
- 上游和下游共享同一个
/64; - 二级路由器必须代理邻居发现;
- 上游 LAN 中的 RA 可能被带到下游;
- 前缀变化、睡眠唤醒、默认路由和地址生命周期都更依赖控制报文被正确中继;
- 某些报文经过 relay 后无法保留原始链路身份。
上一篇IPV6故障排查中 Apple TV RA 所带来的故障就是这种架构复杂度的具体表现。
2. 有 PD 后:每层路由器拥有自己的前缀
改造后的结构是:
光猫桥接
↓
运营商 DHCPv6-PD /60
↓
小米 AX6000
├── 主 LAN:2001:db8:1234:abc0::/64
└── 下发给 OpenWrt:2001:db8:1234:abc1::/64
↓
OpenWrt RA Server
↓
客户端
小米和二级 OpenWrt 的 LAN 不再共享同一个 /64。二级 OpenWrt 是真正的下游路由器,客户端默认路由只由它自己的 RA Server 宣告,不再需要把主路由 LAN 上其他设备的 RA 中继进来。
这一步从架构上消除了此前 Apple TV RA 经 relay 改换身份的条件,也让 RA/DHCPv6/NDP relay 和为它添加的临时 RA Guard 可以退出运行态。
二、先证明:运营商到底给了 /60 还是只给了 /64
声明:以下所有操作均建立在已获取并固化小米路由器 SSH 权限的前提下。文中涉及的命令、配置修改及系统操作,默认读者已经具备基本的 Linux / OpenWrt / 网络配置知识,并能够自行判断操作风险、排查异常以及完成必要的备份与恢复。需要特别说明的是,小米路由器默认并不面向普通用户开放 SSH。自行获取 SSH 权限、修改系统文件、持久化启动项或更改原厂配置,均可能带来设备异常、无法启动、配置丢失等风险,并且可能影响或失去官方保修、售后支持。因此,如果你准备按照本文进行操作,请务必确认自己已经理解相关原理,并对可能出现的后果具备基本处理能力。本文仅记录个人实践过程,不保证适用于所有机型、固件版本或网络环境;因照搬本文操作导致的设备故障、数据损失、网络中断或其他问题,需要由操作者自行承担。
不要妄图从小米的后台页面上的 LAN 地址推测运营商分配了多大前缀。一个接口显示 /64,并不代表 WAN 没拿到更大的 delegated prefix。
在小米 SSH 后台执行:
ubus call network.interface.wan_6 status
现场输出中最关键的部分经过匿名化后类似:
{
"delegation": true,
"ipv6-prefix": [
{
"address": "2001:db8:1234:abc0::",
"mask": 60,
"assigned": {
"lan": {
"address": "2001:db8:1234:abc0::",
"mask": 64
}
}
}
]
}
这段状态同时证明了两件事:
- 运营商确实向小米委派了
/60; - 小米只从中给自己的 LAN assignment 取了一个
/64。
一个 /60 可以划分成 16 个 /64:末尾的子网 nibble 可以从 0 到 f。现场并不是“运营商扣死只给一个 /64”,而是主路由没有把已经获得的地址空间继续作为下游 PD 池使用。
检查配置:
uci get network.lan.ip6assign
原始值为:
64
这里的 ip6assign 不是“向运营商申请前缀”的选项,而是 OpenWrt/netifd 给下游接口分配多大前缀的参数。OpenWrt 官方 IPv6 文档明确说明:当 ip6assign 小于 64 时,DHCPv6 Server 才能把除第一个 /64 之外的空间继续通过 IA_PD 委派给下游路由器。
因此 /60 已经到达小米,问题卡在小米的 LAN assignment 层。
注意:
uci show network可能包含 PPPoE 用户名、密码或其他凭据。不要把未经脱敏的完整输出上传到论坛、聊天记录或博客。
三、下游抓包:OpenWrt 请求了,但小米并没有给
在二级 OpenWrt 的上联接口抓 DHCPv6:
tcpdump -ni eth1 -vvv -s0 'udp port 546 or udp port 547'
然后重新拉起 WAN6:
ifdown wan6
sleep 3
ifup wan6
修复前的关键过程是:
OpenWrt Solicit:请求 IA_PD ::/64
Xiaomi Advertise:返回 IA_PD 容器,但没有 IA_PD-prefix
或返回 NoPrefixAvail
后续 Request:无法带上可用的 delegated prefix
这组报文排除了两个常见误判:
- 不是二级 OpenWrt 没有发起 PD 请求;
- 不是仅仅少配了客户端侧的
reqprefix。
结合小米 wan_6 中明确存在的 /60,故障层可以定位为:
小米已经从 ISP 获得前缀池,但 LAN assignment 为
/64,odhcpd 没有可用于下游 IA_PD 的剩余空间。
四、先做一次最小化验证
1. 备份
修改前至少备份 UCI 配置。/tmp 只适合短期查看,想跨重启保留应放在持久目录或下载到本地。不同小米固件的持久分区布局可能不同。先确认 /data 在自己的型号和固件上跨重启保留,再照搬本文的持久化部分。
2. 在小米上把 LAN assignment 改为 /60
uci set network.lan.ip6assign='60'
uci commit network
/etc/init.d/network restart
这会中断家庭网络一小段时间。
3. 让二级 OpenWrt 请求 /64
二级 OpenWrt 的 WAN6 至少应满足:
uci set network.wan6.proto='dhcpv6'
uci set network.wan6.reqaddress='try'
uci set network.wan6.reqprefix='64'
uci set network.wan6.delegate='1'
uci commit network
ifdown wan6
sleep 3
ifup wan6
再次查看:
ubus call network.interface.wan6 status
修复后第一次成功状态中出现了:
"ipv6-prefix": [
{
"address": "2001:db8:1234:abc1::",
"mask": 64,
"preferred": 43196,
"valid": 43196,
"class": "wan6"
}
]
而小米自己的 LAN 使用的是另一个 /64:
Xiaomi LAN: 2001:db8:1234:abc0::/64
OpenWrt PD: 2001:db8:1234:abc1::/64
这就是决定性证据:二级 OpenWrt 得到的是带租期的 DHCPv6-PD。
五、把二级 OpenWrt 从 relay 改成标准 server 架构
确认 WAN6 已经稳定获得 /64 后,再退出 relay。建议在 LuCI 中逐项核对,而不是盲删未知名称的 UCI section。
最终状态应为:
| 位置 | 配置 | 目标状态 |
|---|---|---|
| OpenWrt WAN6 | 协议 | DHCPv6 Client |
| OpenWrt WAN6 | 请求 IPv6 地址 | try |
| OpenWrt WAN6 | 请求 IPv6 前缀 | /64 |
| OpenWrt WAN6 | 使用内置 IPv6 管理 | 启用 |
| OpenWrt LAN | IPv6 assignment length | /64 |
| OpenWrt LAN | RA 服务 | Server |
| OpenWrt LAN | DHCPv6 服务 | Server |
| OpenWrt LAN | NDP Proxy/Relay | Disabled |
| 旧 WAN6 relay/master section | RA/DHCPv6/NDP relay | Disabled |
对应 LAN 的核心 UCI 可整理为:
uci set network.lan.ip6assign='64'
uci set dhcp.lan.ra='server'
uci set dhcp.lan.dhcpv6='server'
uci set dhcp.lan.ndp='disabled'
uci -q delete dhcp.lan.master
uci commit network
uci commit dhcp
/etc/init.d/network restart
/etc/init.d/odhcpd restart
如果原来为 WAN6 单独建立过 relay master section,应在 LuCI 中将它停用或改回普通上联配置。section 名称会因固件和历史配置不同而变化,不应直接照抄删除命令。
完成后检查:
ubus call network.interface.wan6 status
ubus call network.interface.lan status
ip -6 addr show br-lan
ip -6 route show
应看到:
- WAN6 的
ipv6-prefix中有一个/64; - LAN 的
ipv6-prefix-assignment使用同一个/64; br-lan上有来自当前 PD 的地址;- 客户端默认路由指向二级 OpenWrt;
- RA、DHCPv6 使用 server,NDP relay 已关闭。
六、第一处坑:重拨后小米会把 60 改回 64
单次修改成功后,我们先重启过小米,配置一度仍然存在。但在后续真实 PPPoE/IPv6 重拨中,原厂流程又把:
network.lan.ip6assign='60'
改回了:
network.lan.ip6assign='64'
与此同时,ubus call network.interface.wan_6 status 里运营商下发的 ipv6-prefix 仍是 /60。
因此可以严格区分:
/60租约没有消失;- 小米原厂的网络重建流程覆盖了 LAN assignment;
- 具体是哪一个 vendor 二进制或配置生成器写回
64,本文没有完成逆向,所以不把责任归到某个未经证明的进程。
如果能找到并修改写回源头,那是最优雅的根治。但在“不换主路由、不刷第三方固件、不逆向厂商二进制”的约束下,最现实的方法是在 wan_6 重建后自动校正。
七、第二处坑:只重启 network,PD 池未必恢复
最初的自愈脚本只做了:
发现 ip6assign=64
→ 改回 60
→ network restart
表面状态已经恢复:
ip6assign = 60
wan_6 prefix = /60
assigned.lan.mask = 60
但下游 OpenWrt 再次请求时,抓包仍然看到:
Solicit:IA_PD ::/64
Advertise:只有 IA_PD 容器,没有 IA_PD-prefix
在小米上额外执行:
/etc/init.d/odhcpd restart
随后同一抓包立刻变为:
Solicit
→ Advertise,包含 IA_PD-prefix 2001:db8:1234:abc1::/64
→ Request,携带该 /64
→ Reply,确认该 /64
这证明在本文这台 AX6000 和该版原厂固件中,network restart 后 odhcpd 的下游 PD 池可能没有可靠地同步重建;在 /60 assignment 稳定后重启 odhcpd,是自愈闭环不可缺少的一步。
八、最终使用的重拨自愈方案
1. 主修复脚本
保存在持久分区:
/data/fix-ipv6-pd.sh
整理后的脚本如下。锁文件用于防止 network restart 再次触发 hotplug 后递归执行:
#!/bin/sh
LOCK="/tmp/fix-ipv6-pd.lock"
TAG="fix-ipv6-pd"
[ -e "$LOCK" ] && exit 0
CURRENT="$(uci -q get network.lan.ip6assign)"
[ "$CURRENT" = "60" ] && exit 0
touch "$LOCK" || exit 1
cleanup() {
rm -f "$LOCK"
}
trap cleanup EXIT INT TERM
logger -t "$TAG" "detected lan.ip6assign=$CURRENT, restoring to 60"
uci set network.lan.ip6assign='60'
uci commit network
logger -t "$TAG" "restarting network"
/etc/init.d/network restart
# 等待 WAN6 与 LAN 的 /60 assignment 稳定
sleep 15
logger -t "$TAG" "restarting odhcpd to rebuild downstream PD pool"
/etc/init.d/odhcpd restart
sleep 3
NEW="$(uci -q get network.lan.ip6assign)"
if [ "$NEW" = "60" ]; then
logger -t "$TAG" "success: lan.ip6assign=60, odhcpd restarted"
else
logger -t "$TAG" "failed: lan.ip6assign=$NEW"
fi
exit 0
权限建议限制为 root:
chmod 700 /data/fix-ipv6-pd.sh
2. wan_6 hotplug 触发器
持久模板:
/data/99-fix-ipv6-pd
内容:
#!/bin/sh
[ "$INTERFACE" = "wan_6" ] || exit 0
case "$ACTION" in
ifup|ifupdate)
;;
*)
exit 0
;;
esac
(
sleep 10
/data/fix-ipv6-pd.sh
) &
exit 0
chmod 700 /data/99-fix-ipv6-pd
实际运行位置为:
/etc/hotplug.d/iface/99-fix-ipv6-pd
但这台小米的 /etc/hotplug.d 在重启后可能恢复,因此不能只把脚本放在这里。
3. 开机重新安装 hotplug
创建:
/data/install-ipv6-pd-fix.sh
内容:
#!/bin/sh
mkdir -p /etc/hotplug.d/iface
cp /data/99-fix-ipv6-pd /etc/hotplug.d/iface/99-fix-ipv6-pd
chmod 700 /etc/hotplug.d/iface/99-fix-ipv6-pd
logger -t ipv6-pd-fix "hotplug hook installed"
exit 0
chmod 700 /data/install-ipv6-pd-fix.sh
本文这台 AX6000 使用原厂 UCI firewall include 作为持久化入口:
uci set firewall.ipv6_pd_fix=include
uci set firewall.ipv6_pd_fix.type='script'
uci set firewall.ipv6_pd_fix.path='/data/install-ipv6-pd-fix.sh'
uci set firewall.ipv6_pd_fix.enabled='1'
uci commit firewall
/data/install-ipv6-pd-fix.sh
确认:
uci show firewall.ipv6_pd_fix
ls -l /etc/hotplug.d/iface/99-fix-ipv6-pd
这一做法有明确的设备/固件依赖。firewall include 会以 root 权限执行脚本,因此 /data 中这些文件必须只有 root 可写,SSH 也只能留在可信 LAN 内。其他小米型号或固件应先验证启动机制,不要把本文当成通用官方功能。
最终自愈时间线是:
PPPoE / WAN6 重建
↓
小米原厂流程可能先写回 ip6assign=64
↓
wan_6 ifup / ifupdate 触发 hotplug
↓ 等 10 秒
若当前不是 60,则写回 60 并 network restart
↓ 等 15 秒
重启 odhcpd,重建下游 PD 池
↓
二级 OpenWrt 再次获得 /64
九、如何做一次真正的最终验收
仅仅看到“现在能上 IPv6”不够。至少要验证一次整机重启和一次真实重拨。
1. 小米侧
uci get network.lan.ip6assign
ubus call network.interface.wan_6 status
ubus call network.interface.lan status
logread | grep -E 'fix-ipv6-pd|ipv6-pd-fix'
应满足:
network.lan.ip6assign = 60
WAN6 ipv6-prefix mask = 60
assigned.lan.mask = 60
日志出现 odhcpd restarted / success
2. 二级 OpenWrt 侧
ubus call network.interface.wan6 status
ubus call network.interface.lan status
ip -6 addr show br-lan
ip -6 route show
应满足:
WAN6 delegation = true
WAN6 ipv6-prefix mask = 64
LAN ipv6-prefix-assignment = 同一个 /64
RA = server
DHCPv6 = server
NDP relay = disabled
若 WAN6 的 ipv6-prefix 为空,重新抓 DHCPv6:
tcpdump -ni eth1 -vvv -s0 'udp port 546 or udp port 547'
正常的服务器回复必须包含 IA_PD-prefix .../64,不能只有一个空的 IA_PD 容器。
3. 客户端侧
Windows:
ipconfig /all
route print -6
curl.exe -6 https://api64.ipify.org
macOS/Linux:
ifconfig
netstat -rn -f inet6
curl -6 https://api64.ipify.org
最终再做一次真实场景:
客户端保持联网或进入睡眠
→ 小米重新拨号,公网前缀变化
→ 自愈脚本完成 64 → 60
→ odhcpd 重建下游 PD
→ OpenWrt 获得新 /64
→ 客户端收到新前缀,旧前缀正常退场
→ IPv6 网站继续可用
反复连续重拨可能让路由器和客户端暂时保留多代前缀。测试完成后应给地址生命周期正常收敛的时间;若实验现场已经严重污染,可重启二级 OpenWrt,并让客户端网卡重新入网一次,再从干净状态验收。
十、为什么旧 Apple TV RA Guard 可以退出
旧架构需要中继:
主路由 LAN 上的 Apple TV RA
→ OpenWrt RA relay
→ 下游客户端
新架构中:
小米 /60
→ DHCPv6-PD 给 OpenWrt /64
→ OpenWrt 自己作为 RA Server
→ 客户端
下游客户端不再依赖主路由 LAN 的 RA,也不再接收经 OpenWrt 改换身份的 Apple TV RA。此前为 relay 添加的精确入口规则因此可以停用;更不能恢复那条“全局丢弃所有 Router Lifetime=0 RA”的旧规则,因为它会连合法的前缀退场 PIO 一起丢掉。
本文现场最终将旧规则文件保留为禁用备份:
/etc/nftables.d/20-ra-relay-zero-lifetime-guard.nft.disabled
检查运行态:
nft -a list ruleset | grep -Ei 'apple|zero-lifetime|router-advert'
正常情况下不应再有那条自定义 RA DROP。
十一、完整回滚:恢复小米原厂行为
如果自愈逻辑导致异常,或者以后不再需要给二级 OpenWrt 委派前缀,可以在小米本地 SSH 中执行:
rm -f /data/fix-ipv6-pd.sh
rm -f /data/99-fix-ipv6-pd
rm -f /data/install-ipv6-pd-fix.sh
rm -f /etc/hotplug.d/iface/99-fix-ipv6-pd
uci -q delete firewall.ipv6_pd_fix
uci commit firewall
uci set network.lan.ip6assign='64'
uci commit network
reboot
这会取消本文添加的自愈脚本和持久化入口,并把小米 LAN assignment 恢复为原来的 /64。随后二级 OpenWrt 若仍需 IPv6,要么重新启用原来的 relay 架构,要么改由其他支持下游 DHCPv6-PD 的主路由负责拨号。
在执行前应确认路径与 UCI section 名称完全一致;这些删除命令只针对本文列出的三个 /data 文件、一个 hotplug 文件和 firewall.ipv6_pd_fix section。
十二、结论
已证明
- ISP 已向本文的 AX6000 委派
/60; - 小米最初只给 LAN 分配
/64; - 二级 OpenWrt 确实发出了
IA_PD /64请求; - 修复前,小米返回空 IA_PD 或
NoPrefixAvail; - 把
network.lan.ip6assign改为60后,二级 OpenWrt 获得了独立/64; - 某些真实重拨会把
ip6assign重新写回64; - 在一次已复现现场中,
network restart后 PD 池仍为空,重启 odhcpd 后立即恢复IA_PD-prefix /64; - 重启与再次重拨后,自愈链仍能恢复
/60 → /64 PD。
未经证明
- 所有运营商都会给家庭宽带
/60,V2EX上的帖子中可以发现,不同地区,不同运营商的下发策略可能不同; - 所有小米 AX6000 固件都会以完全相同方式覆盖
ip6assign; - 所有小米/Redmi 路由器都能使用同一套
/data + firewall include持久化; - 写回
64的具体 vendor 进程或二进制已经被定位; - 只要强行设置
ip6assign=60,一个只从 ISP 获得/64的用户也能凭空得到 16 个/64。
最后一点尤其重要:本文只是释放并正确使用运营商已经委派给主路由的 /60。如果 wan_6 status 中确实只有 /64,就没有可继续切分的标准下游 /64;修改一个 UCI 数字不会创造地址空间。
十三、经验
1. 不要用 LAN 地址长度代替 WAN-PD 证据
主路由 LAN 显示 /64,只说明该链路需要一个 /64。判断 ISP 到底委派了多大前缀,应看 wan_6 的 ipv6-prefix,而不是 ip addr 中某个单独地址。
2. IA_NA 和 IA_PD 是两件事
路由器获得一个 IPv6 地址,并不等于获得可继续下发的前缀。二级 OpenWrt 能拿到地址却拿不到 ipv6-prefix 时,应抓 DHCPv6,分别检查 IA_NA 与 IA_PD。
3. 空 IA_PD 也是证据
服务器没有显式打印 NoPrefixAvail,但 Advertise 中存在 IA_PD 容器却没有 IA Prefix Option,同样说明客户端没有获得可用的 delegated prefix。
4. 配置值正确,不等于守护进程运行态正确
本案中 UCI、ubus assignment 都已恢复 /60,但 odhcpd 的 PD 池仍为空。只有抓包看到完整的 Solicit → Advertise(IA_PD-prefix) → Request → Reply,才算真正恢复。
5. 能用 PD,就不要把 relay 当长期等价替代品
relay 解决的是“上游不提供前缀委派”时的兼容问题;PD 解决的是分层路由本身。二者都能让客户端出现公网 IPv6 地址,但控制面复杂度、故障边界和重编号行为完全不同。
结语
这次最反直觉的地方是:我们一度以为运营商只给了 /64,甚至准备联系运营商;但当真正ssh到小米后台看 wan_6 后才发现,完整的 /60 一直都在。
问题不是“没有地址”,而是地址池没有被正确分配到能够继续委派的层级:
ISP 已给 /60
≠ 主路由已经把 /60 用作下游 PD 池
≠ 二级路由器已经拿到 /64
最终的解决也不是给 relay 再叠一层补丁,而是让网络回到 IPv6 原本擅长的方式:上级拿较大前缀,下级拿独立 /64,每一层各自路由和宣告。
从此,二级 OpenWrt 不再“借用”主路由 LAN 的 IPv6,而是真正拥有了自己的前缀。
留言 / CONVERSATION
欢迎交流,评论将在审核后展示,你的邮箱不会被公开。
正在准备评论区…