You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.20 04:10:03