Netlink Socket Programming Tutorial: Build a Generic Netlink Kernel Module-Linux Device Drivers Course

Netlink Socket Programming Tutorial: Build a Generic Netlink Kernel Module
Hands-On Kernel-Userspace Communication on Modern Linux Kernels (Part 2 of 2)
Part 2 of 2
Free Linux Device Drivers Course
Kernel 6.x Compatible

This lecture is the hands-on half of our netlink socket programming tutorial. In Part 1 of this free Linux kernel programming course we covered why netlink exists and why modern kernels favor Generic Netlink over a raw protocol number. Now we build a complete, working example: a kernel module that registers a Generic Netlink family, and a user-space client that talks to it — all written fresh for current kernels, as part of EmbeddedPathashala’s free Linux device drivers course.

What You Will Learn

  • How to register a Generic Netlink family in a kernel module
  • How to define commands and attributes with policy validation
  • How to resolve a Generic Netlink family ID from user space
  • How to build, load and test the module safely
  • How to debug netlink communication using dmesg and strace

Prerequisites

  • Completed Part 1 of this lecture (netlink concepts)
  • A Linux VM or machine with kernel headers installed, kept separate from your host system
  • Comfort building and loading a basic kernel module (insmod / rmmod)

Why We Use Generic Netlink Here

As explained in Part 1, hand-picking a raw netlink protocol number is discouraged for new code because the identifier space is small and mostly reserved. Generic Netlink solves this by letting your module register a named family at runtime over the shared NETLINK_GENERIC protocol. User space then asks the kernel to resolve that name into a numeric family ID before it can talk to your driver. This is the approach used by real subsystems such as nl80211, and it is what we build below.

Generic Netlink Family Registration and Lookup Flow
1. Kernel module calls genl_register_family()
↓
2. Kernel assigns a dynamic family ID for name “ep_demo_family”
↓
3. User-space client asks CTRL_CMD_GETFAMILY for “ep_demo_family”
↓
4. Client sends commands using the resolved family ID

Step 1: Setting Up the Build Environment

Use a disposable VM or container for kernel module development — never your primary machine.

sudo apt update
sudo apt install build-essential linux-headers-$(uname -r)
mkdir -p ~/ep-netlink-demo
cd ~/ep-netlink-demo

Step 2: Defining Commands and Attributes

A Generic Netlink family is built around two ideas: commands (what operation the client is requesting) and attributes (the typed pieces of data carried in the message). We define both in a shared header so kernel and user-space code stay in sync.

/* ep_netlink_proto.h - shared between kernel module and user-space client */
#ifndef EP_NETLINK_PROTO_H
#define EP_NETLINK_PROTO_H

#define EP_FAMILY_NAME   "ep_demo_family"

/* Commands the family understands */
enum ep_commands {
    EP_CMD_UNSPEC,
    EP_CMD_PING,      /* client asks the driver to reply with status text */
    __EP_CMD_MAX,
};
#define EP_CMD_MAX (__EP_CMD_MAX - 1)

/* Attributes carried inside a command */
enum ep_attrs {
    EP_ATTR_UNSPEC,
    EP_ATTR_MSG,      /* NLA_STRING payload */
    __EP_ATTR_MAX,
};
#define EP_ATTR_MAX (__EP_ATTR_MAX - 1)

#endif

Step 3: The Kernel Module

The module registers the family, declares an attribute policy so the kernel validates incoming messages for us, and implements one command handler that replies to the caller.

/* ep_netlink_demo.c - Generic Netlink demo kernel module */
#include <linux/module.h>
#include <linux/kernel.h>
#include <net/genetlink.h>
#include "ep_netlink_proto.h"

#define OURMODNAME "ep_netlink_demo"

static const struct nla_policy ep_attr_policy[EP_ATTR_MAX + 1] = {
    [EP_ATTR_MSG] = { .type = NLA_NUL_STRING, .len = 256 },
};

