【代码模板】如何通过netlink从内核态主动发送消息到用户态?(netlink_kernel_create、nlmsg_new、nlmsg_put、netlink_unicast)

摘要

本文通过一个简单的例子介绍了Netlink机制的使用,展示了内核态与用户态之间的异步通信。主要流程包括:内核态驱动注册Netlink用户自定义消息组,用户态程序通过写入字符设备将PID发送给内核态驱动,内核态使用Netlink单播或广播机制通知对应的PID,用户态收到消息后打印出来自内核的信息。

背景

netlink是内核中常用的机制,可以使内核态主动通知用户态并且是异步的操作。本文实现了一个简单的例子介绍netlink的使用机制。主要流程是:内核态驱动注册netlink用户自定义消息组,然后用户态程序通过write字符设备将pid发送给内核态驱动,内核态然后使用netlink单播/广播机制通知到对应的pid,然后用户态收到消息后打印出来自内核的消息

代码与主要流程

内核态程序 test.c

主要流程:
初始化的时候通过netlink_kernel_create创建netlink的用户自定义的netlink消息组。
然后创建字符设备至此写入和ioctl。写入是指定pid,ioctl是触发发送到用户态的netlink中去。

通过nlmsg_new创建一个sk_buffer
然后nlmsg_put初始化skb
然后将需要传送的消息拷贝到mlh的data域段并且设置mlh的msg len(如果不设置,用户态收到的该部分信息不对)
然后调用netlink_unicast单播到某个pid,指定创建的netlink的自定义消息组句柄、需要传送的skb、用户pid、以及flag。也可以组播,组播需要把netlink_unicast换成netlink_broadcast。

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/uaccess.h>
#include <linux/netlink.h>
#include <linux/skbuff.h>
#include <net/netlink.h>
#include <net/net_namespace.h>

#define NETLINK_USER 31
#define DEVICE_NAME "netlink_device"
#define CLASS_NAME "nlclass"
#define IOCTL_CMD 0x12345678 // Must match the command in the user program

static struct sock *nl_sk = NULL;
static dev_t dev_num;
static struct cdev c_dev;
static struct class *cl;
static int user_pid = 0; // Store the user space process PID

static void send_to_user(void) {
    struct sk_buff *skb;
    char msg[] = "Hello from kernel!";
    int size = NLMSG_SPACE(strlen(msg) + 1); // including null terminator

    if (user_pid == 0) {
        printk(KERN_ERR "User PID not set\n");
        return;
    }

    skb = nlmsg_new(size, GFP_KERNEL);
    if (!skb) {
        printk(KERN_ERR "Failed to allocate new skb\n");
        return;
    }

    struct nlmsghdr *nlh = nlmsg_put(skb, 0, 0, NLMSG_DONE, strlen(msg) + 1, 0);
    if (!nlh) {
        nlmsg_free(skb);
        printk(KERN_ERR "Failed to put nlmsghdr\n");
        return;
    }
    memcpy(NLMSG_DATA(nlh), msg, strlen(msg) + 1);
    nlh->nlmsg_len = strlen(msg) + 1;

    int res = netlink_unicast(nl_sk, skb, user_pid, MSG_DONTWAIT);
    if (res < 0) {
        printk(KERN_ERR "Error while sending to user: %d\n", res);
    }
}

static long device_ioctl(struct file *file, unsigned int cmd, unsigned long arg) {
    switch (cmd) {
        case IOCTL_CMD:
            send_to_user();
            break;
        default:
            return -ENOTTY;
    }
    return 0;
}

static ssize_t device_write(struct file *filp, const char __user *buf, size_t len, loff_t *off) {
    char kbuf[10];
    if (len > sizeof(kbuf)) {
        return -EINVAL;
    }
    if (copy_from_user(kbuf, buf, len)) {
        return -EFAULT;
    }
    kbuf[len] = '\0';
    user_pid = simple_strtol(kbuf, NULL, 10);
    printk(KERN_INFO "User PID set to %d\n", user_pid);
    return len;
}

static struct file_operations fops = {
    .owner = THIS_MODULE,
    .unlocked_ioctl = device_ioctl,
    .write = device_write,
};

static int __init my_init(void) {
    struct netlink_kernel_cfg cfg = {
        .input = NULL,
        .groups = 0,
        .flags = 0,
    };

    printk(KERN_INFO "Module loaded\n");

    nl_sk = netlink_kernel_create(&init_net, NETLINK_USER, &cfg);
    if (!nl_sk) {
        printk(KERN_ALERT "Error creating socket.\n");
        return -ENOMEM;
    }

    alloc_chrdev_region(&dev_num, 0, 1, DEVICE_NAME);
    cdev_init(&c_dev, &fops);
    cdev_add(&c_dev, dev_num, 1);

    cl = class_create(THIS_MODULE, CLASS_NAME);
    device_create(cl, NULL, dev_num, NULL, DEVICE_NAME);

    return 0;
}

