通过C中的Netlink从内核到用户空间的多播

时间:2014-03-27 14:52:56

标签: c linux-kernel kernel multicast netlink

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

  1. 用户空间程序开始绑定到用户定义的多播组。
  2. 插入内核模块
  3. 内核模块向此多播组发送消息
  4. 用户空间程序收到消息
  5. 这是我的代码:

    ======用户空间计划======

    #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
    

    当追溯到错误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;
    

    我认为它不是delivery_failure但由于某些原因仍未送达。

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

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

1 个答案:

答案 0 :(得分:7)

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

  1. 协议族和组播组都需要在内核编程和用户编程中保持一致。您的协议族在用户空间中为NETLINK_USERSOCK(2),在内核空间中为NETLINK_USER(31)。
  2. addr.nl_groups = MYMGRP;由于某种原因无法运作。但这样做:setsockopt(sock, 270, NETLINK_ADD_MEMBERSHIP, &group, sizeof(group))
  3. 不致命:

    1. 在这种情况下,模块没有收听群组消息,因此您不需要在netlink_kernel_create()参数中包含多播群组。
    2. 此外,并非真正与netlink相关,但无论如何都很有用:

      1. strlen()不包含null chara。在消息分配期间,您应该添加一个字节来弥补这一点。
      2. 在这种情况下,NLMSG_DATA(&nlh)是未定义的行为。这是因为您的标头和数据位于不同的内存块中,不能保证粘合,并且所有宏都会在nlh之后访问内存块:
      3. #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。)