【问题标题】:Multicast from kernel to user space via Netlink in C通过 C 中的 Netlink 从内核到用户空间的多播
【发布时间】:2014-05-06 15:27:43
【问题描述】:

我试图编写一个使用 Netlink 在内核和用户空间之间进行通信的简单程序。基本上这就是我想要实现的目标:

  1. 用户空间程序开始绑定到用户定义的多播组。
  2. 插入内核模块
  3. 内核模块向该多播组发送消息
  4. 用户空间程序收到消息

这是我的代码:

======用户空间程序======

#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<sys/socket.h>
#include<linux/netlink.h>
#include<sys/types.h>
#include<unistd.h>

#define MYPROTO NETLINK_USERSOCK
#define MYMGRP 0x21 //User defined group, consistent in both kernel prog and user prog

int open_netlink()
{
        int sock = socket(AF_NETLINK,SOCK_RAW,MYPROTO);
        struct sockaddr_nl addr;

        memset((void *)&addr, 0, sizeof(addr));

        if (sock<0)
                return sock;
        addr.nl_family = AF_NETLINK;
        addr.nl_pid = getpid();
        addr.nl_groups = MYMGRP;
        if (bind(sock,(struct sockaddr *)&addr,sizeof(addr))<0)
                return -1;
        return sock;
}

int read_event(int sock)
{
        struct sockaddr_nl nladdr;
        struct msghdr msg;
        struct iovec iov[2];
        struct nlmsghdr nlh;
        char buffer[65536];
        int ret;
        iov[0].iov_base = (void *)&nlh;
        iov[0].iov_len = sizeof(nlh);
        iov[1].iov_base = (void *)buffer;
        iov[1].iov_len = sizeof(buffer);
        msg.msg_name = (void *)&(nladdr);
        msg.msg_namelen = sizeof(nladdr);
        msg.msg_iov = iov;
        msg.msg_iovlen = sizeof(iov)/sizeof(iov[0]);
        ret=recvmsg(sock, &msg, 0);
        if (ret<0) {
                return ret;
        }
        printf("Received message payload: %s\n", NLMSG_DATA(&nlh));
}

int main(int argc, char *argv[])
{
        int nls = open_netlink();
        if (nls<0) {
                err(1,"netlink");
        }

        while (1)
                read_event(nls);
        return 0;
}

======内核模块======

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <net/sock.h>
#include <linux/socket.h>
#include <linux/net.h>
#include <asm/types.h>
#include <linux/netlink.h>
#include <linux/rtnetlink.h>
#include <linux/skbuff.h>
#include <linux/delay.h>

#define NETLINK_USER 31
#define MYGRP 0x21 //User defined group, consistent in both kernel prog and user prog

struct sock *nl_sk = NULL;

static void send_to_user() {
    struct sk_buff *skb_out;
    struct nlmsghdr *nlh;
    int msg_size;
    char *msg = "Hello from kernel";
    int res;

    printk(KERN_INFO "Entering: %s\n", __FUNCTION__);
    msg_size = strlen(msg);
    skb_out = nlmsg_new(msg_size, 0);

    if (!skb_out) {
        printk(KERN_ERR "Failed to allocate new skb\n");
        return;
    }
    nlh = nlmsg_put(skb_out, 0, 1, NLMSG_DONE, msg_size, 0);
    //NETLINK_CB(skb_out).dst_group = 1; /* Multicast to group 1, 1<<0 */
    strncpy(nlmsg_data(nlh), msg, msg_size);

    res = nlmsg_multicast(nl_sk, skb_out, 0, MYGRP, 0);
    if (res < 0) {
        printk(KERN_INFO "Error while sending bak to user, err id: %d\n", res);
    }
}

static int __init
hello_init(void) {

    struct netlink_kernel_cfg cfg = {
            .groups = MYGRP,
    };
    printk("Entering: %s\n", __FUNCTION__);
    nl_sk = netlink_kernel_create(&init_net, NETLINK_USER, &cfg);
    if (!nl_sk) {
        printk(KERN_ALERT "Error creating socket.\n");
        return -10;
    }

    send_to_user();

    return 0;
}

static void __exit
hello_exit(void) {

    printk(KERN_INFO "exiting hello module\n");
    netlink_kernel_release(nl_sk);
}

module_init(hello_init);
module_exit(hello_exit);

由于内核模块在初始化过程中只会发送一次消息,所以我先运行监听程序然后插入模块,虽然我总是得到这个错误:

