接上篇的内容,我要从公网访问家中的内网服务,这完全依赖 DDNS 脚本能快速更新家里当前的 IPv6 地址,但是经过一段时间的使用,我发现时不时会有连不上的问题,具体体现在路由器重启,或者 WAN 口重新拨号之后,客户端的 DDNS 总是不能及时刷新最新的 IPv6 地址,那么问题出在哪里。

地址如何变化

首先 IPv6 地址是怎么变的,当路由器重启或者手动重启路由器的 WAN 口,一般都会获取新的 IPv6 地址,旧地址失效。但如果不主动重启 WAN 口呢?

经过一段时间的观察,在 OpenWrt 日志看到如下内容:

image-20250730213128097

注意到这边联通为了防止长时间连接,会每 48 小时主动断开 PPPoE 会话然后重新拨号,自然的 IPv6 地址会发生变化。

为什么客户端 DDNS 没有刷新

路由器端的 IPv6 RA 我配置的 SLAAC 模式,客户端的 DDNS 脚本用的是 aliyun-ddns。因为我在路由器上配置了 OpenClash 的 Fake-IP 模式,从在线 API 获取 IPv6 总会有各种问题,所以开启了 CHECKLOCAL 从本地网卡获取 IP。

注意到当 WAN 重新拨号之后,路由器的确向客户端下发了新的 IPv6 前缀,但此时旧的 IPv6 地址依然没有失效,客户端同时拥有新旧两个 IPv6 地址,而 DDNS 脚本依然读取的是旧地址,所以无法及时刷新解析。

如何快速废弃旧地址

现在的问题就来到了如何在下发新前缀之后快速废弃掉旧的 IPv6 地址。这里就要先了解 IPv6 的两个重要参数:valid_lifetimepreferred_lifetime

  1. valid_lifetime(有效生命周期)

    • 表示该地址在多长时间内是“合法的(valid)”。
    • 只要这个时间没过,该地址就仍然存在于系统中,可能用于接收已有连接的数据包。
    • 即使过了 preferred_lifetime 变成了“弃用地址”,只要没过 valid_lifetime,这个地址还是可用的。
  2. preferred_lifetime(首选生命周期)

    • 表示地址在这段时间内是“首选的(preferred)”。
    • 首选状态的地址可以用于新建连接。
    • 一旦过期,就变成“弃用的(deprecated)”地址:不再用于新建连接,但仍可用于维持已有连接(直到 valid_lifetime 结束)。

所以此时我产生了两个修改思路:

  1. preferred_lifetime 配置为 48 小时,这样运营商重新拨号之后旧地址的 preferred_lifetime 应该会到期,系统优先选择 preferred_lifetime 没有到期的新 IP。
  2. 或者让 valid_lifetime 尽可能短,这样旧地址就会快速到期并废弃掉。

但是实际发现并不可行。

先说方法 1,当前 DDNS 脚本并不会优先选择 preferred_lifetime 未到期的地址,虽然似乎只要我自己重新写一个 DDNS 脚本去筛选 preferred_lifetime 就可以解决问题,但是我懒(。

而方法 2 呢,这么干会让客户端的 IPv6 地址频繁发生变动,感觉并不是什么很优雅的解决方案。

随后我又想,那么在路由器 WAN 重新拨号的时候,发一条 RA 报文把旧地址的 valid_lifetime 变成 0 不就好了。然后我就被 ChatGPT 的幻觉骗了(

GPT 像模像样地告诉我可以用 ubus call odhcpd send_ra 自己发送 RA,然后我兴致勃勃地研究了半天,最后发现根本没有这玩意...

继续研究 OpenWrt 的配置,终于让我找到了相对完美的解决方案。

首先 LAN 口的配置中并不能直接设置 valid_lifetime,只能指定 preferred_lifetime,而 valid_lifetime 会由系统自动计算,会比当前 preferred_lifetime 更长。

而配置中还有一项叫做 ra_useleasetime,在 luci 界面中叫做 遵守 IPv4 有效期,此项为 1 时,会把 DHCPv4 的租约时间作为 IPv6 的 valid_lifetime

并且我发现当 WAN 重新拨号时,会发送 RA 广播将旧 IPv6 地址的 preferred_lifetime 置为 0。

那问题就很好解决了,开启 ra_useleasetime,并把 valid_lifetimepreferred_lifetime 设置为一样的值,这里我就改为默认的 12h。此时只要一个 IPv6 地址没有废弃,那么它的 preferred_lifetime 必不为 0,而当 WAN 重新拨号时,旧地址的 preferred_lifetime 就会变成 0,而我要做的就是写一个脚本定时检查,移除 preferred_lifetime 为 0 的地址,就可以快速废弃失效地址。

#!/bin/bash

IFACE="br0"

ip -6 addr show dev "$IFACE" | \
grep -B1 "preferred_lft 0sec" | \
grep inet6 | \
awk '{print $2}' | while read -r addr; do
    echo "[IPv6 Cleanup] Removing deprecated address: $addr from $IFACE"
    ip -6 addr del "$addr" dev "$IFACE"
done
Last modification:July 30th, 2025 at 10:53 pm
If you think my article is useful to you, please feel free to appreciate