分析wpa_supplicant后台程序工作原理
- 一、参考文档
- 二、下载wpa_supplicant
- 三、搭建wpa_supplicant调试环境
- 四、设置debug level
- 五、扫描方式
- 六、BSS(Basic Service Set)
- 七、ctrl_iface
- 八、SCAN
- 九、nl80211 Driver
- 十、利用函数MAP文件查找Timeout Handler
- 十一、Event Loop
- 十二、wpas_periodic分析
- 十三、注册work示例
- https://w1.fi/releases/
- wpa_supplicant -h
wpa_supplicant v2.8-devel Copyright (c) 2003-2019, Jouni Malinen <j@w1.fi> and contributors This software may be distributed under the terms of the BSD license. See README for more details. This product includes software developed by the OpenSSL Project for use in the OpenSSL Toolkit (http://www.openssl.org/) - wpa_supplicant-2.8.tar.gz
- sudo apt-get install dbus libdbus-1-dev libdbus-glib-1-2 libdbus-glib-1-dev libreadline-dev libncurses5-dev
- sudo apt-get install libnl-genl-3-dev
- cd wpa_supplicant-2.8/wpa_supplicant
- make
LD wpa_supplicant CC wpa_cli.c CC ../src/common/wpa_ctrl.c CC ../src/common/cli.c CC ../src/utils/edit_simple.c LD wpa_cli CC wpa_passphrase.c LD wpa_passphrase sed systemd/wpa_supplicant.service.in sed systemd/wpa_supplicant.service.arg.in sed systemd/wpa_supplicant-nl80211.service.arg.in sed systemd/wpa_supplicant-wired.service.arg.in sed dbus/fi.w1.wpa_supplicant1.service.in - whereis wpa_supplicant
wpa_supplicant: /sbin/wpa_supplicant /etc/wpa_supplicant /usr/share/man/man8/wpa_supplicant.8.gz - sudo systemctl restart networking.service
- ps aux | grep wpa
root 419 0.0 0.1 10700 4016 ? Ss Mar27 0:00 /sbin/wpa_supplicant -u -s -O /run/wpa_supplicant root 559 0.2 0.0 11072 3384 ? Ss Mar27 0:10 wpa_supplicant -B -c/etc/wpa_supplicant/wpa_supplicant.conf -iwlan0 -Dnl80211,wext root 2462 0.0 0.0 7348 552 pts/2 S+ 00:48 0:00 grep wpa - kill -9 559
- 运行自己编译的wpa_supplicant
- ./wpa_supplicant -B -c/etc/wpa_supplicant/wpa_supplicant.conf -iwlan0 -Dnl80211,wext
- ./wpa_supplicant -c/etc/wpa_supplicant/wpa_supplicant.conf -iwlan0 -Dnl80211,wext
Successfully initialized wpa_supplicant wlan0: Failed to initiate sched scan wlan0: Failed to initiate sched scan wlan0: Failed to initiate sched scan wlan0: Trying to associate with SSID 'zengjf' wlan0: Associated with c4:9f:4c:b3:3b:52 wlan0: CTRL-EVENT-CONNECTED - Connection to c4:9f:4c:b3:3b:52 completed [id=2 id_str=] wlan0: CTRL-EVENT-SUBNET-STATUS-UPDATE status=0 wlan0: CTRL-EVENT-REGDOM-CHANGE init=COUNTRY_IE type=COUNTRY alpha2=CN wlan0: CTRL-EVENT-DISCONNECTED bssid=c4:9f:4c:b3:3b:52 reason=3 locally_generated=1 wlan0: CTRL-EVENT-REGDOM-CHANGE init=CORE type=WORLD wlan0: Trying to associate with SSID 'zengjf' wlan0: CTRL-EVENT-ASSOC-REJECT bssid=00:00:00:00:00:00 status_code=16 wlan0: Trying to associate with SSID 'zengjf' wlan0: CTRL-EVENT-ASSOC-REJECT bssid=00:00:00:00:00:00 status_code=16 wlan0: Trying to associate with SSID 'zengjf' wlan0: CTRL-EVENT-ASSOC-REJECT bssid=00:00:00:00:00:00 status_code=16 wlan0: CTRL-EVENT-SSID-TEMP-DISABLED id=2 ssid="zengjf" auth_failures=1 duration=10 reason=CONN_FAILED wlan0: CTRL-EVENT-SSID-REENABLED id=2 ssid="zengjf" wlan0: Trying to associate with SSID 'zengjf' wlan0: CTRL-EVENT-ASSOC-REJECT bssid=00:00:00:00:00:00 status_code=16 wlan0: CTRL-EVENT-SSID-TEMP-DISABLED id=2 ssid="zengjf" auth_failures=2 duration=20 reason=CONN_FAILED wlan0: CTRL-EVENT-SSID-REENABLED id=2 ssid="zengjf" wlan0: Trying to associate with SSID 'zengjf' wlan0: Associated with c4:9f:4c:b3:3b:52 wlan0: CTRL-EVENT-CONNECTED - Connection to c4:9f:4c:b3:3b:52 completed [id=2 id_str=] wlan0: CTRL-EVENT-SUBNET-STATUS-UPDATE status=0 wlan0: CTRL-EVENT-REGDOM-CHANGE init=COUNTRY_IE type=COUNTRY alpha2=CN wlan0: CTRL-EVENT-DISCONNECTED bssid=c4:9f:4c:b3:3b:52 reason=3 locally_generated=1 wlan0: CTRL-EVENT-REGDOM-CHANGE init=CORE type=WORLD
- grep DEBUG defconfig
#CONFIG_NO_STDOUT_DEBUG=y CONFIG_DEBUG_FILE=y CONFIG_DEBUG_SYSLOG=y #CONFIG_DEBUG_SYSLOG_FACILITY=LOG_DAEMON #CONFIG_DEBUG_LINUX_TRACING=y- CONFIG_DEBUG_FILE=y
- -f: params.wpa_debug_file_path = optarg;
- 操作方法:
- ./wpa_supplicant -c/etc/wpa_supplicant/wpa_supplicant.conf -iwlan0 -Dnl80211,wext -f log.txt
- tail -f log.txt
- CONFIG_DEBUG_SYSLOG=y
- -s: log output to syslog instead of stdout
- 操作方法:
- ./wpa_supplicant -c/etc/wpa_supplicant/wpa_supplicant.conf -iwlan0 -Dnl80211,wext -s
- tail -f /var/log/syslog
- CONFIG_DEBUG_LINUX_TRACING=y
- -T: record to Linux tracing in addition to logging
- 操作方法:
- sudo apt-get -y install trace-cmd kernelshark
- grep debugfs /proc/mounts
debugfs /sys/kernel/debug debugfs rw,relatime 0 0 - /sys/kernel/debug/tracing/trace_marker
- ./wpa_supplicant -c/etc/wpa_supplicant/wpa_supplicant.conf -iwlan0 -Dnl80211,wext -T
- trace-cmd record -e all -P 6394
- 看man帮助使用
- kernelshark

- 好像这个
trace-cmd、kernelshark是个超级好的东西,不过目前暂时不知道怎么具体使用,后续重点研究
- CONFIG_DEBUG_FILE=y
- debug输出代码:src/utils/wpa_debug.c
/** * wpa_printf - conditional printf * @level: priority level (MSG_*) of the message * @fmt: printf format string, followed by optional arguments * * This function is used to print conditional debugging and error messages. The * output may be directed to stdout, stderr, and/or syslog based on * configuration. * * Note: New line '\n' is added to the end of the text when printing to stdout. */ void wpa_printf(int level, const char *fmt, ...) { va_list ap; va_start(ap, fmt); if (level >= wpa_debug_level) { #ifdef CONFIG_ANDROID_LOG __android_log_vprint(wpa_to_android_level(level), ANDROID_LOG_NAME, fmt, ap); #else /* CONFIG_ANDROID_LOG */ #ifdef CONFIG_DEBUG_SYSLOG if (wpa_debug_syslog) { vsyslog(syslog_priority(level), fmt, ap); } else { #endif /* CONFIG_DEBUG_SYSLOG */ wpa_debug_print_timestamp(); #ifdef CONFIG_DEBUG_FILE if (out_file) { vfprintf(out_file, fmt, ap); fprintf(out_file, "\n"); } else { #endif /* CONFIG_DEBUG_FILE */ vprintf(fmt, ap); printf("\n"); #ifdef CONFIG_DEBUG_FILE } #endif /* CONFIG_DEBUG_FILE */ #ifdef CONFIG_DEBUG_SYSLOG } #endif /* CONFIG_DEBUG_SYSLOG */ #endif /* CONFIG_ANDROID_LOG */ } va_end(ap); #ifdef CONFIG_DEBUG_LINUX_TRACING if (wpa_debug_tracing_file != NULL) { va_start(ap, fmt); fprintf(wpa_debug_tracing_file, WPAS_TRACE_PFX, level); vfprintf(wpa_debug_tracing_file, fmt, ap); fprintf(wpa_debug_tracing_file, "\n"); fflush(wpa_debug_tracing_file); va_end(ap); } #endif /* CONFIG_DEBUG_LINUX_TRACING */ }
- wpa_cli设置debug level
- sudo wpa_cli setting debugging level
- sudo wpa_cli log_level
- 设置默认debug level
- wpa_supplicant/main.c --> params.wpa_debug_level = MSG_INFO;
- 以下这个是参考,会被上面这个设置值修改:
- src/utils/wpa_debug.c --> int wpa_debug_level = MSG_INFO;
手机扫描结果的获取有两种方式:被动和主动:
- AP隔固定时间会发送Beacon帧,Beacon帧中有AP的SSID BSSID等基本信息,手机接收到Beacon帧就认为搜索到该AP创建的网络
- 手机主动发出probe request帧,AP接收到probe request帧后会发送probe response帧,手机接收到response帧后,就认为扫描到该网络。
在手机wlan界面中,点击刷新既采用的第二种方式(当然扫描结果中也会包含部分方式1中扫描到的网络)
@startsalt
* wpa_supplicant/main.c
* wpa_s = wpa_supplicant_add_iface(global, &ifaces[i], NULL);
* wpa_supplicant_init_iface(wpa_s, &t_iface)
* wpa_supplicant/ctrl_iface_unix.c
* wpa_s->ctrl_iface = wpa_supplicant_ctrl_iface_init(wpa_s);
* wpas_ctrl_iface_open_sock(wpa_s, priv)
* os_strlcpy(addr.sun_path, fname, sizeof(addr.sun_path));
* /var/run/wpa_supplicant/wlan0
* eloop_register_read_sock(priv->sock, wpa_supplicant_ctrl_iface_receive, wpa_s, priv);
* wpa_supplicant_ctrl_iface_receive
* wpa_supplicant/ctrl_iface.c
* reply_buf = wpa_supplicant_ctrl_iface_process(wpa_s, buf, &reply_len);
* wpa_msg_register_cb(wpa_supplicant_ctrl_iface_msg_cb);
@endsaltstatic void wpa_supplicant_ctrl_iface_receive(int sock, void *eloop_ctx,
void *sock_ctx)
{
struct wpa_supplicant *wpa_s = eloop_ctx;
struct ctrl_iface_priv *priv = sock_ctx;
char buf[4096];
int res;
struct sockaddr_storage from;
socklen_t fromlen = sizeof(from);
char *reply = NULL, *reply_buf = NULL;
size_t reply_len = 0;
int new_attached = 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_strcmp(buf, "ATTACH") == 0) {
if (wpa_supplicant_ctrl_iface_attach(&priv->ctrl_dst, &from,
fromlen, 0))
reply_len = 1;
else {
new_attached = 1;
reply_len = 2;
}
} else if (os_strcmp(buf, "DETACH") == 0) {
if (wpa_supplicant_ctrl_iface_detach(&priv->ctrl_dst, &from,
fromlen))
reply_len = 1;
else
reply_len = 2;
} else if (os_strncmp(buf, "LEVEL ", 6) == 0) {
if (wpa_supplicant_ctrl_iface_level(priv, &from, fromlen,
buf + 6))
reply_len = 1;
else
reply_len = 2;
} else {
reply_buf = wpa_supplicant_ctrl_iface_process(wpa_s, buf,
&reply_len);
reply = reply_buf;
/*
* There could be some password/key material in the command, so
* clear the buffer explicitly now that it is not needed
* anymore.
*/
os_memset(buf, 0, res);
}
if (!reply && reply_len == 1) {
reply = "FAIL\n";
reply_len = 5;
} else if (!reply && reply_len == 2) {
reply = "OK\n";
reply_len = 3;
}
if (reply) {
wpas_ctrl_sock_debug("ctrl_sock-sendto", sock, reply,
reply_len);
if (sendto(sock, reply, reply_len, 0, (struct sockaddr *) &from,
fromlen) < 0) {
int _errno = errno;
wpa_dbg(wpa_s, MSG_DEBUG,
"ctrl_iface sendto failed: %d - %s",
_errno, strerror(_errno));
if (_errno == ENOBUFS || _errno == EAGAIN) {
/*
* The socket send buffer could be full. This
* may happen if client programs are not
* receiving their pending messages. Close and
* reopen the socket as a workaround to avoid
* getting stuck being unable to send any new
* responses.
*/
sock = wpas_ctrl_iface_reinit(wpa_s, priv);
if (sock < 0) {
wpa_dbg(wpa_s, MSG_DEBUG, "Failed to reinitialize ctrl_iface socket");
}
}
if (new_attached) {
wpa_dbg(wpa_s, MSG_DEBUG, "Failed to send response to ATTACH - detaching");
new_attached = 0;
wpa_supplicant_ctrl_iface_detach(
&priv->ctrl_dst, &from, fromlen);
}
}
}
os_free(reply_buf);
if (new_attached)
eapol_sm_notify_ctrl_attached(wpa_s->eapol);
}- grep SCAN wpa_supplicant/defconfig
#CONFIG_NO_SCAN_PROCESSING=y #CONFIG_AUTOSCAN_EXPONENTIAL=y #CONFIG_AUTOSCAN_PERIODIC=y CONFIG_BGSCAN_SIMPLE=y #CONFIG_BGSCAN_LEARN=y - wpa_supplicant/Makefile
ifdef CONFIG_BGSCAN_SIMPLE CFLAGS += -DCONFIG_BGSCAN_SIMPLE OBJS += bgscan_simple.o NEED_BGSCAN=y endif ifdef CONFIG_BGSCAN_LEARN CFLAGS += -DCONFIG_BGSCAN_LEARN OBJS += bgscan_learn.o NEED_BGSCAN=y endif ifdef NEED_BGSCAN CFLAGS += -DCONFIG_BGSCAN OBJS += bgscan.o endif ifdef CONFIG_AUTOSCAN_EXPONENTIAL CFLAGS += -DCONFIG_AUTOSCAN_EXPONENTIAL OBJS += autoscan_exponential.o NEED_AUTOSCAN=y endif ifdef CONFIG_AUTOSCAN_PERIODIC CFLAGS += -DCONFIG_AUTOSCAN_PERIODIC OBJS += autoscan_periodic.o NEED_AUTOSCAN=y endif ifdef NEED_AUTOSCAN CFLAGS += -DCONFIG_AUTOSCAN OBJS += autoscan.o endif
目前发现bgscan是没有用到的
@startsalt
* wpa_supplicant/main.c
* wpa_s = wpa_supplicant_add_iface(global, &ifaces[i], NULL);
* wpa_supplicant_set_state(wpa_s, WPA_DISCONNECTED);
* wpa_supplicant_start_bgscan(wpa_s); // 在这里就没进入下一层了
* bgscan_init(wpa_s, wpa_s->current_ssid, name)
* ops = bgscan_modules[i];
* wpa_s->bgscan = ops;
* wpa_s->bgscan_ssid = wpa_s->current_ssid;
* scan_res = wpa_supplicant_get_scan_results(wpa_s, NULL, 0);
@endsalt- grep "^wlan0: State:" log.txt
wlan0: State: DISCONNECTED -> DISCONNECTED wlan0: State: DISCONNECTED -> SCANNING wlan0: State: SCANNING -> DISCONNECTED
这里最关键的就是启动了SCANNING,导致注册了scan处理定时器,从而会在后台一直scan,但不是用上面的bgscan;
从这里可知默认scan的周期是5s,第一次在WPA_DISCONNECTED状态下wpa_supplicant_stop_autoscan()会设置进入WPA_SCANNING状态
wpas_trigger_scan_cb()函数是在5s定时之后被调用的,也就是说实际处理Scan的函数是它,其会调用libnl函数进行Scan;
@startsalt
* wpa_supplicant/main.c
* wpa_s = wpa_supplicant_add_iface(global, &ifaces[i], NULL);
* wpa_s = wpa_supplicant_alloc(parent);
* wpa_s->scan_interval = 5; <---- 5s
* wpa_supplicant_set_state(wpa_s, WPA_DISCONNECTED);
* if (state > WPA_SCANNING)
* wpa_supplicant_stop_autoscan(wpa_s);
* autoscan_init(wpa_s, 0)
* scan_plans[0].interval = 5;
* scan_plans[0].iterations = 0;
* wpa_s->sched_scan_plans = scan_plans;
* request_scan(wpa_s);
* wpa_supplicant_req_sched_scan(wpa_s)
* wpa_supplicant_req_scan(wpa_s, wpa_s->scan_interval, 0);
* eloop_register_timeout(sec, usec, wpa_supplicant_scan, wpa_s, NULL);
* wpa_supplicant_scan
* wpa_supplicant_set_state(wpa_s, WPA_SCANNING);
* ret = wpa_supplicant_trigger_scan(wpa_s, scan_params);
* radio_add_work(wpa_s, 0, "scan", 0, wpas_trigger_scan_cb, ctx)
@endsaltwpa_supplicant_alloc()初始化的时候就设置为5s了
static struct wpa_supplicant *
wpa_supplicant_alloc(struct wpa_supplicant *parent)
{
struct wpa_supplicant *wpa_s;
wpa_s = os_zalloc(sizeof(*wpa_s));
if (wpa_s == NULL)
return NULL;
wpa_s->scan_req = INITIAL_SCAN_REQ;
wpa_s->scan_interval = 5;
wpa_s->new_connection = 1;
wpa_s->parent = parent ? parent : wpa_s;
wpa_s->p2pdev = wpa_s->parent;
wpa_s->sched_scanning = 0;
dl_list_init(&wpa_s->bss_tmp_disallowed);
dl_list_init(&wpa_s->fils_hlp_req);
return wpa_s;
}- ./wpa_supplicant -c/etc/wpa_supplicant/wpa_supplicant.conf -iwlan0 -Dnl80211,wext -f log.txt
- -Dnl80211,wext
- driver: nl80211,wext
- 第一个优先匹配规则:nl80211,故使用nl80211驱动控制Wifi
- driver: nl80211,wext
- -Dnl80211,wext
@startsalt
* wpa_supplicant/main.c
* wpa_s = wpa_supplicant_add_iface(global, &ifaces[i], NULL);
* wpa_supplicant_init_iface(wpa_s, &t_iface)
* wpas_init_driver(wpa_s, iface)
* wpa_supplicant_set_driver(wpa_s, driver)
* select_driver(wpa_s, i)
* wpa_s->driver = wpa_drivers[i];
* &wpa_driver_nl80211_ops
* .global_init = nl80211_global_init,
* wpa_driver_nl80211_init_nl_global(global)
* nl_cb_set(global->nl_cb, NL_CB_VALID, NL_CB_CUSTOM, process_global_event, global);
* nl80211_register_eloop_read(&global->nl_event, wpa_driver_nl80211_event_receive, global->nl_cb, 0);
* wpa_driver_nl80211_event_receive
* process_global_event
* wpa_s->global_drv_priv = global->drv_priv[i];
@endsalt分析代码的过程中会发现,最后nl80211驱动调用了libnl库中的方法与内核进行通信;
- NL80211_CMD_GET_SCAN: 32
- NL80211_CMD_TRIGGER_SCAN: 33
- NL80211_CMD_NEW_SCAN_RESULTS: 34
- NL80211_CMD_SCAN_ABORTED: 35
enum nl80211_commands {
// ...省略
NL80211_CMD_GET_SCAN,
NL80211_CMD_TRIGGER_SCAN,
NL80211_CMD_NEW_SCAN_RESULTS,
NL80211_CMD_SCAN_ABORTED,
// ...省略
}开始处理Scan的时候会接收到cmd -> 33,内核扫描完成会接收到cmd -> 34,这里需要非常注意的一点就是,在最后会重新注册wpa_supplicant_req_new_scan进行Scan任务调度,如果扫描到了AP并且连接上了AP,就不会进行扫描了,wpa_bss_update_scan_res()是处理获取到的BSS;
@startsalt
* src/drivers/driver_nl80211.c
* nl_cb_set(global->nl_cb, NL_CB_VALID, NL_CB_CUSTOM, process_global_event, global);
* process_global_event
* do_process_drv_event(bss, gnlh->cmd, tb);
* case NL80211_CMD_TRIGGER_SCAN:
* wpa_supplicant_event(drv->ctx, EVENT_SCAN_STARTED, NULL);
* wpa_supplicant/events.c
* case EVENT_SCAN_STARTED:
* wpa_dbg(wpa_s, MSG_DEBUG, "Own scan request started a scan in %ld.%06ld seconds", diff.sec, diff.usec);
* wpa_msg_ctrl(wpa_s, MSG_INFO, WPA_EVENT_SCAN_STARTED); <-- 将消息发送给wpa_cli
* #define WPA_EVENT_SCAN_STARTED "CTRL-EVENT-SCAN-STARTED "
* case NL80211_CMD_NEW_SCAN_RESULTS:
* wpa_dbg(drv->ctx, MSG_DEBUG, "nl80211: New scan results available");
* send_scan_event(drv, 0, tb, external_scan_event);
* wpa_supplicant_event(drv->ctx, EVENT_SCAN_RESULTS, &event);
* wpa_dbg(wpa_s, MSG_DEBUG, "Scan completed in %ld.%06ld seconds", diff.sec, diff.usec);
* wpa_supplicant_event_scan_results(wpa_s, data)
* _wpa_supplicant_event_scan_results(wpa_s, data, 1, 0);
* scan_res = wpa_supplicant_get_scan_results(wpa_s, data ? &data->scan_info : NULL, 1);
* wpa_bss_update_scan_res(wpa_s, scan_res->res[i], &scan_res->fetch_time); <-- Scan获取到的BSS
* wpa_msg_ctrl(wpa_s, MSG_INFO, WPA_EVENT_SCAN_RESULTS); <-- 将消息发送给wpa_cli
* #define WPA_EVENT_SCAN_RESULTS "CTRL-EVENT-SCAN-RESULTS "
* radio_work_done(work); <-- work会被释放
* wpas_select_network_from_last_scan(wpa_s, 1, own_request);
* wpa_dbg(wpa_s, MSG_DEBUG, "No suitable network found");
* int timeout_sec = wpa_s->scan_interval; <-- 5s
* int timeout_usec = 0;
* wpa_supplicant_req_sched_scan(wpa_s)
* wpa_supplicant_req_new_scan(wpa_s, timeout_sec, timeout_usec);
* wpa_msg_ctrl(wpa_s, MSG_INFO, WPA_EVENT_NETWORK_NOT_FOUND);
* #define WPA_EVENT_NETWORK_NOT_FOUND "CTRL-EVENT-NETWORK-NOT-FOUND "
* radio_work_check_next(wpa_s); <-- 继续处理后续任务
* nl80211_register_eloop_read(&global->nl_event, wpa_driver_nl80211_event_receive, global->nl_cb, 0);
@endsalt- wpa_supplicant/Makefile
wpa_supplicant: $(BCHECK) $(OBJS) $(EXTRA_progs) $(Q)$(LDO) $(LDFLAGS) -o wpa_supplicant $(OBJS) $(LIBS) $(EXTRALIBS) -Wl,-Map=output.map @$(E) " LD " $@
- src/utils/eloop.c
void eloop_run(void) { // ...省略 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); printf("handler 0x%08x timeout.\n", handler); // 输出处理函数地址 handler(eloop_data, user_data); } } // ...省略 }
- handler基本是static函数,所以无法直接查找到,通过地址范围来确定处理函数在哪个文件中
- 示例:
- handler 0x0013d16c timeout.
- 在编译生成的
output.map中查找0x0013d(后面16c不影响范围,不用完全匹配).text 0x0013a7e0 0x466c scan.o [...省略] 0x0013cd70 scan_only_handler 0x0013ce28 wpas_scan_scheduled 0x0013ce3c wpa_scan_clone_params 0x0013d0d0 wpa_supplicant_trigger_scan 0x0013e4d4 wpa_scan_free_params 0x0013e4e0 wpas_start_pno 0x0013e8bc wpas_stop_pno - 可知函数代码在:scan.c
- 在scan.c中查找:
void *eloop_ctx, void *timeout_ctx- 在找到的函数里分别加入:
printf("%s: %s\n", __FILE__, __func__);,基本上就可以确定是什么函数了
- 在找到的函数里分别加入:
参考文档: WPA_SUPPLICANT源码分析(1):EVENT LOOP的实现
struct eloop_data结构体是一个统领全局的数据结构,只有一个实例:static struct eloop_data eloop;
要处理的Event有三大种类型:Socket事件,Timeout事件,Signal事件。其中Socket事件又分为三种类型:
- @EVENT_TYPE_READ: Socket has data available for reading
- @EVENT_TYPE_WRITE: Socket has room for new data to be written
- @EVENT_TYPE_EXCEPTION: An exception has been reported
三大种事件简述:
- Socket事件:有readers, writers, exceptions三个eloop_sock_table结构体, 每个里面都有动态数组,数组的每一项都是一个具体的eloop_sock. 可以向各个Table里面添加、删除eloop_sock. 事件分发就是遍历sock_table, 依次运行里面的每个Handler.
- Timeout事件:每个struct eloop_timeout都被放在一个双向链表中,链表头就是eloop_data中的”timeout”项。这些struct eloop_timeout按超时先后排序。
- Signal事件:每个struct eloop_signal放在动态数组中
int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);
- 找到timeout链表的第一项(因为是按超时先后排序的,所以第一项肯定是最先超时的),计算超时时间距现在还有多久, 并据此设置select的timeout参数。
- 设置readfd, writefds和exceptfds三个fd_set: 方法是遍历各个eloop_sock_table,把每个sock描述符加入相应的fd_set里面。
- 判断是否有超时发生,如果是则调用其Handler, 并从timeout链表移除。然后继续下次循环。需要注意的是这里每次只会处理一个timeout。
- 如果不是超时事件,则应该是readfds, writefds或者exceptfds事件, fd_set里面会被改变,存放发生事件的描述符。因此分别遍历三个sock_table, 如果其描述符在fd_set里面则调用其Handler.
可以注册timeout,那么就可以删除timeout,删除的时候比较的的是handler、eloop_data、user_data参数指针是否一致。
int eloop_register_timeout(unsigned int secs, unsigned int usecs,
eloop_timeout_handler handler,
void *eloop_data, void *user_data)
{
struct eloop_timeout *timeout, *tmp;
os_time_t now_sec;
timeout = os_zalloc(sizeof(*timeout));
if (timeout == NULL)
return -1;
if (os_get_reltime(&timeout->time) < 0) {
os_free(timeout);
return -1;
}
now_sec = timeout->time.sec;
timeout->time.sec += secs;
if (timeout->time.sec < now_sec) {
/*
* Integer overflow - assume long enough timeout to be assumed
* to be infinite, i.e., the timeout would never happen.
*/
wpa_printf(MSG_DEBUG, "ELOOP: Too long timeout (secs=%u) to "
"ever happen - ignore it", secs);
os_free(timeout);
return 0;
}
timeout->time.usec += usecs;
while (timeout->time.usec >= 1000000) {
timeout->time.sec++;
timeout->time.usec -= 1000000;
}
timeout->eloop_data = eloop_data;
timeout->user_data = user_data;
timeout->handler = handler;
wpa_trace_add_ref(timeout, eloop, eloop_data);
wpa_trace_add_ref(timeout, user, user_data);
wpa_trace_record(timeout);
/* Maintain timeouts in order of increasing time */
dl_list_for_each(tmp, &eloop.timeout, struct eloop_timeout, list) { // 超时递增双向链表
if (os_reltime_before(&timeout->time, &tmp->time)) {
dl_list_add(tmp->list.prev, &timeout->list);
return 0;
}
}
dl_list_add_tail(&eloop.timeout, &timeout->list);
return 0;
}整个系统运作均通过Select函数进行处理事件,同时利用Select的timeout来处理timeout链表中注册的定时handler函数,**核心就是理解Select + Timeout**的策略;
@startuml
* wpa_supplicant/main.c
* global = wpa_supplicant_init(¶ms);
* eloop_register_timeout(WPA_SUPPLICANT_CLEANUP_INTERVAL, 0, wpas_periodic, global, NULL);
@enduml参考文档:Android wpa_supplicant源码分析--bss扫描结果
每隔10秒都会刷新一下struct wpa_supplicant中的BSS列表,删除180s内没有找到的BSS;
#ifndef WPA_SUPPLICANT_CLEANUP_INTERVAL
#define WPA_SUPPLICANT_CLEANUP_INTERVAL 10
#endif /* WPA_SUPPLICANT_CLEANUP_INTERVAL */
/* Periodic cleanup tasks */
static void wpas_periodic(void *eloop_ctx, void *timeout_ctx)
{
struct wpa_global *global = eloop_ctx;
struct wpa_supplicant *wpa_s;
// timeout循环处理注册
eloop_register_timeout(WPA_SUPPLICANT_CLEANUP_INTERVAL, 0,
wpas_periodic, global, NULL);
#ifdef CONFIG_P2P
if (global->p2p)
p2p_expire_peers(global->p2p);
#endif /* CONFIG_P2P */
// 循环两次,分别是:p2p-dev-wlan0、wlan0,bss_expiration_age: 180
for (wpa_s = global->ifaces; wpa_s; wpa_s = wpa_s->next) {
wpa_bss_flush_by_age(wpa_s, wpa_s->conf->bss_expiration_age);
#ifdef CONFIG_AP
ap_periodic(wpa_s);
#endif /* CONFIG_AP */
}
}每一个work也是一个timeout回调函数,伪装成task的timeout处理函数,如下是开始处理一个scan work流程
@startsalt
* wpa_supplicant/main.c
* wpa_s = wpa_supplicant_add_iface(global, &ifaces[i], NULL);
* wpa_s = wpa_supplicant_alloc(parent);
* wpa_s->scan_interval = 5; <---- 5s
* wpa_supplicant_set_state(wpa_s, WPA_DISCONNECTED);
* if (state > WPA_SCANNING)
* wpa_supplicant_stop_autoscan(wpa_s);
* autoscan_init(wpa_s, 0)
* scan_plans[0].interval = 5;
* scan_plans[0].iterations = 0;
* wpa_s->sched_scan_plans = scan_plans;
* request_scan(wpa_s);
* wpa_supplicant_req_sched_scan(wpa_s)
* wpa_supplicant_req_scan(wpa_s, wpa_s->scan_interval, 0);
* eloop_register_timeout(sec, usec, wpa_supplicant_scan, wpa_s, NULL);
* wpa_supplicant_scan
* wpa_supplicant_set_state(wpa_s, WPA_SCANNING);
* ret = wpa_supplicant_trigger_scan(wpa_s, scan_params);
* radio_add_work(wpa_s, 0, "scan", 0, wpas_trigger_scan_cb, ctx)
@endsalt
