【WiFi软件开发】hostapd代码学习


前言

本文主要介绍hostapd及其代码框架、主要代码流程和应用等,作为学习笔记用于记录和分享,希望能吸引到更多志同道合的朋友多多交流。


一、hostapd是什么?

首先,重新认识一下WiFi。我们日常使用的WiFi主要基于IEEE802.11协议实现设备认证关联、通信等过程,而IEEE802.11i协议(无线安全标准)作为IEEE802.11协议的一部分,制定了无线安全接入的标准。例如通过输入wifi密码接入wifi就是使用WPA或WPA2或WPA3的加密方式实现的,这些不同的加密方式都在IEEE802.11i协议中给出定义和使用标准。
那么设备(STA)和路由器(AP)是如何实现这样的接入标准呢?Supplicant是无线客户端上实现关联认证功能的组件,wpa_supplicant是STA上实现密钥管理和认证的supplicant软件,对应AP端的软件则为hostapd。
Hostapd是一个运行在linux用户态的守护进程。向上hostapd可以读取用户的配置文件,接收用户的管理信息,向下hostapd可以通过nl80211来控制底层的无线状态,通过发展和迭代,hostapd已经不仅仅支持最初的密钥管理和认证关联,还支持wifi的信道、标准甚至mu-mimo、ofdm等配置。总的来说。hostapd连接了上层用户配置模块和底层无线内核,作为中间件起到了向下set和向上get的功能。

二、hostapd在wifi软件架构中扮演的角色

Linux无线设备的软件架构图如下,可以看到hostapd作为一个用户态进程,主要起到配置和管理的功能。
在这里插入图片描述

三、hostapd的代码框架

1.hostapd框架图

在这里插入图片描述
上图是hostapd的一个代码框架,hostapd是一个后台程序,hostapd_cli是跟hostapd对应的前台命令行程序,hostapd_cli是一个机遇文本的、与hostapd进行交互的前台程序,通过hostapd_cli可以查看当前无线的认证状态、 .11和.1x的MIBS等。hostapd_cli有两种模式:交互模式和命令行模式,没输入参数时,将进入交互模式,help可以查看可用的命令。

2.各个模块的文件位置和功能简介

基于hostapd_v2.9版本的功能模块分析及代码量统计如下:
(hostapd与内核驱动等模块的关系)

四、hostapd核心代码跟读

1.启动流程和eloop

从上文hostapd框架图中可以看到hostapd的核心是一个event loop(eloop)模块,所有消息事件的注册和消息的收发都在此中进行,接下来将对hostapd启动的代码流程和eloop注册过程进行介绍。

首先,hostapd通常通过命令启动,下面给出一个启动命令示例:

hostapd -B HOSTAPD_CONFIG_FILE -g HOSTAPD_GLOBAL_FILE -P HOSTAPD_PID_FILE  &

可以看到启动命令中有-B,-g,-P多个启动参数,分别表示该hostapd进程使用的config文件、global通信文件、pid文件。此外hostapd还支持更多启动参数,具体可见./hostapd/main.c文件。

hostapd中在main函数中读取启动命令,具体的启动代码流程见下:

