看在android5.1平台上看了将近半个月的wpa_supplicant子系统,还是希望能将最近的一些所学整理成文档发表到博客(在学博客过程其实也是对自己的思路的一个在整理),有误地方欢迎指正交流。
wpa_supplicant是一个开源的软件项目,它实现了Station对无线网络进行管理和控制的功能。wpa_supplicant是Android用户空间中无线网络的核心模块,所有Framework中和Wifi相关的操作最终都将有wpa_supplicant来完成。另外,wpa_supplicant对802.11、802.1X以及android Alliance定义的一些规范都有极好的支持。
其内部模块构成如下图:
WPA_supplicant中所有工作都围绕事件(event loop模块)展开。WPA_supplicant的运行机制比较简单,它是基于事件驱动,所有事件都是由主线程完成。
event loop模块下方的driver i/f接口模块用于隔离和底层驱动直接交互的driver控制模块,也正式这种隔离作用是WPA_supplicant中的其他模块能最大程度的保持平台以及驱动的无关性。
EAP、EAOOL为状态机,除此之外WPA_supplicant还定义了自己的状态机WPA/WPA2。WPA_ supplicant中实现了多种EAP方法。如EAP method模块。另外还包含了crypto和TLS模块用于支持EAP方法。
EAP以及EAPOL的消息都属于LLC层数据,所以I2_packet用于接收EAP、EAPOL消息。
WPA_supplicant支持较多的配置参数,这些参数的处理主要在configuration模块中完成。
WPA_supplicant是C/S架构中的服务端,它通过ctrl i/f端口向客户端提供通信接口。
WPA_Supplicant的初始化过程
WPA_Supplicant入口函数位于main.c文件中,路径见3.1章节。
先来看其入口函数main函数:
在这个函数中主要做了做了四件工作:
1)调用os_program_init()函数给wpa_supplicant进程分配权限以及结构体的初始化,解析命令行参数。
2)通过wpa_supplicant_init()函数,初始化struct wpa_global *global局部结构体(结构体详解见后文)同时传递给static struct eloop_data eloop全局结构体初始化事件循环机制。
3)for循环中调用wpa_supplicant_add_iface()函数注册一个或者多个网络接口。
4)调用wpa_supplicant_run()函数启动wpa_supplicant主事件循环机制,如果失败跳转到out或者通过goto跳转到out。
main函数中出现了几个重要的结构体和两个关键函数,先来看下main函数主要的数据
如图2所示:
wpa_interface用于描述一个无线网路设备,参数在wpa_supplicant_add_iface()函数调用
wpa_global是一个全局性质的上下文信息。它通过ifaces变量指向一个wpa_supplicant结构体,该结构体的初始化主要通过调用wpa_supplicant_init()函数实现。
wpa_supplicant结构体是WPA_Supplicant的核心数据结构,一个interface对应一个结构体wap_supplicant。
ctrl_iface_global_priv是全局控制接口的信息,内部包含一个socket句柄。
下面来分析下wpa_supplicant_init()函数和wpa_supplicant_add_iface()函数。
wpa_supplicant_init()函数
wpa_supplicant_init()函数流程如下图3:
wpa_supplicant_init()函数实现的功能是:先分配struct wpa_global *global这个局部指向结构体的内存空间;然后通过main函数传递进来的params参数填充global指向的内嵌结构体对象struct wpa_params;然后调用eloop_init()函数初始化eloop事件循环机制;最后设置消息全局回调函数(共2个)。
比较重要的函数:
wpa_msg_register_ifname_cb()函数:该函数为回调函数,用于获取有需要输出打印网卡接口名的网卡接口名。
wpa_supplicant_global_ctrl_iface_init()函数:初始化全局接口对象。
eap_register_methods()函数:主要将根据编译配置选项来注册锁需要的EAP methold,EAP methold方法见图1所示。
eloop_init()函数:见下文分析。
eloop_init()函数:的主要功能是初始化了WPA_Supplicant中事件驱动的核心数据结构体eloop_data。WPA_Supplicant事件驱动主要支持5中类型的event:
1)read event:读事件
2)write event:写事件
3)exception event:异常事件,如果socket操作发生异常,则由错误事件处理
4)timeout event:定时事件
5)signal:信号事件
这些事件都保存在eloop_data结构体中,如下图4:
另外一个需要注意的地方是wpa_supplicant_init()函数中定义了全局数组变量wpa_driver。wpa_driver数组成员指向一个wpa_driver_ops类型对象,wpa_driver_ops是driver i/f模块的核心数据结构,其内部定义很多的函数指针,正是通过这些函数指针的方法,WPA_Supplicant能够是上层的使用者和底层驱动隔离。
const struct wpa_driver_ops wpa_driver_nl80211_ops = { .name = "nl80211", .desc = "Linux nl80211/cfg80211", .get_bssid = wpa_driver_nl80211_get_bssid, .get_ssid = wpa_driver_nl80211_get_ssid, .set_key = driver_nl80211_set_key, .scan2 = driver_nl80211_scan2, .sched_scan = wpa_driver_nl80211_sched_scan, .stop_sched_scan = wpa_driver_nl80211_stop_sched_scan, .get_scan_results2 = wpa_driver_nl80211_get_scan_results, .deauthenticate = driver_nl80211_deauthenticate, ....... .set_wowlan = nl80211_set_wowlan, .roaming = nl80211_roaming, .set_mac_addr = nl80211_set_mac_addr, }
wpa_supplicant_add_iface()函数
wpa_supplicant_add_iface()这个函数实现的功能是被用于向WPA_Supplicant添加网络接口设备,这一操作将会在主线程启动之前被调用(也就是在wpa_supplicant_run()函数被调用之前被被调用)。wpa_supplicant_add_iface()函数主要涉及到两个重要的数据结构(wpa_supplicant和wpa_ssid)以及一个关键函数wpa_supplicant_init_iface。
进入wpa_supplicant_add_iface()函数:
上图6为wpa_supplicant_add_iface函数的时序图:
wpa_supplicant_add_iface()函数主要功能是被用于向WPA_Supplicant添加一个网络接口。主要做了两件工作:
1)调用wpa_supplicant_alloc()函数分配一个struct wpa_supplicant *wpa_s局部指针空间(该结构体在上文分析)并初始化。
2)将struct wpa_supplicant对象以及struct wpa_interface对象传递给wpa_supplicant_init_iface函数(wpa_supplicant_add_iface函数主要工作在wpa_supplicant_init_iface函数中完成)
下面分别来分析下这两个方面。
重要的数据结构
如上图5为wpa_supplicant结构体和wpa_ssid结构体定义的主要成员变量。
先来看看wpa_supplicant结构体:
pairwise_cipher、group_cipher、key_mgmt、wpa_proto、mgmt_group_cipher:这几个变量表示该WPA_Supplicant选择的安全策略。其中,mgmt_group_cipher和IEEE 802.11W有关。
current_bss:该变量的类型为wpa_bss。wpa_bss是无线网络在wpa_supplicant的代表,wpa_bss的主要成员表述无线网络中的ssid、bssid、freq(频率)、caps(性能)、qual(信号强度)、level(信号等级)等。
drv_priv和global_drv_priv:这两给变量主要用于存储两个初始化函数的返回值,WPA_Supplicant为driver_wrapper以供定义了两个上下文信息(主要与driver i/f接口定义的两个初始化函数global _init、init2相对应)。global_init以及init2返回值为driver_wrapper对象对应的上下文信息,分别存储在drv_priv以及global_drv_priv中,那个WPA_Supplicant都对应有一个driver_wrapper对象。
sched_scan_timeout:该变量与定时扫描功能有关。启用该功能时,需要为驱动设置定时扫描的间隔。
bgscan:这个变量主要与后台扫描功能以及漫游技术有关。漫游技术是指一个STA在ESS中移动时选择连接与自己较近的AP连接的过程。
wpa_supplicant结构体内部还定义了一个枚举变量如下:
enum wpa_states { WPA_DISCONNECTED, WPA_INTERFACE_DISABLED, WPA_INACTIVE, WPA_SCANNING, WPA_AUTHENTICATING, WPA_ASSOCIATING, WPA_ASSOCIATED, WPA_4WAY_HANDSHAKE, WPA_GROUP_HANDSHAKE, WPA_COMPLETED }
WPA_DISCONNECTED:表示当前没有连接任何客户端无线网络。
WPA_INTERFACE_DISABLED:表示当前wpa_supplicant使用的所有接口被禁用。
WPA_INACTIVE:表示当前没有可连接的无线网络。
WPA_SCANNING、WPA_AUTHENTICATING、WPA_ASSOCIATING:表示当前wpa_supplicant分别处于扫描无线网络、身份验证以及关联的过程。
WPA_4WAY_HANDSHAKE:表示WPA_Supplicant处于四次握手处理过程中。当使用PSK策略时,STA收到第一个EAPOL-Key数据包进入此状态。
WPA_GROUP_HANDSHAKE:表示WPA_Supplicant处于组密钥握手协议处理过程。当STA完成四次握手协议并收到组播密钥交换第一帧编进入此状态。
WPA_COMPLETED:表示所有认证已经完成。
对于wpa_ssid结构体:
wpa_ssid结构体是用于存储某个无线网络的配置信息,主要的数据成员如图5所示。
passphrase:该变量只和WPA/WPA-PSK模式有关。主要用于存储用户输入的字符串密码。WPAS将根据它和ssid进行计算得到最终使用的PSK。
pairwise_cipher和group_cipher:这两个主要和规范中的cipher suite定义有关。cipher suite用于指明数据收发双方使用的加密方法,pairwise_cipher和group_cipher分别表示该无线网络设置的单播以及组播的数据加密方法。
key_mgmt:该变量主要与802.11中AKM suite相关。
proto:表示该无线网络支持的安全协议类型。
auth_alg:表示无线网络支持的身份验证算法。
disabled:该变量为0代表当前无线网络可用;为1代表该变量被禁止使用;为2代表该变量和p2p有关。
mode:wpa_ssid结构体内部也定义了的枚举型变量
wpa_supplicant_init_iface()函数分析
wpa_supplicant_init_iface()函数功能如下:
1)调用wpa_config_read()函数读取配置文件信息并将它转换成对应的数据结构。
2)调用wpa_supplicant_set_driver()函数初始化一个driver wrapper全局上下文信息(详细分析见下文)。
3)调用wpa_drv_init()函数初始化单个driver wrapper相关资源(详细分析见下文)。
4)初始化其他资源(wpa_sm状态机等)
重要函数分析如下:
wpa_supplicant_init_iface()函数的第一个工作是调用wpa_config_read()函数解析运行时的配置文件。其中,wpa_s->confname的值为/data/wifi/wpa_supplicant.conf。
wpa_config_read()函数仅是把配置文件中的信息转换成对应的数据结构。这边涉及到的数据结构wpa_config(wpa_supplicant配置参数)和wpa_ssid(Network的配置参数),每个配置项可参考wpa_supplicant.conf配置文件具体含义。wpa__config_read()函数最终的结果就是设置wpa_config结构体中的对应项的值。
wpa_supplicant_init_iface()函数的第二个工作是调用wpas_init_driver()函数初始化接口驱动以及注册驱动相关的处理事件,这些功能实现主要通过wpas_init_driver()调用wpa_supplicant_set_driver以及wpa_drv_init()函数实现。
wpa_supplicant_set_driver()函数将根据driver wrapper名找到nl80211指定的结构体(详细节3.2.1节)wpa_driver_nl80211_ops,然后调用nl80211_global_init()函数完成如下功能:(1)调用netlink_init创建一个netlink socket来接收来之内核的网卡状态的变化事件,然后通过eloop_register_read_sock()函数注册一个netlink_recv()函数用于处理接收到的socket消息。netlink_recv()函数内部将根据消息的类别来调用回调函数newlink_cb()和dellink_cb()处理网卡状态变化事件(这部分代码比较简单自行分析);(2)调用wpa_driver_nl80211_init_nl_global()函数创建了两个nl_handle对象,分别global_nl和global_event并将对应的socket注册到eloop读写事件队列中。其中,global_event用于接收wlan driver发送的netlink消息;global_nl用于向wlan driver发送netlink消息。
上面涉及到一个比较重要的数据结构体nl80211_global,如下:
struct nl80211_global { struct dl_list interfaces; int if_add_ifindex; u64 if_add_wdevid; int if_add_wdevid_set; struct netlink_data *netlink; struct nl_cb *nl_cb; struct nl_handle *nl; int nl80211_id; int ioctl_sock; /* socket for ioctl() use */ struct nl_handle *nl_event; }
主要初始化是在wpa_driver_nl80211_init_nl_global()函数中完成,包含两个上面(2)介绍的两个nl_handle对象,详细介绍见上。
wpa_drv_init()函数:前面以及介绍通过wpa_supplicant_set_driver()函数调用global_init()函数初始化一个driver wrapper全局上下文信息。wpa_drv_init()函数实现的功能是初始化单个driver wrapper,内部将调用
wpa_driver_nl80211_init()函数从而调用到wpa_driver_nl80211_drv_init()函数。
wpa_driver_nl80211_drv_init()函数主要实现了四个功能:
1)分配struct wpa_driver_nl80211_data *drv以及对其进行初始化。
2)调用wpa_driver_nl80211_init_nl()和nl80211_init_bss()函数创建了两个nl_cb对象以及设置相对应回调函数process_drv_event()以及process_bss_event()函数。
3)调用rfkill_init()函数初始化rfkill用户空间相关的资源。
4)通过wpa_driver_nl80211_finish_drv_init()函数对工作模式、设备启用状态、ifopenStatus等进行相关设定,同时获取wifi驱动的capability信息以及接口对应的Mac地址等。
重点来分析下重点函数wpa_driver_nl80211_finish_drv_init(),同样先简单介绍下它的主要工作流程:
1)调用nl80211_get_ifmode()函数获取驱动的接口模式。
2)调用wpa_driver_nl80211_capa()函数获取wifi设备的处理能力(capability)。
3)调用linux_set_iface_flags()函数通过netdevices API启用wifi设备。如果失败,需要判断该设备是否被rfkill block。
4)调用wpa_driver_nl80211_set_mode()函数设置wifi设备类型为NL_80211_IFTYPE_STATION。
5)调用netlink_send_oper_ifla()函数设置网卡的工作状态为IF_OPER_DORMANT。
6)最后,调用linux_get_ifhwaddr()函数获取wifi设备的MAC地址,并判断是否需要设置超时函数。
需要注意方面如下:
对于wifi设备而言,一块网络接口设备对应一个MAC地址。引入虚拟接口(virtual interface)主要考虑到设备在多功能方面的需求,也就是个虚拟接口对应一个MAC地址。NL80211定义了众多的虚拟接口类型,如下所示:
enum nl80211_iftype { NL80211_IFTYPE_UNSPECIFIED, NL80211_IFTYPE_ADHOC, NL80211_IFTYPE_STATION, NL80211_IFTYPE_AP, NL80211_IFTYPE_AP_VLAN, NL80211_IFTYPE_WDS, NL80211_IFTYPE_MONITOR, NL80211_IFTYPE_MESH_POINT, NL80211_IFTYPE_P2P_CLIENT, NL80211_IFTYPE_P2P_GO, NL80211_IFTYPE_P2P_DEVICE, /* keep last */ NUM_NL80211_IFTYPES, NL80211_IFTYPE_MAX = NUM_NL80211_IFTYPES - 1 };
该枚举成员详细定义见NL80211_copy.h。
继续分析wpa_supplicant_add_iface()代码:
.......
(1)初始化wpa上下文信息
if (wpa_supplicant_init_wpa(wpa_s) < 0) { wpa_sm_set_ifname(wpa_s->wpa, wpa_s->ifname, wpa_s->bridge_ifname[0] ? wpa_s->bridge_ifname : NULL); wpa_sm_set_fast_reauth(wpa_s->wpa, wpa_s->conf->fast_reauth); if (wpa_s->conf->dot11RSNAConfigPMKLifetime && wpa_sm_set_param(wpa_s->wpa, RSNA_PMK_LIFETIME, wpa_s->conf->dot11RSNAConfigPMKLifetime)) { ........ (2)获取wifi设备的hardware特性 wpa_s->hw.modes = wpa_drv_get_hw_feature_data(wpa_s, &wpa_s->hw.num_modes,&wpa_s->hw.flags); (3)capability信息 if (wpa_drv_get_capa(wpa_s, &capa) == 0) { wpa_s->drv_capa_known = 1; ...... wpa_s->num_multichan_concurrent =capa.num_multichan_concurrent; } if (wpa_s->max_remain_on_chan == 0)wpa_s->max_remain_on_chan = 1000;
wpa_supplicant_add_iface()函数的第三个工作主要如下:
1)wpa_supplicant_init_wpa()函数用于初始化wpa_sm状态相关的资源。
2)wpa_drv_get_hw_feature_data()函数用于获取hw特性。
3)wpa_drv_get_capa()函数获取一些新的特性。
这里面涉及到一个比较重要的结构体wpa_sm(也叫wpa_sm状态机)如下图8:
如图6 wpa_supplicant_add_iface时序图21~24调用流程主要完成EAPOL模块、ctrl i/f模块相应的初始化工作以及向wpa_supplicant注册一个定时任务用于定时更新保存在wpa_bss中的信息。(相关代码较简单可自行分析)。
WPA_Supplicant的命令处理过程
WPA_Supplicant的相关流程分析主要从扫描流程、关联流程以及EAPOL-Key交换流程三方面分析。
WPA_Supplicant的扫描流程
图9为无线网络扫描过程以及扫描结果的处理过程时序图。其中,1~9为扫描过程;10~23为扫描结果的处理过程。若WPA_Supplicant接收到ENABLE_NETWORK命令触发一个无线网络使能并开始扫描过程,ENABLE_NETWORK命令由wpa_supplicant_ctrl_iface_enable_network()函数进行处理。
1.WPA_Supplicant的扫描过程分析
Wpa_Supplicant扫描过程的关键函数是wpa_supplicant_scan()函数,wpa_supplicant_scan()函数功能:
1)调用wpa_supplicant_enabled_networks()函数判断周围是否存在使能的无线网络设备并通过struct wpa_supplicant结构体中ap_scan参数判定扫描及选择绝大部分工作是由WPAS还是驱动完成(0表示由WPAS完成)。
2)调用wpa_supplicant_set_state()函数设置WPAS为WPA_SCANNING状态然后通过调用wpa_supplicant_associate()函数关联到无线网络设备。如果设置了struct wpa_supplicant结构体的成员connect_without_scan(连接已知无线网络),跳过扫描阶段。
3)搜索无线网络的所有配置项存储于struct wpa_ssid结构体,同时设置频率参数优化扫描过程、过滤不符合的无线网络wpa_supplicant_build_filter_ssids()等存储在sturct wpa_driver_scan_param。
4)通过wpa_supplicant_trigger_scan()直接调用driver_nl80211的scan2函数直接向驱动发起scan,并将扫描参数(存储于sturct wpa_driver_scan_param)传递给driver wrapper。
struct wpa_driver_scan_params {
struct wpa_driver_scan_ssid //用于扫描的ssid
size_t num_ssids;//无线网络个数
const u8 *extra_ies;//
int *freqs;//扫描频率
struct wpa_driver_scan_filter//需要过滤的ssid
size_t num_filter_ssids;//过滤的ssid个数
unsigned int only_new_results:1;//要求驱动广播最新结果
unsigned int low_priority:1;//优先级设定
}
该结构体对配置driver wrapper至关重要!
2.WPA_Supplicant的扫描结果处理
driver_nl80211发送NL80211_CMD_TRIGGER_SCAN命令给wlan driver以通知wlan driver开始扫描周围的无线网络,若底层wlan driver完成扫描工作driver_nl80211会收到NL80211_CMD_NEW_
SCAN_RESULTS netlink消息,这个netlink消息处理主要在process_global_event()回调函数中处理。
driver wrapper主要完成两项工作:
1)获取来之wlan driver的netlink消息信息(NL_80211_ATTR_SCAN_SSIDS、freqs等)并将这些信息存储在一个联合体union wpa_event_data event中。
2)调用wpa_supplicant_event()函数通知driver event。其中,wpa_supplicant_event()函数driver wrapper和WPAS的接口函数。
比较重要的函数如下:
wpa_supplicant_pick_network():在扫描结果中找到一个最佳的无线网络。
wpa_supplicant_need_to_roam():判断是否需要切换无线网络。
wpa_supplicant_rsn_preauth_scan_results():更新PMKSA信息。
wpa_supplicant_connect():向目标AP发起关联等请求以加入无线网络。
其中,wpa_supplicant_connect()开启WPA_Supplicant的第二个流程关联无线网络相关流程。下面分析关联流程。
WPA_Supplicant的关联流程
如图10 WPA_Supplicant无线网络关联流程时序图,主要分为无线网络关联过程(图10中时序1~11);无线网络关联结果处理过程(图10中时序12~21)。
简要分析下这两个流程:
1.WPA_Supplicant的关联无线网络过程
Wpa_Supplicant关联无线网络最重要的工作就是触发STA发起关联操作。关联无线网络的流程从wpa_supplicant_connect()函数开始。Wpa_Supplicant关联无线网络过程的关键函数是wpas_start_assoc_cb()函数,wpas_start_assoc_cb()函数主要功能:
1)和scan请求类似,关联无线网络过程也需要初始化struct wpa_driver_associate_params结构体的对象。
2)调用wpa_drv_associate()函数并通过wpa_drv_associate()函数将结构体对象传递给底层wlan driver(struct wpa_driver_associate_params结构体)。wpa_drv_associate()函熟内部将调用到driver_ nl80211.c中的wpa_driver_nl80211_associate()函数与wlan driver交互。
类似scan流程,这里面有一个非常重要的结构体:struct wpa_driver_associate_params,该结构体主要为关联过程配置相关参数,参数成员的具体分析请自行分析代码。
struct wpa_driver_associate_params { const u8 *bssid; const u8 *bssid_hint; const u8 *ssid; size_t ssid_len; struct hostapd_freq_params freq; int freq_hint; int bg_scan_period; const u8 *wpa_ie; size_t wpa_ie_len; unsigned int wpa_proto; unsigned int key_mgmt_suite; int auth_alg; const char *passphrase; ... const struct ieee80211_vht_capabilities *vhtcaps; const struct ieee80211_vht_capabilities *vhtcaps_mask; }
比较重要的函数:
wpa_supplicant_scard_init()函数:和EAP-SIM/AKA认证方法有关,主要初始化了SIM/USIM相关资源。这个函数在开始无线网络认证时被调用。
wpa_supplicant_associate()函数:初始化一个struct wpa_connect_work *cwork结构体对象并将该结构体对象传递给wpas_start_assoc_cb()函数。
wpa_supplicant_cancel_sched_scan()/wpa_supplicant_cancel_scan()函数:这两个函数的任务是取消扫描任务和取消普通扫描任务。
wpa_sm_set_assoc_wpa_ie()函数:设置WPA/RSN IE信息并通知WPA状态机,这些信息主要用在关联请求帧的过程。
wpa_bss_get_vendor_ie()函数:用于获取wpa_bss中的和vendor相关的IE。
wpa_bss_get_ie()函数:用于获取RSN_IE。
wpa_key_mgmt_wpa()函数:用于判断无线网络配置时设置的key_mgmn是否和WPA相关。
wpa_supplicant_set_suites()函数:目的是生成一个用于关联请求的IE信息(如group_cipher、pairwise_cipher、key_mgmt等)。
wpa_driver_nl80211_connect()函数:构造一个NL80211_CMD_CONNECT命令,并将它发给wlan driver。通过NL80211_CMD_CONNECT命令与驱动交互,驱动接收到该命令将完成Authentication和Association帧处理以及返回NL80211_CMD_CONNECT类型的消息处理结果。
2.WPA_Supplicant的关联结果处理
如上节所示,WPAS中的driver wrapper将收到来之wlan driver的NL80211_CMD_CONNECT类型的消息。这个消息处理同样在process_global_event()回调函数中处理,收到这个消息driver wrapper主要做两件处理工作:
1)获取来之wlan driver的NL80211_CMD_CONNECT类型的结果处理相关的消息(bssid消息、Association Request ie消息、Association Response ie消息、freqs等)并将这些信息存储在一个联合体union wpa_event_data event中。
2)通过wpa_supplicant_event()函数调用wpa_supplicant_event_assoc()函数通知driver event。wpa_supplicant_event_assoc()函数功能主要通过下面主要函数功能分析来了解。
比较重要的函数如下:
wpa_supplicant_event_associnfo():上节中wpa_supplicant对象的assoc_wpa_ie被wpa_sm_set _assoc_wpa_ie()清空,wpa_supplicant_event_associnfo()函数的功能是更新以及保存RSN/WPA的IE信息。
wpa_supplicant_scard_init():上一节已经介绍过。
wpa_sm_notify_assoc():通知WPA状态机无线网络状态被建立
eapol_sm_notify_portEnabled()/eapol_sm_notify_portValid():设置EAPOL相关的外部变量。
wpa_key_mgmt_wpa_psk():采用wpa_psk认证算法。