10 Netlink 内核-用户通信机制与实现详解

10 Netlink 内核-用户通信机制与实现详解

1. 概述

Netlink 是 Linux 内核与用户态之间的 配置与事件总线,对应协议族 PF_NETLINKsocket(AF_NETLINK, SOCK_DGRAM, protocol))。它不承载 IP 数据面流量,而是承载 路由、链路、防火墙、无线、审计 等控制消息。

项目 说明
实现目录 net/netlink/(7 文件)
头文件 include/linux/netlink.hinclude/uapi/linux/netlink.h
系统调用入口 net/socket.csendmsg/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

Makefileobj-y := af_netlink.o genetlink.o policy.o


2. 架构总览

用户态系统调用层net/netlink协议 handler内核 socket用户 socket 队列ip / iproute2iw / wpa_supplicantnft / iptables-nftudevsocket.c: sendmsg recvmsgnetlink_opsnetlink_sendmsgnetlink_recvmsgnetlink_unicast / broadcastnetlink_rcv_skbnetlink_dumpNETLINK_ROUTE: rtnetlink.cNETLINK_GENERIC: genetlink.cNETLINK_NETFILTERNETLINK_AUDIT
类型 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. 核心数据结构

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; /* NETLINK_F_* */
u32 portid; /* 本 socket 端口(类 PID) */
u32 dst_portid; /* connect 后的默认目标 */
u32 dst_group; /* connect 后的默认组播组 */
u32 subscriptions;
u32 ngroups;
unsigned long *groups; /* 订阅的组播位图 */
void (*netlink_rcv)(struct sk_buff *skb); /* 内核 socket 收包回调 */
int (*netlink_bind)(struct net *, int group);
struct netlink_callback cb; /* dump 状态 */
bool cb_running;
/* ... */
};

用户态 socketnetlink_rcv == NULL,消息入 sk_receive_queue,由 recvmsg 读出。
内核 socketnetlink_rcv 指向如 rtnetlink_rcvgenl_rcv,在 netlink_unicast 中同步调用。

全局表,每个 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; /* 消息类型 / genl family id */
__u16 nlmsg_flags; /* NLM_F_REQUEST, NLM_F_MULTI, NLM_F_ACK ... */
__u32 nlmsg_seq;
__u32 nlmsg_pid; /* 发送方 portid */
};

skb 的 NETLINK_CB(skb)struct netlink_skb_parms)携带路由元数据:portiddst_groupcreds 等。

Generic Netlinknlmsghdr 后还有 genlmsghdr(cmd、version),属性区用 NLAstruct nlattr)。


4. 初始化与挂接

af_netlink.c 启动时:

  1. proto_register(&netlink_proto)
  2. nl_table = kcalloc(MAX_LINKS, ...),每表项初始化 rhashtable
  3. netlink_add_usersock_entry() — 预注册 NETLINK_USERSOCK
  4. sock_register(&netlink_family_ops) — 挂到 socket.cnet_families[PF_NETLINK]
  5. register_pernet_subsys — 每 netns 的 /proc/net/netlink
  6. 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_SOCKETnetlink_insert(sk, 0)、填充 nl_table[unit]


5. 用户态 → 内核(发送路径)

  1. msg_namestruct sockaddr_nl)或 connect() 缓存取 dst_portiddst_group
  2. 未 bind → netlink_autobind() 分配 portid 并插入 hash
  3. netlink_alloc_large_skb() 分配 skb,用户数据 memcpy_from_msg
  4. security_netlink_send() LSM 检查
  5. dst_groupnetlink_broadcast();否则 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() # 入接收队列

内核 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 linkRTM_GETLINK 等不会一次返回全部条目:

netlink_dump内核 handler用户态 recvmsgnetlink_dump内核 handler用户态 recvmsgcb_running = truesendmsg(RTM_GETLINK + NLM_F_DUMP)__netlink_dump_start(cb)recvmsg (缓冲有空)cb->dump(skb, cb) 填充多条消息skb (NLM_F_MULTI)recvmsg ...NLMSG_DONE

要点:

  • __netlink_dump_start() 设置 nlk->cbcb->dump 回调
  • 每次 recvmsg 可能触发 netlink_dump(),按用户 max_recvmsg_len 分配 skb
  • 结束发 NLMSG_DONEnetlink_dump_done()
  • dump 期间持有 per-protocol cb_mutex(如 rtnl_mutex

8.1 动机

  • 固定 NETLINK_* 协议号不够用;
  • 需要统一的 family / cmd / attribute 模型与动态注册。

8.2 初始化

1
2
3
4
5
6
// genl_pernet_init
net->genl_sock = netlink_kernel_create(net, NETLINK_GENERIC, &cfg);
// cfg.input = genl_rcv

// genl_init
genl_register_family(&genl_ctrl); /* 控制器 family */

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_ctrlCTRL_CMD_NEWFAMILY 事件。

控制器查询:用户态 GENL_ID_CTRL + CTRL_CMD_GETFAMILY 可发现已注册 family 与 ops。


实现于 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_rcvnetlink_rcv_skb → 各 rtnl_* 处理函数;变更通过 RTMGRP_* 组播 通知监听者。


文件 作用
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 socketbindsendmsgrecvmsgsock_register 分发表
Netlink 协议 net/netlink/af_netlink.c netlink_ops、portid、组播、内核 socket、dump、ACK
业务语义 rtnetlink.cnl80211.cnfnetlink.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 iwwpa_supplicant
防火墙 NETLINK_NETFILTER nft
设备热插拔 NETLINK_KOBJECT_UEVENT udev
接口统计/诊断 NETLINK_SOCK_DIAG ss -x

调试 netlink 可用:strace -e sendmsg,recvmsg ip link shownlmon(抓 netlink 包)、/proc/net/netlink


13. 源码索引

内容 路径
socket 族注册 net/netlink/af_netlink.cnetlink_proto_initnetlink_ops
内核 socket 创建 __netlink_kernel_create
单播/组播 netlink_unicastnetlink_broadcast
消息解析 netlink_rcv_skbnetlink_ack
dump netlink_dump__netlink_dump_start
Generic Netlink net/netlink/genetlink.c
rtnetlink net/core/rtnetlink.c
UAPI include/uapi/linux/netlink.hrtnetlink.hgenetlink.h

14. 相关文档


15. 小结

问题 答案
Netlink 是什么? 内核与用户态的 二进制配置/事件总线PF_NETLINK
核心实现在哪? net/netlink/af_netlink.c + genetlink.c
用户发消息怎么走? sendmsgnetlink_unicast → 内核 netlink_rcv 或用户队列
和 rtnetlink 关系? rtnetlink 是 NETLINK_ROUTE 上的 业务协议
和 genetlink 关系? genetlink 是 NETLINK_GENERIC 上的 可扩展 family 框架
与 NAPI 关系? 无直接关系;netlink 管 控制面,NAPI 管 收包数据面

文章互动

阅读 --

留言

0 条留言

正在加载留言…