C语言 Netlink多播内核组

des4xlb0  于 2023-08-03  发布在  其他
关注(0)|答案(2)|浏览(107)

我试图实现的任务实际上非常简单(将字符串“TEST”多播到用户域守护进程),但是内核模块无法编译。它停止与错误:

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

f87krz0w

f87krz0w1#

您的核心问题是,在使用通用Netlink系列之前,您必须首先注册它(这也适用于普通Netlink系列)。内核不能处理它不知道的家族。除非您正在使用一个已经存在的族,否则这将决定您使用Netlink的方式。
通用Netlink系列属于内核模块。这意味着用户空间客户端无法创建族。反过来,这意味着您不能只启动客户端,然后让模块在创建族之后立即发送消息。这是因为在客户端希望将其自身绑定到该族时,该族不存在。
你需要做的是:
1.在插入模块时创建并注册族和多播组。
1.启动用户空间客户机并将其自身绑定到家庭和多播组。
1.让内核模块在某个时刻发送消息(在客户端绑定之后)。
1.用户空间客户端现在接收到消息。
1.删除模块后,应取消注册该族。
我的代码版本如下。这是driver.c,内核模块。如您所见,我决定在每两秒运行一次的计时器上重复发送消息。这将为您提供启动客户端的时间:

#include <linux/kernel.h>
#include <linux/module.h>
#include <net/genetlink.h>
#include <linux/timer.h>

static struct timer_list timer;
static const int repeat_ms = 2000;

/**
 * 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.
 * Among other reasons, the designers of Netlink want you to use these instead
 * of dumping raw data to the packet payload because it's designed to prevent
 * alignment problems for you.
 * (http://www.catb.org/esr/structure-packing/)
 */
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,
            .doit = hello, /* The dummy function we defined above. */
            .dumpit = NULL,
    },
};

/**
 * Your multicast group. Choose a likely unique name.
 */
struct genl_multicast_group groups[] = {
    { .name = "PotatoGroup" },
};

/**
 * A Generic Netlink family is a group of listeners who can and want to speak
 * your "language" (ie. your set of Attributes).
 * Anyone who wants to hear your messages needs to register to the same family
 * as you.
 * (And because we're using multicast, they will have to register to the same
 * multicast group as well.)
 */
struct genl_family family = {
            .hdrsize = 0,
            .name = "PotatoFamily",
            .version = 1,
            .maxattr = __ATTR_MAX,
            .policy = policies,
            .ops = ops,
            .n_ops = ARRAY_SIZE(ops),
            .mcgrps = groups,
            .n_mcgrps = ARRAY_SIZE(groups),
};

void send_multicast(struct timer_list *t)
{
    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("Adding Generic Netlink header to the 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 'hello' attribute.\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 'foo' attribute.\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:
    // Reschedule the timer to call this function again in repeat_ms
    // milliseconds
    mod_timer(t, jiffies + msecs_to_jiffies(repeat_ms));
}

static int init_socket(void)
{
    int error;

    pr_info("Registering family.\n");
    error = genl_register_family(&family);
    if (error)
            pr_err("Family registration failed: %d\n", error);

    return error;
}

static void initialize_timer(void)
{
    pr_info("Starting timer.\n");

    // Initialize the timer and assign the callback function
    timer_setup(&timer, send_multicast, 0);

    // Set the timer to expire in repeat_ms milliseconds from now
    mod_timer(&timer, jiffies + msecs_to_jiffies(repeat_ms));
}

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)
{
    // Make sure to delete the timer when exiting your module
    del_timer(&timer);
    genl_unregister_family(&family);
    pr_info("Hello removed.\n");
}

module_init(hello_init);
module_exit(hello_exit);

MODULE_LICENSE("GPL");

字符串
这是app.c,用户空间客户端:

#include <netlink/netlink.h>
#include <netlink/socket.h>
#include <netlink/msg.h>
#include <netlink/genl/genl.h>
#include <netlink/genl/ctrl.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;
}


这里也是一个示例Makefile(请验证您的目录):

KERNEL_HEADERS := /lib/modules/$(shell uname -r)/build
LIBNL_INC := /usr/include/libnl3
LIBNL_LIB := /usr/lib/x86_64-linux-gnu

obj-m += driver.o

CC := gcc
CFLAGS := -I$(KERNEL_HEADERS)
APPFLAGS := -I$(LIBNL_INC)
LDFLAGS := -L$(LIBNL_LIB)
LDLIBS := -lnl-3 -lnl-genl-3

.PHONY: all driver app clean

all: driver app

driver:
        $(MAKE) -C $(KERNEL_HEADERS) M=$(shell pwd) modules

app: app.c
        $(CC) $(APPFLAGS) $(LDFLAGS) -o app app.c $(LDLIBS)

clean:
        $(MAKE) -C $(KERNEL_HEADERS) M=$(shell pwd) clean
        rm -f app

ff29svar

ff29svar2#

这不是对网络链接问题的直接回答,而是另一种解决方案。请参阅上面有关netlink限制的注解。
UDP套接字可以在Linux上用于在用户模式进程(如守护进程)和内核模式组件(如可加载模块)之间进行通信。

守护程序代码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内核中的多个示例中派生出来的。

相关问题