main
    getopt(argc, argv, "b:Bde:f:hi:KP:sSTtu:vg:G:q");       //读命令行启动参数
    hostapd_global_init(&interfaces, entropy_file)          //初始化eloop这个全局变量并进入eloop死循环中
        eap_server_register_methods                         //注册eap server支持的安全模式,并存放在一个链表里面
        eloop_init                                         //初始化全局变量eloop结构体
        random_init                                        //对各个事件注册
            eloop_register_read_sock(random_fd, random_read_fd, NULL, NULL);
                eloop_register_sock
                    eloop_sock_table_add_sock               //将相应的handler和data放进sock_table表中,实现注册
    hostapd_interface_init                                  //读取hostapd配置文件并进行分配和解析
        hostapd_init
            config_read_cb(hapd_iface->config_fname);
                hostapd_config_read                         //读取hostapd配置文件
    hostapd_driver_init(interfaces.iface[i])                //获取配置信息保存在iface[i]中
    hostapd_setup_interface(interfaces.iface[i])            //设置接口
        setup_interface
            start_ctrl_iface(iface)							//初始化ctrl_iface接口,用于和上层用户通信
                ctrl_iface_init(hapd)
                    interfaces.ctrl_iface_init = hostapd_ctrl_iface_init;
                        hostapd_ctrl_iface_init
                            hostapd_ctrl_iface_path(hapd)
                                eloop_register_read_sock(s, hostapd_ctrl_iface_receive, hapd, NULL)         //监听ctrl_interface
            hostapd_set_country								//设置WiFi国家码信息
            setup_interface2								//将配置信息写入内核
                hostapd_setup_interface_complete
                    hostapd_setup_interface_complete_sync
                        hostapd_set_freq
                        hostapd_set_rts
                        hostapd_tx_queue_para,              //进入netlink层
                        hostapd_set_state
    hostapd_global_run(&interfaces, daemonize, pid_file)	//启动接口
        tncs_global_init()                                  //tns初始化(Trusted Network Connect)
        os_daemonize(pid_file)                              //后台运行
        eloop_run();
            eloop_process_pending_signals                   //对发生的信号进行处理,会执行eloop_register_signal中注册的信号对应handler函数
            eloop_sock_table_set_fds(&eloop.readers, rfds);                   //将表中的socket加入到集合rfds中,socket是在eloop_register_sock中提前注册并绑定handler函数
            eloop_sock_table_set_fds(&eloop.writers, wfds);
            eloop_sock_table_set_fds(&eloop.exceptions, efds);
            res = select(eloop.max_sock + 1, rfds, wfds, efds, timeout ? &_tv : NULL);          //使用select函数来监听集合中socket的变化
            eloop_sock_table_dispatch(&eloop.readers, rfds);                   //执行相应的提前注册的函数,
            eloop_sock_table_dispatch(&eloop.writers, wfds);
            eloop_sock_table_dispatch(&eloop.exceptions, efds); 

可以看到在hostapd启动时主要做了这样几件事:eloop注册、读config文件、基于config文件内容启动无线接口、运行eloop用于和上层通信。

eloop中注意三点:
1、eloop.pending_terminate在(sig == SIGINT || sig == SIGTERM)时置1,2秒后发送SIGALRM信号并终止进程。而如果能处理到此信号会取消此前的SIGALRM定时,避免了特殊信号被阻塞而报错
2、eloop_run的循环和eloop_sock_table_dispatch均有对.changed标志位进行检测的操作,如果changed为1,则进行下一轮循环或结束执行当前socket对应的handler函数。这样做是因为socket可能在信号处理程序或超时处理程序中被关闭并重新打开,并使用相同的文件描述符(FD),此时我们必须跳过前面的结果,再次检查当前注册的任何套接字是否有事件发生。
3、eloop中不仅有对信号的监听,也对socket进行监听:
hostapd中注册信号举例:eloop_register_signal(SIGHUP, handle_reload, interfaces); SIGHUP对应reload事件。
hostapd中注册socket举例:eloop_register_read_sock(s, hostapd_ctrl_iface_receive, hapd, NULL) ctrl_interface对应socket绑定hostapd_ctrl_iface_receive事件。

2.config文件读取(hostapd和上层通信方式一)和参数下发配置流程

hostapd中的配置文件(config文件)定义了网络接口的关键参数,上层用户可以通过修改config文件设置想要的无线的参数,hostapd基于文件内容对无线接口进行配置。config文件的示例在源代码的./hostapd/hostapd.conf中给出。上文中提到了初始化hostapd时读取config文件的过程,在此给出hostapd读取新的config文件并向下层配置的具体流程:

