簡體   English   中英

Netlink多播內核組

[英]Netlink Multicast Kernel Group

我試圖實現的任務實際上非常簡單(將字符串“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;
}

我想首先說我不是Netlink的忠實粉絲; 我覺得它的設計很差。 那就是說,我認為我對這個問題有了確切的答案,所以就這樣了。

您的核心問題是在使用Generic Netlink系列之前,首先必須注冊它(這也適用於普通的Netlink系列)。 內核無法處理它不知道的家庭。 除非您使用現有的系列,否則這會影響您接觸Netlink的方式。

通用Netlink系列屬於內核模塊 這意味着用戶空間客戶端無法創建系列。 反過來,這意味着您不能只啟動客戶端,然后讓模塊在創建系列后立即發送消息。 這是因為客戶想要將其自身綁定到它時,該族不存在。

你需要做的是:

  1. 在插入模塊時創建並注冊族和組播組。
  2. 啟動用戶空間客戶端,並將其自身綁定到系列和多播組。
  3. 讓內核模塊在某個時刻發送消息(在客戶端綁定之后)。
  4. 用戶空間客戶端現在接收消息。
  5. 刪除模塊后,應取消注冊該系列。

我的代碼版本如下。 這是內核模塊。 如您所見,我決定在每兩秒運行一次的計時器上重復發送消息。 這讓您有時間啟動客戶端:

    #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;
    }

這不是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內核中的多個樣本。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM