前言
2024年的今天,在各方积极的推动下,大陆IPv6的覆盖率已经非常高了;三大运营商新开的家庭宽带已经默认开通了IPv6,现存的宽带也通过改造、下发配置、设备更新的方式在迅速的推进中;而移动端则更是激进,在4G部署时就已经是按照双栈网路配置,可以说是完全覆盖,连二级热点都已经做足了充分的兼容。
就在几天前,工信部还发了一份《两部门关于开展“网络去NAT”专项工作 进一步深化IPv6部署应用的通知》的文件,其中的目标是在进一步的推进IPv6的积极应用的同时,开始为IPv4的NAT网路去除做准备(目前阶段主要关注NAT44设备规模停止增长),同时也提出了IPv6子网标准化要求,要求对IPv6网路的二次分发功能做好兼容。
Ovear自己家的网络在大约五六年前,三大运营商开通IPv6基本厘清大陆内的交换路由后,就实装进来了,也算是见过IPv6早期混乱路由的资深用户了(笑)。
PS:目前IPv6的国内路由已经完全可以满足日常使用了,可以说IPv6在移动端更是主要承载网络;除此之外,得益于政策支撑,IPv6跨境路由方面已经不逊于IPv4了,有些情况下因为使用的人稍微少一些,更是超过了IPv4。
问题描述
废话扯了这么久,还是快点进入正题吧。首先要了解这个问题,必须得知道两个前提;IPv6的SLAAC最小子网大小要求是/64
。
问:这个子网大小是什么概念呢?
答:这个大小比目前整个IPv4的所有地址加起来还要大。
那么这么大的子网,系统为了保护用户隐私,就会利用SLAAC为系统分配该子网下的多个IPv6地址作为临时地址,每段时间就会变更一次。同时因为IPv6极力不推荐使用NAT,就有可能出现一个内网广播域中有多个IPv6子网。
为了更好的管理这些IPv6地址,IPv6引入了两个概念:preferred_time
和valid_time
。在Linux中可以使用ip -6 ad
来确认对应IPv6地址对应的两个时间。其中preferred_lft
就是剩余的preferred_time
,而valid_lft
对应的就是valid_time
,如下面的示例所示。
2: eno1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 state UP qlen 1000
inet6 fdbd:ffff:ffff::fff/128 scope global dynamic noprefixroute
valid_lft 42525sec preferred_lft 1533sec
那么具体而言,IPv6地址的生命周期可大概分为两部分,其中:
0
-preferred_time
的这段时间为IPv6的有效使用时间:即该地址有效,且系统会积极使用该地址- 而
preferred_time
-valid_time
则是该IPv6的非积极有效时间:即该地址有效,但是系统会极力避免使用该地址。 - 通常
preferred_time
为valid_time
的一半。
需要注意:preferred_time
和valid_time
可以通过RA更新。即和IPv4一样,IPv6地址也可以进行“续租”。其具体时间应该是从preferred_left
为preferred_time
一半的时间开始续约(具体记不清了,有一个T0的时间点;如果我记错了还劳烦大家纠正;除此之外还有一个隐私地址机制IPv6 privacy extension
,这个机制开启后系统会定期分配新的地址使用),即在时间到达valid_time
的1/4时系统就会开始积极续约。
对于Linux系统来说每当preferred_lft
到达0以后,在ip -6 ad
中可以观察到地址被标记为deprecated
,意味着对于新发起的连接,系统会尽量避免使用该地址;而当preferred_lft
为0后,则会将该地址从系统中删除。
虽迟但到
Design is prefect, 一切都是这么的完美,会有什么意外呢?不出意外的话,就要出现意外了。
确实在大多数情况下,这套机制能非常完美的工作,但是现实总是没那么完美。当以下问题出现时,这套机制就可能发生问题:
- 路由器[异常/非异常]重启:对于OpenWRT 21.02来说,只要路由器重启了,旧的IPv6地址就不能正常弃用;其实也很好理解,重启后的路由就不知道之前的老的IPv6地址了,应当在重启之前将所有IPv6地址弃用,希望后续版本改进了这个问题。
- Linux在使用Network Manager时,调整了关于
temp_addr
的kernel sysctl
:理论上temp_addr
应当由内核协议栈处理,但是使用Network Manager之后,这部分逻辑似乎会有不同步的情况发生。解决该问题需要统一在Network Manager中配置,而不是系统内核。目前在Arch Linux中遇到过。 - 局域网内丢包:该问题常见于移动设备、设备休眠,感觉理论上还是内核处理不当。当系统未收到路由RA时对于某些IPv6段的弃用申明时,就会发生。或者中间交换机重启的时间,刚好和路由器拨号时间重叠,也会遇到这个问题,只能等待下次路由器RA广播时希望系统收到后能解决。
- 其他BUG:在Network Manager已经设定正确的上述情况下,偶发也会出现只有部分地址被
deprecated
的问题。目前也仅在Arch Linux+Network Manager中遇到,其中同网段的一个地址被正确弃用了,但是另外一个地址却没有被弃用,非常奇怪;只能怀疑不是Kernel的问题就是Network Manager的问题。Network Manager出现问题的可能性会大一些,局域网内其他Linux(Debian/Ubuntu)设备都没遇到这样的问题;Debian默认用的ifupdown
,Ubuntu则是自己的私货netplan
。临时解决方法当然就是重启网络。 - 启动DHCPv6 Statful以后:DHCPv6获取的地址是由DHCP客户端管理的,所以大概率要看DHCP客户端对RA的响应了。目前来看是会慢好几拍才会变,可能还是得看看DHCPv6的租约设定。不过由于系统一般都是会优先使用SLAAC的地址,所以问题倒是还不大。
以上仅仅是Ovear个人遇到的一部分情况,可能还有更多的情况没有列出,欢迎大家一起补充。
解决方案
经过抓包诊断,发现Ovear家电信的宽带设定的时间较长,preferred_time
设置为2天,valid_time
设置为3天;而为了避免运营商强制断线影响到网路正常使用,目前设定的都是每隔24小时自动重播一次,所以可能加剧了这个问题;移动也是类似情况;而联通则都设定为1个小时,联通宽带也基本没有没有观测到这个问题;因此可以初步考虑将电信和移动宽带的设置统一为和联通一样。
明确了原因之后,问题就很好解决了:
- 丢包问题:降低非请求RA广播间隔,降低RA有效时间
- IPv6有效期时间过长,降低最大
preferred_time
和valid_time
通过查询OpenWrt官方文档[1]可知,相关的参数为为:
ra_mininterval
、ra_maxinterval
:控制RA广播间隔preferred_lifetime
:控制最大的preferred_time
ra_useleasetime
:是否设定valid_time
为DHCP
租约时间ra_lifetime
:路由器发送的RA有效期
相关代码
# https://github.com/openwrt/odhcpd/blob/a29882318a4ccb3ae26f7cc0145e06ad4ead224b/src/router.c#L600-L614
if (addr->preferred_lt > (uint32_t)now) {
preferred_lt = TIME_LEFT(addr->preferred_lt, now);
if (preferred_lt > iface->preferred_lifetime) {
/* set to possibly user mandated preferred_lt */
preferred_lt = iface->preferred_lifetime;
}
}
if (addr->valid_lt > (uint32_t)now) {
valid_lt = TIME_LEFT(addr->valid_lt, now);
if (iface->ra_useleasetime && valid_lt > iface->dhcp_leasetime)
valid_lt = iface->dhcp_leasetime;
}
其中ra_mininterval
、ra_maxinterval
支持在luci界面中设定,设定位置为Network=>Interfaces=>lan=>IPv6 RA Settings。
为了方便统一设定,也可以使用编辑文件的方式设定,编辑OpenWrt中的/etc/config/dhcp
,找到对应配置文件段落,编辑为所需要的内容。
# /etc/config/dhcp
config dhcp 'lan'
option ra_mininterval 60
option ra_maxinterval 300
option leasetime '1h' # DHCPv4租约时间
option ra_lifetime 600 # 路由RA有效期
option ra_useleasetime 1 # 使用DHCPv4租约时间作为IPv6 valid_time
option preferred_lifetime '30m' # IPv6 preferred_time
设定完成并重启后可以使用tcpdump
/wireshark
等工具抓包查看是否生效。
# tcpdump -i br-lan -n -vv "icmp6 && ip6[40] == 134"
这样理论上应该能将无效的IPv6地址生命周期控制在可接收范围内。
参考文章
[1] [OpenWrt Wiki] odhcpd
[2] openwrt/immortalwrt修改odhcpd ipv6 preferred_lifetime和valid_lifetime - 海运的博客
OpenWrt中降低preferred_time和valid_time增加IPv6的稳定性没有评论