int hostapd_reload_config(struct hostapd_iface *iface)
    newconf = iface->interfaces->config_read_cb(iface->config_fname);        //读取新的config
        interfaces.config_read_cb = hostapd_config_read;
            hostapd_config_read                   							//读config文件
                hostapd_config_fill
                    hostapd_config_vht_capab                   				//读文件参数,此处以配置vht_cap为例
    if (hostapd_iface_conf_changed(newconf, oldconf))        				//新、旧config不同时,初始化新接口并启用
        iface = hostapd_init(interfaces, fname);
            hostap_ioctl_prism2param(drv, PRISM2_PARAM_HOSTAPD, 1)
        res = hostapd_enable_iface(iface);
            hostapd_setup_interface(hapd_iface)
                ret = setup_interface(iface);
                    hostapd_validate_bssid_configuration(iface)
                    start_ctrl_iface(iface)									//启用无线接口
                        iface->interfaces->ctrl_iface_init(hapd)
                        int hostapd_ctrl_iface_init(struct hostapd_data *hapd)
                    hostapd_set_state(iface, HAPD_IFACE_COUNTRY_UPDATE);	//设置无线接口状态
                    hostapd_set_country(hapd, country)						//设置国家码信息
                    setup_interface2(iface)									//设置无线接口,主要用于向内核和驱动中配置参数
                        hostapd_set_oper_chwidth(iface->conf, ch_width);
                        ret = hostapd_select_hw_mode(iface);
                        hostapd_setup_interface_complete
                            hostapd_setup_interface_complete_sync
                                hostapd_set_freq
                                    hapd->driver->set_freq(hapd->drv_priv, &data)
                                    .set_freq = i802_set_freq,
                                        nl80211_set_channel(bss, freq, 0)
                                            send_and_recv_msgs(drv, msg, NULL, NULL, NULL, NULL);   //和nl80211的通信本质上还是socket

3.hostapd接收上层socket消息(hostapd和上层通信方式二)流程

除了上文中提到的config文件读写方式,hostapd还可以通过socket接收上层消息。
hostapd中本身提供了两个socket用于通信:global_ifacectrl_interface。其中global_iface主要用于接口开关等消息接收,ctrl_interface用于接口的配置消息接收,例如wps启动等。下文给出ctrl_interface相关代码流程:

**socket注册:**
hostapd_ctrl_iface_init
	eloop_register_read_sock(s, hostapd_ctrl_iface_receive, hapd, NULL)			//注册ctrl_interface对应的handler函数

**接收SET消息**
	hostapd_ctrl_iface_receive                      								//接收消息
	recvfrom(sock, buf, sizeof(buf) - 1, 0, (struct sockaddr *) &from, &fromlen)	//接收socket消息
    hostapd_ctrl_iface_receive_process
    	os_strncmp(buf, "SET ", 4)
	        hostapd_ctrl_iface_set(hapd, buf + 4)
	            hostapd_set_iface(hapd->iconf, hapd->conf, cmd, value)
	                hostapd_config_fill(conf, bss, field, value, 0)                 //读配置文件
	sendto(sock, reply, reply_len, 0, (struct sockaddr *) &from, fromlen)			//回复socket消息

五、hostapd的实际使用

1.与上层实现socket双向通信

ctrl_interface实现的接收消息过程通常是单向的,如果我们作为上层用户想获取hostapd中的某项参数,同样可以通过socket的方式进行获取,实现双向通信。下面给出实例代码:

**构建请求消息**
    memcpy(macAddr, mpskStaId->macAddr, sizeof(macAddr));

    memset(&tCmdBuf, 0, sizeof(T_PRIVATE_CMD_BUF));
    memcpy(tCmdBuf.cmdFlag, PRIVATE_CMD_TO_HOSTAPD, strlen(PRIVATE_CMD_TO_HOSTAPD));
    tCmdBuf.cmdId = GET_STA_MPSK_KEY;

    memset(pBuf, 0, iLen);
    memcpy(pBuf, &tCmdBuf, sizeof(T_PRIVATE_CMD_BUF));
    memcpy(pBuf + sizeof(T_PRIVATE_CMD_BUF), macAddr, sizeof(macAddr));

    memset(rcvBuf, '\0', rcvLen);
    iRet = _wlan_send_rcv_to_hostapd(iIfIndex, pBuf, iLen, rcvBuf, rcvLen);
