C language under Linux uses netlink sockets to communicate with the kernel module

Introduction to netlink

Netlink socket is a special inter-process communication (IPC) used to communicate between user processes and kernel processes. It is also the most commonly used interface for network applications to communicate with the kernel. In the Linux standard kernel, the system integrates many netlink instances by default, such as log reporting, routing system, etc. Netlink messages are bidirectional. The application layer can send messages to the kernel, and the kernel can also send messages to the application layer process, which is very suitable for involving To the kernel information collection module.

Differences from ioctl

Netlink is implemented using sock, while ioctl is implemented using driver registration. Netlink is usually used to transmit a large number of messages, while ioctl is usually used to issue system commands and does not support the kernel actively sending messages to the application layer.
When to use netlink?
During the development process, we will face the selection of message communication mechanism, which is the same as the selection of application layer message communication. Each communication method has its own advantages and disadvantages. Using the netlink mechanism generally requires the application layer to start a separate process to monitor messages sent by the kernel. The development workload is larger than that of ioctl, and ioctl is mainly used to drive message delivery. For example, the iwpriv command implementation of wireless drivers uses the ioctl mechanism. .

Kernel module hello_kernel.c

#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)
{
 
    printk("Entering: %s\n", __FUNCTION__);
    //nl_sk = netlink_kernel_create(&init_net, NETLINK_USER, 0, hello_nl_recv_msg, NULL, THIS_MODULE);
    struct netlink_kernel_cfg cfg = {
        .input = hello_nl_recv_msg,
    };
 
    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");

Application layer code hello.c

#include <linux/netlink.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <unistd.h>
 
#define NETLINK_USER 31
 
#define MAX_PAYLOAD 1024 /* maximum payload size*/
struct sockaddr_nl src_addr, dest_addr;
struct nlmsghdr *nlh = NULL;
struct iovec iov;
int sock_fd;
struct msghdr msg;
 
int main()
{
    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));
    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", NLMSG_DATA(nlh));
    close(sock_fd);
 
    return 0;
}

 Makefile

bin=hello
obj-m = hello_kernel.o
KVERSION = $(shell uname -r)
all:$(bin)
	make -C /lib/modules/$(KVERSION)/build M=$(PWD) modules
bin:
	gcc hello.c -o hello
clean:
	make -C /lib/modules/$(KVERSION)/build M=$(PWD) clean
	rm -rf hello.o hello

Makefile problem record

chat:KVERSION = $(shell uname -r) What is the difference if you remove the shell?

In a Makefile, `$(shell ...)` is a built-in function used to execute a command and get its output. In the statement you mentioned, `$(shell uname -r)` is used to execute the `uname -r` command to get the current Linux kernel version and assign the result to the `KVERSION` variable. If you remove `shell`, the entire statement will become:
KVERSION = uname -r
This will no longer be a command execution statement, but a simple variable assignment statement. This means that the value of `KVERSION` will be set directly to the string `"uname -r"`, instead of executing the `uname -r` command and getting its output.
So the difference is:
- `KVERSION = $(shell uname -r)`: This executes the `uname -r` command, gets the kernel version, and assigns the result as a string to the `KVERSION` variable.
- `KVERSION = uname -r`: This just assigns the string `"uname -r"` to the `KVERSION` variable and does not execute the command or get the kernel version.
If you want to assign the kernel version to the `KVERSION` variable, you should use `$(shell ...)` to execute the command. Otherwise, you will just assign the command string itself to the variable and not get the output of the command.

What does make -C mean?

`make -C` is an option of the `make` command, used to specify the directory in which the `make` command should be executed.
The syntax is:
make -C <directory>
where `<directory>` is the path to the directory where you want the `make` command to execute.
This option is typically used to execute the `make` command in a subdirectory of a project in order to build subprojects or projects that depend on code in other directories. This is useful for managing the build process of multiple modules or components in large projects, as you can maintain a separate `Makefile` file in each subdirectory and use the `-C` option to specify which subdirectory to build.
For example, if you have a subdirectory named `subproject` and a build file named `Makefile` in that directory, you can use the following command to execute `make` in the `subproject` directory: make
-C subproject
This will cause the `make` command to find and execute the `Makefile` file in the `subproject` directory, performing the build tasks associated with that subproject.

operation result:

Application layer

For the kernel, load .ko first, and after running hello, use dmesg to view the received messages.

Code source:

How does C language use netlink sockets to communicate with the kernel module under Linux? _Usage of nlmsghdr in c language_wellnw's blog-CSDN blog

Guess you like

Origin blog.csdn.net/buhuidage/article/details/132749305