使用 Netlink 在内核 space 和用户 space 之间进行通信的 msghdr 行为

msghdr behavior using Netlink to communicate between kernel space and user space

我目前正在为一个学校项目开发一个 linux 内核模块,其中涉及向用户公开内核哈希表实现 space。

为此,我还创建了一个用户 space API,它通过 Netlink 套接字与 LKM 通信。

我现在确实可以使用它了,但是我 运行 遇到了一个颠簸,这让我很困惑,我真的无法回过头来。在阅读了所有文档之后,这确实对我理解问题没有帮助,"going down the rabbit hole" 并查看了 Netlink 的源代码,我想我会在这里问这个问题,看看是否有人知道什么,以及为什么会发生这种情况。

因此,为了隔离问题,我创建了一个小测试程序,该程序是 运行 通用 Netlink 用户-space 和内核-space 通信示例。通过这个,我将展示用户 space 程序的 3 个小变体,它们都有不同的行为,这就是我想知道的行为。

首先是内核模块,所有 3 个变体都相同:

#include <linux/module.h>
#include <net/sock.h> 
#include <linux/netlink.h>
#include <linux/skbuff.h> 
#define NETLINK_USER 31

struct sock *nl_sk = NULL;

static void hello_nl_recv_msg(struct sk_buff *skb){

    struct nlmsghdr *nlh;
    int pid;
    struct sk_buff *skb_out;
    int msg_size;
    char *msg = "Hello from kernel";
    int res;

    printk(KERN_INFO "Entering: %s\n", __FUNCTION__);

    msg_size = strlen(msg);

    nlh = (struct nlmsghdr *)skb->data;
    printk(KERN_INFO "Netlink received msg payload:%s\n", (char *)nlmsg_data(nlh));
    pid = nlh->nlmsg_pid; //pid of sending process

    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, 0, NLMSG_DONE, msg_size, 0);
    NETLINK_CB(skb_out).dst_group = 0; // not in mcast group 
    strncpy(nlmsg_data(nlh), msg, msg_size);

    res = nlmsg_unicast(nl_sk, skb_out, pid);
    if (res < 0)
        printk(KERN_INFO "Error while sending bak to user\n");
}

static int __init hello_init(void){

    struct netlink_kernel_cfg cfg = {
        .input = hello_nl_recv_msg,
    };
    printk(KERN_INFO "Loading kernel module\n");
    nl_sk = netlink_kernel_create(&init_net, NETLINK_USER, &cfg);
    if (!nl_sk) {
        printk(KERN_ALERT "Error creating socket.\n");
        return -10;
    }

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

然后是用户-space程序:

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

#define NETLINK_USER 31

#define MAX_PAYLOAD 1024 /* maximum payload size*/

struct msghdr msg;

int main(){
    struct sockaddr_nl src_addr, dest_addr;
    struct nlmsghdr *nlh = NULL;
    struct iovec iov;
    int sock_fd;
    sock_fd = socket(PF_NETLINK, SOCK_RAW, NETLINK_USER);
    if (sock_fd < 0)
        return -1;

    memset(&src_addr, 0, sizeof(src_addr));
    src_addr.nl_family = AF_NETLINK;
    src_addr.nl_pid = getpid(); /* self pid */

    bind(sock_fd, (struct sockaddr *)&src_addr, sizeof(src_addr));

    memset(&dest_addr, 0, sizeof(dest_addr));
    memset(&dest_addr, 0, sizeof(dest_addr));
    dest_addr.nl_family = AF_NETLINK;
    dest_addr.nl_pid = 0; /* For Linux Kernel */
    dest_addr.nl_groups = 0; /* unicast */

    nlh = (struct nlmsghdr *)malloc(NLMSG_SPACE(MAX_PAYLOAD));
    memset(nlh, 0, NLMSG_SPACE(MAX_PAYLOAD));
    nlh->nlmsg_len = NLMSG_SPACE(MAX_PAYLOAD);
    nlh->nlmsg_pid = getpid();
    nlh->nlmsg_flags = 0;

    strcpy(NLMSG_DATA(nlh), "Hello");

    iov.iov_base = (void *)nlh;
    iov.iov_len = nlh->nlmsg_len;
    msg.msg_name = (void *)&dest_addr;
    msg.msg_namelen = sizeof(dest_addr);
    msg.msg_iov = &iov;
    msg.msg_iovlen = 1;

    printf("Sending message to kernel\n");
    sendmsg(sock_fd, &msg, 0);
    printf("Waiting for message from kernel\n");

    /* Read message from kernel */
    recvmsg(sock_fd, &msg, 0);
    printf("Received message payload: %s\n", (char*)NLMSG_DATA(nlh));
    close(sock_fd);
    return 0;
}

现在如果我 运行 这个,一切都很好,它给了我控制台输出:

Sending message to kernel
Waiting for message from kernel
Received message payload: Hello from kernel

和 dmesg 的内核日志输出:

[ 3160.679609] exiting hello module
[ 3165.140816] Loading kernel module
[ 3169.678258] Entering: hello_nl_recv_msg
[ 3169.678260] Netlink received msg payload:Hello

但是对于这个项目,我们正在使用调用 API 的多线程应用程序,所以我想尝试为每个调用线程提供一个自己的 Netlink 套接字。为此,我必须

struct msghdr msg;

进入局部声明的变量。

出现问题

当我将它移到主函数中时,事情立即崩溃了。这是内核甚至没有进入 Netlink 回调函数的方式,所以我猜用户 space 程序甚至无法写入它,但它仍然 returns 正确的写入字节数来自sendmsg() 函数。

这是在本地声明 msghdr 时输出到控制台的内容:

Sending message to kernel
Waiting for message from kernel

然后挂起,需要 SIGINT,内核日志没有显示任何关于 LKM 接收任何数据的信息。

所以我开始怀疑它是否可能是在本地声明时发生的寻址错误,所以为了尝试一下,我将 msghdr 转换为本地范围内的动态分配指针,你知道吗,它起作用了! 它给出了与原始示例相同的控制台和内核日志输出。

Soooo,我的实际问题确实是出于教育目的并理解为什么它会以这种方式表现。

为什么全局声明的变量起作用,而局部声明的变量却不起作用?

此外,为什么本地声明的、动态分配的指针起作用?

我是不是漏掉了一些基本的东西?

TL;DR:

为什么在用户 space 程序中局部声明 msghdr 结构不起作用,而全局声明或局部动态指针却起作用?

也许当它在堆栈上时,它的内存未归零,并且您在某些字段中有垃圾。