You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用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函数访问非法内存,触发段错误。

修复步骤

  1. 直接从recvfrom缓冲区获取struct nlmsghdr,无需转换为nl_msg。
  2. 解析前先验证消息的有效性和长度,避免越界访问。
  3. 处理netlink可能返回的多消息片段,用nlmsg_ok和nlmsg_next遍历所有消息。
  4. 调用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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.14 00:07:31