Multidifusión desde el kernel al espacio del usuario a través de Netlink en C

10 minutos de lectura

Estaba tratando de escribir un programa simple que se comunicara entre el kernel y el espacio del usuario usando Netlink. Básicamente esto es lo que quería lograr:

  1. El programa de espacio de usuario comienza a vincularse a un grupo de multidifusión definido por el usuario.
  2. Insertar módulo del núcleo
  3. El módulo del núcleo envía un mensaje a este grupo de multidifusión
  4. El programa de espacio de usuario recibe el mensaje.

Aquí está mi código:

======Programa de espacio de usuario======

#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<sys/socket.h>
#include<linux/netlink.h>
#include<sys/types.h>
#include<unistd.h>

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

======Módulo del núcleo======

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <net/sock.h>
#include <linux/socket.h>
#include <linux/net.h>
#include <asm/types.h>
#include <linux/netlink.h>
#include <linux/rtnetlink.h>
#include <linux/skbuff.h>
#include <linux/delay.h>

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

Dado que el módulo del kernel solo enviará el mensaje una vez durante la inicialización, primero ejecuto el programa de escucha y luego inserto el módulo, aunque siempre recibí este error:

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

Cuando rastrea el id de error, se refleja en este fragmento de código en 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;

Supongo que no es delivery_failure pero aún no se entregó por algunas razones.

me referia a esto ejemplo en el que el programa del autor sigue escuchando el cambio de rutas. Aunque me gustaría usar un grupo de multidifusión definido por el usuario.

¿Algunas ideas? ¡Gracias por adelantado!

  • También tuve un problema similar hace mucho tiempo; puede verificar las siguientes cosas: 1: vea que el valor 31 de NETLINK_USER no está siendo utilizado por otros componentes; si es así, elija algún valor no utilizado. Supongo que 31 está asignado al componente LTT. 2: Al crear un socket en el espacio del usuario, use el mismo valor de NETLINK_USER como se define en el kernel. int sock = socket(AF_NETLINK,SOCK_RAW, NETLINK_USER);

    – Gyan Gupta

    28 de marzo de 2014 a las 9:15


  • ¡Gracias por su respuesta! Aunque por lo que entendí, netlink_kernel_create() es el que crea un socket en el espacio del kernel en lugar de socket(), ¿o me estoy perdiendo algo?

    – guoger

    28 de marzo de 2014 a las 9:49

  • Quise decir en su programa de espacio de usuario; al abrir el zócalo con socket(AF_NETLINK,SOCK_RAW,MYPROTO); hágalo con el mismo valor de NETLINK_USER como lo definió en el kernel. Así que podría ser ` #define NETLINK_USER 30`
    socket(AF_NETLINK,SOCK_RAW, NETLINK_USER);

    – Gyan Gupta

    28 de marzo de 2014 a las 10:04


  • Recuerdo cómo traté de hacer esto. Hay varios ejemplos y tutoriales en Internet y la mayoría de ellos no me funciona. Así que terminé usando la familia netlink genérica y libnl. Puedes ver mi ejemplo de trabajo en github.com/dzeban/keymon

    – Alejandro Dzyoba

    31 de marzo de 2014 a las 8:50

Estos son los dos problemas clave que encontré en su código:

  1. Tanto la familia de protocolos como el grupo de multidifusión deben ser consistentes tanto en el programa del kernel como en el programa del usuario. Su familia de protocolos es NETLINK_USERSOCK (2) en el espacio de usuario y NETLINK_USER (31) en el espacio del núcleo.
  2. addr.nl_groups = MYMGRP; no funciona por alguna razón. Sin embargo, esto sí: setsockopt(sock, 270, NETLINK_ADD_MEMBERSHIP, &group, sizeof(group)).

No fatal:

  1. En este caso, el módulo no está escuchando los mensajes del grupo, por lo que no necesita incluir el grupo de multidifusión en el netlink_kernel_create() parámetros

Además, no está realmente relacionado con netlink pero es útil de todos modos:

  1. strlen() no incluye el carácter nulo. Durante las asignaciones de mensajes, probablemente debería agregar un byte para compensar esto.
  2. En este caso, NLMSG_DATA(&nlh) es un comportamiento indefinido. Esto se debe a que su encabezado y datos están en fragmentos de memoria separados, no se garantiza que estén pegados, y todo lo que hace la macro es acceder al fragmento de memoria después nlh:

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

Esta es mi versión de tu código:

Programa de espacio de usuario:

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <linux/netlink.h>
#include <unistd.h>

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

Y este es el módulo del núcleo:

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/netlink.h>
#include <net/netlink.h>
#include <net/net_namespace.h>

/* 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");

Probado en el núcleo 3.13.

(¿Puedo sugerir que la gente use libnl-3 en lugar de sockets sin formato para el programa de espacio de usuario. Su documentación de Netlink de multidifusión es realmente decente.)

  • Que deberia ser netlink_kernel_create() en el caso de versiones anteriores del kernel?

    – Nitinkumar Ambekar

    29 de enero de 2016 a las 11:45

  • @NTN Por lo que he visto, siempre son los mismos argumentos, solo reorganizados. Ver este ejemplo. Tú también puedes ver netlink_kernel_create()definición de. (piratee la versión del kernel en la URL para ver diferentes versiones del archivo; por ejemplo, cambie “v=3.13” a “v=”3.10”)

    – Yd Ahhrk

    29 de enero de 2016 a las 23:15


  • @NTN para responder a su pregunta directamente, eso sería netlink_kernel_create(&init_net, NETLINK_USERSOCK, 0, NULL, NULL, THIS_MODULE) (asumiendo que su kernel es 3.5 o inferior).

    – Yd Ahhrk

    29 de enero de 2016 a las 23:20


  • Especificar nl_groups no funciona porque nl_groups es una máscara de bits de grupos permitidos mientras que NETLINK_ADD_MEMBERSHIP toma un número de grupo individual. Creo que en este caso necesitarías configurar el bit 20 (para el valor 0x100000).

    – Mate

    9 de junio de 2016 a las 16:48

  • @river Aquí hay una suposición: intente ejecutar el programa con sudo.

    – Yd Ahhrk

    29 de junio de 2018 a las 23:49


De acuerdo con la conductor del conector:

Si no hay oyentes para un grupo dado, se puede devolver -ESRCH.

Entonces, su programa de espacio de usuario no estaba funcionando bien cuando su kernel envió el mensaje.

¿Ha sido útil esta solución?

Esta web utiliza cookies propias y de terceros para su correcto funcionamiento y para fines analíticos y para mostrarte publicidad relacionada con sus preferencias en base a un perfil elaborado a partir de tus hábitos de navegación. Al hacer clic en el botón Aceptar, acepta el uso de estas tecnologías y el procesamiento de tus datos para estos propósitos. Configurar y más información
Privacidad