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 适合人类可读单值。第八部分(网络协议栈)五章至此全部完成。