10 Netlink 内核-用户通信机制与实现详解
1. 概述
Netlink 是 Linux 内核与用户态之间的 配置与事件总线,对应协议族 PF_NETLINK(socket(AF_NETLINK, SOCK_DGRAM, protocol))。它不承载 IP 数据面流量,而是承载 路由、链路、防火墙、无线、审计 等控制消息。
| 项目 |
说明 |
| 实现目录 |
net/netlink/(7 文件) |
| 头文件 |
include/linux/netlink.h、include/uapi/linux/netlink.h |
| 系统调用入口 |
net/socket.c(sendmsg/recvmsg 分发给 netlink_ops) |
| 与数据面 |
与 NAPI/收包路径正交;配置网卡/路由后影响数据面行为 |
1.1 目录与编译单元
1 2 3 4 5 6 7 8
| net/netlink/ ├── af_netlink.c # 核心:socket、单播/组播、dump、ACK、nl_table ├── af_netlink.h # netlink_sock、netlink_table(内部) ├── genetlink.c # Generic Netlink:family 注册与分发 ├── policy.c # nla_policy 序列化导出 ├── diag.c # CONFIG_NETLINK_DIAG ├── Makefile └── Kconfig
|
Makefile:obj-y := af_netlink.o genetlink.o policy.o
2. 架构总览
2.1 两类 Netlink
| 类型 |
protocol 常量 |
典型用途 |
内核注册 |
| 固定协议 |
NETLINK_ROUTE(0)、NETLINK_NETFILTER(12) 等 |
rtnetlink、nfnetlink |
netlink_kernel_create() + cfg.input |
| Generic Netlink |
NETLINK_GENERIC(16) |
nl80211、tipc、ovs |
genl_register_family() |
固定协议号有限;新子系统普遍走 genetlink,在 NETLINK_GENERIC 上再分 family id + cmd。
3. 核心数据结构
3.1 struct netlink_sock(af_netlink.h)
在 struct sock 之上扩展:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| struct netlink_sock { struct sock sk; unsigned long flags; u32 portid; u32 dst_portid; u32 dst_group; u32 subscriptions; u32 ngroups; unsigned long *groups; void (*netlink_rcv)(struct sk_buff *skb); int (*netlink_bind)(struct net *, int group); struct netlink_callback cb; bool cb_running; };
|
用户态 socket:netlink_rcv == NULL,消息入 sk_receive_queue,由 recvmsg 读出。
内核 socket:netlink_rcv 指向如 rtnetlink_rcv、genl_rcv,在 netlink_unicast 中同步调用。
3.2 nl_table[MAX_LINKS]
全局表,每个 protocol 号一项:
| 字段 |
作用 |
hash |
rhashtable,按 (netns, portid) 查找 socket |
mc_list |
已 bind 组播的 socket 链表 |
listeners |
内核侧组播监听者位图 |
registered |
该协议是否已由内核模块注册 |
module / cb_mutex / bind / unbind |
模块引用与 per-protocol 锁 |
查找用 RCU;插入/删除用 nl_table_lock + per-bucket 锁。
3.3 消息格式:nlmsghdr + 载荷
1 2 3 4 5 6 7
| struct nlmsghdr { __u32 nlmsg_len; __u16 nlmsg_type; __u16 nlmsg_flags; __u32 nlmsg_seq; __u32 nlmsg_pid; };
|
skb 的 NETLINK_CB(skb)(struct netlink_skb_parms)携带路由元数据:portid、dst_group、creds 等。
Generic Netlink 在 nlmsghdr 后还有 genlmsghdr(cmd、version),属性区用 NLA(struct nlattr)。
4. 初始化与挂接
4.1 netlink_proto_init()(core_initcall)
af_netlink.c 启动时:
proto_register(&netlink_proto)
nl_table = kcalloc(MAX_LINKS, ...),每表项初始化 rhashtable
netlink_add_usersock_entry() — 预注册 NETLINK_USERSOCK
sock_register(&netlink_family_ops) — 挂到 socket.c 的 net_families[PF_NETLINK]
register_pernet_subsys — 每 netns 的 /proc/net/netlink
rtnetlink_init() — 在 net/core/rtnetlink.c 注册 NETLINK_ROUTE
4.2 用户态创建 socket
1 2 3 4
| socket(AF_NETLINK, SOCK_DGRAM, NETLINK_ROUTE) → socket.c __sock_create() → netlink_create() # 检查 nl_table[protocol].registered → __netlink_create() # sk_alloc(netlink_sock)、设置 netlink_ops
|
netlink_create() 若协议未注册,可 request_module("net-pf-16-proto-N") 加载对应模块。
4.3 内核创建监听 socket
1 2 3 4 5 6 7 8
| struct netlink_kernel_cfg cfg = { .groups = RTNLGRP_MAX, .input = rtnetlink_rcv, .cb_mutex = &rtnl_mutex, .flags = NL_CFG_F_NONROOT_RECV, .bind = rtnetlink_bind, }; sk = netlink_kernel_create(net, NETLINK_ROUTE, &cfg);
|
__netlink_kernel_create() 会:创建内核 socket、设置 NETLINK_F_KERNEL_SOCKET、netlink_insert(sk, 0)、填充 nl_table[unit]。
5. 用户态 → 内核(发送路径)
5.1 netlink_sendmsg()
- 从
msg_name(struct sockaddr_nl)或 connect() 缓存取 dst_portid、dst_group
- 未 bind →
netlink_autobind() 分配 portid 并插入 hash
netlink_alloc_large_skb() 分配 skb,用户数据 memcpy_from_msg
security_netlink_send() LSM 检查
- 有
dst_group → netlink_broadcast();否则 netlink_unicast()
5.2 netlink_unicast() 分支
1 2 3 4
| netlink_getsockbyportid(ssk, portid) ├─ 目标为内核 socket → netlink_unicast_kernel() │ └─ nlk->netlink_rcv(skb) # 如 rtnetlink_rcv / genl_rcv └─ 目标为用户 socket → netlink_sendskb() # 入接收队列
|
5.3 netlink_rcv_skb() — 统一解析入口
内核 handler 普遍通过此函数处理 一个 skb 内多条消息:
1 2 3 4 5 6 7 8 9 10 11
| while (skb->len >= nlmsg_total_size(0)) { nlh = nlmsg_hdr(skb); if (!(nlh->nlmsg_flags & NLM_F_REQUEST)) goto ack; if (nlh->nlmsg_type < NLMSG_MIN_TYPE) goto ack; err = cb(skb, nlh, &extack); if (nlh->nlmsg_flags & NLM_F_ACK || err) netlink_ack(skb, nlh, err, &extack); skb_pull(skb, NLMSG_ALIGN(nlh->nlmsg_len)); }
|
仅带 NLM_F_REQUEST 的消息由内核处理;需要确认时发 netlink_ack()(含扩展 ACK TLV)。
6. 内核 → 用户态(接收/通知)
| API |
用途 |
netlink_unicast(ssk, skb, portid, nonblock) |
单播到指定 portid |
netlink_broadcast(ssk, skb, portid, group, gfp) |
组播到订阅 group 的 socket |
nlmsg_notify(sk, skb, portid, group, report, flags) |
组播 + 可选单播回执 |
netlink_broadcast() 遍历 nl_table[protocol].mc_list,对 nlk->groups 中对应 bit 已置位的 socket 调用 netlink_broadcast_deliver()。
用户态 netlink_recvmsg():
skb_recv_datagram() 取 skb
- 拷贝数据到用户缓冲区
- 填充
sockaddr_nl(来源 portid、组)
- 若
cb_running 且接收缓冲有空 → 触发 netlink_dump() 继续 dump
7. Dump 机制(大数据量查询)
ip link、RTM_GETLINK 等不会一次返回全部条目:
要点:
__netlink_dump_start() 设置 nlk->cb 与 cb->dump 回调
- 每次
recvmsg 可能触发 netlink_dump(),按用户 max_recvmsg_len 分配 skb
- 结束发
NLMSG_DONE(netlink_dump_done())
- dump 期间持有 per-protocol
cb_mutex(如 rtnl_mutex)
8. Generic Netlink(genetlink.c)
8.1 动机
- 固定
NETLINK_* 协议号不够用;
- 需要统一的 family / cmd / attribute 模型与动态注册。
8.2 初始化
1 2 3 4 5 6
| net->genl_sock = netlink_kernel_create(net, NETLINK_GENERIC, &cfg);
genl_register_family(&genl_ctrl);
|
8.3 收包分发
1 2 3 4 5 6
| genl_rcv(skb) → netlink_rcv_skb(skb, genl_rcv_msg) → family = genl_family_find_byid(nlh->nlmsg_type) → genl_family_rcv_msg() ├─ op.dumpit → genl_family_rcv_msg_dumpit └─ op.doit → genl_family_rcv_msg_doit
|
8.4 子系统注册
1 2 3 4 5 6 7 8
| static struct genl_family nl80211_fam = { .name = NL80211_GENL_NAME, .ops = nl80211_ops, .n_ops = ..., .mcgrps = nl80211_mcgrps, }; genl_register_family(&nl80211_fam);
|
genl_register_family():校验 ops → idr_alloc 分配 family id → 分配组播组 → 向 genl_ctrl 发 CTRL_CMD_NEWFAMILY 事件。
控制器查询:用户态 GENL_ID_CTRL + CTRL_CMD_GETFAMILY 可发现已注册 family 与 ops。
9. 固定协议示例:rtnetlink
实现于 net/core/rtnetlink.c(非 net/netlink/ 目录,但建立在 netlink 框架上):
| 消息 |
工具对应 |
作用 |
RTM_NEWLINK / RTM_DELLINK |
ip link |
接口增删改 |
RTM_NEWADDR / RTM_DELADDR |
ip addr |
地址 |
RTM_NEWROUTE / RTM_DELROUTE |
ip route |
路由 |
RTM_NEWNEIGH |
ip neigh |
邻居 |
注册(每 network namespace):
1 2
| sk = netlink_kernel_create(net, NETLINK_ROUTE, &cfg); net->rtnl = sk;
|
rtnetlink_rcv → netlink_rcv_skb → 各 rtnl_* 处理函数;变更通过 RTMGRP_* 组播 通知监听者。
10. 其它 netlink 文件
| 文件 |
作用 |
| policy.c |
netlink_policy_dump_*:将内核 nla_policy 导出给用户态查询 |
| diag.c |
CONFIG_NETLINK_DIAG:通过 sock_diag 导出 netlink socket 状态 |
| af_netlink.c |
/proc/net/netlink、BPF iter、netlink tap、notifier |
10.1 常见 protocol 号(节选)
| 常量 |
值 |
模块/用途 |
NETLINK_ROUTE |
0 |
rtnetlink |
NETLINK_USERSOCK |
2 |
用户自定义 |
NETLINK_SOCK_DIAG |
4 |
socket 诊断 |
NETLINK_NETFILTER |
12 |
nfnetlink |
NETLINK_GENERIC |
16 |
genetlink |
NETLINK_KOBJECT_UEVENT |
15 |
udev 热插拔 |
完整列表见 include/uapi/linux/netlink.h。
11. 与 socket.c 的分工
| 层次 |
文件 |
职责 |
| 系统调用 |
net/socket.c |
socket、bind、sendmsg、recvmsg;sock_register 分发表 |
| Netlink 协议 |
net/netlink/af_netlink.c |
netlink_ops、portid、组播、内核 socket、dump、ACK |
| 业务语义 |
rtnetlink.c、nl80211.c、nfnetlink.c 等 |
具体消息类型与属性解析 |
netlink_ops 注册的操作(节选):
1 2 3 4
| .sendmsg = netlink_sendmsg, .recvmsg = netlink_recvmsg, .bind = netlink_bind, .connect = netlink_connect,
|
12. RK3588 使用场景
Netlink 为上游通用实现,无 Rockchip 专属补丁于 net/netlink/。RK3588 板上典型用法:
| 场景 |
协议 |
用户态工具 |
| 以太网 UP/DOWN、IP |
NETLINK_ROUTE |
ip link / ip addr |
| 路由 |
NETLINK_ROUTE |
ip route |
| WiFi 扫描/连接 |
NETLINK_GENERIC + nl80211 |
iw、wpa_supplicant |
| 防火墙 |
NETLINK_NETFILTER |
nft |
| 设备热插拔 |
NETLINK_KOBJECT_UEVENT |
udev |
| 接口统计/诊断 |
NETLINK_SOCK_DIAG |
ss -x 等 |
调试 netlink 可用:strace -e sendmsg,recvmsg ip link show、nlmon(抓 netlink 包)、/proc/net/netlink。
13. 源码索引
| 内容 |
路径 |
| socket 族注册 |
net/netlink/af_netlink.c:netlink_proto_init、netlink_ops |
| 内核 socket 创建 |
__netlink_kernel_create |
| 单播/组播 |
netlink_unicast、netlink_broadcast |
| 消息解析 |
netlink_rcv_skb、netlink_ack |
| dump |
netlink_dump、__netlink_dump_start |
| Generic Netlink |
net/netlink/genetlink.c |
| rtnetlink |
net/core/rtnetlink.c |
| UAPI |
include/uapi/linux/netlink.h、rtnetlink.h、genetlink.h |
14. 相关文档
15. 小结
| 问题 |
答案 |
| Netlink 是什么? |
内核与用户态的 二进制配置/事件总线(PF_NETLINK) |
| 核心实现在哪? |
net/netlink/af_netlink.c + genetlink.c |
| 用户发消息怎么走? |
sendmsg → netlink_unicast → 内核 netlink_rcv 或用户队列 |
| 和 rtnetlink 关系? |
rtnetlink 是 NETLINK_ROUTE 上的 业务协议 |
| 和 genetlink 关系? |
genetlink 是 NETLINK_GENERIC 上的 可扩展 family 框架 |
| 与 NAPI 关系? |
无直接关系;netlink 管 控制面,NAPI 管 收包数据面 |