Multicast du kernel vers l’espace utilisateur via Netlink en C

J’essayais d’écrire un programme simple communiquant entre le kernel et l’espace utilisateur à l’aide de Netlink. En gros, voici ce que je voulais réaliser:

  1. Le programme d’espace utilisateur commence à se lier à un groupe de multidiffusion défini par l’utilisateur.
  2. Insérer le module du kernel
  3. Le module de kernel envoie un message à ce groupe de multidiffusion
  4. Le programme spatial utilisateur reçoit le message

Voici mon code:

====== Programme d’espace utilisateur ======

#include #include #include #include #include #include #include #define MYPROTO NETLINK_USERSOCK #define MYMGRP 0x21 //User defined group, consistent in both kernel prog and user prog int open_netlink() { int sock = socket(AF_NETLINK,SOCK_RAW,MYPROTO); struct sockaddr_nl addr; memset((void *)&addr, 0, sizeof(addr)); if (sock<0) return sock; addr.nl_family = AF_NETLINK; addr.nl_pid = getpid(); addr.nl_groups = MYMGRP; if (bind(sock,(struct sockaddr *)&addr,sizeof(addr))<0) return -1; return sock; } int read_event(int sock) { struct sockaddr_nl nladdr; struct msghdr msg; struct iovec iov[2]; struct nlmsghdr nlh; char buffer[65536]; int ret; iov[0].iov_base = (void *)&nlh; iov[0].iov_len = sizeof(nlh); iov[1].iov_base = (void *)buffer; iov[1].iov_len = sizeof(buffer); msg.msg_name = (void *)&(nladdr); msg.msg_namelen = sizeof(nladdr); msg.msg_iov = iov; msg.msg_iovlen = sizeof(iov)/sizeof(iov[0]); ret=recvmsg(sock, &msg, 0); if (ret<0) { return ret; } printf("Received message payload: %s\n", NLMSG_DATA(&nlh)); } int main(int argc, char *argv[]) { int nls = open_netlink(); if (nls<0) { err(1,"netlink"); } while (1) read_event(nls); return 0; } 

====== Module du kernel ======

 #include  #include  #include  #include  #include  #include  #include  #include  #include  #include  #include  #define NETLINK_USER 31 #define MYGRP 0x21 //User defined group, consistent in both kernel prog and user prog struct sock *nl_sk = NULL; static void send_to_user() { struct sk_buff *skb_out; struct nlmsghdr *nlh; int msg_size; char *msg = "Hello from kernel"; int res; printk(KERN_INFO "Entering: %s\n", __FUNCTION__); msg_size = strlen(msg); skb_out = nlmsg_new(msg_size, 0); if (!skb_out) { printk(KERN_ERR "Failed to allocate new skb\n"); return; } nlh = nlmsg_put(skb_out, 0, 1, NLMSG_DONE, msg_size, 0); //NETLINK_CB(skb_out).dst_group = 1; /* Multicast to group 1, 1<<0 */ strncpy(nlmsg_data(nlh), msg, msg_size); res = nlmsg_multicast(nl_sk, skb_out, 0, MYGRP, 0); if (res < 0) { printk(KERN_INFO "Error while sending bak to user, err id: %d\n", res); } } static int __init hello_init(void) { struct netlink_kernel_cfg cfg = { .groups = MYGRP, }; printk("Entering: %s\n", __FUNCTION__); nl_sk = netlink_kernel_create(&init_net, NETLINK_USER, &cfg); if (!nl_sk) { printk(KERN_ALERT "Error creating socket.\n"); return -10; } send_to_user(); 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); 

Comme le module du kernel n’enverra le message qu’une fois lors de l’initialisation, j’exécute d’abord le programme d’écoute, puis insère le module, même si j’ai toujours eu cette erreur:

 Error while sending bak to user, err id: -3 

Lorsque vous recherchez l’ID d’erreur, cela se reflète dans ce morceau de code dans netlink / af_netlink.c:

 if (info.delivery_failure) { kfree_skb(info.skb2); return -ENOBUFS; } consume_skb(info.skb2); if (info.delivered) { if (info.congested && (allocation & __GFP_WAIT)) yield(); return 0; } return -ESRCH; 

Je présume que ce n’est pas delivery_failure mais pas encore livré pour certaines raisons.

Je faisais référence à cet exemple dans lequel le programme de l’auteur maintient les itinéraires d’écoute changer. Bien que je voudrais utiliser un groupe de multidiffusion défini par l’utilisateur.

Des idées? Merci d’avance!

Voici les deux problèmes clés que j’ai trouvés dans votre code:

  1. La famille de protocoles et le groupe de multidiffusion doivent être cohérents dans les progs du kernel et de l’utilisateur. Votre famille de protocoles est NETLINK_USERSOCK (2) en espace utilisateur et NETLINK_USER (31) en espace kernel.
  2. addr.nl_groups = MYMGRP; ne fonctionne pas pour une raison quelconque. Cela se produit cependant: setsockopt(sock, 270, NETLINK_ADD_MEMBERSHIP, &group, sizeof(group)) .

