Netlink多播内核组

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

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

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

以下是“澄清”的代码:

 #include  #include  #include  #include  #include  #include  #include  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  #include  #include  #include  #include  /* * 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  #include  #include  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  #include  #include  #include  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限制的评论。

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

守护进程代码my_udp.c:

 #include  #include  #include  #include  #include  #include  #include  #include  #include  #include  #include  #include  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  #include  #include  #include  #include  #include  #include  #include  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内核中的多个样本。