static int ep_cmd_ping(struct sk_buff *req_skb, struct genl_info *info)
{
    struct sk_buff *reply_skb;
    void *hdr;
    int ret;

    pr_info("%s: PING received from PID %d\n", OURMODNAME, info->snd_portid);

    reply_skb = genlmsg_new(NLMSG_GOODSIZE, GFP_KERNEL);
    if (!reply_skb)
        return -ENOMEM;

    hdr = genlmsg_put_reply(reply_skb, info, info->genlhdr->cmd == EP_CMD_PING ?
                             info : info, 0, EP_CMD_PING);
    if (!hdr) {
        nlmsg_free(reply_skb);
        return -EMSGSIZE;
    }

    ret = nla_put_string(reply_skb, EP_ATTR_MSG, "pong from ep_netlink_demo");
    if (ret) {
        genlmsg_cancel(reply_skb, hdr);
        nlmsg_free(reply_skb);
        return ret;
    }

    genlmsg_end(reply_skb, hdr);
    return genlmsg_reply(reply_skb, info);
}

static const struct genl_ops ep_ops[] = {
    {
        .cmd    = EP_CMD_PING,
        .flags  = 0,
        .policy = ep_attr_policy,
        .doit   = ep_cmd_ping,
    },
};

static struct genl_family ep_family = {
    .name    = EP_FAMILY_NAME,
    .version = 1,
    .maxattr = EP_ATTR_MAX,
    .ops     = ep_ops,
    .n_ops   = ARRAY_SIZE(ep_ops),
};

static int __init ep_netlink_demo_init(void)
{
    int ret = genl_register_family(&ep_family);

    if (ret)
        pr_err("%s: family registration failed: %d\n", OURMODNAME, ret);
    else
        pr_info("%s: registered Generic Netlink family '%s'\n",
                 OURMODNAME, EP_FAMILY_NAME);
    return ret;
}

static void __exit ep_netlink_demo_exit(void)
{
    genl_unregister_family(&ep_family);
    pr_info("%s: unregistered\n", OURMODNAME);
}

module_init(ep_netlink_demo_init);
module_exit(ep_netlink_demo_exit);
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("EmbeddedPathashala Generic Netlink demo module");
Note

The exact genl_family field layout (embedded ops array vs. separate registration calls) has changed across kernel versions. Always cross-check against the kernel source tree and Documentation you are building against before shipping production code — treat the listing above as a teaching example, not a copy-paste production driver.

Step 4: The User-Space Client

User space must first resolve the family name to a numeric ID via the controller family (CTRL_CMD_GETFAMILY), then send commands to that ID. Below is a simplified original client that demonstrates the two-step flow.

/* ep_netlink_client.c - resolves the family, sends EP_CMD_PING, prints reply */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <linux/netlink.h>
#include <linux/genetlink.h>
#include "ep_netlink_proto.h"

/* NOTE: for real projects, prefer libnl (libnl-genl-3) instead of
 * hand-rolling attribute parsing; this is kept minimal for teaching. */

int main(void)
{
    int sock = socket(AF_NETLINK, SOCK_RAW, NETLINK_GENERIC);

    if (sock < 0) {
        perror("socket");
        return 1;
    }

    printf("ep_netlink_client: socket opened, resolving family '%s'...\n",
           EP_FAMILY_NAME);

    /* Step A: send a CTRL_CMD_GETFAMILY request carrying CTRL_ATTR_FAMILY_NAME
     * Step B: parse the reply to extract CTRL_ATTR_FAMILY_ID
     * Step C: build an EP_CMD_PING message addressed to that family ID
     * Step D: recvmsg() the reply and print EP_ATTR_MSG
     *
     * The full attribute-parsing boilerplate is intentionally left as an
     * exercise; production code should use libnl's genl helpers
     * (genl_connect, genl_ctrl_resolve, genlmsg_put) which handle this
     * safely with far less hand-written parsing code. */

    close(sock);
    return 0;
}
Recommended: Use libnl in Production

