Skip to main content

2.6 Generic Netlink Protocol

Remember the question we left you with at the end of the last section?

If you write a non-standard kernel module—say, to control some cool piece of hardware—and want to provide a user-space control interface for it, what would you do?

You face a dilemma: use IOCTL? It's too old, and it can't handle multicast or asynchronous notifications. Use Netlink? It's modern, but there are only 32 Netlink protocol family ID slots (MAX_LINKS). NETLINK_ROUTE, NETLINK_KOBJECT_UEVENT, and other heavyweights have already taken the best spots, leaving very few vacancies. If every module requests a dedicated Netlink protocol family, the system will quickly run out of IDs.

To solve this "not enough sockets" crisis, the kernel introduced Generic Netlink.

You can think of Generic Netlink as a multiplexer. It occupies only a single slot in the standard Netlink protocol family—NETLINK_GENERIC—but over this single channel, it can carry an unlimited number of custom "families." It's like running multiple VLANs over a single physical cable, or setting up a "general information desk" in a lobby that always knows how to find the right clerk for you.


Core Mechanism: The General Information Desk

From a developer's perspective, the most critical thing Generic Netlink does is replace "hardcoded IDs" with "runtime dynamically allocated IDs."

In standard Netlink, the protocol family ID is hardcoded in header files. But in Generic Netlink, you only need to define a name (such as "nlctrl" or "nl80211"), and the kernel will automatically assign a unique numeric ID when you register it.

To manage this dynamic allocation process, Generic Netlink introduces a special family called the Controller (nlctrl). It is the "general information desk" for all families, its ID is fixed (GENL_ID_CTRL, which is 0x10), and it is the cornerstone of the entire Generic Netlink mechanism.

If a user-space program doesn't know the numeric ID of a certain family (such as the wireless configuration family "nl80211"), it will first send a message to ask nlctrl: "Hey, what's the ID of the guy called 'nl80211'?" nlctrl looks up the table and replies: "It's 21." Then user-space takes ID 21 to send the actual configuration command.

At this point, the boundaries of our analogy become clear: The "general information desk" analogy only explains the lookup functionality, but it misses a crucial point—nlctrl is itself a registered Netlink family. It is on the same level as ordinary families in the kernel code; it just has a special function.

Initialization in the Kernel

The kernel entry point for Generic Netlink is at net/netlink/genetlink.c. It is initialized along with the network namespace:

static int __net_init genl_pernet_init(struct net *net) {
..
struct netlink_kernel_cfg cfg = {
.input = genl_rcv,
.cb_mutex = &genl_mutex,
.flags = NL_CFG_F_NONROOT_RECV,
};
net->genl_sock = netlink_kernel_create(net, NETLINK_GENERIC, &cfg);
...
}

Note here that when it calls netlink_kernel_create(), it passes NETLINK_GENERIC. This creates the main entry socket for Generic Netlink.

Every line is critical:

  1. .input = genl_rcv: All NETLINK_GENERIC messages coming from user-space enter this genl_rcv() callback function. It is the main entry point for all Generic Netlink messages.
  2. net->genl_sock: This socket pointer is stored in the network namespace object. This means Generic Netlink is network namespace aware. Can different containers see different Netlink families? No, families are usually global, but the socket's lifecycle follows the namespace.
  3. cb_mutex: This global lock genl_mutex protects the family registration table. This shows that the core operations of Generic Netlink are locked, not completely lock-free.

Immediately after, the code registers that most important "general information desk"—genl_ctrl:

static struct genl_family genl_ctrl = {
.id = GENL_ID_CTRL, // 固定 ID:16
.name = "nlctrl",
.version = 0x2,
.maxattr = CTRL_ATTR_MAX,
.netnsok = true,
};

static int __net_init genl_pernet_init(struct net *net) {
...
err = genl_register_family_with_ops(&genl_ctrl, &genl_ctrl_ops, 1);
...
}

genl_ctrl is the only family with a hardcoded ID (GENL_ID_CTRL, which is 16). Apart from this, all other families have their ID set to GENL_ID_GENERATE (actually 0) when registering, and then the kernel allocates a unique number between 16 and 1023 for it via find_first_zero_bit() in genl_register_family().

Returning to our "general information desk" analogy: now you can see that genl_ctrl doesn't just provide services—it is the first family registered after the entire Generic Netlink mechanism starts up. Without it, the subsequent lookup process cannot function.


How to Define Your Own Family

Suppose you are writing a wireless driver, or an NFC subsystem like the example in the book, and you want to use Generic Netlink for communication. You need to do two things:

  1. Define and register a genl_family (family).
  2. Define and register one or more genl_ops (operations).

You can use genl_register_family_with_ops() to register both the family and the operations array all at once. The book uses the wireless subsystem nl80211 as an example, which is the most classic real-world case.

