Netlink多播内核组

时间:2014-10-08 20:04:26

标签: c linux-kernel kernel-module multicast netlink

我试图实现的任务实际上非常简单(将字符串" TEST"多播到userland守护程序),但内核模块不编译。它会因错误而停止:

passing argument 4 of ‘genlmsg_multicast_allns’ makes integer from pointer without a cast [enabled by default]

但它不应该只是我定义的组播组吗?

以下是"澄清"的代码:

#include <linux/module.h>
#include <net/sock.h>
#include <linux/netlink.h>
#include <linux/skbuff.h>
#include <linux/string.h>
#include <net/netlink.h>
#include <net/genetlink.h>

struct sock *nl_sk = NULL;

static void daemon(void){
        struct sk_buff *skb;
        void* msg_head;
        unsigned char *msg;

        struct genl_family my_genl_family = {
                .id = GENL_ID_GENERATE,
                .hdrsize = 0,
                .name = "family_name",
                .version = 1,
                .maxattr = 5
        };

        struct genl_multicast_group my_mc_group = {
                .name = "mc_group",
        };


        msg = "TEST";
        skb = genlmsg_new(NLMSG_GOODSIZE, GFP_KERNEL);

        msg_head = genlmsg_put(skb, 0, 0, &my_genl_family, 0, 21);

        nla_put(skb, 0, sizeof(msg), msg);

        genlmsg_end(skb, msg_head);

        genlmsg_multicast_allns( &my_genl_family, skb, 0, my_mc_group, GFP_KERNEL);

}

static int __init hello_init(void)
{
    printk("Entering: %s\n", __FUNCTION__);

    printk(KERN_INFO "Calling main function with sockets\n");

      struct netlink_kernel_cfg cfg = {
        .groups = 1,
        .flags  = NL_CFG_F_NONROOT_RECV,
      };

      nl_sk = netlink_kernel_create(&init_net, NETLINK_GENERIC, &cfg);


    daemon();

    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);

MODULE_LICENSE("GPL");

感谢您的帮助。

修改

这是客户端代码:

#include <netlink/netlink.h>
#include <netlink/socket.h>
#include <netlink/msg.h>
#include <netlink/genl/genl.h>
#include <linux/genetlink.h>

/*
 * This function will be called for each valid netlink message received
 * in nl_recvmsgs_default()
 */
static int my_func(struct nl_msg *msg, void *arg)
{
        //struct nl_msg *nlmsg = nlmsg_alloc_size(GENL_HDRLEN+nla_total_size(sizeof(msg))+36);

        printf("Test\n");

        return 0;
}

int main(){
        struct nl_sock *sk;

        int gr_id;

        /* Allocate a new socket */
        sk = nl_socket_alloc();

        /*
         * Notifications do not use sequence numbers, disable sequence number
         * checking.
         */
        nl_socket_disable_seq_check(sk);

        /*
         * Define a callback function, which will be called for each notification
         * received
         */
        nl_socket_modify_cb(sk, NL_CB_VALID, NL_CB_CUSTOM, my_func, NULL);
       /* Connect to netlink generic protocol */
        nl_connect(sk, NETLINK_GENERIC);

        gr_id = genl_family_get_id("family_name");

        /* Subscribe to link notifications group */
        nl_socket_add_memberships(sk, gr_id, 0);

        /*
         * Start receiving messages. The function nl_recvmsgs_default() will block
         * until one or more netlink messages (notification) are received which
         * will be passed on to my_func().
        */
        while (1){
                nl_recvmsgs_default(sk);
        }

        return 0;
}

2 个答案:

答案 0 :(得分:3)

我想首先说我不是Netlink的忠实粉丝;我认为它设计得很差。也就是说,我认为我对这个问题有了确切的答案,所以就这样了。

您的核心问题是,在使用Generic Netlink系列之前,您首先必须注册它(这也适用于普通的Netlink系列)。内核无法处理它不知道的家庭。除非您使用现有的家庭,否则这会影响您接触Netlink的方式。

A Generic Netlink Family belongs to a kernel module。这意味着用户空间客户端无法创建系列。反过来,这意味着您无法启动客户端,然后让模块在创建系列后立即发送消息。这是因为在客户想要将自己绑定到它的时刻,家庭并不存在。

