Groupe de kernelx de multidiffusion Netlink

La tâche que j’essaie d’accomplir est en fait assez simple (multidiffuser la chaîne “TEST” dans un démon userland), mais le module du kernel ne comstack pas. Cela s’arrête avec l’erreur:

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

Mais ne devrait-il pas s’agir uniquement du groupe de multidiffusion que j’ai défini?

Voici le code pour “clarification”:

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

Merci de votre aide.

MODIFIER

C’est le code côté client:

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

J’aimerais commencer par dire que je ne suis pas un grand fan de Netlink; Je pense que c’est plutôt mal conçu. Cela dit, je pense avoir la réponse exacte à la question, alors voilà.

Votre principal problème est qu’avant d’utiliser une famille Netlink générique, vous devez d’abord l’enregistrer (cela s’applique également aux familles Netlink normales). Le kernel ne peut pas gérer les familles qu’il ne connaît pas. À moins que vous n’utilisiez une famille existante, cela détermine la façon dont vous devez aborder Netlink.

Une famille générique Netlink appartient à un module de kernel . Cela signifie qu’un client en espace utilisateur ne peut pas créer de famille. En retour, cela signifie que vous ne pouvez pas simplement démarrer le client, puis que le module envoie le message juste après avoir créé la famille. En effet, la famille n’existe pas au moment où le client souhaite s’y associer.

Ce que vous devez faire est la suivante:

  1. Créez et enregistrez la famille et le groupe de multidiffusion pendant que vous insérez le module.
  2. Démarrez le client en espace utilisateur et associez-le à la famille et au groupe de multidiffusion.
  3. Demandez au module du kernel d’envoyer le message à un moment donné (après la liaison du client).
  4. Le client de l’espace utilisateur reçoit maintenant le message.
  5. Lorsque le module est supprimé, il devrait annuler l’enregistrement de la famille.

Ma version de votre code suit. Ceci est le module du kernel. Comme vous pouvez le constater, j’ai décidé d’envoyer le message à plusieurs resockets sur une timer programmée toutes les deux secondes. Cela vous donne le temps de démarrer le client:

  #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; } /** * Atsortingbutes 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 atsortingbutes { /* * The first one has to be a throwaway empty atsortingbute; 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 atsortingbutes 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 ssortingng.\n"); error = nla_put_ssortingng(skb, ATTR_HELLO, msg); if (error) { pr_err("nla_put_ssortingng() 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"); 

Et voici le client de l’espace utilisateur:

  #include  #include  #include  #include  static struct nl_sock *sk = NULL; /** * Atsortingbutes 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 atsortingbutes { 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; } 

Ce n’est pas une réponse directe à la question de netlink, mais une solution alternative. Voir les commentaires ci-dessus sur les limitations de netlink.

Les sockets UDP peuvent être utilisés sous Linux pour communiquer entre un processus en mode utilisateur, tel qu’un démon, et un composant en mode kernel, tel qu’un module chargeable.

Code démon 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); } 

Code du module de kernel 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; } } 

Remarque: j’ai renommé certaines variables et noms de fonctions du code que j’utilise. Vous devrez donc peut-être modifier la compilation (si j’ai oublié quelque chose). Assurez-vous de faire correspondre les ports entre les composants utilisateur / kernel.

Le code ci-dessus a été dérivé de plusieurs exemples sur le net et dans le kernel Linux.