Family Definition: nl80211

The family defined by the wireless subsystem looks like this:

static struct genl_family nl80211_fam = {
.id = GENL_ID_GENERATE, // 让内核自动分配 ID
.name = "nl80211", // 用户空间通过这个名字查找
.hdrsize = 0, // 没有私有头部
.version = 1, // 版本号(实际意义不大)
.maxattr = NL80211_ATTR_MAX, // 支持的属性最大值(用于校验)
.netnsok = true, // 支持网络命名空间
.pre_doit = nl80211_pre_doit, // 操作前的钩子(比如加锁)
.post_doit = nl80211_post_doit,// 操作后的钩子(比如解锁)
};

Field Breakdown:

  • .name: "nl80211". This is the contract between user-space and the kernel. User-space doesn't know if the ID is 20 or 21, but it knows the name is "nl80211".
  • .maxattr: NL80211_ATTR_MAX. This number defines how many attributes this family supports. Used in conjunction with nla_policy for parameter validation. If a user sends an attribute ID exceeding this value, the kernel will reject it outright.
  • .pre_doit / .post_doit: These two hooks are very practical. Before executing the specific command (doit), the kernel usually calls pre_doit to acquire a lock (like the RTNL lock) or take a reference to a network device; after execution, post_doit handles cleanup. This avoids writing repetitive locking/unlocking code in every command function.
  • .netnsok: Set to true to indicate that this family can be used inside containers.

Operation Definition: genl_ops

With the family defined, we still need specific commands. Generic Netlink uses the genl_ops structure to describe how a command is handled:

struct genl_ops {
u8 cmd; // 命令 ID
u8 internal_flags; // 内部标志位
unsigned int flags; // 操作标志(如权限)
const struct nla_policy *policy; // 属性校验策略
int (*doit)(struct sk_buff *skb, struct genl_info *info);
int (*dumpit)(struct sk_buff *skb, struct netlink_callback *cb);
int (*done)(struct netlink_callback *cb);
struct list_head ops_list;
};

