Netlink Communication between Kernel and User space
Tags: netlink, kernel, ipc
Published: 2022-01-09 15:45:08

Last year， I have used **Netlink** in my development work for collecting some events from kernel space. I present in this post some basic practice I have done when I learnt to program with **Netlink**.


## Introduction
**Netlink** is a Linux kernel interface used for** inter-process communication (IPC)** between both the kernel and userspace processes, and between different userspace processes, in a way similar to the **Unix domain sockets**. Similarly to the Unix domain sockets, and unlike **INET** sockets, Netlink communication cannot traverse host boundaries. **Netlink** provides a standard socket-based interface for userspace processes, and a kernel-side API for internal use by kernel modules. Originally, **Netlink** used the **AF_NETLINK** socket family. **Netlink** is designed to be a more flexible successor to **ioctl**; RFC 3549 describes the protocol in detail. 


## My practice


              

#### Kernel module

Below is the content of my source file "netlink_kernel.c" which yields a kernel module. There is macro `MY_NETLINK 30` which defines my customized netlink protocol. One can also choose available protocols such like **NETLINK_ROUTE** or **NETLINK_INET_DIAG**. All available protocols can be seen in [this page]( https://elixir.bootlin.com/linux/v5.15.13/source/include/uapi/linux/netlink.h). Function **netlink_kernel_create()** creates a netlink socket for user space application to communicate with. More information about netlink programming can found [here](https://man7.org/linux/man-pages/man7/netlink.7.html).


netlink_kernel.c
```c
#include <linux/module.h>
#include <net/sock.h>
#include <linux/netlink.h>
#include <linux/skbuff.h>

/*
  refer to https://elixir.bootlin.com/linux/v5.15.13/source/include/linux/netlink.h
*/

#define MY_NETLINK 30  // cannot be larger than 31, otherwise we shall get "insmod: ERROR: could not insert module netlink_kernel.ko: No child processes"


struct sock *nl_sk = NULL;

static void myNetLink_recv_msg(struct sk_buff *skb)
{
	struct nlmsghdr *nlhead;
	struct sk_buff *skb_out;
	int pid, res, msg_size;
	char *msg = "Hello msg from kernel";
	

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

	msg_size = strlen(msg);

	nlhead = (struct nlmsghdr*)skb->data;    //nlhead message comes from skb's data... (sk_buff: unsigned char *data)

	printk(KERN_INFO "MyNetlink has received: %s\n",(char*)nlmsg_data(nlhead));


	pid = nlhead->nlmsg_pid; // Sending process port ID, will send new message back to the 'user space sender'


	skb_out = nlmsg_new(msg_size, 0);    //nlmsg_new - Allocate a new netlink message: skb_out

	if(!skb_out)
	{
		printk(KERN_ERR "Failed to allocate new skb\n");
		return;
	}

	nlhead = nlmsg_put(skb_out, 0, 0, NLMSG_DONE, msg_size, 0);   // Add a new netlink message to an skb
	
	NETLINK_CB(skb_out).dst_group = 0;                  

    
	strncpy(nlmsg_data(nlhead), msg, msg_size); //char *strncpy(char *dest, const char *src, size_t count)

	res = nlmsg_unicast(nl_sk, skb_out, pid); 

	if(res < 0)
		printk(KERN_INFO "Error while sending back to user\n");
}

static int __init myNetLink_init(void)
{
	struct netlink_kernel_cfg cfg = {
		.input = myNetLink_recv_msg,
	};

       /*netlink_kernel_create() returns a pointer, should be checked with == NULL */
	nl_sk = netlink_kernel_create(&init_net, MY_NETLINK, &cfg);
	printk("Entering: %s, protocol family = %d \n",__FUNCTION__, MY_NETLINK);
	if(!nl_sk)
	{
		printk(KERN_ALERT "Error creating socket.\n");
		return -10;
	}

    printk("MyNetLink Init OK!\n");
	return 0;
}

static void __exit myNetLink_exit(void)
{
	printk(KERN_INFO "exiting myNetLink module\n");
	netlink_kernel_release(nl_sk);
}

module_init(myNetLink_init);
module_exit(myNetLink_exit);
MODULE_LICENSE("GPL");

```






#### User space application

Below are the content of my file "netlink_client.c". This program shall open a netlink socket with the same protocol and allow user to send/receive msg to/from kernel module in previous section.

"netlink_client.c"
```c
#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <string.h>
#include <asm/types.h>
#include <linux/netlink.h>
#include <linux/socket.h>
#include <errno.h>

#define NETLINK_USER 30 // same customized protocol as in my kernel module
#define MAX_PAYLOAD 1024 // maximum payload size

struct sockaddr_nl src_addr, dest_addr;
struct nlmsghdr *nlh = NULL;
struct nlmsghdr *nlh2 = NULL;
struct msghdr msg, resp;  // famous struct msghdr, it includes "struct iovec *   msg_iov;"
struct iovec iov, iov2;
int sock_fd;

int main(int args, char *argv[])
{
    //int socket(int domain, int type, int protocol);
    sock_fd = socket(PF_NETLINK, SOCK_RAW, NETLINK_USER); //NETLINK_KOBJECT_UEVENT	

    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 */

    //int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
    if(bind(sock_fd, (struct sockaddr*)&src_addr, sizeof(src_addr))){
        perror("bind() error\n");
        close(sock_fd);
        return -1;
    }

    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: contains "Hello" msg
    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();  //self pid
    nlh->nlmsg_flags = 0; 

    //nlh2: contains received msg
    nlh2 = (struct nlmsghdr *)malloc(NLMSG_SPACE(MAX_PAYLOAD));
    memset(nlh2, 0, NLMSG_SPACE(MAX_PAYLOAD));
    nlh2->nlmsg_len = NLMSG_SPACE(MAX_PAYLOAD);
    nlh2->nlmsg_pid = getpid();  //self pid
    nlh2->nlmsg_flags = 0; 

    strcpy(NLMSG_DATA(nlh), "Hello this is a msg from userspace");   //put "Hello" msg into nlh

    iov.iov_base = (void *)nlh;         //iov -> nlh
    iov.iov_len = nlh->nlmsg_len;
    msg.msg_name = (void *)&dest_addr;  //msg_name is Socket name: dest
    msg.msg_namelen = sizeof(dest_addr);
    msg.msg_iov = &iov;                 //msg -> iov
    msg.msg_iovlen = 1;

    iov2.iov_base = (void *)nlh2;         //iov -> nlh2
    iov2.iov_len = nlh2->nlmsg_len;
    resp.msg_name = (void *)&dest_addr;  //msg_name is Socket name: dest
    resp.msg_namelen = sizeof(dest_addr);
    resp.msg_iov = &iov2;                 //resp -> iov
    resp.msg_iovlen = 1;



    printf("Sending message to kernel\n");

    int ret = sendmsg(sock_fd, &msg, 0);   
    printf("send ret: %d\n", ret); 

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

    /* Read message from kernel */
    recvmsg(sock_fd, &resp, 0);  //msg is also receiver for read

    printf("Received message payload: %s\n", (char *) NLMSG_DATA(nlh2));  
    
    char usermsg[MAX_PAYLOAD];
    while (1) {
	printf("Input your msg for sending to kernel: ");
    	scanf("%s", usermsg);
    
    	strcpy(NLMSG_DATA(nlh), usermsg);   //put "Hello" msg into nlh


    	printf("Sending message \" %s \" to kernel\n", usermsg);

    	ret = sendmsg(sock_fd, &msg, 0);   
    	printf("send ret: %d\n", ret); 

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

        /* Read message from kernel */
	recvmsg(sock_fd, &resp, 0);  //msg is also receiver for read

	printf("Received message payload: %s\n", (char *)NLMSG_DATA(nlh2));   
    
}
    close(sock_fd);

    return 0;
}
```



Makefile

Create a Makefile with the following content which will enable us to easily compile the source files.

Makefile
```shell
obj-m += netlink_kernel.o

#generate the path
CURRENT_PATH:=$(shell pwd)

#the current kernel version number
LINUX_KERNEL:=$(shell uname -r)

#the absolute path
LINUX_KERNEL_PATH:=/usr/src/linux-headers-$(LINUX_KERNEL)



#complie object
#   extension of "make modules" cmd with -C option and "M=dir" configuration
#   this cmd will switch working directory to the given path followed by the -C option
#   and will search specified source files from the given path configured by "M=" 
#   and compile them to generate ko files

all:
	@echo $(LINUX_KERNEL_PATH)
	make -C $(LINUX_KERNEL_PATH) M=$(CURRENT_PATH) modules

client:
	gcc netlink_client.c -o netlink_client -g

#clean
clean:
	make -C $(LINUX_KERNEL_PATH) M=$(CURRENT_PATH) clean
	rm netlink_client
```


#### Test the communication
Make sure that files "netlink_kernel.c", "netlink_client.c" and "Makefile" are in the same directory. In a Linux terminal window, `cd` into this directory and start the compilation:
```shell
make 
make client
```
Normally kernel module file "netlink_kernel.ko" and user application file "netlink_client" will be generated.

Load the generated kernel module "netlink_kernel.ko" to linux kernel:
```shell
sudo insmod netlink_kernel.ko
```

Execute in a new terminal the following cmd to monitor the kernel messages:
```
dmesg -Hw
```

Now start the user application to start the communication:
```
./netlink_client
```

Below is the screenshot of my test:
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/swlujhqpxn2x9fz4gm0w.png)