(七)hostapd整体梳理

最后更新于:2022-04-01 10:53:39

这篇主要对hostapd这个程序做一个整体的梳理,自己也觉得前面说的都好乱,尽是一些代码的分析,过于细节了。 关于EAP状态机的转化,这里就不多说了,[点击打开链接](http://download.csdn.net/detail/lee244868149/8288875) 这里可以下载原文档,知道的是自己不会比原文档写的还好的,所以就放弃了介绍状态机了。 先理解一下 struct hapd_interfaces interfaces 这个结构体对象, 这个结构体里面封装的是一个count成员和一个 struct hostapd_iface  **iface成员: ~~~ struct hapd_interfaces interfaces { size_t  count; struct hostapd_iface  **iface;   } ~~~ 所以interfaces其实是对接口的封装,也许有多张无线网卡,那么就会有多个无线接口(注意区分空中接口),那么hostapd程序的interfaces就会封装多个 iface对象,并用count计数。比如我现在hostapd需要用两张网卡来创建两个接口,一个是2.4G、一个是5G的网络,那么调用hostapd 传入参数时,就需要传入两个配置文件,count将会获得值2,并且会创建两个结构体指针 iface[0]和iface[1] (  struct hostapd_iface  *iface[ ] ),用来分别存放两张网卡的各自操作和信息等。   下面只分析一张网卡(注意下面说的网卡都是指无线网卡)的整体过程,因为不同无线网卡的区别主要体现在驱动方面,所以hostapd程序来说,不同网卡之间的处理过程都差不多的(比较数据包格式和通信协议没差啊)。 main函数开始的for循环就不在重复了,主要注意的是-B选项和-e选项指定了 程序以后台进程运行和指定了entropy文件罢了。 下面将以3条线开始整理,分别是 hostapd_global_init()    hostapd_interfaces_init()   hostapd_global_run() , 关于出错处理时,需要注意一下这个进程的重新加载,判断reloading以后调用execvp()函数实现重载。 ### 一、 hostapd_global_init()   实现安全方法的注册、eloop初始化、entropy文件的读取和处理、退出中断的注册 1. 注册方法 eap_server_register_methods() 首先调用各种方法对应的注册函数,定义struct eap_method *eap 对像,给这个对象申请空间后,给eap对象中的操作函数指针赋值,最后使用eap_server_method_register()将本方法添加进 method的链表中。 2.eloop初始化 eloop_init() 将struct eloop_data  eloop对象清空后,将eloop.timeout添加到双链表中,最后注册一个SIGSAVE中断,以在特定情况下终止进程。 3.entropy文件的读取和处理 random_init() 4.退出中断的注册  eloop_register_signal_terminate() 这里主要完成eloop.teminate = 1 ,当信号产生时, 这个变量置1,能够让后面的while循环退出,终止程序,第二个参数interfaces主要用来指定这个信号发送给哪张无线网卡(当前无线网卡)。 ### 二、 hostapd_interfaces_init()    这个函数很关键,接下来慢慢细说! struct hostapd_iface *iface  这个对象封装了对接口操作的各种函数以及对接口描述的各种参数。 iface = hostapd_init (config_fname);  注意着句话,函数的返回值赋值给了iface,而传入的参数是config_fname(配置文件路径及名称),我们可以猜想这句话就是要完成将配置文件中的内容读取出来,并存放到iface对象中。 1. 根据配置文件配置接口  hostapd_init()  为了具体一点,现在将代码列出来: ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-04-15_57108d8e0217a.jpg) 首先定义三个结构体对象,分别用来存放接口信息、接口配置信息和报文协议等信息——hostapd_iface、 hostapd_conf、 hostapd_data hapd_iface->reload_config = hostapd_reload_config  需要刷新或者修改了配置文件需要重新加载的时候,调用这个函数 hapd_iface->config_read_cb = hostapd_config_read 先加载默认的配置,然后加载配置文件中的配置,存放到conf对象中 hapd_iface-> config_fname  存放配置文件的路径信息 hapd_iface->ctrl_iface_init =hostapd_ctrl_iface_init   这个函数很重要,主要对初始化套接字通信,包括调用socket和bind函数eloop_register_read_sock,实现套接字通信,这里调用了hostapd_ctrl_iface_receive(),里面有receivefrom和sendto函数实现通信。 hapd_iface->conf = conf 存放读取的配置信息 hapd->msg_ctx = hapd   这里存放各种要发送的信息,比如报文信息、协议信息等, 因为hapd是struct hostapd_data 结构体对象 接下来对bss进行初始化 2.驱动程序初始化  hostapd_driver_init() 首先对启动可能用到的各种params进行初始化赋值,然后用hapd->driver_priv = hapd->driver-> hostapd_init( hapd, &params) 语句,用params中的参数对驱动进行初始化。 那么hostapd_init调用的是哪里的函数呢?这个路径的指定在初始化iface时,在iface->BSS[0] 中,hostapd_init将会调用 src/ driver / dirver_madwifi.c 中的 madwifi_init函数,具体驱动应用程序做了什么,就不拓展了。 hapd->driver->get_capa 需要留意一下。 3.设置接口配置 hostapd_setup_interface()      "setup_interface" 使用的hostapd_data对象还是来自于 iface->bss[0]  ,首先使用hostapd_validate_bssid_configuration验证iface中的BSSID是否可用,调用hosta_get_hw_features获取硬件信息,最后调用hostapd_setup_interface_complete返回结果。 其实在上面那个函数中,是做了好多设置才返回的,比如通道、频率、硬件模式(hw_mode)、传输速率、RTS、帧、热拔插、WPS等等。 ### 三、 hostapd_global_run() 1. tncs_global_init 初始化 2.os_daemonize()让本进行以守护进程运行 3.eloop_run() 将申请的标准输入、输出、出错文件描述符集,用select函数进行监听,调用eloop_process_pending_signals处理即将到来的中断信号,接下来进行超时处理。 还有关于驱动那一块还没有整理,先留着吧,说不定哪一天兴趣来了在看看,花这么多时间才弄清楚这个小小的hostapd,感觉有点不值,有时候问自己为什么要花时间去弄明白人家已经做好的东西呢,直接拿过来用不就好了吗,没办法,也许嵌入式开发就是这样吧,也许是性格使然。
';

(六)疑问整理

最后更新于:2022-04-01 10:53:37

1. 程序在哪里加载了配置文件呢? 在哪里设进去了呢? main 中hostapd_interface_init  调用 hostapd_init ,conf = interfaces->config_read_cb(hapd_iface->config_fname);  其实config_read_cb 指向hostapd_config_read 函数,它将配置文件(config_fname)中的配置选项读取到 conf 对象中,便后面使用。 hostapd_interface_init有调用hostapd_setup_interface 将config里面的值设置进进程里面。 2.中断处理函数在哪里注册? 注册了哪些中断处理函数呢? 在main函数的开始,定义了一个对象: struct hostapd_interfaces interfaces 这个对象在整个程序中起到一定贯穿作用,我们来看看hostapd_global_init()中对中断注册函数的调用(这个调用其他版本也可能在别的地方): ~~~ eloop_register_signal_terminate(handle_term, interfaces); ~~~ 这里分成了两部分,handle_term将调用函数 ~~~ static void handle_term(int sig, void *eloop_ctx, void *signal_ctx) { wpa_printf(MSG_DEBUG, "Signal %d received - terminating", sig); eloop_terminate(); } void eloop_terminate(void) { eloop.terminate = 1; } ~~~ 这里将terminate 置1,在后面能够引起 eloop_run中的while循环退出,而interfaces部分,将会在eloop_register_signal函数中赋值给 eloop.signal.user_data,以便中断处理函数调用,同时将eloop.pending_terminate 置1。 3.socket在哪里创建的呢? socket文件描述符集怎么实现select监听, ioctl控制呢? main 中hostapd_interface_init  调用 hostapd_init, hapd_inface-> ctrl_inface_init = hostapd_ctrl_inface_init, 其实调用的是hostapd_ctrl_inface_init函数,这个函数的重要性就不强调了,因为它在数据 收发中起到关键的作用。 hostapd_ctrl_inface_init函数使用了socket函数(注意第一个选项是PF_UNIX)、bind函数、connect函数、eloop_register_read_sock函数等直接和 l2_packet_linux.c 相关。 接着eloop_run函数while循环中将使用select对socket进行监听,示意图如下。 ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-04-15_57108d8ddd9cb.jpg) 4.数据包是怎么发送接收的呢? l2_packet_send 和 l2_packet_receive这两个函数在 src/ l2_packet/ l2_packet_linux.c 中, l2_packet_init 函数中使用ioctrl监听数据,然后通过socket将数据发送出去。 5.那么hostapd进程是怎么样和madwifi进行交互的呢?  将在后面madwifi部分介绍了 6.驱动是在什么时候加载进内核的呢? main 中hostapd_interface_init  调用hostapd_driver_init,里面有一句hapd->driver->hapd_init(hapd, &params),将会调用 src/ driver / dirver_madwifi.c 中的 madwifi_init函数。 Madwifi_init 调用了l2_packet_init() 函数, 该函数有实现很多功能,参见src下的l2_packet目录,譬如windows下可用的winpcap等,这实际就是类似抓包的了,不过在链路层而已。 调用的时候drv->sock_xmit = l2_packet_init(drv->iface, NULL, ETH_P_EAPOL,handle_read, drv, 1);注册了handle_read。意思就是收到ETH_P_EAPOL协议帧后调用handle_read handle_read实现如下: ~~~ static void handle_read(void *ctx, const u8 *src_addr, const u8 *buf, size_t len) { struct madwifi_driver_data *drv = ctx; struct hostapd_data *hapd = drv->hapd; struct sta_info *sta; sta = ap_get_sta(hapd, src_addr); if (!sta || !(sta->flags & WLAN_STA_ASSOC)) { printf("Data frame from not associated STA %s/n", ether_sprintf(src_addr)); /* XXX cannot happen */ return; } ieee802_1x_receive(hapd, src_addr, buf + sizeof(struct l2_ethhdr), len - sizeof(struct l2_ethhdr)); } ~~~ 在此,调用了ieee802_1x_receive。 l2_packet_init里面调用了int eloop_register_read_sock(int sock, eloop_sock_handler handler, void *eloop_data, void *user_data) 这里handler即l2_packet_receive,最后通过 static int eloop_sock_table_add_sock(struct eloop_sock_table *table, int sock, eloop_sock_handler handler,  void *eloop_data, void *user_data) 将socket可读消息的handler回调函数设为了l2_packet_receive。 再一次看这个,当select到read-sock有消息时,最终call到这里 static void l2_packet_receive(int sock, void *eloop_ctx, void *sock_ctx) 到这里hostapd这个程序写的差不多了,里面好多东西都是参考或者说是copy   [点击打开链接](http://blog.csdn.net/njzhujinhua/article/category/2442809) 这里的,作为一个菜鸟深知理解的还不够到位,写这些东西也花了好几天时间,主要是为了加强自己的学习效果,里面肯定有错误的地方,希望能够得到指正。 接下来将写一篇hostapd的整体梳理,给自己一个完整的交代吧。   
';

(五)hostapd_global_run函数

最后更新于:2022-04-01 10:53:35

main函数的前面部分做好了传入参数的处理、config文件的读取、初始化等准备工作,这里将进入最主要的部分,hostapd_global_run函数。 这里就不贴繁琐的代码了,这个函数主要分三步: 1.调用tncs_global_init完成tnc相关的初始化,这里就不详细说了 2.  调用os_daemonize函数实现将该程序以后台进程运行,它主要实现过程是调用os_daemon将标准输入、标准输出和标准出错都重定向到/dev/null文件下,这样就不能通过 终端进行交互了,但是交互过程是使用hostapd_cli这个进程实现的,前面章节有介绍;然后检查pid_file文件的合法性。 3.  eloop_run核心函数,这个函数很重要,所以下面将详细介绍。 ~~~ void eloop_run(void) { #ifdef CONFIG_ELOOP_POLL int num_poll_fds; int timeout_ms = 0; #endif /* CONFIG_ELOOP_POLL */ #ifdef CONFIG_ELOOP_SELECT fd_set *rfds, *wfds, *efds; struct timeval _tv; #endif /* CONFIG_ELOOP_SELECT */ #ifdef CONFIG_ELOOP_EPOLL int timeout_ms = -1; #endif /* CONFIG_ELOOP_EPOLL */ int res; struct os_reltime tv, now; #ifdef CONFIG_ELOOP_SELECT rfds = os_malloc(sizeof(*rfds)); wfds = os_malloc(sizeof(*wfds)); efds = os_malloc(sizeof(*efds)); if (rfds == NULL || wfds == NULL || efds == NULL) goto out; #endif /* CONFIG_ELOOP_SELECT */ while (!eloop.terminate && (!dl_list_empty(&eloop.timeout) || eloop.readers.count > 0 || eloop.writers.count > 0 || eloop.exceptions.count > 0)) { struct eloop_timeout *timeout; timeout = dl_list_first(&eloop.timeout, struct eloop_timeout, list); if (timeout) { os_get_reltime(&now); if (os_reltime_before(&now, &timeout->time)) os_reltime_sub(&timeout->time, &now, &tv); else tv.sec = tv.usec = 0; #if defined(CONFIG_ELOOP_POLL) || defined(CONFIG_ELOOP_EPOLL) timeout_ms = tv.sec * 1000 + tv.usec / 1000; #endif /* defined(CONFIG_ELOOP_POLL) || defined(CONFIG_ELOOP_EPOLL) */ #ifdef CONFIG_ELOOP_SELECT _tv.tv_sec = tv.sec; _tv.tv_usec = tv.usec; #endif /* CONFIG_ELOOP_SELECT */ } #ifdef CONFIG_ELOOP_POLL num_poll_fds = eloop_sock_table_set_fds( &eloop.readers, &eloop.writers, &eloop.exceptions, eloop.pollfds, eloop.pollfds_map, eloop.max_pollfd_map); res = poll(eloop.pollfds, num_poll_fds, timeout ? timeout_ms : -1); #endif /* CONFIG_ELOOP_POLL */ #ifdef CONFIG_ELOOP_SELECT eloop_sock_table_set_fds(&eloop.readers, rfds); 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); #endif /* CONFIG_ELOOP_SELECT */ #ifdef CONFIG_ELOOP_EPOLL if (eloop.count == 0) { res = 0; } else { res = epoll_wait(eloop.epollfd, eloop.epoll_events, eloop.count, timeout_ms); } #endif /* CONFIG_ELOOP_EPOLL */ if (res < 0 && errno != EINTR && errno != 0) { wpa_printf(MSG_ERROR, "eloop: %s: %s", #ifdef CONFIG_ELOOP_POLL "poll" #endif /* CONFIG_ELOOP_POLL */ #ifdef CONFIG_ELOOP_SELECT "select" #endif /* CONFIG_ELOOP_SELECT */ #ifdef CONFIG_ELOOP_EPOLL "epoll" #endif /* CONFIG_ELOOP_EPOLL */ , strerror(errno)); goto out; } eloop_process_pending_signals(); /* check if some registered timeouts have occurred */ timeout = dl_list_first(&eloop.timeout, struct eloop_timeout, list); if (timeout) { os_get_reltime(&now); if (!os_reltime_before(&now, &timeout->time)) { void *eloop_data = timeout->eloop_data; void *user_data = timeout->user_data; eloop_timeout_handler handler = timeout->handler; eloop_remove_timeout(timeout); handler(eloop_data, user_data); } } if (res <= 0) continue; #ifdef CONFIG_ELOOP_POLL eloop_sock_table_dispatch(&eloop.readers, &eloop.writers, &eloop.exceptions, eloop.pollfds_map, eloop.max_pollfd_map); #endif /* CONFIG_ELOOP_POLL */ #ifdef CONFIG_ELOOP_SELECT eloop_sock_table_dispatch(&eloop.readers, rfds); eloop_sock_table_dispatch(&eloop.writers, wfds); eloop_sock_table_dispatch(&eloop.exceptions, efds); #endif /* CONFIG_ELOOP_SELECT */ #ifdef CONFIG_ELOOP_EPOLL eloop_sock_table_dispatch(eloop.epoll_events, res); #endif /* CONFIG_ELOOP_EPOLL */ } eloop.terminate = 0; out: #ifdef CONFIG_ELOOP_SELECT os_free(rfds); os_free(wfds); os_free(efds); #endif /* CONFIG_ELOOP_SELECT */ return; ~~~ 首先为三个文件描述符集申请空间:  rfds = os_malloc(sizeof( * rfds)); wfds = os_malloc(sizeof( * wfds)); efds = os_malloc(sizeof( * efds)); 然后进入while循环: while (!eloop.terminate && (eloop.timeout || eloop.readers.count > 0 || eloop.writers.count > 0 || eloop.exceptions.count > 0)) 它的循环条件如括号里面描述,正常情况都在这里面循环,除非terminate为1,而这个有信号处理设置,参见 ~~~ eloop_register_signal_terminate(handle_term, NULL); static void handle_term(int sig, void *eloop_ctx, void *signal_ctx) {    wpa_printf(MSG_DEBUG, "Signal %d received - terminating", sig);    eloop_terminate(); } void eloop_terminate(void) {    eloop.terminate = 1; } ~~~ 接下来对超时时间进行设置timeout,主要是为下面调用的select函数会用到超时时间做准备。 ~~~      if (timeout) {          os_get_reltime(&now);          if (os_reltime_before(&now, &timeout->time))              os_reltime_sub(&timeout->time, &now, &tv);          else              tv.sec = tv.usec = 0;          _tv.tv_sec = tv.sec;          _tv.tv_usec = tv.usec;      } ~~~ 将申请的文件描述符集与eloop对象相结合,并调用select函数对这些文件发生异常进行监听: ~~~      eloop_sock_table_set_fds(&eloop.readers, rfds);      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); ~~~ 最后eloop_process_pending_signals对发生的信号进行处理: ~~~ static void eloop_process_pending_signals(void) { int i; if (eloop.signaled == 0) //有没有信号产生,如果有,那么这个标志位将为1,说明有信号需要处理,如果为0,那么没有信号要处理,函数返回 return; eloop.signaled = 0; //将信号标示为置0,以便下次有信号产生时,置1 if (eloop.pending_terminate) { //如果不用处理后面将会产生的信号,则立即向进程发送一个SIGALARM信号,然后将这个标志置0 #ifndef CONFIG_NATIVE_WINDOWS alarm(0); #endif /* CONFIG_NATIVE_WINDOWS */ eloop.pending_terminate = 0; } for (i = 0; i < eloop.signal_count; i++) { //对信号标示进行处理 if (eloop.signals[i].signaled) { eloop.signals[i].signaled = 0; #ifndef CONFIG_NATIVE_WINDOWS eloop_register_signal(SIGHUP, handle_reload, NULL); //对中断信号和中断处理函数进行注册 eloop_register_signal(SIGUSR1, handle_dump_state, NULL); #endif /* CONFIG_NATIVE_WINDOWS */ eloop_register_signal_terminate(handle_term, NULL); eloop.signals[i].handler(eloop.signals[i].sig, //调用处理函数对相应的信号进行处理,那么到底调用的是什么处理函数呢?这些处理函数的注册是在 eloop.user_data, //什么地方呢?这个进程是怎么样对数据包进行处理的呢?在哪里处理呢? eloop.signals[i].user_data); } } } ~~~ 到这里,应该对hostapd这个程序的整体有了一定的把握,应该能看懂第一篇中的那张结构图了,但也有局限的地方,比如好多细节的地方没有讲清楚, 比如:数据包是在哪里接收的? 数据包是在哪里发送的? 数据包是这样存放的?这些处理函数是在哪里注册的? 客户选择的加密方式是怎么起作用的? hostapd怎么将一块网卡切换到了ap模式? 等等。 接下来将尽力弄清楚这些问题。
';

(四)interface的初始化

最后更新于:2022-04-01 10:53:32

前面讲了的初始,这一节将介绍interface的初始化 关于interface这个词在这里具体指的是什么,开始的时候自己也很模糊,经过多次询问和验证,个人将这个interface理解为无线网卡物理口,即我们用ifconfig打印出的wifi0和wifi1这样的接口。我们知道,hostapd的一个主要功能是将一个无线网卡切换成ap模式,让它以server端的角色运行,能够让其他无线设备连接上来。现在的路由器一般都支持2.4G和5G两种无线频段,所以在硬件上自然会有两块无线网卡,那么在这两块无线网卡都运行的时候,能够在ifconfig中看到wifi0和wifi1的状态都是up的,使用ps -w | grep hostapd 也能够查看到,hostapd到底使用的是哪个网卡的配置文件,到底启动了那块网卡。 这里需要辨别一下物理网卡口和空中接口的区别,空中接口多以ath0,ath1等字样命名,它们作为无线用户的接入点,可以创建多个,甚至多于网卡的数量,比如在路由器启动guest network功能的时候,用iwconfig命令可以查看到4个空中接口,这些都可以作为用户的接入点,他们主要是需要使用不同的用户名和密码进行认证,但最终数据包的处理还是经过实际的两块网卡进行的。 下面说一下,程序时怎么初始化interface的。 ~~~ for (i = 0; i < interfaces.count; i++) { interfaces.iface[i] = hostapd_interface_init(&interfaces, argv[optind + i], debug); if (!interfaces.iface[i]) { wpa_printf(MSG_ERROR, "Failed to initialize interface"); goto out; } } ~~~ 这个循环控制变量i的值是由interfaces.count = argc - optind 获得的,argc的值是所有传入参数的个数加一,optind的值是选项的偏移数,初始值是1.  比如 hostapd -B -e /etc/entropy  /etc/ath1.conf  /etc/ath0.conf 以这样的参数运行hostapd时,argc=6   optind=4,那么count的值将等于2,其实也可以这样理解:argc - optind就是非选项参数的个数,比如上面 -B是不带参数的选项,-e是带一个参数/etc/entropy的选项,那么剩下的/etc/ath1.conf   /etc/ath0.conf不是某个选项的参数,所以不需要经过getopt类似函数的处理,不会使optind的值加一,所以差计算后他们的个数是2. 接下来好好说一下hostapd_interface_init这个函数。 ~~~ static struct hostapd_iface * hostapd_interface_init(struct hapd_interfaces *interfaces, const char *config_fname, int debug) { struct hostapd_iface *iface; int k; wpa_printf(MSG_ERROR, "Configuration file: %s", config_fname); iface = hostapd_init(interfaces, config_fname); if (!iface) return NULL; iface->interfaces = interfaces; for (k = 0; k < debug; k++) { if (iface->bss[0]->conf->logger_stdout_level > 0) iface->bss[0]->conf->logger_stdout_level--; } if (iface->conf->bss[0]->iface[0] == '\0' && !hostapd_drv_none(iface->bss[0])) { wpa_printf(MSG_ERROR, "Interface name not specified in %s", config_fname); hostapd_interface_deinit_free(iface); return NULL; } return iface; } ~~~ 1.先看struct hostapd_iface 对interface的操作函数以及标示变量的封装 ~~~ struct hostapd_iface { struct hapd_interfaces *interfaces; void *owner; char *config_fname; // 这里存放配置文件路径和文件名 struct hostapd_config *conf; //将配置文件中的信息读取后,将存放在这个结构体中 char phy[16]; /* Name of the PHY (radio) */ //无线网卡芯片名 enum hostapd_iface_state { //描述接口状态 HAPD_IFACE_UNINITIALIZED, HAPD_IFACE_DISABLED, HAPD_IFACE_COUNTRY_UPDATE, HAPD_IFACE_ACS, HAPD_IFACE_HT_SCAN, HAPD_IFACE_DFS, HAPD_IFACE_ENABLED } state; size_t num_bss; //基础服务集合个数 struct hostapd_data **bss; //存放bss数据 unsigned int wait_channel_update:1; unsigned int cac_started:1; /* * When set, indicates that the driver will handle the AP * teardown: delete global keys, station keys, and stations. */ unsigned int driver_ap_teardown:1; int num_ap; /* number of entries in ap_list */ struct ap_info *ap_list; /* AP info list head */ struct ap_info *ap_hash[STA_HASH_SIZE]; unsigned int drv_flags; /* * A bitmap of supported protocols for probe response offload. See * struct wpa_driver_capa in driver.h */ unsigned int probe_resp_offloads; /* extended capabilities supported by the driver */ const u8 *extended_capa, *extended_capa_mask; unsigned int extended_capa_len; unsigned int drv_max_acl_mac_addrs; struct hostapd_hw_modes *hw_features; int num_hw_features; struct hostapd_hw_modes *current_mode; /* Rates that are currently used (i.e., filtered copy of * current_mode->channels */ int num_rates; struct hostapd_rate_data *current_rates; int *basic_rates; int freq; u16 hw_flags; /* Number of associated Non-ERP stations (i.e., stations using 802.11b * in 802.11g BSS) */ int num_sta_non_erp; /* Number of associated stations that do not support Short Slot Time */ int num_sta_no_short_slot_time; /* Number of associated stations that do not support Short Preamble */ int num_sta_no_short_preamble; int olbc; /* Overlapping Legacy BSS Condition */ /* Number of HT associated stations that do not support greenfield */ int num_sta_ht_no_gf; /* Number of associated non-HT stations */ int num_sta_no_ht; /* Number of HT associated stations 20 MHz */ int num_sta_ht_20mhz; /* Number of HT40 intolerant stations */ int num_sta_ht40_intolerant; /* Overlapping BSS information */ int olbc_ht; u16 ht_op_mode; /* surveying helpers */ /* number of channels surveyed */ unsigned int chans_surveyed; /* lowest observed noise floor in dBm */ s8 lowest_nf; unsigned int dfs_cac_ms; struct os_reltime dfs_cac_start; /* Latched with the actual secondary channel information and will be * used while juggling between HT20 and HT40 modes. */ int secondary_ch; #ifdef CONFIG_ACS unsigned int acs_num_completed_scans; #endif /* CONFIG_ACS */ void (*scan_cb)(struct hostapd_iface *iface); int num_ht40_scan_tries; ~~~ 2.hostapd_init()函数 ~~~ struct hostapd_iface * hostapd_init(struct hapd_interfaces *interfaces, const char *config_file) { struct hostapd_iface *hapd_iface = NULL; struct hostapd_config *conf = NULL; struct hostapd_data *hapd; //这个结构体很重要,存放了数据以及一些报文相关的操作和信息 size_t i; hapd_iface = os_zalloc(sizeof(*hapd_iface)); if (hapd_iface == NULL) goto fail; hapd_iface->config_fname = os_strdup(config_file); if (hapd_iface->config_fname == NULL) goto fail; <pre name="code" class="cpp"> interfaces->config_read_cb=hostapd_config_read conf = interfaces->config_read_cb(hapd_iface->config_fname); if (conf == NULL) goto fail; hapd_iface->conf = conf; hapd_iface->num_bss = conf->num_bss; hapd_iface->bss = os_calloc(conf->num_bss, sizeof(struct hostapd_data *)); if (hapd_iface->bss == NULL) goto fail; for (i = 0; i < conf->num_bss; i++) { hapd = hapd_iface->bss[i] = hostapd_alloc_bss_data(hapd_iface, conf, conf->bss[i]); if (hapd == NULL) goto fail; hapd->msg_ctx = hapd; } return hapd_iface; ~~~ hostapd_init内以该interface的配置文件作为参数调用hostapd_config_read。并将读取到的配置信息赋给成员变量struct hostapd_config *conf. 根据配置文件中bss的配置个数conf->num_bss的值通过调用hostapd_alloc_bss_data分配空间及相关设置,并对hapd_iface->bss[i]进行赋值, 其中hostapd_alloc_bss_data的定义为 static struct hostapd_data *hostapd_alloc_bss_data(struct hostapd_iface *hapd_iface, struct hostapd_config *conf, struct hostapd_bss_config *bss) 三个参数分别为本interface信息,本interface的配置信息,本interface配置信息中第i个bss的配置部分。 其中代码 (struct hostapd_data *hapd) hapd->iconf = conf; hapd->conf = bss; hapd->iface = hapd_iface; 使得hostapd_data中均有指向这几个配置的指针了。 既有: hapd_iface->bss[i]->iconf == hapd_iface->conf hapd_iface->bss[i]->conf == &hapd_iface->conf->bss[i] hapd_iface->bss[i]->iface == hapd_iface 所以struct hostapd_data,  struct hostapd_iface, struct hostapd_config, struct hostapd_bss_config之间是相互联系的。
';

(三)hostapd_global_init()函数

最后更新于:2022-04-01 10:53:30

### 一、预备知识(eap_sm、eap_method结构体) ~~~ struct eap_sm { //状态机,存储eap的状态 enum { //枚举eap的各种状态 EAP_DISABLED, EAP_INITIALIZE, EAP_IDLE, EAP_RECEIVED, EAP_INTEGRITY_CHECK, EAP_METHOD_RESPONSE, EAP_METHOD_REQUEST, EAP_PROPOSE_METHOD, EAP_SELECT_ACTION, EAP_SEND_REQUEST, EAP_DISCARD, EAP_NAK, EAP_RETRANSMIT, EAP_SUCCESS, EAP_FAILURE, EAP_TIMEOUT_FAILURE, EAP_PICK_UP_METHOD, EAP_INITIALIZE_PASSTHROUGH, EAP_IDLE2, EAP_RETRANSMIT2, EAP_RECEIVED2, EAP_DISCARD2, EAP_SEND_REQUEST2, EAP_AAA_REQUEST, EAP_AAA_RESPONSE, EAP_AAA_IDLE, EAP_TIMEOUT_FAILURE2, EAP_FAILURE2, EAP_SUCCESS2 } EAP_state; /* Constants */ int MaxRetrans; //最大重传次数,eap支持超时重发机制.eap_sm在初始化时赋值为5 struct eap_eapol_interface eap_if;//主要放些直接与消息相关的,如req及resp的数据,当前是req还是resp,是否到了重传的时机(retransWhile)等 /* Full authenticator state machine local variables */ /* Long-term (maintained between packets) */ EapType currentMethod; //当前采用的Method,初始为EAP_TYPE_NONE,其后根据响应中的type定或自选 int currentId; //当前eap id,开始设为-1,作为backend_AAA时被设为响应消息eapid,需要发送eapreq的时候设为nextId enum { METHOD_PROPOSED, METHOD_CONTINUE, METHOD_END } methodState; int retransCount;//传送次数 struct wpabuf *lastReqData;//记下已经发出的请求数据,如需要重传时需要发此数据 int methodTimeout; /* Short-term (not maintained between packets) */ Boolean rxResp; //收到消息的id为resp时设置rxResp为TRUE int respId;//收到的resp消息的id EapType respMethod; int respVendor; u32 respVendorMethod; Boolean ignore; enum { DECISION_SUCCESS, DECISION_FAILURE, DECISION_CONTINUE, DECISION_PASSTHROUGH } decision; /* Miscellaneous variables */ const struct eap_method *m; /* selected EAP method,当前选定的eap method */ /* not defined in RFC 4137 */ Boolean changed;//状态机是否改变,在不变时则退出状态机运行,后续可能要发送eapreq,eapsuccess或eapfailure,或在pending时不做事情 void *eapol_ctx, *msg_ctx;//eapol_ctx:上下文信息,在状态机初始化时指向session,之后不动;msg_ctx:尚未使用 struct eapol_callbacks *eapol_cb;//状态机初始化时设置eapol_cb。struct eapol_callbacks为多个需要用到的回调函数如get_eap_user等组成的结构体 void *eap_method_priv;//由各个eap method定义的数据,在EAP_INITIALIZE是sm->eap_method_priv = sm->m->initPickUp(sm)   指向eap_identity_data //在具体EAP method阶段则是具体eap_xxx_data u8 *identity; //在eap_identity_process内赋值,取自eap-resp/identity size_t identity_len; /* Whether Phase 2 method should validate identity match */ int require_identity_match; //EAP-GTC用到 int lastId; /* Identifier used in the last EAP-Packet */ struct eap_user *user; int user_eap_method_index; int init_phase2; //eap_ttls_phase2_eap_init  eap_peap_phase2_init两个函数内设置为1 void *ssl_ctx; //在状态机初始化eap_server_sm_init内设置为一个全局的g_ssl_context上下文。后者通过g_ssl_context = tls_init(NULL)实现初始化 struct eap_sim_db_data *eap_sim_db_priv;//指向系统配置的eap_sim/aka的配置信息,为eap_sim_db_data结构。主要含有与hlr的通信套接字信息,假名表, //重鉴权用户信息,pending的用户查询等 Boolean backend_auth; //是否作为backend authentication server Boolean update_user; //sm->identity是否更新了的标志,如为true时可能需要重新获取用户信息 int eap_server; //是作为eapserver还是passthrough int num_rounds; //eap交互次数,最大允许EAP_MAX_AUTH_ROUNDS=50次 enum { METHOD_PENDING_NONE, METHOD_PENDING_WAIT, METHOD_PENDING_CONT } method_pending; /*状态机初始化时method_pending为METHOD_PENDING_NONE,因业务需要,可以将method_pending设置为METHOD_PENDING_WAIT。 eap状态机在处理EAP_PROPOSE_METHOD或EAP_METHOD_RESPONSE时,如果为WAIT则什么不做,退出状态机。 如果为CONT则设置method_pending = METHOD_PENDING_NONE并继续执行EAP_METHOD_RESPONSE状态。 eap具体method业务在收到响应等需要的时候调用eap_sm_pending_cb,他会设置method_pending为CONT,这样再激活状态机他会继续执行*/ u8 *auth_challenge; u8 *peer_challenge; //均是eap-mschapv2鉴权过程中的参数,分别由server和peer生成的随机数 u8 *pac_opaque_encr_key; u8 *eap_fast_a_id; size_t eap_fast_a_id_len; char *eap_fast_a_id_info; enum { NO_PROV, ANON_PROV, AUTH_PROV, BOTH_PROV } eap_fast_prov; int pac_key_lifetime; int pac_key_refresh_time; int eap_sim_aka_result_ind; int tnc; //以上均取自配置文件,eap server用不着这些,可以到配置文件中查看配置的这些变量的值 u16 pwd_group; struct wps_context *wps; struct wpabuf *assoc_wps_ie; struct wpabuf *assoc_p2p_ie; Boolean start_reauth; u8 peer_addr[ETH_ALEN]; /* Fragmentation size for EAP method init() handler */ int fragment_size; int pbc_in_m1; const u8 *server_id; size_t server_id_len; #ifdef CONFIG_TESTING_OPTIONS u32 tls_test_flags; #endif /* CONFIG_TESTING_OPTIONS */ }; ~~~ ~~~ struct eap_method { //这个结构体用来存放每种加密方法的各种操作函数和变量 int vendor; //存放eap vender ID EapType method; //EapType是一个枚举类型,里面的值定义了method type const char *name; //存放method的名字,比如PSK void * (*init)(struct eap_sm *sm); //初始化eap method void * (*initPickUp)(struct eap_sm *sm); void (*reset)(struct eap_sm *sm, void *priv); struct wpabuf * (*buildReq)(struct eap_sm *sm, void *priv, u8 id); //处理一个eap request 请求包 int (*getTimeout)(struct eap_sm *sm, void *priv); Boolean (*check)(struct eap_sm *sm, void *priv, struct wpabuf *respData); void (*process)(struct eap_sm *sm, void *priv,struct wpabuf *respData); Boolean (*isDone)(struct eap_sm *sm, void *priv); u8 * (*getKey)(struct eap_sm *sm, void *priv, size_t *len); //从eap method中获取秘钥内容 Boolean (*isSuccess)(struct eap_sm *sm, void *priv); void (*free)(struct eap_method *method); //释放eap method 数据 #define EAP_SERVER_METHOD_INTERFACE_VERSION 1 int version; //peer端EAP interface版本 struct eap_method *next; // 用于建立链表,指向下一个节点 u8 * (*get_emsk)(struct eap_sm *sm, void *priv, size_t *len);//获取扩展的秘钥内容 }; ~~~ 上面两个结构体封装了很多参数和方法,显得尤其重要,接下来,我们进入hostapd_global_init()函数。 ~~~ <span style="color:#000000;">static int hostapd_global_init(struct hapd_interfaces *interfaces,const char *entropy_file) { os_memset(&global, 0, sizeof(global));//重置global变量 hostapd_logger_register_cb(hostapd_logger_cb); if (eap_server_register_methods()) { //注册eap server的加密方法 wpa_printf(MSG_ERROR, "Failed to register EAP methods"); return -1; } if (eloop_init()) { // wpa_printf(MSG_ERROR, "Failed to initialize event loop"); return -1; } random_init(entropy_file); #ifndef CONFIG_NATIVE_WINDOWS eloop_register_signal(SIGHUP, handle_reload, interfaces); eloop_register_signal(SIGUSR1, handle_dump_state, interfaces); #endif /* CONFIG_NATIVE_WINDOWS */ eloop_register_signal_terminate(handle_term, interfaces); for (i = 0; wpa_drivers[i]; i++) global.drv_count++; if (global.drv_count == 0) { wpa_printf(MSG_ERROR, "No drivers enabled"); return -1; } global.drv_priv = os_calloc(global.drv_count, sizeof(void *)); if (global.drv_priv == NULL) return -1; return 0; }</span> ~~~ 1. 使用eap_server_register_methods函数注册eap server支持的安全模式,并存放在一个链表里面,下图是支持的安全模式。 ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-04-15_57108d8dc08ea.jpg) ~~~ int eap_server_register_methods(void) { int ret = 0; #ifdef EAP_SERVER_IDENTITY if (ret == 0) ret = eap_server_identity_register(); #endif /* EAP_SERVER_IDENTITY */ #ifdef EAP_SERVER_MD5 if (ret == 0) ret = eap_server_md5_register(); #endif /* EAP_SERVER_MD5 */ #ifdef EAP_SERVER_TLS if (ret == 0) ret = eap_server_tls_register(); ....... ~~~ 根据宏开关来确认哪些安全模式是支持的,并调用相应协议的注册函数,注册一个加密安全模式放在struct eap_method链表中,因为这些安全模式注册函数都差不多,所以只介绍其中一种模式eap_server_psk_register()。 ~~~ int eap_server_psk_register(void) { struct eap_method *eap; int ret; eap = eap_server_method_alloc(EAP_SERVER_METHOD_INTERFACE_VERSION, EAP_VENDOR_IETF, EAP_TYPE_PSK, "PSK"); if (eap == NULL) return -1; eap->init = eap_psk_init; eap->reset = eap_psk_reset; eap->buildReq = eap_psk_buildReq; eap->check = eap_psk_check; eap->process = eap_psk_process; eap->isDone = eap_psk_isDone; eap->getKey = eap_psk_getKey; eap->isSuccess = eap_psk_isSuccess; eap->get_emsk = eap_psk_get_emsk; ret = eap_server_method_register(eap); if (ret) eap_server_method_free(eap); return ret; } ~~~ 首先定义一个struct eap_method 对象,用eap_server_method_alloc给这个对象申请一块空间,然后给这个对象根据不同的安全模式指向不同的操作函数,最后将这个eap对象通过eap_server_method_register函数添加到struct eap_method 结构体对象的链表里面。 2.使用eloop_init()对struct eloop_data eloop对象进行初始化,至于struct eloop_data的作用将在后面介绍。 ~~~ int eloop_init(void) { os_memset(&eloop, 0, sizeof(eloop)); dl_list_init(&eloop.timeout); #ifdef CONFIG_ELOOP_EPOLL eloop.epollfd = epoll_create1(0); if (eloop.epollfd < 0) { wpa_printf(MSG_ERROR, "%s: epoll_create1 failed. %s\n", __func__, strerror(errno)); return -1; } eloop.readers.type = EVENT_TYPE_READ; eloop.writers.type = EVENT_TYPE_WRITE; eloop.exceptions.type = EVENT_TYPE_EXCEPTION; #endif /* CONFIG_ELOOP_EPOLL */ #ifdef WPA_TRACE signal(SIGSEGV, eloop_sigsegv_handler); #endif /* WPA_TRACE */ return 0; ~~~ 这个函数主要是重置eloop对象和初始化链表,然后对eloop成员的一些赋值等 3.random_init() ~~~ void random_init(const char *entropy_file) { os_free(random_entropy_file); if (entropy_file) random_entropy_file = os_strdup(entropy_file); else random_entropy_file = NULL; random_read_entropy(); #ifdef __linux__ if (random_fd >= 0) return; random_fd = open("/dev/random", O_RDONLY | O_NONBLOCK); if (random_fd < 0) { #ifndef CONFIG_NO_STDOUT_DEBUG int error = errno; perror("open(/dev/random)"); wpa_printf(MSG_ERROR, "random: Cannot open /dev/random: %s", strerror(error)); #endif /* CONFIG_NO_STDOUT_DEBUG */ return; } wpa_printf(MSG_DEBUG, "random: Trying to read entropy from " "/dev/random"); eloop_register_read_sock(random_fd, random_read_fd, NULL, NULL); #endif /* __linux__ */ random_write_entropy(); } ~~~ 这里面eloop_register_read_sock很重要,具体的需要用源代码去深入跟踪。 4.最后是中断的注册和global对像的赋值等操作。 这篇主要对初始化过程进行了介绍,功能的具体实现将在后面篇幅中讲述。
';

(二)main之for循环

最后更新于:2022-04-01 10:53:28

欢迎转载,转载请注明出处。 下面从main函数开始源代码的分析,这一篇主要对开始处的for循环介绍,里面的选项和参数的处理方式,主要依赖于命令行或者脚本调用hostapd时,传进来的参数是什么 ~~~ int main(int argc, char *argv[]) { struct hapd_interfaces interfaces; int ret = 1, k; size_t i, j; int c, debug = 0, daemonize = 0, tnc = 0; char *pid_file = NULL; hostapd_logger_register_cb(hostapd_logger_cb); for (;;) { c = getopt(argc, argv, "Bde:hKP:tv"); switch (c) { case 'h': usage(); break; case 'd': debug++; if (wpa_debug_level > 0) wpa_debug_level--; break; case 'e': entropy_file = optarg; break;  case 'B': daemonize++; break; case 'K': wpa_debug_show_keys++; break; case 'P': os_free(pid_file); pid_file = os_rel2abs_path(optarg); break; case 't': wpa_debug_timestamp++; break; case 'v': show_version(); exit(1); break; default: usage(); break; } .... ~~~ 理解这个for循环,首先要很准确的理解getopt函数的使用,以及对argc和argv的把握,可以先参考 <a target=_blank href="http://blog.csdn.net/lee244868149/article/details/42025221">getopt 和getopt_long </a> 和 <a target=_blank href="http://blog.csdn.net/lee244868149/article/details/42002741">getopt解析命令行选项参数 </a>, 特别是理解optarg和optind代表的含义很重要。 -h 如果选项是-h,那么调用usage()函数,打印命令帮助信息 -d 当选择-d选项时,debug变量加一,wpa_debug_level调试级别变量减一。 在src/utils/wpa_debug.c中有定义:                                int wpa_debug_level = MSG_INFO;                                int wpa_debug_show_keys = 0;                                int wpa_debug_timestamp = 0; MSG_INFO是一个枚举成员,enum { MSG_MSGDUMP, MSG_DEBUG, MSG_INFO, MSG_WARNING, MSG_ERROR };它在第三个位置,所以MSG_INFO值为2,这个是默认值,在该级别时正常情况下基本没有日志输出。在这种情况下,就能打印MSG_INFO, MSG_WARNING, MSG_ERROR这三个级别的信息,在该级别时正常情况下基本没有 日志输出的,因而要想获得更多执行流程中的输出就需要增加debug级别,如果想把码流及各radius属性均打印出的话就将级别调为MSG_MSGDUMP了。 debug级别越高,那么对应的wpa_debug_level值就越小,输出的信息就越多 -e 指定entropy文件的路径 -B 该选项将hostapd进程作为守护进程运行,如果含有该选项则执行os_daemonize(pid_file),在utils/os_win32.c中该函数未实现, 在utils/os_unix.c中定义为int os_daemonize(const char *pid_file),<span style="color:#FF0000;">其中os_daemon即为daemon系统调,如果调用成功则获取进程id并写入pid_file指定的文件中</span>。 -K 该选项用于控制在调试时是否输出密钥相关key值。如需输出密钥还需要设置日志级别为MSG_DEBUG或以上 例如:wpa_hexdump_key(MSG_DEBUG, "EAP-TTLS: Derived implicit challenge", challenge, len); 如果选项中没有K显式说明,则即使日志级别为MSG_DEBUG或MSG_MSGDUMP也不会输出的 -P 设定pid_file的值,os_rel2abs_path 将参数提供的相对路径转为绝对路径。 -t 该项作用为在日志中包含时间戳信息,wpa_debug_timestamp的定义参见选项-d的说明,默认值为0,即不包含。 带时间戳效果为: 1270995804.000000: EAP: EAP entering state RECEIVED 1270995804.000000: EAP: parseEapResp: rxResp=1 respId=77 respMethod=21 respVendor=0 respVendorMethod=0 -v 打印版本信息并退出 这些选项主要用于hostapd程序被调用时,实现交互和传入参数的处理,下一篇将介绍hostapd_global_init()函数所完成的初始化工作。
';

(一)hostapd是干嘛的

最后更新于:2022-04-01 10:53:26

最近在学习无线,看了许多脚本和代码,虽然在整体上对这个模块实现的大致流程有了一定的掌握,但是对一些细节还不是很清楚,所以用这种方式来记录学习过程中的一些总结和体会,有理解错误或者描述不当的地方,还望有识者指正。 看代码的时候主要借鉴的是[ hostapd的radius/eap server代码分析](http://blog.csdn.net/njzhujinhua/article/details/5473970)系列和源代码,里面讲的很具体,很清楚。 **hostapd是干嘛用的?** hostapd能够使得无线网卡切换为master模式,模拟AP(路由器)功能,作为AP的认证服务器,负责控制管理stations(带无线网卡的pc或能连wifi的手机等)的接入和认证。简单的说,我们买回来一个无线网卡,默认情况下是STA模式的,在这种模式下,可以用它来连接到其他路由器、AP或者extender上,所以在被连接的master端看来,这个网卡就是client端。 但是现在我们想自己做一个路由器或者ap,让其他设备能够连接上来,并进行管理,那么就要将这个无线网卡切换成AP/master模式,但是怎么实现这个切换过程呢,就需要使用hostapd这个工具来帮忙了,我们可以通过修改它的配置文件,编译好来管理我们的无线网卡,以建立一个开放式的或者加密(WEP、WPA、WPA2等)的无线网络。修改配置文件hostapd.conf,可以设置无线网络的各种参数,包括频率、信号、beacon包时间间隔、mac地址过滤条件等。 hostapd在我看来主要实现两个功能:将无线网卡切换成AP模式,通过EAP/RADIUS管理加密方式。 ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-04-15_57108d8bd86ce.jpg) 上图是hostapd的一个代码框架,hostapd是一个后台程序,hostapd_cli是跟hostapd对应的前台命令行程序,hostapd_cli是一个机遇文本的、与hostapd进行交互的前台程序,通过hostapd_cli可以吃查看当前无线的认证状态、 .11和.1x的MIBS等。hostapd_cli有两种模式:交互模式和命令行模式,没输入参数时,将进入交互模式,help可以查看可用的命令。 下图是配置一个无线网络涉及到的命令和工具,以及无线工作的流程: ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-04-15_57108d8d777f1.jpg)
';

前言

最后更新于:2022-04-01 10:53:23

> 原文出处:[无线局域网开发](http://blog.csdn.net/column/details/wlan.html) 作者:[lee244868149](http://blog.csdn.net/lee244868149) **本系列文章经作者授权在看云整理发布,未经作者允许,请勿转载!** # 无线局域网开发 > 分享无线局域网相关的东西,包括无线原理,无线工具,无线开发等内容,从硬件,驱动到软件三个层面进行分析。
';