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

【WiFi软件开发】hostapd代码学习本文介绍了 wifi 软件开发中常用的 hostapd 模块 对 hostapd 在 wifi 软件架构中的功能 hostapd 核心代码和基于 hostapd 的开发给出了讲解

大家好,欢迎来到IT知识分享网。


前言

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


一、hostapd是什么?

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

三、hostapd的代码框架

1.hostapd框架图

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

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

四、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用于和上层通信。

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和上层通信方式二)流程

**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的学习总结,欢迎讨论交流。

免责声明:本站所有文章内容,图片,视频等均是来源于用户投稿和互联网及文摘转载整编而成,不代表本站观点,不承担相关法律责任。其著作权各归其原作者或其出版社所有。如发现本站有涉嫌抄袭侵权/违法违规的内容,侵犯到您的权益,请在线联系站长,一经查实,本站将立刻删除。 本文来自网络,若有侵权,请联系删除,如若转载,请注明出处:https://haidsoft.com/116803.html

(0)
上一篇 2025-11-26 09:15
下一篇 2025-11-26 09:26

相关推荐

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注微信