This is a typical object-oriented design in C.

  • .cmd: The command number. For example, NL80211_CMD_GET_SCAN (do a scan and see what's out there).
  • .doit: Callback for handling "single" requests. For example, "set a specific SSID".
  • .dumpit: Callback for handling "list" dumps. For example, "list all scanned APs". This is a crucial design: if you just want to get information about one object, use doit; if you want to get a list of objects, use dumpit. The Netlink framework handles segmented sending for you (since the message might exceed the MTU); you just need to return objects one by one in dumpit.
  • .policy: Points to a nla_policy array, used to validate parameters sent in by the user. For example, "this must be a 32-bit integer", "that must be a string". If they don't match, the kernel intercepts them before they even reach your function.
  • .flags: Can be set to GENL_ADMIN_PERM, indicating that this command requires CAP_NET_ADMIN privileges (i.e., root).

In nl80211, the command table looks like this:

static struct genl_ops nl80211_ops[] = {
{
...
.cmd = NL80211_CMD_GET_SCAN,
.policy = nl80211_policy,
.dumpit = nl80211_dump_scan,
},
...
};

Here, only .dumpit is defined, without .doit. This indicates that NL80211_CMD_GET_SCAN is a command used to "list results".

⚠️ Note: When registering genl_ops, at least one of .doit and .dumpit must be specified, otherwise the kernel will directly return -EINVAL. Defining a command without giving it a handler function is definitely wrong.


User-Space Interaction Flow

Alright, the kernel-side socket is built, and the family is registered. How does user-space play with it?

If you type iw dev wlan0 scan on the command line, the iw tool actually runs a complete flow behind the scenes using the libnl-genl library.

This flow is very standard; any Generic Netlink program must follow these steps:

  1. Create Socket: socket(AF_NETLINK, SOCK_RAW, NETLINK_GENERIC).
  2. Resolve Family Name: Send a CTRL_CMD_GETFAMILY command to the kernel's nlctrl, asking what the ID of "nl80211" is.
  3. Receive ID: The kernel replies, telling it that the ID of "nl80211" is 21.
  4. Send Actual Command: Send a NL80211_CMD_GET_SCAN message, with the target protocol family ID in the Netlink header set to 21.

Looking at the code makes it clearer. Here is the simplified logic when the iw tool initializes:

// 1. 分配 socket
state->nl_sock = nl_socket_alloc();

// 2. 连接并绑定 (内部做了 socket() 和 bind())
genl_connect(state->nl_sock);

// 3. 解析 "nl80211" 家族名称到 ID
// 内部会发送 CTRL_CMD_GETFAMILY,并阻塞等待回复
int family_id = genl_ctrl_resolve(state->nl_sock, "nl80211");

This step, genl_ctrl_resolve, is the most critical. It abstracts away the underlying "ask-wait-parse" details. If you don't use libnl, you have to construct the CTRL_CMD_GETFAMILY message yourself and manually parse the returned Netlink attributes (TLV).


Message Anatomy

Let's dive down to the byte level of the message. Generic Netlink messages are nested layer by layer, like Russian matryoshka dolls.

From outside to inside, the order is:

  1. Netlink Header (struct nlmsghdr): This is present in all standard Netlink, containing length, type, and flags.
  2. Generic Netlink Header (struct genlmsghdr): This is unique to Generic Netlink.
  3. Private Header (optional): Some protocols (like nl80211) might add a few custom fields here; hdrsize is exactly for this purpose.
  4. Payload (TLV Attributes): The actual data.

Let's look at struct genlmsghdr:

struct genlmsghdr {
__u8 cmd; // 命令类型
__u8 version; // 版本号
__u16 reserved; // 保留字段
};
  • cmd: This is the command ID you previously defined in genl_ops (such as NL80211_CMD_GET_SCAN).
  • version: Allows for protocol evolution. If you change the message format and increment the version number by 1, the kernel can recognize it and maintain backward compatibility with older versions.

Sending Messages from the Kernel: If you want to send a message from the kernel to user-space (such as an event notification), the flow looks like this:

// 1. 分配 skb 缓冲区
struct sk_buff *skb = genlmsg_new(payload_size, GFP_KERNEL);

// 2. 添加 Generic Netlink 头部(返回指向 payload 起始的指针)
void *hdr = genlmsg_put(skb, 0, 0, &nl80211_fam, 0, NL80211_CMD_NEW_AP);

// 3. 填充 TLV 属性
nla_put_u32(skb, NL80211_ATTR_WIPHY, phy_id);
nla_put_string(skb, NL80211_ATTR_IFNAME, "wlan0");

// 4. 发送单播
genlmsg_unicast(genl_info_net(info), skb, info->snd_portid);
// 或者广播
genlmsg_multicast(&nl80211_fam, skb, 0, 0, GFP_KERNEL);

Here, genlmsg_put() helps you properly set up both nlmsghdr and genlmsghdr, and leaves space for you to fill in attributes.


Real-World Case: Socket Monitoring

As an application case of Generic Netlink, the book mentions the Socket Monitoring Interface (NETLINK_SOCK_DIAG).

You may have used the ss command (ss -tntp). It is much more powerful than the old relic netstat. Why can ss know which process a socket belongs to? Why can it accurately display the internal TCP state (such as the retransmit count)?

It relies on this NETLINK_SOCK_DIAG.

Why build this? Although the /proc filesystem exposes socket information, it has two drawbacks:

  1. The format is human-readable and hard to parse.
  2. Key information is missing. The most typical example is UNIX Domain Sockets: /proc won't tell you who this socket is connected to. But if you want to do process migration (CRIU), you must know who the peer is in order to rebuild the connection.

Therefore, the kernel introduced sock_diag.

Its implementation in the kernel also goes through the Generic Netlink mechanism. Although it uses the dedicated protocol family NETLINK_SOCK_DIAG, its design philosophy is directly descended from Generic Netlink: register a handler table and dispatch to different handlers based on the protocol type.

struct sock_diag_handler {
__u8 family; // 协议族,如 AF_INET, AF_UNIX
int (*dump)(struct sk_buff *skb, struct nlmsghdr *nlh);
};

For example, the UNIX socket diagnostic module:

static const struct sock_diag_handler unix_diag_handler = {
.family = AF_UNIX,
.dump = unix_diag_handler_dump,
};

After registration, when the user-space ss tool sends a query request for AF_UNIX, the kernel calls unix_diag_handler_dump to pack detailed information about all UNIX sockets (including those not available in /proc) into Netlink messages and sends them back.

This is why you can see extremely detailed UNIX socket connection graphs when using ss -x.


Chapter Summary

Generic Netlink solves the problem of limited Netlink protocol family IDs. By introducing a "Controller" family, it implements name-based dynamic family registration.

The core understanding we built in this section is:

  1. Multiplexing: NETLINK_GENERIC is a highway carrying countless custom protocol families.
  2. Dynamic Lookup: User-space finds kernel families by name, no longer relying on hardcoded IDs.
  3. Standardized Operations: The separation of doit (single operations) and dumpit (list dumps), along with the policy validation mechanism, forms the standard development pattern for kernel Netlink interfaces.

By understanding Generic Netlink, you've acquired a key. The door to the modern Linux networking subsystem (especially wireless) is now open to you.

But this is not enough. Networking needs not only management and configuration, but also diagnostics and probing. In the next chapter, we will stop focusing on "how to send control commands" and instead focus on the "voice" of the network itself—the ICMP protocol. When the network has problems, it is the messenger.