Netlink 套接字 是 Linux 特有的一种 IPC(进程间通信)机制,用于用户态进程和内核模块之间的通信。它可以用来完成路由管理、设备通知、网络状态更新等任务。
1. Netlink 的基本工作原理
- Netlink 是一种双向通信机制。
- Netlink 消息分为请求和响应:
- 用户态进程发送请求消息到内核。
- 内核处理请求并返回响应消息到用户态进程。
- 也可以由内核主动向用户态进程发送事件通知。
Netlink 的消息通常通过结构体 struct nlmsghdr
包装,消息正文是可选的数据内容。
2. Netlink 的常用 API
Netlink 通信使用的是普通的 BSD 套接字接口:
创建套接字
int socket(int domain, int type, int protocol);
domain
使用AF_NETLINK
。type
通常为SOCK_RAW
或SOCK_DGRAM
。protocol
指定 Netlink 子系统编号(例如NETLINK_ROUTE
)。
绑定套接字
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
- 使用
struct sockaddr_nl
作为地址。
- 使用
发送消息
ssize_t sendto(int sockfd, const void *buf, size_t len, int flags, const struct sockaddr *dest_addr, socklen_t addrlen);
接收消息
ssize_t recv(int sockfd, void *buf, size_t len, int flags);
3. Netlink 示例代码
以下示例演示了如何使用 Netlink 套接字与内核进行简单的通信。
完整代码:用户态程序
#include <iostream>
#include <cstring>
#include <cstdlib>
#include <sys/socket.h>
#include <linux/netlink.h>
#include <unistd.h>
#define NETLINK_USER 31 // 自定义 Netlink 协议号(大于 31 时需内核支持)
#define MSG_LEN 1024 // 消息缓冲区大小
int main() {
// 创建 Netlink 套接字
int sock_fd = socket(AF_NETLINK, SOCK_RAW, NETLINK_USER);
if (sock_fd < 0) {
std::cerr << "Error creating Netlink socket\n";
return -1;
}
// 本地地址配置
struct sockaddr_nl src_addr;
memset(&src_addr, 0, sizeof(src_addr));
src_addr.nl_family = AF_NETLINK;
src_addr.nl_pid = getpid(); // 绑定到当前进程
src_addr.nl_groups = 0; // 不订阅多播
if (bind(sock_fd, (struct sockaddr *)&src_addr, sizeof(src_addr)) < 0) {
std::cerr << "Error binding Netlink socket\n";
close(sock_fd);
return -1;
}
// 目标地址配置(内核)
struct sockaddr_nl dest_addr;
memset(&dest_addr, 0, sizeof(dest_addr));
dest_addr.nl_family = AF_NETLINK;
dest_addr.nl_pid = 0; // 发送到内核
dest_addr.nl_groups = 0; // 不订阅多播
// 构造发送消息
struct nlmsghdr *nlh = (struct nlmsghdr *)malloc(NLMSG_SPACE(MSG_LEN));
memset(nlh, 0, NLMSG_SPACE(MSG_LEN));
nlh->nlmsg_len = NLMSG_SPACE(MSG_LEN); // 消息长度
nlh->nlmsg_pid = getpid(); // 发送者 PID
nlh->nlmsg_flags = 0; // 无特殊标志位
strcpy((char *)NLMSG_DATA(nlh), "Hello from user space!"); // 消息内容
// 发送消息到内核
if (sendto(sock_fd, nlh, nlh->nlmsg_len, 0, (struct sockaddr *)&dest_addr, sizeof(dest_addr)) < 0) {
std::cerr << "Error sending message\n";
free(nlh);
close(sock_fd);
return -1;
}
std::cout << "Message sent to kernel: " << (char *)NLMSG_DATA(nlh) << "\n";
// 接收内核响应
memset(nlh, 0, NLMSG_SPACE(MSG_LEN));
if (recv(sock_fd, nlh, NLMSG_SPACE(MSG_LEN), 0) < 0) {
std::cerr << "Error receiving message\n";
free(nlh);
close(sock_fd);
return -1;
}
std::cout << "Message received from kernel: " << (char *)NLMSG_DATA(nlh) << "\n";
// 清理资源
free(nlh);
close(sock_fd);
return 0;
}
4. 编写内核模块以响应 Netlink 消息
内核模块代码
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/netlink.h>
#include <linux/skbuff.h>
#include <net/sock.h>
#define NETLINK_USER 31
struct sock *nl_sk = NULL;
static void netlink_recv_msg(struct sk_buff *skb) {
struct nlmsghdr *nlh;
int pid;
struct sk_buff *skb_out;
char *msg = "Hello from kernel!";
int msg_size = strlen(msg);
int res;
// 获取 Netlink 消息头部
nlh = (struct nlmsghdr *)skb->data;
printk(KERN_INFO "Kernel received message: %s\n", (char *)NLMSG_DATA(nlh));
pid = nlh->nlmsg_pid; // 获取用户进程 PID
// 构造响应消息
skb_out = nlmsg_new(msg_size, 0);
if (!skb_out) {
printk(KERN_ERR "Failed to allocate new skb\n");
return;
}
nlh = nlmsg_put(skb_out, 0, 0, NLMSG_DONE, msg_size, 0);
strncpy(NLMSG_DATA(nlh), msg, msg_size);
res = nlmsg_unicast(nl_sk, skb_out, pid); // 发送响应
if (res < 0) {
printk(KERN_INFO "Error sending message to user\n");
}
}
static int __init netlink_init(void) {
struct netlink_kernel_cfg cfg = {
.input = netlink_recv_msg, // 注册消息接收回调
};
nl_sk = netlink_kernel_create(&init_net, NETLINK_USER, &cfg);
if (!nl_sk) {
printk(KERN_ALERT "Error creating Netlink socket\n");
return -10;
}
printk(KERN_INFO "Netlink module loaded\n");
return 0;
}
static void __exit netlink_exit(void) {
netlink_kernel_release(nl_sk);
printk(KERN_INFO "Netlink module unloaded\n");
}
module_init(netlink_init);
module_exit(netlink_exit);
MODULE_LICENSE("GPL");
5. 编译和测试
编译内核模块
make -C /lib/modules/$(uname -r)/build M=$(pwd) modules
加载模块
sudo insmod netlink_test.ko
运行用户态程序
./user_netlink
6. 输出示例
用户态程序:
Message sent to kernel: Hello from user space! Message received from kernel: Hello from kernel!
内核日志(
dmesg
):[INFO] Kernel received message: Hello from user space!
总结
通过以上代码,用户态程序和内核模块实现了简单的双向 Netlink 通信。根据实际需求,可以扩展消息内容和通信逻辑。