Error while sending bak to user, err id: -3

当追踪到err id时,它反映在netlink/af_netlink.c中的这段代码中:

if (info.delivery_failure) {
    kfree_skb(info.skb2);
    return -ENOBUFS;
}
consume_skb(info.skb2);

if (info.delivered) {
    if (info.congested && (allocation & __GFP_WAIT))
    yield();
    return 0;
}
return -ESRCH;

我认为这不是交付失败,但由于某些原因仍未交付。

我指的是example,其中作者的程序不断监听路由变化。虽然我想使用用户定义的多播组。

有什么想法吗?提前致谢!

【问题讨论】:

  • 很久以前我也遇到过类似的问题;您可以检查以下内容: 1:看到 NETLINK_USER 的 31 值尚未被其他一些组件使用;如果是,则选择一些未使用的值。我猜 31 分配给 LTT 组件。 2:在用户空间创建套接字时,使用与 kernel 中定义的相同的 NETLINK_USER 值。 int sock = socket(AF_NETLINK,SOCK_RAW, NETLINK_USER);
  • 感谢您的回复!虽然据我了解,netlink_kernel_create() 是在内核空间中创建套接字而不是 socket(),还是我遗漏了什么?
  • 我的意思是在你的用户空间程序中;当使用socket(AF_NETLINK,SOCK_RAW,MYPROTO); 打开套接字时,请使用与内核中定义的 NETLINK_USER 相同的值。所以它可能是`#define NETLINK_USER 30`
    socket(AF_NETLINK,SOCK_RAW, NETLINK_USER);
  • 我记得我是如何尝试这样做的。互联网上有各种示例和教程,其中大多数对我不起作用。所以我最终使用了通用的 netlink 系列和 libnl。您可以在 github.com/dzeban/keymon 上查看我的工作示例

标签: c linux-kernel kernel multicast netlink


【解决方案1】:

根据connector driver

如果给定组没有侦听器,则可以返回 ESRCH。

所以当你的内核发送消息时你的用户空间程序没有正确运行。

