Search code examples
socketslinux-kernelkernel-modulenetlinkkernel-mode

Can kernel module take initiative to send message to user space with netlink?


I am trying to run following code, which was copied from here. I have made few changes to run it with older kernel versions.

When I insert kernel module, nlmsg_multicast() fails and logs as nlmsg_multicast() error: -3 in /var/log/messages. While running user space program, socket() fails.

What exactly I want to do is,

  • kernel module creates a socket, regardless of any process in user space
  • kernel module send some events to user space
  • If any process in user space reply to an event, kernel module process on that reply

Since, It may happen that no process in user space available to reply on event, even in that case module must send event and wait for a while for response.

Is it possible to send first message from kernel module to a process in user space? How can I do this?

Kernel module code:

Makefile

obj-m   := foo.o

KDIR    := /lib/modules/$(shell uname -r)/build
PWD    := $(shell pwd)

default:
        $(MAKE) -C $(KDIR) SUBDIRS=$(PWD) modules

clean:
        $(MAKE) -C $(KDIR) SUBDIRS=$(PWD) clean

foo.c

#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);
        nl_sk = netlink_kernel_create(&init_net, MYPROTO, 0, NULL, NULL, THIS_MODULE);
        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");

The user space program: (Compiled with gcc somename.c)

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

Thank you for your time!


Solution

  • This looks like bad design (because upper layers should depend on lower layers, not the other way around). But if you're convinced the kernel cannot sit idle or operate using default configuration until userspace can fetch info, then first install this tool (might want to read the core guide too), and then do something like this:

    Kernel:

    #include <linux/module.h>
    #include <linux/kernel.h>
    #include <net/netlink.h>
    #include <net/net_namespace.h>
    
    #define MYPROTO NETLINK_USERSOCK
    #define MYGRP 22
    
    static struct sock *nl_sk;
    static struct timer_list timer;
    
    void try_send(unsigned long data)
    {
        struct sk_buff *skb;
        struct nlmsghdr *nlh;
        char *msg = "Hello from kernel";
        int msg_size = strlen(msg) + 1;
        int res;
    
        skb = nlmsg_new(NLMSG_ALIGN(msg_size + 1), GFP_ATOMIC);
        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 multicast.\n");
        res = nlmsg_multicast(nl_sk, skb, 0, MYGRP, GFP_ATOMIC);
        if (res < 0) {
            pr_info("nlmsg_multicast() error: %d. Will try again later.\n", res);
            /* Wait 1 second. */
            mod_timer(&timer, jiffies + msecs_to_jiffies(1000));
        } else {
            pr_info("Success.\n");
        }
    }
    
    static int handle_netlink_message(struct sk_buff *skb_in, struct nlmsghdr *nl_hdr)
    {
        char *hello;
        hello = NLMSG_DATA(nl_hdr);
        pr_info("Userspace says '%s.'\n", hello);
        return 0;
    }
    
    static void receive_answer(struct sk_buff *skb)
    {
        netlink_rcv_skb(skb, &handle_netlink_message);
    }
    
    static int __init hello_init(void)
    {
        pr_info("Inserting module.\n");
    
        nl_sk = netlink_kernel_create(&init_net, MYPROTO, 0, receive_answer, NULL, THIS_MODULE);
        if (!nl_sk) {
            pr_err("Error creating socket.\n");
            return -10;
        }
    
        init_timer(&timer);
        timer.function = try_send;
        timer.expires = jiffies + 1000;
        timer.data = 0;
        add_timer(&timer);
    
        return 0;
    }
    
    static void __exit hello_exit(void)
    {
        del_timer_sync(&timer);
        netlink_kernel_release(nl_sk);
        pr_info("Exiting module.\n");
    }
    
    module_init(hello_init);
    module_exit(hello_exit);
    
    MODULE_LICENSE("GPL");
    

    User (I'm compiling using gcc usr.c -I/usr/include/libnl3 -lnl-3 -Wall, your mileage may vary):

    #include <netlink/netlink.h>
    #include <netlink/msg.h>
    
    #define MYPROTO NETLINK_USERSOCK
    #define MYMGRP 22
    
    struct nl_sock *sk;
    
    void respond_to_kernel(void)
    {
        char *response = "foo bar";
        int error;
    
        error = nl_send_simple(sk, 12345, NLMSG_DONE, response, strlen(response) + 1);
        if (error < 0) {
            printf("nl_send_simple() threw errcode %d.\n", error);
            printf("libnl's message: %s", nl_geterror(error));
        } else {
            printf("Responded %d bytes.\n", error);
        }
    }
    
    int receive_kernel_request(struct nl_msg *msg, void *arg)
    {
        char *hello;
    
        hello = nlmsg_data(nlmsg_hdr(msg));
        printf("Kernel says '%s'.\n", hello);
        respond_to_kernel();
    
        return 0;
    }
    
    int prepare_socket(void)
    {
        int error;
    
        sk = nl_socket_alloc();
        if (!sk) {
            printf("nl_socket_alloc() returned NULL.\n");
            return -1;
        }
    
        nl_socket_disable_seq_check(sk);
    
        error = nl_socket_modify_cb(sk, NL_CB_FINISH, NL_CB_CUSTOM, receive_kernel_request, NULL);
        if (error < 0) {
            printf("Could not register callback function. Errcode: %d\n", error);
            goto fail;
        }
    
        error = nl_connect(sk, MYPROTO);
        if (error < 0) {
            printf("Connection failed: %d\n", error);
            goto fail;
        }
    
        error = nl_socket_add_memberships(sk, MYMGRP, 0);
        if (error) {
            printf("Could not register to the multicast group. %d\n", error);
            goto fail;
        }
    
        return 0;
    
    fail:
        printf("libnl's message: %s\n", nl_geterror(error));
        nl_socket_free(sk);
        return error;
    }
    
    int wait_for_kernel_message(void)
    {
        int error;
    
        printf("Waiting for kernel request...\n");
        error = nl_recvmsgs_default(sk);
        if (error < 0) {
            printf("nl_send_simple() threw errcode %d.\n", error);
            printf("libnl's message: %s\n", nl_geterror(error));
            return error;
        }
    
        return 0;
    }
    
    void destroy_socket(void)
    {
        nl_socket_free(sk);
    }
    
    int main(int argc, char *argv[])
    {
        int error;
    
        error = prepare_socket();
        if (error)
            return error;
    
        error = wait_for_kernel_message();
        destroy_socket();
        return error;
    }
    

    Tested on kernel 3.2. (Sorry; that's the lowest I have right now.)