(七)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, ¶ms) 语句,用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, ¶ms),将会调用 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)
**本系列文章经作者授权在看云整理发布,未经作者允许,请勿转载!**
# 无线局域网开发
> 分享无线局域网相关的东西,包括无线原理,无线工具,无线开发等内容,从硬件,驱动到软件三个层面进行分析。