static void __exit my_exit(void) {
    if (nl_sk) {
        netlink_kernel_release(nl_sk);
        nl_sk = NULL;
    }

    device_destroy(cl, dev_num);
    class_unregister(cl);
    class_destroy(cl);
    unregister_chrdev_region(dev_num, 1);
    cdev_del(&c_dev);

    printk(KERN_INFO "Module unloaded\n");
}

module_init(my_init);
module_exit(my_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Your Name");
MODULE_DESCRIPTION("A simple Netlink example with ioctl and write");

makefile:

obj-m := test.o
KDIR := /lib/modules/$(shell uname -r)/build
PWD := $(shell pwd)
default:
                $(MAKE) -C $(KDIR) M=$(PWD) modules
clean:
                rm -rf *.o *.ko *.mod.c *.symvers *.order

用户态程序 listen.c

可以看到,先将设备pid写到驱动。
然后用socket(PF_NETLINK, SOCK_RAW, NETLINK_USER); 创建netlink的socket注意是PF_NETLINK不是AF_NETLINK。
然后创建struct sockaddr_nl src_addr初始化地址为AF_NETLINK,以及指定pid和组播地址(如果组播0可能有问题用1)
然后用bind将src_addr信息绑定。
然后分配nlmsg hdr头部
然后创建msg(注意msg还有自己的header和nlmsghdr不一样),并且初始化dst_addr,以及初始化msg的名字(dst addr地址)和长度。并且将创建的nlh放入iov的base和len中。
然后通过ioctl下发消息过去触发kernel发送消息,本来kernel也可以自己主动发送,本实验需要额外触发。
然后输入recvmsg进行收录数据。收到后从mlmsg中获取出来。

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

#define NETLINK_USER 31
#define MAX_PAYLOAD 1024 /* maximum payload size*/
#define IOCTL_CMD 0x12345678 // Must match the command in the kernel module

int main(int argc, char* argv[]) {
    struct sockaddr_nl src_addr, dest_addr;
    struct nlmsghdr *nlh = NULL;
    struct iovec iov;
    int sock_fd;
    struct msghdr msg;
    int fd;
    char pid_str[10];

    // Open the device file
    fd = open("/dev/netlink_device", O_RDWR);
    if (fd < 0) {
        perror("Cannot open device file");
        exit(EXIT_FAILURE);
    }

    // Write the current PID to the device file
    snprintf(pid_str, sizeof(pid_str), "%d", getpid());
    if (write(fd, pid_str, strlen(pid_str)) != strlen(pid_str)) {
        perror("Write to device file failed");
        close(fd);
        exit(EXIT_FAILURE);
    }

    // Close the device file after writing the PID
    close(fd);

    // Create Netlink socket
    sock_fd = socket(PF_NETLINK, SOCK_RAW, NETLINK_USER);
    if (sock_fd < 0) {
        perror("Socket creation failed");
        return -1;
    }

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

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

    printf("Waiting for message from kernel...\n");

    // Allocate memory for nlmsghdr and set up iovec and msghdr
    nlh = (struct nlmsghdr *)malloc(NLMSG_SPACE(MAX_PAYLOAD));
    if (!nlh) {
        perror("Failed to allocate memory for nlmsghdr");
        close(sock_fd);
        exit(EXIT_FAILURE);
    }

    iov.iov_base = (void *)nlh;
    iov.iov_len = NLMSG_SPACE(MAX_PAYLOAD);

    memset(&dest_addr, 0, sizeof(dest_addr));
    dest_addr.nl_family = AF_NETLINK;

    memset(&msg, 0, sizeof(msg));
    msg.msg_name = (void *)&dest_addr;
    msg.msg_namelen = sizeof(dest_addr);
    msg.msg_iov = &iov;
    msg.msg_iovlen = 1;

    // Trigger the ioctl call to send a message from the kernel
    fd = open("/dev/netlink_device", O_RDWR);
    if (fd < 0) {
        perror("Cannot open device file for ioctl");
        free(nlh);
        close(sock_fd);
        exit(EXIT_FAILURE);
    }

    if (ioctl(fd, IOCTL_CMD, 0) < 0) {
        perror("IOCTL failed");
        close(fd);
        free(nlh);
        close(sock_fd);
        exit(EXIT_FAILURE);
    }

    close(fd);

    ssize_t len = recvmsg(sock_fd, &msg, 0);
    if (len < 0) {
        perror("recvmsg failed");
        free(nlh);
        close(sock_fd);
        exit(EXIT_FAILURE);
    }

    // Ensure the received message is valid
    if (nlh->nlmsg_len < NLMSG_HDRLEN || len != nlh->nlmsg_len) {
    //if (nlh->nlmsg_len < NLMSG_HDRLEN){
        fprintf(stderr, "Invalid message length:%d. NLMSG_HDRLEN %d\n", nlh->nlmsg_len, NLMSG_HDRLEN);
        free(nlh);
        close(sock_fd);
        exit(EXIT_FAILURE);
    }

    printf("Received message payload: %s\n", (char*)NLMSG_DATA(nlh));

    free(nlh);
    close(sock_fd);

    return 0;
}

编译运行

make
insmod test.ko
gcc listen.c
./a.out

实操:
在这里插入图片描述

在这里插入图片描述
可以看到收到了来自于内核态的信息
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值