**上层设置socket发送和接收消息**
INT32 _wlan_send_rcv_to_hostapd(INT32 iIfIndex, VOID *msg, INT32 msgLen, VOID *recvBuf, INT32 recvBufLen)
{
    INT32 iSock, iLen, iCardIndex;
    struct sockaddr_un socket_to = {0};
    struct sockaddr_un sun = {0};

    if (NULL == msg || 0 == msgLen || iIfIndex < 0 || iIfIndex >= MULTICARD_WLAN_CFG_MAX)
    {
        LogError("param error!");
        return MGR_FAIL;
    }

    iCardIndex = iIfIndex / WLAN_BSSTOTAL_PER_CARD;

    iSock = socket(PF_UNIX, SOCK_DGRAM, 0);
    if (iSock < 0)
    {
        LogError("create socket error!");
        return MGR_FAIL;
    }
    sun.sun_family = AF_UNIX;
    SNPRINTF(sun.sun_path, sizeof(sun.sun_path), "%s", HOSTAPD_MPSK_CTRL_IFACE_DIR);		//这里绑定一个新的socket文件HOSTAPD_MPSK_CTRL_IFACE_DIR进行发送和接收

    if (bind(iSock, (struct sockaddr *)&sun, sizeof(struct sockaddr_un)) < 0)
    {
        close(iSock);
        LogError("bind socket error!");
        return MGR_FAIL;
    }

    socket_to.sun_family = AF_UNIX;
    SNPRINTF(socket_to.sun_path, sizeof(socket_to.sun_path), HOSTAPD_CTRL_IFACE_DIR"%d/wlan%d", iCardIndex, iIfIndex);
    iLen = sendto(iSock, msg, msgLen, 0, (struct sockaddr *)&socket_to, sizeof(struct sockaddr_un));
    if (iLen <= 0)
    {
        LogDebug("send msg to hostapd error! iLen=%d", iLen);
        close(iSock);
        unlink(HOSTAPD_MPSK_CTRL_IFACE_DIR);
        return MGR_FAIL;
    }

    iLen = recv(iSock, recvBuf, recvBufLen, 0);
    if (iLen <= 0)
    {
        LogDebug("receive msg from hostapd error! iLen=%d", iLen);
        close(iSock);
        unlink(HOSTAPD_MPSK_CTRL_IFACE_DIR);
        return MGR_FAIL;
    }

    LogError("recvBuf=%s,", recvBuf);
    close(iSock);
    unlink(HOSTAPD_MPSK_CTRL_IFACE_DIR);
    return MGR_OK;
}
**修改hostapd中hostapd_ctrl_iface_receive函数进行消息接收和发送**
static void hostapd_ctrl_iface_receive(int sock, void *eloop_ctx,
                                       void *sock_ctx)
{
	...
    u8 stamac[6] = {0};
    struct sta_info *sta;
    const char *keyid = {0};

	//接收消息
    res = recvfrom(sock, buf, sizeof(buf) - 1, 0,
                   (struct sockaddr *) &from, &fromlen);
    if (res < 0)
    {
        wpa_printf(MSG_ERROR, "recvfrom(ctrl_iface): %s",
                   strerror(errno));
        return;
    }
    buf[res] = '\0';

    if (os_strncmp(buf, PRIVATE_CMD_TO_HOSTAPD, os_strlen(PRIVATE_CMD_TO_HOSTAPD)) == 0)	//对比消息格式
    {
        T_PRIVATE_CMD_BUF *pCmdBuf = (T_PRIVATE_CMD_BUF *)buf;
        if (pCmdBuf->cmdId == GET_STA_MPSK_KEY)
        {
            reply = os_malloc(reply_size);
            if (reply == NULL)
            {
                wpa_printf(MSG_ERROR, "reply malloc error");
                if (sendto(sock, "FAIL\n", 5, 0, (struct sockaddr *) &from,
                        fromlen) < 0)
                {
                    wpa_printf(MSG_DEBUG, "CTRL: sendto failed: %s",
                            strerror(errno));
                }
                return;
            }

            memcpy(stamac, buf + sizeof(T_PRIVATE_CMD_BUF), sizeof(stamac));
            wpa_printf(MSG_ERROR, "stamac=%x:%x:%x:%x:%x:%x", stamac[0], stamac[1], stamac[2], stamac[3], stamac[4], stamac[5]);

            sta = ap_get_sta(hapd, stamac);
            if (sta == NULL)
            {
                wpa_printf(MSG_ERROR, "sta NULL");
                memcpy(reply, "sta NULL\0", 9);
                reply_len = strlen(reply);
                goto done;
            }
            keyid = ap_sta_wpa_get_keyid(hapd, sta);
            if (keyid ==  NULL)
            {
                wpa_printf(MSG_ERROR, "keyid NULL");
                memcpy(reply, "keyid NULL\0", 11);
                reply_len = strlen(reply);
                goto done;
            }

            wpa_printf(MSG_ERROR, "keyid=%s", keyid);
            memcpy(reply, "keyid=", 6);
            memcpy(reply + 6, keyid, strlen(keyid));
            memcpy(reply + 6 + strlen(keyid), "\0", 1);
            reply_len = strlen(reply);
            wpa_printf(MSG_ERROR, "keyid=%s, reply=%s, reply_len=%d", keyid, reply, reply_len);
            goto done;
        }
    }

	...
done:
    if (sendto(sock, reply, reply_len, 0, (struct sockaddr *) &from,
               fromlen) < 0)
    {
        wpa_printf(MSG_DEBUG, "CTRL: sendto failed: %s",
                   strerror(errno));
    }
    os_free(reply);
}

