Free Linux Kernel Programming
Intermediate
6.x (Modern)
2 of 3
← Previous Lecture | Next Lecture →
In the previous lecture of this free Linux device drivers course, we covered what netlink sockets are and why the kernel relies on them. Now it’s time to get hands-on: in this lecture we write a user space C program that opens a netlink socket, sends a message toward a kernel module, and waits for a reply — the exact same pattern real tools like udevadm and network configuration utilities use under the hood.
What You Will Learn
Prerequisites
You should have completed the introductory lecture on netlink sockets, and be comfortable reading basic POSIX socket code (socket(), bind()). No prior netlink experience is assumed.
Step 1: Create the Netlink Socket
Every netlink conversation starts the same way any socket-based program does — with a call to socket(2). The three arguments matter a lot here: the domain must be PF_NETLINK, the type is almost always SOCK_RAW (netlink doesn’t use the stream/datagram distinction the way TCP/UDP do), and the third argument selects which netlink family you’re talking to. For a custom kernel module, you pick an unused protocol number in that third slot; for standard kernel subsystems, you’d use their reserved constant (for example NETLINK_ROUTE).
#define EP_NETLINK_PROTO 25 /* example custom protocol number */
int sock_fd = socket(PF_NETLINK, SOCK_RAW, EP_NETLINK_PROTO);
if (sock_fd < 0) {
perror("socket");
exit(EXIT_FAILURE);
}
Step 2: Bind a Local Address
Next, fill in a struct sockaddr_nl describing your side of the conversation and bind the socket to it. The nl_pid field is your local port ID — using your process ID is the conventional (though not mandatory) choice.
struct sockaddr_nl local_addr;
memset(&local_addr, 0, sizeof(local_addr));
local_addr.nl_family = AF_NETLINK;
local_addr.nl_pid = getpid();
local_addr.nl_groups = 0; /* no multicast groups */
if (bind(sock_fd, (struct sockaddr *)&local_addr, sizeof(local_addr)) < 0) {
perror("bind");
close(sock_fd);
exit(EXIT_FAILURE);
}
Step 3: Prepare the Kernel Destination Address
The destination side is simpler — setting nl_pid to zero is the well-known convention meaning “the kernel itself” rather than any particular user space process.
struct sockaddr_nl kernel_addr;
memset(&kernel_addr, 0, sizeof(kernel_addr));
kernel_addr.nl_family = AF_NETLINK;
kernel_addr.nl_pid = 0; /* 0 = destined for the kernel */
kernel_addr.nl_groups = 0;
Step 4: Build the Netlink Message
A netlink message always starts with a fixed struct nlmsghdr header, followed by your actual payload. The kernel gives you helper macros — NLMSG_SPACE() to compute the total allocation size and NLMSG_DATA() to get a pointer to where your payload begins — so you rarely need to compute offsets by hand.
#define EP_PAYLOAD_MAX 256
struct nlmsghdr *nlh;
const char *payload = "hello from user space";
nlh = (struct nlmsghdr *)malloc(NLMSG_SPACE(EP_PAYLOAD_MAX));
memset(nlh, 0, NLMSG_SPACE(EP_PAYLOAD_MAX));
nlh->nlmsg_len = NLMSG_SPACE(EP_PAYLOAD_MAX);
nlh->nlmsg_pid = getpid();
nlh->nlmsg_type = 0;
memcpy(NLMSG_DATA(nlh), payload, strlen(payload) + 1);
Step 5: Send the Message
Sending uses sendmsg(2) rather than the simpler send(2), because netlink needs the extra addressing flexibility that struct msghdr and struct iovec provide.
struct iovec iov;
struct msghdr msg;
iov.iov_base = nlh;
iov.iov_len = nlh->nlmsg_len;
memset(&msg, 0, sizeof(msg));
msg.msg_name = &kernel_addr;
msg.msg_namelen = sizeof(kernel_addr);
msg.msg_iov = &iov;
msg.msg_iovlen = 1;
if (sendmsg(sock_fd, &msg, 0) < 0) {
perror("sendmsg");
}
Step 6: Receive the Kernel’s Reply
Reading a reply back is symmetric to sending — reuse the same iovec and msghdr structures with recvmsg(2). This call blocks by default until the kernel module sends something back.
if (recvmsg(sock_fd, &msg, 0) < 0) {
perror("recvmsg");
} else {
printf("Kernel replied: %s\n", (char *)NLMSG_DATA(nlh));
}
free(nlh);
close(sock_fd);
Common Mistakes and Troubleshooting
| Symptom | Likely Cause |
|---|---|
bind() fails with EADDRINUSE |
Another socket already bound the same port ID |
sendmsg() returns EPERM |
Netlink family requires elevated privileges |
Program hangs on recvmsg() |
Kernel module never replied — check it’s loaded correctly |
| Garbage data received | Buffer size mismatch between NLMSG_SPACE on both sides |
Best Practices
- Always check the return value of every socket call — netlink failures are silent otherwise.
- Size your payload buffer generously; undersized buffers cause silent truncation.
- Free heap-allocated
nlmsghdrstructures once you’re done to avoid leaks in long-running daemons. - For anything beyond a simple demo, consider using
libnlor generic netlink instead of hand-rolling raw netlink parsing.
Key Takeaways
Writing a netlink user space client follows a predictable six-step pattern: create the socket, bind a local port, prepare the kernel destination address, build the message with the NLMSG_* macros, send with sendmsg(), and receive with recvmsg(). The next lecture completes the picture by building the matching kernel module.
Frequently Asked Questions
Q1. Why use sendmsg() instead of write() or send()?sendmsg() lets you specify the destination address structure alongside the payload, which plain write()/send() cannot do for netlink sockets.
Q2. Can I use select() or poll() with a netlink socket?
Yes, a netlink socket’s file descriptor works with the standard select(), poll(), and epoll() APIs, which is useful for event-driven daemons.
Q3. What happens if the kernel module isn’t loaded when I run this?
The initial socket() call will typically fail immediately if no kernel component has registered that protocol number.
Q4. Is this program safe to run without root?
For a custom protocol number registered by your own kernel module, root is usually not required unless your module’s netlink configuration restricts access.
Q5. How is this different from using libnl?libnl wraps this exact raw socket workflow in higher-level, safer APIs; learning the raw version first makes libnl’s abstractions much easier to understand.
Next: build the kernel module counterpart that receives this message and replies back.
Next Lecture: Kernel Module Programming → Back to Course Index
2 Comments