您需要做的是:

  1. 在您插入模块时创建并注册家庭和多播组。
  2. 启动用户空间客户端,并将其自身绑定到系列和多播组。
  3. 让内核模块在某个时刻发送消息(在客户端绑定之后)。
  4. 用户空间客户端现在收到消息。
  5. 删除模块后,应取消注册该系列。
  6. 我的代码版本如下。这是内核模块。如您所见,我决定在每两秒运行一次的计时器上重复发送消息。这让您有时间启动客户端:

        #include <linux/kernel.h>
        #include <linux/module.h>
        #include <net/genetlink.h>
    
        static struct timer_list timer;
    
        /**
         * This callback runs whenever the socket receives messages.
         * We don't use it now, but Linux complains if we don't define it.
         */
        static int hello(struct sk_buff *skb, struct genl_info *info)
        {
                pr_info("Received a message in kernelspace.\n");
                return 0;
        }
    
        /**
         * Attributes are fields of data your messages will contain.
         * The designers of Netlink really want you to use these instead of just dumping
         * data to the packet payload... and I have really mixed feelings about it.
         */
        enum attributes {
                /*
                 * The first one has to be a throwaway empty attribute; I don't know
                 * why.
                 * If you remove it, ATTR_HELLO (the first one) stops working, because
                 * it then becomes the throwaway.
                 */
                ATTR_DUMMY,
                ATTR_HELLO,
                ATTR_FOO,
    
                /* This must be last! */
                __ATTR_MAX,
        };
    
        /**
         * Here you can define some constraints for the attributes so Linux will
         * validate them for you.
         */
        static struct nla_policy policies[] = {
                        [ATTR_HELLO] = { .type = NLA_STRING, },
                        [ATTR_FOO] = { .type = NLA_U32, },
        };
    
        /**
         * Message type codes. All you need is a hello sorta function, so that's what
         * I'm defining.
         */
        enum commands {
                COMMAND_HELLO,
    
                /* This must be last! */
                __COMMAND_MAX,
        };
    
        /**
         * Actual message type definition.
         */
        struct genl_ops ops[] = {
                {
                        .cmd = COMMAND_HELLO,
                        .flags = 0,
                        .policy = policies,
                        .doit = hello,
                        .dumpit = NULL,
                },
        };
    
        /**
         * A Generic Netlink family is a group of listeners who can and want to speak
         * your language.
         * Anyone who wants to hear your messages needs to register to the same family
         * as you.
         */
        struct genl_family family = {
                        .id = GENL_ID_GENERATE,
                        .hdrsize = 0,
                        .name = "PotatoFamily",
                        .version = 1,
                        .maxattr = __ATTR_MAX,
        };
    
        /**
         * And more specifically, anyone who wants to hear messages you throw at
         * specific multicast groups need to register themselves to the same multicast
         * group, too.
         */
        struct genl_multicast_group groups[] = {
                { .name = "PotatoGroup" },
        };
    
        void send_multicast(unsigned long arg)
        {
                struct sk_buff *skb;
                void *msg_head;
                unsigned char *msg = "TEST";
                int error;
    
                pr_info("----- Running timer -----\n");
    
                pr_info("Newing message.\n");
                skb = genlmsg_new(NLMSG_GOODSIZE, GFP_KERNEL);
                if (!skb) {
                        pr_err("genlmsg_new() failed.\n");
                        goto end;
                }
    
                pr_info("Putting message.\n");
                msg_head = genlmsg_put(skb, 0, 0, &family, 0, COMMAND_HELLO);
                if (!msg_head) {
                        pr_err("genlmsg_put() failed.\n");
                        kfree_skb(skb);
                        goto end;
                }
    
                pr_info("Nla_putting string.\n");
                error = nla_put_string(skb, ATTR_HELLO, msg);
                if (error) {
                        pr_err("nla_put_string() failed: %d\n", error);
                        kfree_skb(skb);
                        goto end;
                }
    
                pr_info("Nla_putting integer.\n");
                error = nla_put_u32(skb, ATTR_FOO, 12345);
                if (error) {
                        pr_err("nla_put_u32() failed: %d\n", error);
                        kfree_skb(skb);
                        goto end;
                }
    
                pr_info("Ending message.\n");
                genlmsg_end(skb, msg_head);
    
                pr_info("Multicasting message.\n");
                /*
                 * The family has only one group, so the group ID is just the family's
                 * group offset.
                 * mcgrp_offset is supposed to be private, so use this value for debug
                 * purposes only.
                 */
                pr_info("The group ID is %u.\n", family.mcgrp_offset);
                error = genlmsg_multicast_allns(&family, skb, 0, 0, GFP_KERNEL);
                if (error) {
                        pr_err("genlmsg_multicast_allns() failed: %d\n", error);
                        pr_err("(This can happen if nobody is listening. "
                                        "Because it's not that unexpected, "
                                        "you might want to just ignore this error.)\n");
                        goto end;
                }
    
                pr_info("Success.\n");
        end:
                mod_timer(&timer, jiffies + msecs_to_jiffies(2000));
        }
    
        static int init_socket(void)
        {
                int error;
    
                pr_info("Registering family.\n");
                error = genl_register_family_with_ops_groups(&family, ops, groups);
                if (error)
                        pr_err("Family registration failed: %d\n", error);
    
                return error;
        }
    
        static void initialize_timer(void)
        {
                pr_info("Starting timer.\n");
    
                init_timer(&timer);
                timer.function = send_multicast;
                timer.expires = 0;
                timer.data = 0;
    
                mod_timer(&timer, jiffies + msecs_to_jiffies(2000));
        }
    
        static int __init hello_init(void)
        {
                int error;
    
                error = init_socket();
                if (error)
                        return error;
    
                initialize_timer();
    
                pr_info("Hello module registered.\n");
                return 0;
        }
    
        static void __exit hello_exit(void)
        {
                del_timer_sync(&timer);
                genl_unregister_family(&family);
                pr_info("Hello removed.\n");
        }
    
        module_init(hello_init);
        module_exit(hello_exit);
    
        MODULE_LICENSE("GPL");
    

    这是用户空间客户端:

        #include <netlink/netlink.h>
        #include <netlink/socket.h>
        #include <netlink/msg.h>
        #include <netlink/genl/genl.h>
    
        static struct nl_sock *sk = NULL;
    
        /**
         * Attributes and commands have to be the same as in kernelspace, so you might
         * want to move these enums to a .h and just #include that from both files.
         */
        enum attributes {
                ATTR_DUMMY,
                ATTR_HELLO,
                ATTR_FOO,
    
                /* This must be last! */
                __ATTR_MAX,
        };
    
        enum commands {
                COMMAND_HELLO,
    
                /* This must be last! */
                __COMMAND_MAX,
        };
    
        static int fail(int error, char *func_name)
        {
                printf("%s() failed.\n", func_name);
                return error;
        }
    
        static int nl_fail(int error, char *func_name)
        {
                printf("%s (%d)\n", nl_geterror(error), error);
                return fail(error, func_name);
        }
    
        /*
         * This function will be called for each valid netlink message received
         * in nl_recvmsgs_default()
         */
        static int cb(struct nl_msg *msg, void *arg)
        {
                struct nlmsghdr *nl_hdr;
                struct genlmsghdr *genl_hdr;
                struct nlattr *attrs[__ATTR_MAX];
                int error;
    
                printf("The kernel module sent a message.\n");
    
                nl_hdr = nlmsg_hdr(msg);
                genl_hdr = genlmsg_hdr(nl_hdr);
    
                if (genl_hdr->cmd != COMMAND_HELLO) {
                        printf("Oops? The message type is not Hello; ignoring.\n");
                        return 0;
                }
    
                error = genlmsg_parse(nl_hdr, 0, attrs, __ATTR_MAX - 1, NULL);
                if (error)
                        return nl_fail(error, "genlmsg_parse");
    
                /* Remember: attrs[0] is a throwaway. */
    
                if (attrs[1])
                        printf("ATTR_HELLO: len:%u type:%u data:%s\n",
                                        attrs[1]->nla_len,
                                        attrs[1]->nla_type,
                                        (char *)nla_data(attrs[1]));
                else
                        printf("ATTR_HELLO: null\n");
    
                if (attrs[2])
                        printf("ATTR_FOO: len:%u type:%u data:%u\n",
                                        attrs[2]->nla_len,
                                        attrs[2]->nla_type,
                                        *((__u32 *)nla_data(attrs[2])));
                else
                        printf("ATTR_FOO: null\n");
    
                return 0;
        }
    
        static int do_things(void)
        {
                struct genl_family *family;
                int group;
                int error;
    
                /* Socket allocation yadda yadda. */
                sk = nl_socket_alloc();
                if (!sk)
                        return fail(-1, "nl_socket_alloc");
    
                nl_socket_disable_seq_check(sk);
    
                error = nl_socket_modify_cb(sk, NL_CB_VALID, NL_CB_CUSTOM, cb, NULL);
                if (error)
                        return nl_fail(error, "nl_socket_modify_cb");
    
                error = genl_connect(sk);
                if (error)
                        return nl_fail(error, "genl_connect");
    
                /* Find the multicast group identifier and register ourselves to it. */
                group = genl_ctrl_resolve_grp(sk, "PotatoFamily", "PotatoGroup");
                if (group < 0)
                        return nl_fail(group, "genl_ctrl_resolve_grp");
    
                printf("The group is %u.\n", group);
    
                error = nl_socket_add_memberships(sk, group, 0);
                if (error) {
                        printf("nl_socket_add_memberships() failed: %d\n", error);
                        return error;
                }
    
                /* Finally, receive the message. */
                nl_recvmsgs_default(sk);
    
                return 0;
        }
    
        int main(void)
        {
                int error;
    
                error = do_things();
    
                if (sk)
                        nl_socket_free(sk);
    
                return error;
        }
    

