Linux内核分析之网络协议栈-04
This language version is unavailable; showing the other language.
48.1 Netlink 协议族与消息格式
Netlink 是内核与用户态之间的"双工消息总线":一个 socket 族(AF_NETLINK,按 NETLINK_* 子协议再分)、一套 TLV 消息格式(nlmsghdr + nlattr)、单播/多播两种投递。ip/ss/conntrack 的一切配置查询、udev 设备事件(39.1.3 节)、路由变化通知、审计日志都经它。本节解剖消息骨架、请求应答协议与投递语义。
48.1.1 nlmsghdr:消息骨架
// include/uapi/linux/netlink.h:52(节选要点)
struct nlmsghdr {
__u32 nlmsg_len; /* 含头的消息总长 */
__u16 nlmsg_type; /* 消息类型 (RTM_NEWLINK 等) */
__u16 nlmsg_flags; /* NLM_F_REQUEST/ACK/MULTI... */
__u32 nlmsg_seq; /* 序号 (请求应答配对) */
__u32 nlmsg_pid; /* 端口 id (发送方标识) */
/* 后随: 协议固定头 + nlattr 属性流 */
};
nlmsg_flags 的请求语义 (协议的"会话控制"):
NLM_F_REQUEST 这是一条请求 (必设)
NLM_F_ACK 请内核回执 (成功也回 — 错误检测)
NLM_F_MULTI 多部分消息 (大表分页, NLMSG_DONE 收尾)
NLM_F_ECHO 改动回显给发起者 (让多客户端同步)
配对 GET: NLM_F_ROOT|NLM_F_MATCH = DUMP (全表)
配对 NEW: NLM_F_EXCL (存在即错) | NLM_F_REPLACE
(REST API 的 PUT/POST 语义同构!)
seq 配对机制 (多请求并发的会话管理):
发请求 seq=42 → 内核应答原样带回 seq=42
→ 用户态按 seq 路由到等待者 (无连接 socket
上的"逻辑连接", 45 章 fd 语义之上的应用层)
48.1.2 nlattr:TLV 属性流
struct nlattr { __u16 nla_len; __u16 nla_type; }; /* TLV, 4 字节对齐 */
消息的完整封装图:
┌─────────────────────────────────────┐
│ nlmsghdr (16B) │
├─────────────────────────────────────┤
│ 协议固定头 (ifinfomsg/rtmsg/...) │ ← 结构化公共部分
├─────────────────────────────────────┤
│ nlattr [IFLA_ADDRESS] = MAC │
│ nlattr [IFLA_MTU] = 1500 │ ← TLV 属性流
│ nlattr [IFLA_LINKINFO] │
│ └─ 嵌套 nlattr [...] │ (可嵌套!)
└─────────────────────────────────────┘
属性遍历的内核模板 (防畸形消息):
nlmsg_valid_hdr(hdr, len) 头与长度校验
nla_for_each_attr(pos, head, len, rem) 逐属性
case IFLA_MTU: nla_get_u32(pos); break;
nla_ok(pos, rem) 边界门卫 — 解析越过缓冲
即停, -EINVAL 回报 (不可信输入的防御, 47 章
同款"门卫+校验"纪律)
版本演进: 新版本内核加新属性类型 — 老消费者
跳过未知属性继续解析 (TLV 的向前兼容);
固定头极少变更 (变更即 ABI 破坏)
48.1.3 投递:单播、多播与请求应答骨架
// net/netlink/af_netlink.c:1327 与 :1554
int netlink_unicast(struct sock *ssk, struct sk_buff *skb,
u32 portid, int nonblock)
int netlink_broadcast(struct sock *ssk, struct sk_buff *skb, u32 portid,
u32 group, gfp_t allocation)
// :2524
int netlink_rcv_skb(struct sk_buff *skb, int (*cb)(struct sk_buff *, struct nlmsghdr *, ...))
两种投递:
单播: portid 定位目标 socket
(内核 socket 的 portid=0, 用户态按进程分配)
接收缓冲满 → EAGAIN/挂起 (SO_RCVBUF, 45.2 节同源)
多播: group 位掩码 (RTNLGRP_LINK/IPV4_ROUTE...)
一份 skb 经 skb_clone (44.1.4 节!) 复制给
每个订阅组 — 数据共享, 描述符独立
慢消费者 → ENOBUFS 丢事件 → 订阅者必须
支持"全量 dump 重同步" (事件+快照协议,
47.3 节 conntrack 工具同款)
请求应答骨架 (netlink_rcv_skb, :2524):
逐 nlmsghdr: 权限检查 → 调子协议 cb →
ACK/错误自动封包回送
(各子协议只写 cb, 封包/回执/分页框架化 —
与 39 章"模型留白"、47 章"钩子留白"同一
设计哲学: 框架管协议, 消费者管语义)
权限与 namespace:按子协议检查能力(RTNL 需 CAP_NET_ADMIN、audit 需 CAP_AUDIT_*);网络类子协议 namespace 感知——设备事件只在对应 netns 广播(9 章隔离的具体落点)。
小结
Netlink 协议面 = nlmsghdr 请求应答骨架(seq 配对、ACK/MULTI/DUMP 标志)+ nlattr TLV 属性流(向前兼容的版本演进)+ 单播/多播投递(skb_clone 一对多、ENOBUFS 触发快照重同步);netlink_rcv_skb 把封包/回执/权限框架化,子协议只写回调。它以"结构化、可编程、可广播"全面替代 ioctl/proc 的配置职责——本卷 46-47 章的一切"ip/conntrack 命令"的底层通道。下一节看三个重量级消费者。
48.2 Netlink 在内核中的应用 —— 路由、SELinux、audit
三个重量级子系统把 Netlink 用出了三种范式:rtnetlink(网络配置的事实 API:请求应答 + 事件多播)、audit(不可丢日志流)、SELinux/uevent(纯通知广播)。本节逐一解剖,并以"消费者协议矩阵"收束第八部分。
48.2.1 rtnetlink:网络配置的事实 API
// include/net/rtnetlink.h:56 与 :107
int __rtnl_register_many(const struct rtnl_msg_handler *handlers, int n);
/* struct rtnl_link_ops - rtnetlink link operations (虚拟设备族插件) */
rtnetlink 的覆盖面 (ip 命令的全部本事):
RTM_NEWLINK/DELLINK/GETLINK 设备生灭与查询
虚拟设备插件: bridge/bond/vlan/vxlan/ipvlan...
各自实现 rtnl_link_ops.newlink/changelink/
get_size/fill_info — "ip link add type vxlan"
的内核入口 (44.2 节 net_device 的批量生成器)
RTM_NEWROUTE/DELROUTE/GETROUTE 路由表 (46.1 节 FIB)
RTM_NEWADDR/DELADDR IP 地址
RTM_NEWRULE 策略路由规则
RTM_NEWQDISC/TFILTER tc (47.2.3 节)
注册框架: __rtnl_register_many (:56) 按
(协议族, 消息类型) 登记 doit(配置单条)/
dumpit(全表分页) 回调 — 48.1.3 节骨架的填充者
dump 协议 (全表查询的标准形):
用户: GETROUTE + NLM_F_DUMP
内核: 逐条 RTM_NEWROUTE (NLM_F_MULTI) → NLMSG_DONE
分页/过滤/统计框架化 — conntrack -L (47.3)、
ss (45 章 sock diag) 同款模式
配置的全局锁: rtnl_lock
全网络配置单锁互斥 — 著名的可扩展性瓶颈
(批量变更经 netlink 批处理缓冲 + 各子系统
逐步细粒度化缓解)
"事件 + 快照"的完整状态同步是 rtnetlink 的公共协议:订阅 RTNLGRP_LINK/ROUTE 多播获得增量事件,周期性 dump 校准快照;ENOBUFS 丢事件(48.1.3 节)即触发立即重同步——任何 Netlink 消费者都必须实现这一对。
48.2.2 audit:不可丢的日志流
audit (NETLINK_AUDIT, kernel/audit.c):
形态: 内核 → auditd 守护的日志流 (单向为主)
内容: syscall 审计记录 (52 章 LSM 的拒绝事件/
文件访问/网络操作), 规则面 (auditctl 下发规则
是反向请求)
权限: CAP_AUDIT_WRITE (内核侧写入) /
CAP_AUDIT_CONTROL (规则管理)
可靠性语义与路由多播相反:
事件不可静默丢失 → 背压时丢弃计数显式上报 +
守护重启后重新绑定 — "审计完整性"优先于
"投递即时性"
观测: ausearch/aulast (auditd 工具链),
/proc/self/loginuid (会话标记)
48.2.3 SELinux 通知与 uevent
SELinux netlink (NETLINK_SELINUX, security/selinux/netlink.c):
selnl_notify_policyload: 策略版本变化广播
(用户态库的缓存策略需失效重读)
selnl_notify_setenforce: 强制/宽容态切换通知
(策略本体加载走 /sys/fs/selinux — 52.2 节;
netlink 只做"变化了"的轻量通知)
uevent (NETLINK_KOBJECT_UEVENT, 39.1.3 节):
设备生灭广播 → udev 在线消费 + coldplug 重放
— 纯多播、任意用户可收 (只读事件)
48.2.4 消费者协议矩阵与本卷收束
| 消费者 | 子协议 | 形态 | 权限 | 丢失语义 |
|---|---|---|---|---|
| iproute2/libnl | NETLINK_ROUTE | 请求应答+多播 | CAP_NET_ADMIN(配置) | 事件可丢+dump 重同步 |
| conntrack 工具 | NETLINK_NETFILTER | 同上(ctnetlink,47.3) | CAP_NET_ADMIN | 同上 |
| auditd | NETLINK_AUDIT | 日志流(内核→用户) | CAP_AUDIT_* | 不可丢(计数上报) |
| udev | KOBJECT_UEVENT | 纯多播广播 | 任意(只读事件) | 可丢+coldplug 重放 |
| SELinux 通知 | NETLINK_SELINUX | 事件通知 | — | 可丢 |
第八部分收束——一个 HTTP 请求的完整内核旅程:NIC DMA → 硬中断/NAPI 轮询(44.3)→ GRO 合成 skb(44.1)→ 协议分发 → IP 路由与 PREROUTING(46.1/47.1)→ conntrack 记账(47.3)→ TCP 握手与状态机(46.2)→ sock 入队 + sock_def_readable(45.2)→ ep_poll_callback 摘入就绪链(45.3)→ 应用 recv——而 ip link/conntrack/nft 的每次管理动作走 48 章的 Netlink,全程无一章缺席。网络的"大"正在于此:不是一个模块,而是一台机器内的完整分工体系。
小结
Netlink 的三个范式覆盖内核对外通信的主要需求:rtnetlink 以 doit/dumpit 框架 + rtnl_lock 串行承载全部网络配置(虚拟设备经 rtnl_link_ops 插件化扩展,dump+多播构成状态同步协议);audit 以单向不可丢日志流提供审计出口(LSM 的审计面);SELinux/uevent 以纯广播完成轻量通知。选型哲学与数据结构同源——ioctl 适合单设备精确控制、netlink 适合状态表+事件广播、procfs 适合人类可读单值。第八部分(网络协议栈)五章至此全部完成。