一次Keepalived的coredump问题定位
发表于 2025/04/22
0
作者|张久同
问题现象
在某个项目中,Keepalived工作异常,无法正常启动。Keepalived的第三个进程pid一直在不停更换,查看messages日志:

会不断打印keepalived进程core dump信息。
应用信息
软件名称 | 版本 | 说明 |
麒麟 | v10 sp1 | 操作系统 |
Keepalived | 1.2.9 | Keepalived |
Keepalived是用来业务组件的做心跳探活,集群检查等工作的开源组件。
对比分析
* X86+centos运行正常
* 鲲鹏+centos运行正常
* 鲲鹏+kylin运行core dump
推断是内核差异导致的core dump,centos为4k页表,麒麟为64k页表。
实验室重新编译4k内核(openEuler 20.03)后运行正常。
推断是程序中存在与内核页表大小相关的变量导致core dump,同时对比。
问题定位
1. 分析core dump文件

是在vrrp_parser.c文件的176行报错。
查看源码: https://github.com/acassen/keepalived/blob/v1.2.9/keepalived/vrrp/vrrp_parser.c
访问vrrp->ifp->ifindex时报错。

该变量时从if_get_by_name函数返回。同时gdb打印vrrp->ifp为空,所以应该是访问了空指针导致的core dump。

该if_get_by_ifname函数逻辑就是遍历if_queue队列,如果找到了该name的对象,就返回该ifp,如果没找到就返回NULL。
在if_get_by_ifname中添加测试代码,打印出来if_queue的所有数据:


在实验室环境,指定的网口为enp4s0,但是只是循环打印以上内容,定位到原因是没找到enp4s0网口导致的core dump。
2. 定位为什么if_queue中缺少网卡数据
在源码中只有这部分是往if_queue队列中添加

该函数为netlink_if_link_filter,继续向上查看:

调用该函数的是netlink_interface_lookup,且该函数没有变量输入,为最上层函数,查看netlink_parse_info函数,结合message出现的报错:

该netlink_parse_info会有两种情况下出现该ERROR。如下所示:

3. 定位Netlink:error的原因

这里将不同触发条件写到指定文件中,运行后打印该文件:

发现是第二部分出现的error,也就是msg.msg_flags的标记为被标记为了MSG_TRUNC。
查看msg的赋值以及初始化:

msg变量是由recvmsg函数进行赋值修改的。通过(man recvmsg)查看recvmsg函数介绍

该标志位表示:由于数据报大于提供的缓冲区,因此丢弃了数据报的尾部。而buf是硬编码的4096。也就是说是由于buf太小导致recvmsg函数丢弃了数据包的尾部。导致网口信息解析失败。
4. core dump的根因
该进程core dump根因为buf数组设置过小,导致recvmsg函数丢弃了socket 数据包的尾部,导致部分网口信息解析失败,读不到指定网口,访问了空指针引起的core dump。
问题解决
查找相关patch,在debian的1.2.24版本中有一个对应的patch。#846292 - keepalived: Fix netlink message truncation with large page sizes - Debian Bug report logs
这个patch的核心思想就是把这个4096通过netlink_set_recv_buf_size函数来初始化,初始化的逻辑:通过getpagesize函数得到当前系统的页表大小,如果超过8k,就设置成8k。可以将其patch的代码在1.2.9版本中实现重新编译后:
可以读到指定网卡。


