Fundamentals 45 min read

Unlocking Linux Kernel‑User Communication: How Netlink Bridges the Gap

This article explains how Linux’s Netlink mechanism provides efficient, full‑duplex communication between kernel space and user space, overcoming the limitations of ioctl, /proc, and system calls, and details its architecture, data structures, API usage, and real‑world applications.

Deepin Linux
Deepin Linux
Deepin Linux
Unlocking Linux Kernel‑User Communication: How Netlink Bridges the Gap

In Linux, isolating kernel space from user space ensures system stability, but communication is required for tasks such as configuring kernel parameters, retrieving system status, and receiving asynchronous events. Traditional methods like ioctl, the /proc filesystem, and system calls have notable limitations.

What is the Netlink mechanism

Netlink is a Linux‑specific inter‑process communication (IPC) mechanism that provides full‑duplex communication between kernel and user space. It uses the standard socket API, allowing developers familiar with socket programming to interact with the kernel via functions such as socket(), bind(), sendmsg(), recvmsg(), and close().

Compared with traditional IPC methods (pipes, named pipes, message queues, shared memory), Netlink supports true bidirectional communication, asynchronous messaging, modularity, and multicast, making it suitable for high‑concurrency scenarios.

Netlink mechanism operation

2.1 Communication model

Netlink adopts an asynchronous communication model. When a process (kernel or user) sends a Netlink message, the message is placed into the receiver’s socket receive queue and the sender can continue without waiting for a response, similar to dropping a package at a courier’s depot and moving on.

For example, a user‑space network configuration tool sends a request to the kernel via Netlink; the tool is not blocked and can continue handling UI tasks while the kernel processes the request and later sends a response.

2.2 Protocol types

Linux defines many Netlink protocol families, each associated with a specific kernel service:

NETLINK_ROUTE : network routing and device configuration (e.g., ip command).

NETLINK_KOBJECT_UEVENT : device hot‑plug notifications to user‑space (e.g., udev).

NETLINK_NFLOG : network packet logging for firewall and security tools.

Custom protocols can be defined by developers for bespoke kernel‑user communication.

2.3 Data structures

The core Netlink data structures are:

sockaddr_nl : address structure containing nl_family (always AF_NETLINK), nl_pid (process ID, 0 for kernel), and nl_groups (multicast group mask).

nlmsghdr : message header with fields nlmsg_len, nlmsg_type, nlmsg_flags, nlmsg_seq, and nlmsg_pid.

nlattr : attribute structure using TLV (type‑length‑value) encoding, allowing extensible payloads.

Netlink API functions

3.1 Kernel‑space API

Key kernel functions:

struct sock *netlink_kernel_create(struct net *net, int unit, struct netlink_kernel_cfg *cfg);

Creates a Netlink socket; unit specifies the protocol (e.g., NETLINK_ROUTE), and the cfg structure provides an input callback for received messages. void netlink_kernel_release(struct sock *sk); Releases a Netlink socket.

int netlink_unicast(struct sock *ssk, struct sk_buff *skb, __u32 pid, int nonblock);

Sends a message to a specific user‑space PID.

int netlink_broadcast(struct sock *ssk, struct sk_buff *skb, __u32 portid, __u32 group, gfp_t allocation);

Broadcasts a message to a multicast group.

3.2 User‑space API

Typical user‑space workflow:

int sock_fd = socket(AF_NETLINK, SOCK_RAW, NETLINK_GENERIC);

Create a Netlink socket.

struct sockaddr_nl src_addr;
memset(&src_addr, 0, sizeof(src_addr));
src_addr.nl_family = AF_NETLINK;
src_addr.nl_pid = getpid();
src_addr.nl_groups = 0;
bind(sock_fd, (struct sockaddr *)&src_addr, sizeof(src_addr));

Bind the socket to the process ID.

struct nlmsghdr *nlh = (struct nlmsghdr *)malloc(NLMSG_SPACE(MAX_PAYLOAD));
nlh->nlmsg_len = NLMSG_SPACE(MSG_LEN);
nlh->nlmsg_type = 0; // custom type
strcpy(NLMSG_DATA(nlh), "Hello, Kernel!");

