4.3.2 wpa_supplicant_init函数分析

wpa_supplicant_init代码如下所示。

[—>wpa_supplicant.c::wpa_supplicant_init]

  1. struct wpa_global * wpa_supplicant_init(struct wpa_params *params)
  2. {
  3. struct wpa_global *global;
  4. int ret, i;
  5. ......
  6. #ifdef CONFIG_DRIVER_NDIS
  7. ......// windows driver支持
  8. #endif
  9. #ifndef CONFIG_NO_WPA_MSG
  10. // 设置全局回调函数,详情见下文解释
  11. wpa_msg_register_ifname_cb(wpa_supplicant_msg_ifname_cb);
  12. #endif /* CONFIG_NO_WPA_MSG */
  13. // 输出日志文件设置,本例未设置该文件
  14. wpa_debug_open_file(params->wpa_debug_file_path);
  15. ......
  16. ret = eap_register_methods();// ①注册EAP方法
  17. ......
  18. global = os_zalloc(sizeof(*global)); // 创建一个wpa_global对象
  19. ...... // 初始化global中的其他参数
  20. wpa_printf(MSG_DEBUG, "wpa_supplicant v" VERSION_STR);
  21. // ②初始化事件循环机制
  22. if (eloop_init()) {......}
  23. // 初始化随机数相关资源,用于提升后续随机数生成的随机性
  24. // 这部分内容不是本书的重点,感兴趣的读者请自行研究
  25. random_init(params->entropy_file);
  26.  
  27. // 初始化全局控制接口对象。由于本例中未设置全局控制接口,故该函数的处理非常简单,请读者自行阅读该函数
  28. global->ctrl_iface = wpa_supplicant_global_ctrl_iface_init(global);
  29. ......
  30. // 初始化通知机制相关资源,它和dbus有关。本例没有包括dbus相关内容,略
  31. if (wpas_notify_supplicant_initialized(global)) {......}
  32. // ③wpa_driver是一个全局变量,其作用见下文解释
  33. for (i = 0; wpa_drivers[i]; i++)
  34. global->drv_count++;
  35. ......
  36. // 分配全局driver wrapper上下文信息数组
  37. global->drv_priv = os_zalloc(global->drv_count * sizeof(void *));
  38. ......
  39. return global;
  40. }

wpa_supplicant_init函数的主要功能是初始化wpa_global以及一些与整个程序相关的资源,包括随机数资源、eloop事件循环机制以及设置消息全局回调函数。

此处先简单介绍消息全局回调函数,一共有两个。

·wpa_msg_get_ifname_func:有些输出信息中需要打印出网卡接口名。该回调函数用于获取网卡接口名。

·wpa_msg_cb_func:除了打印输出信息外,还可通过该回调函数进行一些特殊处理,如把输出信息发送给客户端进行处理。

上述两个回调函数相关的代码如下所示。

[—>wpa_debug.c]

  1. // wpa_msg_ifname_cb用于获取无线网卡接口名
  2. // WPAS为其设置的实现函数为wpa_supplicant_msg_ifname_cb
  3. // 读者可自行阅读此函数
  4. static wpa_msg_get_ifname_func wpa_msg_ifname_cb = NULL;
  5. void wpa_msg_register_ifname_cb(wpa_msg_get_ifname_func func){
  6. wpa_msg_ifname_cb = func;
  7. }
  8. // WPAS中,wpa_msg_cb的实现函数是wpa_supplicant_ctrl_iface_msg_cb,它将输出信息发送给客户端
  9. // 图4-2最后两行的信息就是由此函数发送给客户端的。而且前面的"<3>"也是由它添加的
  10. static wpa_msg_cb_func wpa_msg_cb = NULL;
  11. void wpa_msg_register_cb(wpa_msg_cb_func func){
  12. wpa_msg_cb = func;
  13. }

现在来看wpa_supplicant_init中列出的三个关键点,首先是eap_register_method函数。

1.eap_register_methods函数

该函数本身非常简单,它主要根据编译时的配置项来初始化不同的eap方法。其代码如下所示。

[—>eap_register.c::eap_register_methods]

  1. int eap_register_methods(void)
  2. {
  3. int ret = 0;
  4. #ifdef EAP_MD5 // 作为supplicant端,编译时将定义EAP_MD5
  5. if (ret == 0)
  6. ret = eap_peer_md5_register();
  7. #endif /* EAP_MD5 */
  8. ......
  9. #ifdef EAP_SERVER_MD5 // 作为Authenticator端,编译时将定义EAP_SERVER_MD5
  10. if (ret == 0)
  11. ret = eap_server_md5_register();
  12. #endif /* EAP_SERVER_MD5 */
  13. ......
  14. return ret;
  15. }

