记一次Client无法获取IPv6地址问题的分析过程
近日SQA报了一个bug,对路由器经过6天左右的压力测试后,无论是有线设备还是无线设备都拿不到IPv6
地址了。经过层层分析发现可能是kernel
内存泄漏。本文便记录这一问题的分析过程。
检查网络状态
首先打开Router的console,使用ifconfig br0
(br0是Router LAN端的桥接地址)查看当前的网络状态
$ ifconfig br0
br0 Link encap:Ethernet HWaddr A0:63:91:A7:63:07
inet addr:192.168.27.1 Bcast:192.168.27.255 Mask:255.255.255.0
UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1
RX packets:530995052 errors:0 dropped:0 overruns:0 frame:0
TX packets:1012058984 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:0
RX bytes:108097854210 (100.6 GiB) TX bytes:1331062907089 (1.2 TiB)
可以看到,Router自身也只有IPv4
地址,没有IPv6
地址,所以问题出在Router自身,不可能是Client配置错误,那为什么Router自己都没有拿到IPv6
呢?下面来分析压力测试过程保存的console log.
分析console log
在压力测试的前几天,从以下log可知Router是有IPv6
的,Router貌似正常运行几天后就失去了Ipv6
br0 Link encap:Ethernet HWaddr A0:63:91:A7:63:07
inet addr:192.168.27.1 Bcast:192.168.27.255 Mask:255.255.255.0
inet6 addr: 2002:76a7:859d:0:44f3:adff:fef9:f52/64 Scope:Global
inet6 addr: fe80::a263:91ff:fea7:6307/64 Scope:Link
UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1
RX packets:1184678 errors:0 dropped:0 overruns:0 frame:0
TX packets:1476645 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:0
RX bytes:1200677624 (1.1 GiB) TX bytes:1867999218 (1.7 GiB)
其中的IPv6
地址如下:
inet6 addr: 2002:76a7:859d:0:44f3:adff:fef9:f52/64 Scope:Global
inet6 addr: fe80::a263:91ff:fea7:6307/64 Scope:Link
第一个IPv6
是用来与外部互联网通信的地址,第二个以fe80::
开头的称为链路本地地址(Link-local address),与IPv4
中的169.254.0.0/16
地址类似,此类地址不需要与外部网络通信,通常用于本地主机间的相互通讯。
IPv6相关进程分析
好了,现在的问题是什么导致IPv6
忽然不见了,我最初想法是Router的dhcpv6
服务器挂了,但是ps |grep dhcp
后发现是正常的。
$ ps |grep dhcp
2951 root 368 S udhcpd /tmp/udhcpd.conf
28323 root 352 S /usr/sbin/dhcp6s -3 -c /tmp/dhcp6s.conf br0
此处说明一点,由于WAN端使用的PPPoE
拨号上网方式,所以没有启动dhcp6c
(用于获取ipv6的客户端程序)。那是不是还有其它相关的进程挂了呢?为此我问了下组长(大牛),发现确实还有个radvd
进程!对比前后log
中的ps
输出发现确实是这个进程挂了!!!
路由广播守护(The Router Advertisement Daemon,简称:
radvd
)是一个符合RFC 2461
使用邻居发现协议用于实现IPv6
地址本地链接广播和IPv6
路由前缀的开源软件。该软件是给系统管理员用于实现在IPv6
下对主机进行无状态自动配置地址。
--- 维基百科
总之,radvd
也是IPv6
必不可少的进程,而且莫名其妙的挂了。
radvd进程退出原因分析
为了分析radvd
进程退出原因,我尝试手动重启进程
$ /usr/sbin/radvd -C /tmp/radvd.conf
[Feb 26 06:29:51] radvd: syntax error in /tmp/radvd.conf, line 19: {
[Feb 26 06:29:51] radvd: error parsing or activating the config file: /tmp/radvd.conf
提示配置文件第19语法错误,那来看一下/tmp/radvd.conf
$ cat /tmp/radvd.conf
interface br0 {
AdvSendAdvert on;
AdvCurHopLimit 64;
MinRtrAdvInterval 198;
MaxRtrAdvInterval 600;
AdvOtherConfigFlag on;
AdvDefaultLifetime 1800;
AdvReachableTime 0;
AdvRetransTimer 0;
AdvDefaultPreference low;
AdvHomeAgentFlag off;
AdvManagedFlag off;
prefix 2002:24e0:69cf:0::/64 {
AdvOnLink on;
AdvAutonomous on;
AdvValidLifetime 2400;
AdvPreferredLifetime 1800;
};
RDNSS {
AdvRDNSSPreference 8;
AdvRDNSSLifetime 1200;
};
};
第19行RDNSS {
与正常情况RDNSS fe80::a263:91ff:fea7:6307 {
相比少了链路本地地址,也就是fe80::
开头的地址。那么这个问题是怎么产生的呢?为什么会少参数呢?这就需要来看具体的代码了。
net6conf脚本分析
分析GUI相关代码后发现,在点击Router的IPv6
配置页面的Apply
按钮后,后台会执行net6conf restart
,那么就需要分析net6conf
代码
$ grep -rn fe80:: net6conf
net6conf:97: $IP -6 addr add fe80::$eui64/64 dev $bridge
net6conf:103: $IP -6 addr add fe80::$ipv6_interface_id/64 dev $bridge
使用grep
初步定位到链路本地地址的生成处,由于net6conf
是个shell
脚本,所以可以在代码附近添加一行set -x
,然后手动执行net6conf restart
,那么代码执行过程就会很详细的打印出来,接着找到生成链路本地地址的那一部分
+ /usr/sbin/ip -6 addr add fe80::a263:91ff:fea7:6307/64 dev br0
RTNETLINK answers: Cannot allocate memory
OK,总算找到问题的关键所在了,程序在添加地址时出现了无法内存分配的错误。
无法分配内存原因分析
对于RTNETLINK answers: Cannot allocate memory
,常规思路当然是内存不足了,但是通过free
查看还有200M+
绝对足够。然后谷歌一下,貌似遇到类似问题的也不少,比如IPv6 routing/neighbor table suspected memory leak,也是跑了几天就出这个问题了。
大部分的解决方案是修改一个系统级的配置参数net.ipv6.route.max_size
,这个参数定义了IPv6
路由表的最大尺寸,默认值为4096Bytes,将其改大一点就可以了。
$ sysctl net.ipv6.route.max_size
net.ipv6.route.max_size = 4096
$ sysctl -w net.ipv6.route.max_size=16384
net.ipv6.route.max_size = 16384
$ /usr/sbin/ip -6 addr add fe80::a263:91ff:fea7:6307/64 dev br0
修改完后重启IPv6
,发现一切都回归正常了,Oh yeah😀
那么最后还剩一个问题,按理说net.ipv6.route.max_size
是系统自带的默认参数,参数值也是经过实践验证的,一般来说是够用的,为什么会出现不够用的情况?依据网上的资料显示,可能是kernel memory leak, 具体原因还需进一步测试和验证,但至少目前增大这个参数确实能够解决问题!
参考
版权声明:本博客所有文章除特殊声明外,均采用 CC BY-NC 4.0 许可协议。转载请注明出处 litreily的博客!