使用recvfrom接收Generic Netlink消息后解析出现段错误的问题
Generic Netlink消息解析段错误修复方案
问题
我尝试通过Generic Netlink族套接字从内核向用户空间获取事件,已参考相关实现完成通信,用户空间回调可被触发。但我没有用nl_socket_modify_cb设置回调,而是用libev的ev_io_init配置读就绪处理函数,在解析recvfrom获取的消息时出现段错误。
示例代码
ev_io *ei; struct nl_sock *mcsk; /* Allocate netlink socket and connect to generic netlink */ int conn() { if (!mcsk) { mcsk = nl_socket_alloc(); if (!mcsk) { return -ENOMEM; } return genl_connect(mcsk); } return 0; } /* Disconnect and release socket */ void disconn() { if (mcsk) { nl_close(mcsk); nl_socket_free(mcsk); } } ret = conn(); nl_socket_disable_seq_check(mcsk); nl_socket_disable_auto_ack(mcsk); int fam = genl_ctrl_resolve(mcsk, GENLTEST_GENL_NAME); int mcgrp = genl_ctrl_resolve_grp(mcsk, GENLTEST_GENL_NAME, GENLTEST_MC_GRP_NAME); ret = nl_socket_add_membership(mcsk, mcgrp) ev_io_init(ei, nl_arp_event_cb, nl_socket_get_fd(mcsk), EV_READ); ev_io_start(gloop, ei); void nl_arp_event_cb(EV_P_ ev_io *w, int revents) { char buf[256]; int left; struct sockaddr_nl from; socklen_t fromlen; fromlen = sizeof(from); left = recvfrom(w->fd, buf, sizeof(buf), MSG_DONTWAIT, (struct sockaddr *) &from, &fromlen); if (left < 0) { if (errno != EINTR && errno != EAGAIN) PR_ERR( "netlink: recvfrom failed: %s", strerror(errno)); return; } struct nl_msg *msg = (struct nl_msg*)buf; struct nlattr *tb[GENLTEST_A_MAX + 1]; //struct genlmsghdr *genlhdr = nlmsg_data(nlmsg_hdr(msg)); genlmsg_parse(nlmsg_hdr(msg), 0, tb, GENLTEST_A_MAX, NULL); if (!tb[GENLTEST_A_MSG]) { PR_ERR("GNA: msg attribute missing from message"); return; } PR_INFO("GNA: msg rcvd: %s", nla_get_string(tb[GENLTEST_A_MSG])); return; }
GDB回溯信息
(gdb) bt #0 0x76c7e564 in nlmsg_valid_hdr () from target/usr/lib/libnl-3.so.200 #1 0x76c625f0 in genlmsg_valid_hdr () from target/usr/lib/libnl-genl-3.so.200 #2 0x76c62734 in genlmsg_parse () from target/usr/lib/libnl-genl-3.so.200 #3 0x00013d12 in nl_arp_event_cb (loop=<optimized out>, w=0x8fe738, revents=<optimized out>) at arp_inspection.c:48 #4 0x76dd1364 in ev_invoke_pending () from target/usr/lib/libev.so.4 #5 0x76dd33bc in ev_run () from target/usr/lib/libev.so.4 #6 0x00000006 in ?? () Backtrace stopped: previous frame identical to this frame (corrupt stack?) (gdb) f 3 #3 0x00013d12 in nl_arp_event_cb (loop=<optimized out>, w=0x8fe738, revents=<optimized out>) at arp_inspection.c:48 48 genlmsg_parse(nlmsg_hdr(msg), 0, tb, GENLTEST_A_MAX, NULL);
解决方案
段错误的核心原因是直接把recvfrom的缓冲区强制转换成struct nl_msg*。nl_msg是libnl内部维护的结构体,包含引用计数、缓冲区管理等额外元数据,并非原始netlink消息的格式。直接强制类型转换会导致libnl函数访问非法内存,触发段错误。
修复步骤
- 直接从
recvfrom缓冲区获取struct nlmsghdr,无需转换为nl_msg。 - 解析前先验证消息的有效性和长度,避免越界访问。
- 处理netlink可能返回的多消息片段,用
nlmsg_ok和nlmsg_next遍历所有消息。 - 调用
genlmsg_parse时,正确传入Generic Netlink消息头的偏移量。
修复后的回调函数
void nl_arp_event_cb(EV_P_ ev_io *w, int revents) { char buf[256]; int left; struct sockaddr_nl from; socklen_t fromlen; struct nlmsghdr *nlh; fromlen = sizeof(from); left = recvfrom(w->fd, buf, sizeof(buf), MSG_DONTWAIT, (struct sockaddr *) &from, &fromlen); if (left < 0) { if (errno != EINTR && errno != EAGAIN) PR_ERR( "netlink: recvfrom failed: %s", strerror(errno)); return; } // 直接从缓冲区获取netlink消息头 nlh = (struct nlmsghdr *)buf; // 验证消息基本有效性,确保长度足够 if (!nlmsg_valid_hdr(nlh, left)) { PR_ERR("Invalid netlink message header"); return; } // 遍历所有消息片段(netlink可能一次返回多条消息) for (; nlmsg_ok(nlh, left); nlh = nlmsg_next(nlh, &left)) { struct nlattr *tb[GENLTEST_A_MAX + 1]; struct genlmsghdr *genlhdr; // 跳过错误消息和控制消息,只处理自定义Generic Netlink消息 if (nlh->nlmsg_type != NLMSG_ERROR && nlh->nlmsg_type != GENL_ID_CTRL) { genlhdr = (struct genlmsghdr *)nlmsg_data(nlh); // 解析属性时,传入genlmsghdr的大小作为偏移量 genlmsg_parse(nlh, sizeof(struct genlmsghdr), tb, GENLTEST_A_MAX, NULL); if (tb[GENLTEST_A_MSG]) { PR_INFO("GNA: msg rcvd: %s", nla_get_string(tb[GENLTEST_A_MSG])); } else { PR_ERR("GNA: msg attribute missing from message"); } } } return; }
额外注意事项
- 缓冲区大小需足够容纳最大可能的netlink消息,若消息可能超过256字节,建议增大缓冲区或使用动态分配。
- 必须处理
NLMSG_ERROR类型的消息,避免遗漏内核返回的错误通知。 - 确保libnl的版本与编译环境匹配,避免API兼容问题。
内容的提问来源于stack exchange,提问作者RootPhoenix
相关产品推荐
相关产品推荐