如上述代码所示,eap_register_methods函数将根据编译配置项来注册所需的eap method。例如,MD5身份验证方法对应的注册函数是eap_peer_md5_register,该函数内部将填充一个名为eap_method的数据结构,其定义如图4-8所示。

图4-8所示的struct eap_method结构体声明于eap_i.h中,其内部一些变量及函数指针的定义和RFC4137有较大关系。此处,我们暂时列出其中一些简单的成员变量。4.4节将详细介绍RFC4137相关的知识。

来看第二个关键函数eloop_init,它和图4-1所示WPAS软件架构中的event loop模块有关。

2.eloop_init函数及event loop模块

eloop_init函数本身特别简单,它仅初始化了WPAS中事件驱动的核心数据结构体eloop_data。WPAS事件驱动机制的实现非常简单,它就是利用epoll(如果编译时设置了CONFIG_ELOOP_POLL选项)或select实现了I/O复用。

提醒 select(或epoll)是I/O复用的重要函数,属于基础知识范畴。请不熟悉的读者自行学习相关内容。

从事件角度来看,WPAS的事件驱动机制支持5种类型的event。

·read event:读事件,例如来自socket的可读事件。

·write event:写事件,例如socket的可写事件。

·exception event:异常事件,如果socket操作发生错误,则由错误事件处理。

·timeout event:定时事件,通过select的等待超时机制来实现定时事件。

·signal:信号事件,信号事件来源于Kernel。WPAS允许为一些特定信号设置处理函数。

以上这些事件相关的信息都保存在eloop_data结构体中,如图4-9所示。

4.3.2 wpa_supplicant_init函数分析 - 图1

图4-8 eap_method数据结构

4.3.2 wpa_supplicant_init函数分析 - 图2

图4-9 eloop_data结构体

简单介绍一下eloop提供的事件注册API及eloop事件循环核心处理函数eloop_run。首先是事件注册API函数,相关代码如下所示。

[—>eloop.h]

  1. // 注册socket读事件处理函数,参数sock代表一个socket句柄。一旦该句柄上有读事件发生,则handler函数
  2. // 将被事件处理循环(见下文eloop_run函数)调用
  3. int eloop_register_read_sock(int sock, eloop_sock_handler handler,
  4. void *eloop_data, void *user_data);
  5. // 注册socket事件处理函数,具体是哪种事件(只能是读、写或异常)由type参数决定
  6. int eloop_register_sock(int sock, eloop_event_type type,
  7. eloop_sock_handler handler,void *eloop_data, void *user_data);
  8. // 注册超时事件处理函数
  9. int eloop_register_timeout(unsigned int secs, unsigned int usecs,
  10. eloop_timeout_handler handler, void *eloop_data, void *user_data);
  11. // 注册信号事件处理函数,具体要处理的信号由sig参数指定
  12. int eloop_register_signal(int sig, eloop_signal_handler handler, void *user_data);

最后,向读者展示一下WPAS事件驱动机制的运行原理,其代码在eloop_run函数中,如下所示。