Pas fatal:

  1. Dans ce cas, le module n’écoute pas les messages du groupe. Vous n’avez donc pas besoin d’inclure le groupe de multidiffusion dans les netlink_kernel_create() .

Aussi, pas vraiment lié à netlink mais utile quand même:

  1. strlen() n’inclut pas le chara nul. Lors de l’allocation des messages, vous devriez probablement append un octet pour compenser cela.
  2. Dans ce cas, NLMSG_DATA(&nlh) est un comportement indéfini. En effet, votre en-tête et vos données se trouvent dans des fragments de mémoire distincts, il n’est pas garanti qu’ils soient collés et la macro n’accède qu’à l’access au bloc de mémoire après nlh :

#define NLMSG_DATA(nlh) ((void*)(((char*)nlh) + NLMSG_LENGTH(0)))

Ceci est ma version de votre code:

Programme de l’espace utilisateur:

 #include  #include  #include  #include  #include  #include  /* Protocol family, consistent in both kernel prog and user prog. */ #define MYPROTO NETLINK_USERSOCK /* Multicast group, consistent in both kernel prog and user prog. */ #define MYMGRP 21 int open_netlink(void) { int sock; struct sockaddr_nl addr; int group = MYMGRP; sock = socket(AF_NETLINK, SOCK_RAW, MYPROTO); if (sock < 0) { printf("sock < 0.\n"); return sock; } memset((void *) &addr, 0, sizeof(addr)); addr.nl_family = AF_NETLINK; addr.nl_pid = getpid(); /* This doesn't work for some reason. See the setsockopt() below. */ /* addr.nl_groups = MYMGRP; */ if (bind(sock, (struct sockaddr *) &addr, sizeof(addr)) < 0) { printf("bind < 0.\n"); return -1; } /* * 270 is SOL_NETLINK. See * http://lxr.free-electrons.com/source/include/linux/socket.h?v=4.1#L314 * and * http://stackoverflow.com/questions/17732044/ */ if (setsockopt(sock, 270, NETLINK_ADD_MEMBERSHIP, &group, sizeof(group)) < 0) { printf("setsockopt < 0\n"); return -1; } return sock; } void read_event(int sock) { struct sockaddr_nl nladdr; struct msghdr msg; struct iovec iov; char buffer[65536]; int ret; iov.iov_base = (void *) buffer; iov.iov_len = sizeof(buffer); msg.msg_name = (void *) &(nladdr); msg.msg_namelen = sizeof(nladdr); msg.msg_iov = &iov; msg.msg_iovlen = 1; printf("Ok, listening.\n"); ret = recvmsg(sock, &msg, 0); if (ret < 0) printf("ret < 0.\n"); else printf("Received message payload: %s\n", NLMSG_DATA((struct nlmsghdr *) &buffer)); } int main(int argc, char *argv[]) { int nls; nls = open_netlink(); if (nls < 0) return nls; while (1) read_event(nls); return 0; } 

Et voici le module du kernel:

 #include  #include  #include  #include  #include  /* Protocol family, consistent in both kernel prog and user prog. */ #define MYPROTO NETLINK_USERSOCK /* Multicast group, consistent in both kernel prog and user prog. */ #define MYGRP 21 static struct sock *nl_sk = NULL; static void send_to_user(void) { struct sk_buff *skb; struct nlmsghdr *nlh; char *msg = "Hello from kernel"; int msg_size = strlen(msg) + 1; int res; pr_info("Creating skb.\n"); skb = nlmsg_new(NLMSG_ALIGN(msg_size + 1), GFP_KERNEL); if (!skb) { pr_err("Allocation failure.\n"); return; } nlh = nlmsg_put(skb, 0, 1, NLMSG_DONE, msg_size + 1, 0); strcpy(nlmsg_data(nlh), msg); pr_info("Sending skb.\n"); res = nlmsg_multicast(nl_sk, skb, 0, MYGRP, GFP_KERNEL); if (res < 0) pr_info("nlmsg_multicast() error: %d\n", res); else pr_info("Success.\n"); } static int __init hello_init(void) { pr_info("Inserting hello module.\n"); nl_sk = netlink_kernel_create(&init_net, MYPROTO, NULL); if (!nl_sk) { pr_err("Error creating socket.\n"); return -10; } send_to_user(); netlink_kernel_release(nl_sk); return 0; } static void __exit hello_exit(void) { pr_info("Exiting hello module.\n"); } module_init(hello_init); module_exit(hello_exit); MODULE_LICENSE("GPL"); 

Testé dans le kernel 3.13.

(Puis-je suggérer que les gens utilisent libnl-3 au lieu de sockets bruts pour le programme de l'espace utilisateur. Sa documentation sur la multidiffusion Netlink est en réalité décente .)