答案 1 :(得分:1)

这不是netlink问题的直接答案,而是另一种解决方案。请参阅上面有关netlink限制的评论。

可以在Linux上使用UDP套接字在用户模式进程(如守护程序)和内核模式组件(如可加载模块)之间进行通信。

守护程序代码my_udp.c:

#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <string.h>
#include <signal.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <arpa/inet.h>
#include <string.h>
#include <errno.h>
#include <pthread.h>

static int rcv_sock;
static int snd_sock;
static struct sockaddr_in rcv_addr_in;
static struct sockaddr_in snd_addr_in;
static pthread_t rcv_thread;

static void *rcv_thread_fn(void *data);

int my_udp_init(void)
{
    int sendlen, receivelen;
    int received = 0;

    if ((rcv_sock = socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP)) < 0) {
            perror("socket");
            return -1;
    }

    if ((snd_sock = socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP)) < 0) {
            perror("socket");
            return -1;
    }

    memset(&rcv_addr_in, 0, sizeof(rcv_addr_in));
    rcv_addr_in.sin_family = AF_INET;
    rcv_addr_in.sin_addr.s_addr = inet_addr("127.0.0.1");
    rcv_addr_in.sin_port = htons(MY_IN_PORT);

    receivelen = sizeof(rcv_addr_in);
    if (bind(rcv_sock, (struct sockaddr *) &rcv_addr_in, receivelen) < 0) {
            perror("bind");
            return -1;
    }

    memset(&snd_addr_in, 0, sizeof(snd_addr_in));
    snd_addr_in.sin_family = AF_INET;
    snd_addr_in.sin_addr.s_addr = inet_addr("127.0.0.1");
    snd_addr_in.sin_port = htons(MY_OUT_PORT);

    if (pthread_create(&rcv_thread, NULL, rcv_thread_fn, (void *)"rcv_thread")) {
            return -ENOMEM;
    }

    return 0;
}

