1

I have written a kernel module and userspace program such that the kernel module sends netlink multicast messages, and the userspace program reads these messages and prints them out. The kernel module and userspace program are available here (https://github.com/akshayknarayan/netlink-test) and replicated below. The code was adapted from this post: Multicast from kernel to user space via Netlink in C

If line 69 of the userspace program (the call to usleep) is commented out, then everything works; once the kernel module is loaded, it repeatedly multicasts messages and the userspace program prints them out.

However, if line 69 of the userspace program is uncommented, within a second of loading the kernel module, my VM hangs and becomes unresponsive.

Why is this the case? How can I prevent the kernel from hanging?

Linux ubuntu-xenial 4.4.0-75-generic #96-Ubuntu SMP Thu Apr 20 09:56:33 UTC 2017 x86_64 x86_64 x86_64 GNU/Linux

Userspace program:

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

/* Multicast group, consistent in both kernel prog and user prog. */
#define MYMGRP 22

int nl_open(void) {
    int sock;
    struct sockaddr_nl addr;
    int group = MYMGRP;

    sock = socket(AF_NETLINK, SOCK_RAW, NETLINK_USERSOCK);
    if (sock < 0) {
        printf("sock < 0.\n");
        return sock;
    }

    memset((void *) &addr, 0, sizeof(addr));
    addr.nl_family = AF_NETLINK;
    addr.nl_pid = getpid();

    if (bind(sock, (struct sockaddr *) &addr, sizeof(addr)) < 0) {
        printf("bind < 0.\n");
        return -1;
    }

    if (setsockopt(sock, 270, NETLINK_ADD_MEMBERSHIP, &group, sizeof(group)) < 0) {
        printf("setsockopt < 0\n");
        return -1;
    }

    return sock;
}

void nl_recv(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;

    ret = recvmsg(sock, &msg, 0);
    if (ret < 0)
        printf("ret < 0.\n");
    else
        printf("Received message payload: %s\n", (char*) NLMSG_DATA((struct nlmsghdr *) &buffer));
}

int main(int argc, char *argv[]) {
    int nls;

    nls = nl_open();
    if (nls < 0)
        return nls;

    while (1) {
        nl_recv(nls);
        usleep(5000);
    }

    return 0;
}

Kernel module:

#include <linux/module.h>
#include <linux/netlink.h>
#include <linux/skbuff.h>
#include <linux/gfp.h>
#include <net/sock.h>

#define MYMGRP 22

struct sock *nl_sk = NULL;
static struct timer_list timer;

void nl_send_msg(unsigned long data) {
    struct sk_buff *skb_out;
    struct nlmsghdr *nlh;
    int res;
    char *msg = "hello from kernel!\n";
    int msg_size = strlen(msg);

    skb_out = nlmsg_new(
        NLMSG_ALIGN(msg_size), // @payload: size of the message payload
        GFP_KERNEL             // @flags: the type of memory to allocate.
    );
    if (!skb_out) {
        printk(KERN_ERR "Failed to allocate new skb\n");
        return;
    }

    nlh = nlmsg_put(
        skb_out,    // @skb: socket buffer to store message in
        0,          // @portid: netlink PORTID of requesting application
        0,          // @seq: sequence number of message
        NLMSG_DONE, // @type: message type
        msg_size,   // @payload: length of message payload
        0           // @flags: message flags
    );

    memcpy(nlmsg_data(nlh), msg, msg_size+1);
    res = nlmsg_multicast(
            nl_sk,     // @sk: netlink socket to spread messages to
            skb_out,   // @skb: netlink message as socket buffer
            0,         // @portid: own netlink portid to avoid sending to yourself
            MYMGRP,    // @group: multicast group id
            GFP_KERNEL // @flags: allocation flags
    );
    if (res < 0) {
        printk(KERN_INFO "Error while sending to user: %d\n", res);
    } else {
        mod_timer(&timer, jiffies + msecs_to_jiffies(1));
    }
}

static int __init nl_init(void) {
    struct netlink_kernel_cfg cfg = {};

    printk(KERN_INFO "init NL\n");
    nl_sk = netlink_kernel_create(&init_net, NETLINK_USERSOCK, &cfg);
    if (!nl_sk) {
        printk(KERN_ALERT "Error creating socket.\n");
        return -10;
    }

    init_timer(&timer);
    timer.function = nl_send_msg;
    timer.expires = jiffies + 1000;
    timer.data = 0;
    add_timer(&timer);
    nl_send_msg(0);

    return 0;
}

static void __exit nl_exit(void) {
    printk(KERN_INFO "exit NL\n");
    del_timer_sync(&timer);
    netlink_kernel_release(nl_sk);
}

module_init(nl_init);
module_exit(nl_exit);

MODULE_LICENSE("GPL");
Community
  • 1
  • 1
akn320
  • 573
  • 4
  • 13

1 Answers1

0

For posterity: I believe the problem was the allocation in nlmsg_new, which should not occur inside an interrupt handler (the timer handler, nl_send_msg), as explained here.

Without the sleep, I believe nlmsg_new does not need to sleep when allocating, so the requirement that an interrupt handler not sleep is not violated. However, if the userspace process lags behind the kernel, it is possible for the kernel to sleep during the allocation, causing a hang.

akn320
  • 573
  • 4
  • 13