[—>eloop.c::eloop_run]

  1. void eloop_run(void)
  2. {
  3. fd_set *rfds, *wfds, *efds; // fd_set是select中用到的一种参数类型
  4. struct timeval _tv;
  5. int res;
  6. struct os_time tv, now;
  7. // 事件驱动循环
  8. while (!eloop.terminate &&
  9. (!dl_list_empty(&eloop.timeout) || eloop.readers.count > 0 ||
  10. eloop.writers.count > 0 || eloop.exceptions.count > 0)) {
  11. struct eloop_timeout *timeout;
  12. // 判断是否有超时事件需要等待
  13. timeout = dl_list_first(&eloop.timeout, struct eloop_timeout,list);
  14. if (timeout) {
  15. os_get_time(&now);
  16. if (os_time_before(&now, &timeout->time))
  17. os_time_sub(&timeout->time, &now, &tv);
  18. else
  19. tv.sec = tv.usec = 0;
  20. _tv.tv_sec = tv.sec;
  21. _tv.tv_usec = tv.usec;
  22. }
  23. // 将外界设置的读事件添加到对应的fd_set中
  24. eloop_sock_table_set_fds(&eloop.readers, rfds);
  25. ......// 设置写、异常事件到fd_set中
  26. // 调用select函数
  27. res = select(eloop.max_sock + 1, rfds, wfds, efds,timeout ? &_tv : NULL);
  28. if(res &lt; 0) {......// 错误处理}
  29. // 先处理信号事件
  30. eloop_process_pending_signals();
  31. // 判断是否有超时事件发生
  32. timeout = dl_list_first(&eloop.timeout, struct eloop_timeout,list);
  33. if (timeout) {
  34. os_get_time(&now);
  35. if (!os_time_before(&now, &timeout->time)) {
  36. void *eloop_data = timeout->eloop_data;
  37. void *user_data = timeout->user_data;
  38. eloop_timeout_handler handler = timeout->handler;
  39. eloop_remove_timeout(timeout); // 注意,超时事件只执行一次
  40. handler(eloop_data, user_data); // 处理超时事件
  41. }
  42. }
  43. ......// 处理读/写/异常事件。方法和下面这个函数类似
  44. eloop_sock_table_dispatch(&eloop.readers, rfds);
  45. ......// 处理wfds和efds
  46. }
  47. out:
  48. return;
  49. }

eloop_run中的while循环是WPAS进程的运行中枢。不过其难度也不大。

下面来看wpa_supplicant_init代码中的第三个关键点,即wpa_drivers变量。

3.wpa_drivers数组和driver i/f模块

wpa_drivers是一个全局数组变量,它通过extern方式声明于main.c中,其定义却在drivers.c中,如下所示。

[—>drivers.c::wpa_drivers定义]

  1. struct wpa_driver_ops *wpa_drivers[] =
  2. {
  3. #ifdef CONFIG_DRIVER_WEXT
  4. &wpa_driver_wext_ops,
  5. #endif /* CONFIG_DRIVER_WEXT */
  6. #ifdef CONFIG_DRIVER_NL80211
  7. &wpa_driver_nl80211_ops,
  8. #endif /* CONFIG_DRIVER_NL80211 */
  9. ......// 其他driver接口
  10. }

wpa_drivers数组成员指向一个wpa_driver_ops类型的对象。wpa_driver_ops是driver i/f模块的核心数据结构,其内部定义了很多函数指针。而正是通过定义函数指针的方法,WPAS能够隔离上层使用者和具体的driver。

注意 此处的driver并非通常意义所指的那些运行于Kernel层的驱动。读者可认为它们是Kernel层wlan驱动在用户空间的代理模块。上层使用者通过它们来和Kernel层的驱动交互。为了避免混淆,本书后续将用driver wrapper一词来表示WPAS中的driver。而driver一词将专指Kernel里对应的wlan驱动。

另外,wpa_drivers数组包含多少个driver wrapper对象也由编译选项来控制(如代码中所示的CONFIG_DRIVER_WEXT宏,它们可在android.cfg中被修改)。

此处先列出wpa_driver_nl80211_ops的定义。

[—>driver_nl80211.c::wpa_driver_nl80211_ops]

  1. const struct wpa_driver_ops wpa_driver_nl80211_ops = {
  2. .name = "nl80211", // driver wrapper的名称
  3. .desc = "Linux nl80211/cfg80211", // 描述信息
  4. .get_bssid = wpa_driver_nl80211_get_bssid, // 用于获取bssid
  5. ......
  6. .scan2 = wpa_driver_nl80211_scan, // 扫描函数
  7. ......
  8. .get_scan_results2 = wpa_driver_nl80211_get_scan_results,
  9. // 获取扫描结果
  10. ......
  11. .disassociate = wpa_driver_nl80211_disassociate, // 触发disassociation操作
  12. .authenticate = wpa_driver_nl80211_authenticate, // 触发authentication操作
  13. .associate = wpa_driver_nl80211_associate, // 触发association操作
  14. // driver wrapper全局初始化函数,该函数的返回值保存在wpa_global成员变量drv_pri数组中
  15. .global_init = nl80211_global_init,
  16. ......
  17. .init2 = wpa_driver_nl80211_init, // driver wrapper初始化函数
  18. ......
  19. #ifdef ANDROID // Android平台定义了该宏
  20. .driver_cmd = wpa_driver_nl80211_driver_cmd,// 该函数用于处理和具体驱动相关的命令
  21. #endif
  22. };

本节介绍了main函数中第一个的关键点wpa_supplicant_init,其中涉及的知识有:几个重要数据结构,如wpa_global、wpa_interface、eap_method、wpa_driver_ops等;event loop的工作原理;消息全局回调函数和wpa_drivers等内容。

下面来分析main中第二个关键函数wpa_supplicant_add_iface。