struct iovec iov = { nlh, nlh->nlmsg_len };
struct msghdr msg = { .msg_name = &dest_addr, .msg_namelen = sizeof(dest_addr), .msg_iov = &iov, .msg_iovlen = 1 };
sendmsg(sock_fd, &msg, 0);

Construct and send a Netlink message.

ssize_t len = recvmsg(sock_fd, &msg, 0);
if (len > 0) {
    char *data = NLMSG_DATA(nlh);
    printf("Received: %s
", data);
}

Receive a response from the kernel.

Practical applications

Network configuration : tools like iproute2 use Netlink to manage interfaces, IP addresses, and routing tables.

Device driver interaction : drivers notify user‑space of hot‑plug events via Netlink (e.g., udev).

System monitoring : utilities such as nlbwmon retrieve bandwidth and connection statistics through Netlink.

Firewall management : iptables communicates with the kernel’s netfilter subsystem via Netlink.

Example project: building a Netlink communication system

The project consists of a kernel module that creates a Netlink socket, registers a receive callback, and echoes messages back to user space, and a user‑space program that sends a message and prints the kernel’s reply.

Kernel module (simplified):

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

#define NETLINK_TEST 31
static struct sock *nl_sk;
static void nl_recv_msg(struct sk_buff *skb) {
    struct nlmsghdr *nlh = (struct nlmsghdr *)skb->data;
    int pid = nlh->nlmsg_pid;
    printk(KERN_INFO "Received: %s from pid %d", (char *)nlmsg_data(nlh), pid);
    nlh->nlmsg_type = NLMSG_DONE;
    strcpy(NLMSG_DATA(nlh), "Hello from Kernel");
    netlink_unicast(nl_sk, skb, pid, MSG_DONTWAIT);
}
static int __init hello_init(void) {
    struct netlink_kernel_cfg cfg = { .input = nl_recv_msg };
    nl_sk = netlink_kernel_create(&init_net, NETLINK_TEST, &cfg);
    if (!nl_sk) return -ENOMEM;
    printk(KERN_INFO "Netlink module loaded");
    return 0;
}
static void __exit hello_exit(void) {
    netlink_kernel_release(nl_sk);
    printk(KERN_INFO "Netlink module unloaded");
}
module_init(hello_init);
module_exit(hello_exit);
MODULE_LICENSE("GPL");

User‑space program (simplified):

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

#define NETLINK_TEST 31
#define MAX_PLOAD 1024
int main() {
    int sock = socket(PF_NETLINK, SOCK_RAW, NETLINK_TEST);
    struct sockaddr_nl src, dst;
    memset(&src, 0, sizeof(src));
    src.nl_family = AF_NETLINK;
    src.nl_pid = getpid();
    bind(sock, (struct sockaddr *)&src, sizeof(src));
    memset(&dst, 0, sizeof(dst));
    dst.nl_family = AF_NETLINK;
    dst.nl_pid = 0; // kernel
    struct nlmsghdr *nlh = (struct nlmsghdr *)malloc(NLMSG_SPACE(MAX_PLOAD));
    nlh->nlmsg_len = NLMSG_LENGTH(strlen("Hello from User") + 1);
    nlh->nlmsg_pid = getpid();
    strcpy(NLMSG_DATA(nlh), "Hello from User");
    sendto(sock, nlh, nlh->nlmsg_len, 0, (struct sockaddr *)&dst, sizeof(dst));
    recvfrom(sock, nlh, NLMSG_SPACE(MAX_PLOAD), 0, NULL, NULL);
    printf("Received from kernel: %s
", (char *)NLMSG_DATA(nlh));
    free(nlh);
    close(sock);
    return 0;
}

After compiling the kernel module and user program, loading the module with insmod and running the user binary yields the expected exchange: the user prints “Received from kernel: Hello from Kernel” and the kernel log shows the received user message.

Netlink communication diagram
Netlink communication diagram
KernelLinuxSystem ProgrammingIPCSocketNetlink
Deepin Linux
Written by

Deepin Linux

Research areas: Windows & Linux platforms, C/C++ backend development, embedded systems and Linux kernel, etc.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.