2.实现多个config文件分别控制不同vap

hostapd的config文件通常包含了当前设备所有网络接口(VAP)的配置信息,这导致了其中一个VAP通过hostapd重启,所有VAP均会重启。可以通过配置多个config文件,每个config文件包含一个VAP的配置信息实现各个VAP独立配置,配置命令如下,通过global_iface接口实现:

wpa_cli -g  HOSTAPD_CLOBAL_IFACE raw ADD bss_config=IFACE_NAME:IFACE_CONFIG_FILE_NAME 

总结

以上就是个人对hostapd的学习总结,欢迎讨论交流。

  • 21
    点赞
  • 28
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Hostapd是一种用于创建无线接入点的软件,可以配合无线适配器在计算机上运行。5G WiFi是指IEEE 802.11ac标准的WiFi协议,可以提供更快的速度和更高的带宽,比以前的802.11n协议更先进。 要配置Hostapd以支持5G WiFi,需要按照以下步骤进行: 1. 确认计算机的无线适配器支持5G WiFi,并且已经正确安装和配置。可以通过运行lspci或lsusb命令来查看适配器的详细信息。 2. 安装Hostapd软件,并确保版本足够新,支持802.11ac标准。可以通过命令sudo apt-get install hostapd来安装Hostapd,根据自己的操作系统选择合适的命令。 3. 配置Hostapd的配置文件,添加以下几行代码: interface=wlan0 driver=nl80211 ssid=5g_wifi hw_mode=a channel=36 ieee80211n=1 ieee80211ac=1 这里,wlan0是无线适配器名称,可以根据自己的适配器名称进行修改;driver是驱动类型,一般选择nl80211;ssid是无线网络名称,可以自定义;hw_mode指定使用5GHz频段;channel是频道号,根据需要选择;ieee80211n和ieee80211ac指定支持的WiFi协议,都需要设置为1才能支持5G WiFi的速度。 4. 运行Hostapd,启动无线接入点。可以使用命令sudo hostapd /etc/hostapd/hostapd.conf来启动Hostapd,其中/etc/hostapd/hostapd.conf是配置文件的路径。 通过上述步骤,就可以成功配置Hostapd以支持5G WiFi了。实际上,Hostapd还可以配置更多的参数,如安全性设置、客户端连接限制等,可以根据自己的需求进行配置。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值