【讨论】:

    【解决方案2】:

    这是我在您的代码中发现的两个关键问题:

    1. 在内核 prog 和用户 prog 中,协议族和多播组都需要保持一致。您的协议族是用户空间中的NETLINK_USERSOCK (2) 和内核空间中的NETLINK_USER (31)。
    2. addr.nl_groups = MYMGRP; 由于某种原因不起作用。不过确实如此:setsockopt(sock, 270, NETLINK_ADD_MEMBERSHIP, &amp;group, sizeof(group))

    不致命:

    1. 在这种情况下,模块不会监听组消息,因此您不需要在netlink_kernel_create() 参数中包含多播组。

    另外,与 netlink 无关,但还是有用的:

    1. strlen() 不包括空字符。在消息分配期间,您可能应该添加一个字节来弥补这一点。
    2. 在这种情况下,NLMSG_DATA(&amp;nlh) 是未定义的行为。这是因为你的标头和数据在单独的内存块中,不能保证被粘合,而宏所做的只是访问nlh之后的内存块:

    #define NLMSG_DATA(nlh) ((void*)(((char*)nlh) + NLMSG_LENGTH(0)))

    这是我的代码版本:

    用户空间程序:

    #include <stdio.h>
    #include <string.h>
    #include <stdlib.h>
    #include <sys/socket.h>
    #include <linux/netlink.h>
    #include <unistd.h>
    
    /* Protocol family, consistent in both kernel prog and user prog. */
    #define MYPROTO NETLINK_USERSOCK
    /* Multicast group, consistent in both kernel prog and user prog. */
    #define MYMGRP 21
    
    int open_netlink(void)
    {
        int sock;
        struct sockaddr_nl addr;
        int group = MYMGRP;
    
        sock = socket(AF_NETLINK, SOCK_RAW, MYPROTO);
        if (sock < 0) {
            printf("sock < 0.\n");
            return sock;
        }
    
        memset((void *) &addr, 0, sizeof(addr));
        addr.nl_family = AF_NETLINK;
        addr.nl_pid = getpid();
        /* This doesn't work for some reason. See the setsockopt() below. */
        /* addr.nl_groups = MYMGRP; */
    
        if (bind(sock, (struct sockaddr *) &addr, sizeof(addr)) < 0) {
            printf("bind < 0.\n");
            return -1;
        }
    
        /*
         * 270 is SOL_NETLINK. See
         * http://lxr.free-electrons.com/source/include/linux/socket.h?v=4.1#L314
         * and
         * http://stackoverflow.com/questions/17732044/
         */
        if (setsockopt(sock, 270, NETLINK_ADD_MEMBERSHIP, &group, sizeof(group)) < 0) {
            printf("setsockopt < 0\n");
            return -1;
        }
    
        return sock;
    }
    
    void read_event(int sock)
    {
        struct sockaddr_nl nladdr;
        struct msghdr msg;
        struct iovec iov;
        char buffer[65536];
        int ret;
    
        iov.iov_base = (void *) buffer;
        iov.iov_len = sizeof(buffer);
        msg.msg_name = (void *) &(nladdr);
        msg.msg_namelen = sizeof(nladdr);
        msg.msg_iov = &iov;
        msg.msg_iovlen = 1;
    
        printf("Ok, listening.\n");
        ret = recvmsg(sock, &msg, 0);
        if (ret < 0)
            printf("ret < 0.\n");
        else
            printf("Received message payload: %s\n", NLMSG_DATA((struct nlmsghdr *) &buffer));
    }
    
    int main(int argc, char *argv[])
    {
        int nls;
    
        nls = open_netlink();
        if (nls < 0)
            return nls;
    
        while (1)
            read_event(nls);
    
        return 0;
    }
    

    这是内核模块:

    #include <linux/module.h>
    #include <linux/kernel.h>
    #include <linux/netlink.h>
    #include <net/netlink.h>
    #include <net/net_namespace.h>
    
    /* Protocol family, consistent in both kernel prog and user prog. */
    #define MYPROTO NETLINK_USERSOCK
    /* Multicast group, consistent in both kernel prog and user prog. */
    #define MYGRP 21
    
    static struct sock *nl_sk = NULL;
    
    static void send_to_user(void)
    {
        struct sk_buff *skb;
        struct nlmsghdr *nlh;
        char *msg = "Hello from kernel";
        int msg_size = strlen(msg) + 1;
        int res;
    
        pr_info("Creating skb.\n");
        skb = nlmsg_new(NLMSG_ALIGN(msg_size + 1), GFP_KERNEL);
        if (!skb) {
            pr_err("Allocation failure.\n");
            return;
        }
    
        nlh = nlmsg_put(skb, 0, 1, NLMSG_DONE, msg_size + 1, 0);
        strcpy(nlmsg_data(nlh), msg);
    
        pr_info("Sending skb.\n");
        res = nlmsg_multicast(nl_sk, skb, 0, MYGRP, GFP_KERNEL);
        if (res < 0)
            pr_info("nlmsg_multicast() error: %d\n", res);
        else
            pr_info("Success.\n");
    }
    
    static int __init hello_init(void)
    {
        pr_info("Inserting hello module.\n");
    
        nl_sk = netlink_kernel_create(&init_net, MYPROTO, NULL);
        if (!nl_sk) {
            pr_err("Error creating socket.\n");
            return -10;
        }
    
        send_to_user();
    
        netlink_kernel_release(nl_sk);
        return 0;
    }
    
    static void __exit hello_exit(void)
    {
        pr_info("Exiting hello module.\n");
    }
    
    module_init(hello_init);
    module_exit(hello_exit);
    
    MODULE_LICENSE("GPL");
    

    在内核 3.13 中测试。

    (我可以建议人们在用户空间程序中使用libnl-3 而不是原始套接字。它的多播Netlink 文档是actually decent。)

    【讨论】:

    • 如果内核版本较旧,netlink_kernel_create() 应该是什么?
    • @NTN 据我所见,它总是相同的参数,只是重新排列。见this example。你也可以see netlink_kernel_create()'s definition。 (破解 URL 中的内核版本以查看文件的不同版本;例如将“v=3.13”更改为“v="3.10”)
    • @NTN 直接回答您的问题,即netlink_kernel_create(&amp;init_net, NETLINK_USERSOCK, 0, NULL, NULL, THIS_MODULE)(假设您的内核为 3.5 或更低版本)。
    • 指定 nl_groups 不起作用,因为 nl_groups 是允许组的位掩码,而 NETLINK_ADD_MEMBERSHIP 采用单个组号。我认为在这种情况下,您需要设置第 20 位(值 0x100000)。
    • @river 猜测一下:尝试使用sudo 运行程序。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2013-09-27
    • 2011-03-02
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-08-18
    相关资源
    最近更新 更多