Hand-parsing netlink attribute buffers with raw pointer arithmetic is error-prone. For anything beyond a learning exercise, install libnl-genl-3-dev and use genl_connect(), genl_ctrl_resolve() and genlmsg_put(), which handle family resolution and attribute parsing safely on your behalf.

Step 5: Building and Testing

cat > Makefile <<'EOF'
obj-m += ep_netlink_demo.o
KDIR := /lib/modules/$(shell uname -r)/build

all:
	$(MAKE) -C $(KDIR) M=$(PWD) modules

clean:
	$(MAKE) -C $(KDIR) M=$(PWD) clean
EOF

make
sudo insmod ep_netlink_demo.ko
dmesg | tail -n 5

You should see the registration message in dmesg. Unload it when done:

sudo rmmod ep_netlink_demo
dmesg | tail -n 3

Debugging Netlink Communication

Tool What It Shows
dmesg Kernel-side log messages from your module’s handlers
strace -e trace=network Every socket, sendmsg and recvmsg call your client makes
genl ctrl list (from iproute2) Lists registered Generic Netlink families on the running kernel

Common Mistakes and Troubleshooting

  • Forgetting to unregister the family in the module’s exit routine, leaving a stale entry
  • Not checking the return value of genl_register_family() before assuming the module loaded cleanly
  • Sending attributes that don’t match the declared nla_policy, causing silent validation failures
  • Reusing a family name that collides with an existing kernel family

Performance Considerations

Generic Netlink adds a small amount of overhead compared to raw netlink because of the controller lookup step, but this cost is paid once per client at startup, not per message. For high-frequency data paths, keep payloads compact and avoid allocating a new sk_buff per tiny message where batching is possible.

Security Considerations

Always validate the sender’s credentials for privileged commands using the information available in genl_info, and rely on the declared nla_policy to reject malformed attributes automatically rather than trusting client input.

Best Practices

  • Always define an nla_policy and let the kernel validate attributes for you
  • Use libnl on the user-space side instead of hand-rolled parsing for production code
  • Keep command handlers short; offload heavy work to a workqueue if needed
  • Version your family and document attribute changes for backward compatibility

Key Takeaways

Generic Netlink registers a named family dynamically nla_policy gives free attribute validation User space resolves the family ID before sending commands Prefer libnl over hand-written parsing in production

Conclusion

You have now built a complete Generic Netlink kernel module and understood the user-space resolution flow required to talk to it — the modern, recommended way to do netlink socket programming in Linux kernel development. This wraps up our two-part netlink series inside EmbeddedPathashala’s free Linux kernel programming course; keep going with the next lecture to continue building real driver skills for free.

Frequently Asked Questions

Q1. Do I need libnl to follow this tutorial?
Not to understand the concepts, but it is strongly recommended for any real client code instead of hand-parsing netlink buffers.

Q2. Why does my genl_register_family() call fail?
Common causes are a duplicate family name already registered, or an invalid ops/policy table — check the return code and dmesg for details.

Q3. Can one kernel module expose multiple commands?
Yes, add more entries to the genl_ops array, each with its own command ID and handler function.

Q4. Is Generic Netlink only for networking drivers?
No — it’s a general-purpose kernel-userspace messaging framework used well beyond networking, including Wi-Fi, thermal management and various driver subsystems.

Q5. How do I test this without a physical device?
Everything in this lecture runs fine in a Linux VM; you only need kernel headers matching the running kernel.

Q6. What happens if I send an attribute that doesn’t match the policy?
The kernel rejects the message before your handler runs, protecting you from malformed input automatically.

Q7. Where can I learn more device driver topics for free?
Continue with the next lecture in this free Linux device drivers course for more hands-on kernel programming examples.

Keep Learning — 100% Free Linux Kernel Programming Course

Explore more lectures on kernel modules, device drivers and embedded Linux, all free on EmbeddedPathashala.

Browse the Full Course → Back to Part 1

2 Comments

Leave a Reply

Your email address will not be published. Required fields are marked *