void my_udp_cleanup(void)
{
    pthread_join(rcv_thread, NULL);
    close(rcv_sock);
    close(snd_sock);
}

int my_snd_msg(const char *buf, int size)
{
    sendto(snd_sock, buf, size, 0, (struct sockaddr *)&snd_addr_in, sizeof(snd_addr_in));
    return 0;
}

int my_rcv_msg(char *buf, int size)
{
    int cnt = 0;
    if ((cnt = recv(rcv_sock, buf, size, MSG_DONTWAIT)) < 0) {
            if (errno == EAGAIN) {
                    /* This is ok in the non-blocking case. */
                    sleep(1);
                    return 0;
            } else {
                    perror("recv");
                    return -1;
            }
    }
    return cnt;
}

static void *rcv_thread_fn(void *data)
{
    char buffer[64];
    int cnt;

    while (!g_stop) {
            cnt = my_rcv_msg(buffer, 63);
            if (cnt > 0) {
                    printf("message: %s\n", buffer);
            }
    }

    pthread_exit(0);
}

内核模块代码k_udp.c:

#include <linux/module.h>
#include <linux/init.h>
#include <linux/in.h>
#include <net/sock.h>
#include <linux/skbuff.h>
#include <linux/delay.h>
#include <linux/inet.h>
#include <linux/kthread.h>

static struct work_struct rcv_worker;
static struct socket *in_socket = NULL;
static struct socket *out_socket = NULL;
static struct workqueue_struct *wq = NULL;
static struct task_struct *notify_thread = NULL;

void rcv_work_queue(struct work_struct *data)
{
        int len;
        printk(KERN_INFO "%s: *******\n", __func__);

        while ((len = skb_queue_len(&in_socket->sk->sk_receive_queue)) > 0) {
                struct sk_buff *skb = NULL;

                skb = skb_dequeue(&in_socket->sk->sk_receive_queue);
                printk("message len: %i message: %s\n", skb->len - 8, skb->data+8);
                kfree_skb(skb);
        }
}

static void cb_data(struct sock *sk, int bytes)
{
        printk(KERN_INFO "%s: *******\n", __func__);
        queue_work(wq, &rcv_worker);
}

