Linux Netlink 通信示例程序
本项目提供了一个完整的 Linux Netlink 通信示例,用于学习 Netlink 的核心工作原理。
项目结构
.
├── kernel_netlink.c # 内核模块源代码
├── user_netlink.c # 用户空间客户端源代码
├── Makefile # 编译脚本
├── test.sh # 自动化测试脚本
└── README.md # 本文档
核心概念
1. Netlink 寻址方式
基于PID的单播通信
- 每个Netlink套接字绑定到一个唯一的进程ID (PID)
- 内核通过PID将消息发送到特定的用户空间进程
nlmsg_pid字段标识消息发送者的PID
基于多播组的通信
- 多播组允许多个进程同时接收相同的消息
- 进程通过
setsockopt()加入多播组 nl_groups字段指定多播组ID
2. Netlink消息格式
+------------------+
| nlmsghdr | <- 消息头 (16字节)
+------------------+
| |
| Payload Data | <- 数据负载
| |
+------------------+
nlmsghdr 结构体
c
struct nlmsghdr {
__u32 nlmsg_len; // 消息总长度(包括头部)
__u16 nlmsg_type; // 消息类型
__u16 nlmsg_flags; // 消息标志
__u32 nlmsg_seq; // 序列号
__u32 nlmsg_pid; // 发送者PID
};
3. 常用Netlink宏
| 宏 | 功能 | 说明 |
|---|---|---|
NLMSG_LENGTH(len) |
计算消息长度 | 头部长度 + len(未对齐) |
NLMSG_SPACE(len) |
计算空间大小 | 头部长度 + len(对齐到4字节) |
NLMSG_DATA(nlh) |
获取数据指针 | 返回消息头之后的数据区地址 |
NLMSG_NEXT(nlh, len) |
获取下一条消息 | 用于遍历多条消息 |
NLMSG_OK(nlh, len) |
验证消息有效性 | 检查消息是否完整 |
NLMSG_PAYLOAD(nlh, len) |
获取负载长度 | 返回数据负载的实际长度 |
NLMSG_ALIGN(len) |
对齐长度 | 将len对齐到4字节边界 |
NLMSG_HDRLEN |
消息头长度 | 对齐后的头部长度 |
编译和运行
前提条件
- Linux内核头文件
- GCC编译器
- make工具
- root权限(用于加载内核模块)
编译步骤
bash
# 编译内核模块和用户空间程序
make
# 或者分别编译
make kernel_module # 仅编译内核模块
make user_program # 仅编译用户程序
运行步骤
1. 加载内核模块
bash
sudo insmod kernel_netlink.ko
2. 检查模块是否加载成功
bash
lsmod | grep kernel_netlink
dmesg | tail
3. 运行用户空间程序
bash
# 单播通信演示
./user_netlink unicast
# 多播通信演示(需要两个终端)
# 终端1:接收多播消息
./user_netlink multicast
# 终端2:触发内核发送多播消息
./user_netlink trigger
# 演示Netlink宏
./user_netlink macros
4. 卸载内核模块
bash
sudo rmmod kernel_netlink
自动化测试
bash
chmod +x test.sh
./test.sh
测试场景详解
场景1:单播通信
单播通信演示了基于PID的寻址方式:
- 用户程序创建Netlink套接字并绑定到自己的PID
- 构造Netlink消息,设置目标PID为0(内核)
- 通过
sendmsg()发送消息 - 内核模块接收消息,根据
nlmsg_pid识别发送者 - 内核构造响应消息,通过
nlmsg_unicast()发送回用户程序
bash
./user_netlink unicast
场景2:多播通信
多播通信演示了基于多播组的寻址方式:
- 接收者进程通过
setsockopt()加入多播组 - 触发者进程发送MULTICAST类型消息给内核
- 内核通过
nlmsg_multicast()向多播组发送消息 - 所有加入该多播组的进程都会收到消息
bash
# 终端1
./user_netlink multicast
# 终端2
./user_netlink trigger
场景3:观察消息处理
使用dmesg查看内核日志:
bash
# 实时查看内核日志
dmesg -w | grep netlink_kernel
# 查看最近的日志
dmesg | tail -50
消息类型定义
本示例定义了三种消息类型:
| 类型 | 值 | 说明 |
|---|---|---|
NLMSG_SETPID |
0x11 | 客户端注册PID |
NLMSG_GETPID |
0x12 | 请求内核信息 |
NLMSG_MULTICAST |
0x13 | 触发多播消息 |
关键代码解析
内核模块关键函数
- netlink_kernel_create() - 创建Netlink套接字
- nlmsg_new() - 分配新的Netlink消息
- nlmsg_put() - 填充消息头
- nlmsg_unicast() - 发送单播消息
- nlmsg_multicast() - 发送多播消息
用户空间关键函数
- socket(AF_NETLINK, ...) - 创建Netlink套接字
- bind() - 绑定到本地地址(PID)
- setsockopt() - 加入多播组
- sendmsg() - 发送消息
- recvmsg() - 接收消息
调试技巧
1. 使用strace跟踪系统调用
bash
strace -e socket,bind,sendmsg,recvmsg ./user_netlink unicast
2. 查看Netlink套接字状态
bash
cat /proc/net/netlink
3. 使用netlink监控工具
bash
# 安装libnl工具
sudo apt-get install libnl-3-dev libnl-genl-3-dev
# 使用nl-sock-list查看Netlink套接字
nl-sock-list
常见问题
Q1: 加载内核模块失败
确保已安装正确的内核头文件:
bash
sudo apt-get install linux-headers-$(uname -r)
Q2: 权限不足
Netlink操作需要root权限:
bash
sudo ./user_netlink unicast
Q3: 多播消息接收不到
确保接收进程先启动并加入多播组,然后再触发多播。
Q4: 内核模块无法卸载
检查是否有进程正在使用:
bash
lsmod | grep kernel_netlink
扩展学习
1. Generic Netlink (genl)
Generic Netlink提供了更灵活的Netlink使用方式,适合需要动态注册协议的场景。
2. Netlink属性
使用nla_*系列函数可以更方便地处理复杂的数据结构。
3. Netlink Family
查看系统支持的Netlink协议族:
bash
cat /usr/include/linux/netlink.h | grep NETLINK_