如何在hostapd及驱动中使用Netlink套接字?求完整学习路径与示例
Netlink套接字学习路径与hostapd+驱动实践示例
一、清晰的学习路径
1. 基础概念打底
先搞懂Netlink的核心定位:它是专门用于内核与用户空间双向通信的IPC机制,比ioctl更灵活,支持多播,完全兼容socket API。重点看两个官方资料:
- 内核文档:
Documentation/netlink/index.rst(内核源码树内) - 系统手册:
man 7 netlink
2. 核心API拆解
分用户空间和内核空间分别掌握关键函数:
- 用户空间:
socket()创建套接字、bind()绑定进程地址、sendmsg()/recvmsg()收发消息、setsockopt()配置组播等选项 - 内核空间:
netlink_kernel_create()创建内核套接字、netlink_unicast()/netlink_broadcast()发送消息、注册回调函数处理接收消息、netlink_kernel_release()销毁套接字
3. 最小示例入门
先写一个极简的内核模块+用户程序,实现“用户发消息→内核接收并回复”的基础流程,重点理解Netlink消息结构(nlmsghdr头、nla_*宏处理属性),跑通后再扩展到hostapd和驱动场景。
二、hostapd+驱动的完整实践示例
1. 内核驱动端Netlink实现
代码实现
#include <net/sock.h> #include <linux/netlink.h> // 自定义Netlink协议类型(选未被占用的编号,比如31) #define NETLINK_WIFI_CUSTOM 31 static struct sock *nl_sk; // 处理用户空间(hostapd)发来的消息 static void nl_recv_msg(struct sk_buff *skb) { struct nlmsghdr *nlh; char *msg_data; struct sk_buff *reply_skb; int msg_len; nlh = nlmsg_hdr(skb); msg_data = nlmsg_data(nlh); msg_len = nlh->nlmsg_len - NLMSG_HDRLEN; // 解析hostapd的命令,这里示例仅打印消息 printk(KERN_INFO "Wifi driver received: %.*s\n", msg_len, msg_data); // 构造回复消息 reply_skb = nlmsg_new(msg_len, GFP_KERNEL); if (!reply_skb) return; nlh = nlmsg_put(reply_skb, 0, 0, NLMSG_DONE, msg_len, 0); strncpy(nlmsg_data(nlh), "ACK from driver", msg_len); // 发送回复到对应进程(nlmsg_pid是hostapd的PID) netlink_unicast(nl_sk, reply_skb, nlh->nlmsg_pid, MSG_DONTWAIT); } // 驱动初始化时创建Netlink套接字 static int __init wifi_drv_init(void) { struct netlink_kernel_cfg cfg = { .input = nl_recv_msg, // 绑定接收回调 }; nl_sk = netlink_kernel_create(&init_net, NETLINK_WIFI_CUSTOM, &cfg); if (!nl_sk) { printk(KERN_ERR "Failed to create Netlink socket\n"); return -ENOMEM; } printk(KERN_INFO "Wifi driver Netlink initialized\n"); return 0; } // 驱动卸载时销毁套接字 static void __exit wifi_drv_exit(void) { netlink_kernel_release(nl_sk); printk(KERN_INFO "Wifi driver Netlink released\n"); } module_init(wifi_drv_init); module_exit(wifi_drv_exit); MODULE_LICENSE("GPL");
关键注意点
- 用
netlink_kernel_cfg注册回调是新内核推荐的方式,比旧版参数更清晰 - 消息构造必须用
nlmsg_new和nlmsg_put,避免内存越界 - 回复时要复用收到消息的
nlmsg_pid,确保发送到正确的hostapd进程
2. hostapd端Netlink实现
代码实现(可集成到hostapd源码中)
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <sys/socket.h> #include <linux/netlink.h> #define NETLINK_WIFI_CUSTOM 31 #define MAX_PAYLOAD 1024 static int nl_sock; struct nlmsghdr *nlh = NULL; // 初始化Netlink套接字 int hostapd_nl_init(void) { struct sockaddr_nl src_addr; nl_sock = socket(PF_NETLINK, SOCK_RAW, NETLINK_WIFI_CUSTOM); if (nl_sock < 0) { perror("Netlink socket create failed"); return -1; } memset(&src_addr, 0, sizeof(src_addr)); src_addr.nl_family = AF_NETLINK; src_addr.nl_pid = getpid(); // 绑定当前hostapd进程ID src_addr.nl_groups = 0; if (bind(nl_sock, (struct sockaddr *)&src_addr, sizeof(src_addr)) < 0) { perror("Netlink bind failed"); close(nl_sock); return -1; } nlh = (struct nlmsghdr *)malloc(NLMSG_SPACE(MAX_PAYLOAD)); if (!nlh) { perror("Netlink msg malloc failed"); close(nl_sock); return -1; } return 0; } // 发送消息到驱动 int hostapd_nl_send(const char *cmd) { struct sockaddr_nl dest_addr; struct iovec iov; struct msghdr msg; int cmd_len = strlen(cmd); memset(&dest_addr, 0, sizeof(dest_addr)); dest_addr.nl_family = AF_NETLINK; dest_addr.nl_pid = 0; // 内核端的PID固定为0 dest_addr.nl_groups = 0; memset(nlh, 0, NLMSG_SPACE(MAX_PAYLOAD)); nlh->nlmsg_len = NLMSG_LENGTH(cmd_len); nlh->nlmsg_pid = getpid(); nlh->nlmsg_flags = 0; strncpy(NLMSG_DATA(nlh), cmd, cmd_len); iov.iov_base = (void *)nlh; iov.iov_len = nlh->nlmsg_len; msg.msg_name = (void *)&dest_addr; msg.msg_namelen = sizeof(dest_addr); msg.msg_iov = &iov; msg.msg_iovlen = 1; if (sendmsg(nl_sock, &msg, 0) < 0) { perror("Netlink send failed"); return -1; } return 0; } // 接收驱动的回复(建议放到hostapd的事件循环中,避免阻塞主进程) void hostapd_nl_recv(void) { char buffer[MAX_PAYLOAD]; struct sockaddr_nl dest_addr; struct iovec iov; struct msghdr msg; memset(buffer, 0, MAX_PAYLOAD); memset(&msg, 0, sizeof(msg)); iov.iov_base = buffer; iov.iov_len = sizeof(buffer); msg.msg_name = (void *)&dest_addr; msg.msg_namelen = sizeof(dest_addr); msg.msg_iov = &iov; msg.msg_iovlen = 1; recvmsg(nl_sock, &msg, 0); printf("Hostapd received driver reply: %s\n", NLMSG_DATA((struct nlmsghdr *)buffer)); } // 清理资源 void hostapd_nl_cleanup(void) { free(nlh); close(nl_sock); } // 可在hostapd的初始化函数(如hostapd_main)中调用hostapd_nl_init()
关键注意点
- hostapd是守护进程,建议把Netlink接收逻辑整合到现有epoll事件循环中,避免阻塞主流程
- 要处理消息完整性:如果消息过长,需循环调用
recvmsg直到读取完整的nlmsg_len - 若需要驱动主动通知多进程,可通过
setsockopt加入对应组播组
三、调试验证方法
- 内核端:用
dmesg查看printk输出 - 用户空间:添加日志打印,或用
strace -e trace=sendmsg,recvmsg跟踪Netlink消息 - 流量分析:加载
nlmon内核模块,用tcpdump捕获Netlink报文
内容的提问来源于stack exchange,提问作者kaleeswaran
相关产品推荐
相关产品推荐