void send_notification(char *text)
{
        struct sockaddr_in to_addr;
        struct msghdr msg;
        struct iovec iov;
        mm_segment_t oldfs;
        int len = 0;

        if (out_socket->sk == NULL) {
                printk(KERN_ERR "%s: socket skbuff is null\n", __func__);
                return;
        }

        iov.iov_base = text;
        len = strlen(text);
        iov.iov_len = len;

        memset(&to_addr, 0, sizeof(to_addr));
        to_addr.sin_family = AF_INET;
        to_addr.sin_addr.s_addr = in_aton("127.0.0.1");
        to_addr.sin_port = htons(MY_OUT_PORT);

        msg.msg_flags = 0;
        msg.msg_name = &to_addr;
        msg.msg_namelen = sizeof(struct sockaddr_in);
        msg.msg_control = NULL;
        msg.msg_controllen = 0;
        msg.msg_iov = &iov;
        msg.msg_iovlen = 1;
        msg.msg_control = NULL;

        oldfs = get_fs();
        set_fs(KERNEL_DS);
        sock_sendmsg(out_socket, &msg, len);
        set_fs(oldfs);
}

static int k_udp_notify_thread(void *data)
{
        int i = 0;
        while (!kthread_should_stop()) {
                char buf[64];

                sprintf(buf, "test from kernel%d\n", i++);
                send_notification(buf);
                msleep(1000);
        }
        return 0;
}

int k_udp_init(void)
{
        struct sockaddr_in addr_out;
        struct sockaddr_in addr_in;
        int rc = 0;
        printk("%s\n", __func__);
        if (in_socket) {
                printk(KERN_INFO "%s: socket already set up\n", __func__);
                return 0;
        }

        if (sock_create(PF_INET, SOCK_DGRAM, IPPROTO_UDP, &in_socket) < 0) {
                printk( KERN_ERR "%s: failed to create socket\n", __func__);
                return -EIO;
        }
        addr_in.sin_family = AF_INET;
        addr_in.sin_addr.s_addr = in_aton("127.0.0.1");
        addr_in.sin_port = htons( (unsigned short)MY_IN_PORT);
        rc = in_socket->ops->bind(in_socket, (struct sockaddr *)&addr_in, sizeof(addr_in));
        if (rc) {
                printk(KERN_ERR "%s: failed to bind\n", __func__);
                sock_release(in_socket);
                in_socket = NULL;
                return -EIO;
        }
        in_socket->sk->sk_data_ready = cb_data;

        if (sock_create(PF_INET, SOCK_DGRAM, IPPROTO_UDP, &out_socket) < 0) {
                printk( KERN_ERR "%s: failed to create socket\n", __func__);
                sock_release(in_socket);
                in_socket = NULL;
                return -EIO;
        }
        addr_out.sin_family = AF_INET;
        addr_out.sin_addr.s_addr = in_aton("127.0.0.1");
        addr_out.sin_port = htons( (unsigned short)MY_OUT_PORT);
        rc = out_socket->ops->connect(out_socket, (struct sockaddr *)&addr_out, sizeof(addr_out), 0);
        if (rc) {
                printk(KERN_ERR "%s: failed to connect\n", __func__);
                sock_release(in_socket);
                in_socket = NULL;
                sock_release(out_socket);
                out_socket = NULL;
                return -EIO;
        }

        notify_thread = kthread_create(k_udp_notify_thread, NULL, "k_notify_thread");
        if (notify_thread) {
                printk(KERN_INFO "%s: notify thread created\n", __func__);
                wake_up_process(notify_thread);
        } else {
                printk(KERN_ERR "%s: failed to create notify thread\n", __func__);
        }

        INIT_WORK(&rcv_worker, rcv_work_queue);
        wq = create_singlethread_workqueue("k_rcv_wq");
        if (!wq) {
                return -ENOMEM;
        }

        printk(KERN_INFO "%s: success\n", __func__);

        return 0;
}

void k_udp_cleanup(void)
{
        /* Should we check that the thread is still valid (hasn't exited)? */
        if (notify_thread) {
                kthread_stop(notify_thread);
                notify_thread = NULL;
        }

        if (in_socket) {
                sock_release(in_socket);
                in_socket = NULL;
        }

        if (out_socket) {
                sock_release(out_socket);
                out_socket = NULL;
        }
        if (wq) {
                flush_workqueue(wq);
                destroy_workqueue(wq);
                wq = NULL;
        }
}

注意:我已经从我正在使用的代码中重命名了一些变量和函数名,因此您可能需要对编译进行更改(如果我遗漏了某些内容)。确保端口在用户/内核组件之间匹配。

以上代码源自网络上和Linux内核中的多个样本。