第4章 深入理解wpa_supplicant
本章所涉及的源代码文件名及位置
·BoardConfig.mk build/ target/ board/ generic/ BoardConfig.mk
·android.cfg externel/ wpa_supplicant_8/ wpa_supplicant/ android.cfg
·wpa_supplicant.conf
externel/ wpa_supplicant_8/ wpa_supplicant/ wpa_supplicant.conf
·main.c externel/ wpa_supplicant_8/ wpa_supplicant/ main.c
·wpa_supplicant.c
externel/ wpa_supplicant_8/ wpa_supplicant/ wpa_supplicant.c
·wpa_debug.c externel/ wpa_supplicant_8/ wpa_supplicant/ wpa_debug.c
·eap_register.c externel/ wpa_supplicant_8/ wpa_supplicant/ eap_register.c
·eap_i.h externel/ wpa_supplicant_8/ src/ eap_peer/ eap_i.h
·eloop.h externel/ wpa_supplicant_8/ src/ utils/ eloop.h
·eloop.c externel/ wpa_supplicant_8/ src/ utils/ eloop.c
·if.h externel/ kernel-headers/ original/ linux/ if.h
·config_ssid.h externel/ wpa_supplicant_8/ wpa_supplicant/ config_ssid.h
·config_file.c externel/ wpa_supplicant_8/ wpa_supplicant/ config_file.c
·config.c external/ wpa_supplicant_8/ wpa_supplicant/ config.c
·drivers.mk external/ wpa_supplicant_8/ src/ drivers/ drivers.mk
·driver_nl80211.c externel/ wpa_supplicant_8/ src/ drivers/ driver_nl80211.c
·rfkill.c externel/ wpa_supplicant_8/ src/ drivers/ rfkill.c
·wpas_glue.c externel/ wpa_supplicant_8/ wpa_supplicant/ wpas_glue.c
·ctrl_iface_unix.c
externel/ wpa_supplicant_8/ wpa_supplicant/ ctrl_iface_unix.c
·bss.c externel/ wpa_supplicant_8/ wpa_supplicant/ bss.c
·wpa.c externel/ wpa_supplicant_8/ src/ rsn_supp/ wpa.c
·eap_defs.h external/ wpa_supplicant_8/ src/ eap_common/ eap_defs.h
·defs.h external/ wpa_supplicant_8/ src/ common/ defs.h
·eap.h external/ wpa_supplicant_8/ src/ eap_peer/ eap.h
·eapol_supp_sm.c
external/ wpa_supplicant_8/ src/ eapol_supp/ eapol_supp_sm.c
·ctrl_iface.c externel/ wpa_supplicant_8/ wpa_supplicant/ ctrl_iface.c
·scan.c externel/ wpa_supplicant_8/ wpa_supplicant/ scan.c
·wpa_i.h externel/ wpa_supplicant_8/ src/ rsn_supp/ wpa_i.h
·wpa.c externel/ wpa_supplicant_8/ src/ rsn_supp/ wpa.c
·eapol_supp_sm.c externel/ wpa_supplicant_8/ src/ eapol_supp_sm.c
4.1 概述
wpa_supplicant ① 是一个开源软件项目,它实现了Station对无线网络进行管理和控制
的功能。根据官方描述,wpa_supplicant所支持的功能非常多,此处列举其中几个重要的
功能点。
1)支持W PA和IEEE 802.11i所定义的大部分功能。
这部分功能集中在安全方面,包括以下。
·支持W PA-PSK(即W PA-Personal)和W PA-Enterprise(即利用RAIDUS认证
服务器来完成身份认证的情况)。
·数据加密方面支持CCMP、TKIP、W EP104和W EP40。注意,W EP104和W EP40
中的数字代表密钥的长度。104表示密钥长度为104个二进制位(如以ASCII字符个数来计
算的话,W EP104支持的密钥长度为13个ASCII字符)。
·完全支持W PA和W PA2,包括PMKSA缓存,预认证(pre-authentication)等功
能。
·支持IEEE 802.11r和802.11w,其中802.11r规范定义了快速基础服务转移(Fast
Transition)功能,而802.11w则新增了对管理帧的安全保护机制。
·支持W FA制定的W i-Fi Protected Setup功能、P2P、TDLS等。
2)支持多种EAP Method。
主要和802.1X中Supplicant的功能有关,wpa_supplicant支持多达25种EAP
Method,包括以下。
·EAP-TLS:TLS(Transport Layer Security)本身是一种传输层安全协议,它利
用密钥算法提供端点身份认证与通讯保密,其基础是公钥基础设施(Public Key
Infrastructure,PKI)。EAP-TLS定义于RFC 5216。
·EAP-PEAP:PEAP(Protected Extensible Authentication Protocol,可扩展
EAP)由微软、思科以及RSA Security三个公司共同开发,是一种利用证书加用户名和密
码来进行身份验证的方法。
·EAP-TTLS:TTLS(Tunneled Transport Layer Security,隧道传输层安全协
议)是TLS的拓展,相比TLS,它简化了认证过程中客户端的工作。
·EAP-SIM、EAP-PSK、EAP-GPSK等其他认证方法。
提示 可阅读参考资料[1]以了解更多EAP方法的知识。
3)对各种无线网卡和驱动的支持。
·支持nl80211/ cfg80211驱动和Linux W ireless Extension驱动 ② 。
·支持W indows平台中的NDIS驱动。
提示 wpa_supplicant虽然支持W indows平台,但相信绝大多数读者使用的是
W indows自带的无线网络管理程序(或者Intel芯片相关软件提供的无线网络管理程序)。
从功能角度来说,读者可认为wpa_supplicant是这些私有程序的一种开源实现。
Android作为开源世界的集大成者,在无线网络管理和控制方面直接使用了
wpa_supplicant。Android 4.1中,external目录下有两个和wpa_supplicant相关的目
录,分别是wpa_supplicant_6和wpa_supplicant_8。6和8分别代表对应wpa_supplicant
的版本号为0.6.10和2.0-devel。
提示 关于wpa_supplicant的发布历史,请读者参考
http:/ / hostap.epitest.fi/ releases.html。
本书的分析目标是wpa_supplicant_8,它包含三个主要子目录,分别如下。
·hostapd:当手机进入Soft AP模式时,手机将扮演AP的角色,故需要hostapd来提
供AP的功能。
·wpa_supplicant:Station模式,也叫Managed模式。本书分析的重点。
·src:hostapd和wpa_supplicant中都包含了一些通用的数据结构和处理方法,这些
内容都放在此src目录中。注意,hostapd/ src和wpa_supplicant/ src子目录均链接到此src
目录。
wpa_supplicant是Android用户空间中无线网络部分的核心模块,所有Framework层
中和W i-Fi相关的操作最终都将借由wpa_supplicant来完成。另外,wpa_supplicant本身
对802.11、802.1X和W i-Fi Alliance定义的一些规范都有极好的支持。所以,分析它将是
加深理解802.11及相关理论知识的一个非常重要的途径。
本章分两条路线来分析wpa_supplicant和相关的功能模块。
·路线一:首先介绍wpa_supplicant的初始化过程。这条路线将帮助读者了解
wpa_supplicant中常见的数据结构及之间的关系。这条路非常难走,请读者做好心理准
备。
·路线二:通过命令行发送命令的方式触发wpa_supplicant进行相关工作,使手机加
入一个利用W PA-PSK进行认证的无线网络。这条路线将帮助读者了解wpa_supplicant中
的命令处理、scan、association、4-W ay Handshake等相关处理流程。
提示 后续章节还将围绕Android中无线网络技术开展更多的讨论。第5章将介绍
Android Framework中的W ifiService及其相关模块。第6、7章将继续wpa_supplicant之
旅,其内容和W PS、W i-Fi P2P以及W ifiP2pService有关。
为了行文方便,本书将用W PAS来表示wpa_supplicant。另外,后文代码分析中还能
见到一种重要的数据结构,也叫wpa_supplicant。请读者根据上下文信息来理解
wpa_supplicant的含义。
正式开始分析之旅前,先简单了解wpa_supplicant。
① 注意,wpa_supplicant项目中还包含一个名为hostapd程序的代码,它实现了AP的功
能,本书不讨论。官方地址为http:/ / hostap.epitest.fi/ 。
② 根据审稿专家的反馈,wpa_supplicant仅支持Linux W ireless Extension V19以后的
版本。
4.2 初识wpa_supplicant
本节介绍W PAS一些外围知识,包括软件结构、编译配置、控制命令和对应控制API的
用法。其中,控制命令的格式和API的用法将在介绍W ifiService相关模块时见到。另外,
在研究W PAS时,能熟练使用git查询历史版本信息也非常关键。首先从W PAS软件架构开
始。
4.2.1 wpa_supplicant架构
wpa_supplicant是一个比较庞大的开源软件项目,包含500多个文件,20万行代码,
其内部模块构成如图4-1所示 [2] 。
图4-1 wpa_supplicant软件架构
图4-1所示的W PAS软件架构包括如下重要模块。
·W PAS所有工作都围绕事件(event loop模块)展开。它是基于事件驱动的。事件驱
动和消息驱动类似,主线程等待事件的发生并处理它们。W PAS没有使用多线程编程,所有
事件处理都在主线程中完成。从这一点看,W PAS的运行机制很简单。
·位于event loop模块下方的driver i/ f(i/ f代表interface)接口模块用于隔离和底层
驱动直接交互的那些driver控制模块(如wext、ndiswrapper等,W PAS中称为driver
wrapper)。这些driver wrapper和平台以及芯片所使用的驱动相关。不过,由于driver
i/ f的隔离作用,W PAS中其他模块将能最大程度保持平台以及驱动无关性。
·driver wrapper经常要返回一些信息给上层。W PAS中,这些信息将通过driver
events的方式反馈给W PAS其他模块进行处理。
·上一章曾介绍过EAP以及EAPOL协议。除了定义消息格式外,RFC4137文档定义了
EAP状态机,而802.1X文档中还定义了EAPOL状态机。W PAS根据这两个协议分别实现了
EAP和EAPOL状态机。本章后续将详细分析这两个状态机以及背后的协议。除此之
外,W PAS还定义了自己的状态机(即W PA/ W PA2 State Machine)。
·W PAS实现了多种EAP方法,如EAP method模块。另外它还包含了TLS模块和
crypto模块用于支持对应的EAP方法。
·EAPOL以及EAP消息都属于LLC层数据,所以W PAS的l2_packet模块用于收发
EAPOL和EAP消息。
·W PAS支持较多的配置参数,这些参数的处理由configuration模块完成。
·W PAS是C/ S结构中的Server端,它通过ctrl i/ f模块向客户端提供通信接口。
Linux/ UNIX平台中,Client端利用Unix域socket与其通信。目前常用的Client端
wpa_cli(无界面的命令行程序)和wpa_gui(UI用Qt实现)。
W PAS支持众多功能,使用前往往需根据平台或驱动的特性进行编译配置,下面通过一
个实例来介绍如何在Android中编译wpa_supplicant。
4.2.2 wpa_supplicant编译配置
先介绍本实例的背景情况。有一台三星Galaxy Note 2手机,其OS为Android 4.1.2。
现在,编译一个AOSP(Android Open Source Project)的wpa_supplicant程序以替换
Note 2中原有的wpa_supplicant。
提示 AOSP即Google公版Android源码。几乎所有手机厂商都会根据芯片、硬件以
及厂商自定义的特性去修改它。由于Note 2源码不公开,所以笔者只能编译AOSP版的
wpa_supplicant。
假设读者已经按第1章要求部署Android 4.1源码和开发环境,接下来要做的事情如
下。
cd 4.1source #首先进入4.1源码根目录
source build/envsetup #建立Android源码编译环境
lunch #选择要编译的设备和版本。笔者选择了1,代表full-eng。eng代表工程版,该选项对应的目标设备类型
#(TARGET_PRODUCT)为generic,其编译出来的镜像文件可由模拟器加载并运行
由上述配置可知,笔者将使用generic版本编译一个wpa_supplicant以运行在真实的机
器上。
提示 通过执行lunch命令可知,不同的设备应有对应的编译配置项。由于笔者没有
Note 2的源码,所以只能尝试编译generic版本。
接下来要为generic平台定制所使用的wpa_supplicant版本,通过修改
BoardConfig.mk来完成的。
[–>BoardConfig.mk]
#在此文件最后添加如下内容
WPA_SUPPLICANT_VERSION := VER_0_8_X #表明使用wpa_supplicant_8
BOARD_WPA_SUPPLICANT_DRIVER := NL80211 #表明驱动使用Nl80211
BOARD_WLAN_DEVICE := bcmdhd #表明Kernel中的Wi-Fi设备为博通公司的bcmdhd
#编译博通公司驱动相关的静态库,该库对应的代码也在AOSP源码中,位置是
#hardware/broadcom/wlan/bcmdhd/wpa_supplicant_8_lib/
BOARD_WPA_SUPPLICANT_PRIVATE_LIB := lib_driver_cmd_bcmdhd
巧合的是,Note 2使用的wlan芯片刚好为bcmdhd。
除了修改BoardConfig.mk外,W PAS也定义了自己的编译配置文件android.config,
其内容如下。
…#该文件主要定义了编译时生成的宏,各平台根据自己的硬件情况去设置需要编译的内容
Driver interface for generic Linux wireless extensions
CONFIG_DRIVER_WEXT=y #可注释这一条以取消编译WEXT相关代码
Driver interface for Linux drivers using the nl80211 kernel interface
CONFIG_DRIVER_NL80211=y #可去掉此行的注释符号以增加对Nl80211的支持
CONFIG_LIBNL20=y
…#其他很多编译配置项都可在此文件中修改
#注意,此文件中对CONFIG_DRIVER_NL80211的修改和BoardConfig.mk中的BOARD_WPA_SUPPLICANT_DRIVER
#相重合。BoardConfig.mk的优先级较高,所以请读者先修改它
配置完毕后,开始编译。
#首先要编译wpa_supplicant依赖的静态库lib_driver_cmd_bcmdhd
mmm hardware/broadcom/wlan/bcmdhd/wpa_supplicant_8_lib/
mmm external/wpa_supplicant_8 #生成wpa_supplicant,同时也会生成wpa_cli
将编译后的wpa_supplicant替换Note 2的/ system/ bin/ wpa_supplicant并设置其为可
运行(通过chmod命令设置其权限位0755)。同时,把wpa_cli"push"到/ system/ bin下为
后续测试做准备。
经过测试发现,AOSP的wpa_supplicant以及wpa_cli均能正常工作在Note 2上。这
也间接表明Note 2并未对wpa_supplicant以及博通芯片相关的代码做较大改动。
注意 严格来说,android.cfg应该是唯一的编译控制文件。但由于底层wlan芯片不
同,W PAS可能还依赖其他模块。所以,在具体实施时,BoardConfig.mk(或其他文件,
视具体情况而定)也需要做修改。
4.2.3 wpa_supplicant命令和控制API
由图4-1可知,W PAS对外通过控制接口模块与客户端通信。在Android平台
中,W PAS的客户端是位于Framework中的W ifiService。用户在Settings界面进行W i-Fi
相关的操作最终都会经由W ifiService通过发送命令的方式转交给wpa_supplicant去执
行。
1.命令
W PAS定义了许多命令,常见命令如下。
·PING:心跳检测命令。客户端用它判断W PAS是否工作正常。W PAS收到"PING"命
令后需要回复"PONG"。
·MIB:客户端用该命令获取设备的MIB信息。
·STATUS:客户端用该命令来获取W PAS的工作状态。
·ADD_NETW ORK:为W PAS添加一个新的无线网络。它将返回此新无线网络的
ID(从0开始)。注意,此network id非常重要,客户端后续将通过它来指明自己想操作的
无线网络。
·SET_NETW ORK:network id是无线网络的
ID。此命令用于设置指定无线网络的信息。其中variable为参数名,value为参数的值。
·ENABLE_NETW ORK:使能某个无线网络。此命令最终将促使
W PAS发起一系列操作以加入该无线网络。
除了接收来自Client的命令外,W PAS也会主动给Client发送命令。例如,W PAS需用
户为某个无线网络输入密码。这类命令称为Interactive Request,其格式如下。
W PAS向客户端发送的命令遵循以下格式。
CTRL-REQ---
如以下命令表示需要用户为0号网络输入密码。
CTRL-REQ-PASSW ORD-0-Passwork needed for SSID test-network
客户端处理完后,需回复:
CTRL-RSP---
目前支持的field包括PASSW ORD、IDENTITY(EAP中的identity或者用户名)、
PIN等。
最后,W PAS还可通过形如以下命令向客户端通知一些事情。
CTRL-EVENT--
提示 除了"CTRL-EVENT-XXX"之外,W PAS还支持形如"W PA:XXX"和"W PS-
XXX"的通知事件。这些事件和W PA和W PS有关。下一章分析W ifiService时还能见到它
们。
图4-2所示为利用wpa_cli测试status命令得到的结果。图中最后几行显示W PAS向
wpa_cli返回了两个CTRL-EVENT信息。
图4-2 wpa_cli命令测试
2.控制API
Android平台中W ifiService是W PAS的客户端,它和W PAS交互时必须使用
wpa_supplicant提供的API。这些API声明于wpa_ctrl.h中,其用法如下。
// 必须包含此头文件,链接时需包含libwpa_client.so动态库
#include “wpa_ctrl.h”
客户端使用wpa_ctrl时首先要分配控制对象。下面两个API用于创建和销毁控制对象
wpa_ctrl。
// 创建一个wpa控制端对象wpa_ctrl。Android平台中,参数ctrl_path代表unix域socket的位置
struct wpa_ctrl * wpa_ctrl_open(const char *ctrl_path);
void wpa_ctrl_close(struct wpa_ctrl *ctrl); // 注销wpa_ctrl控制对象
下面这个函数用于发送命令给W PAS。
// 客户端发送命令给wpa_supplicant,回复的消息保存在reply中
int wpa_ctrl_request(struct wpa_ctrl *ctrl, const char *cmd, size_t cmd_len,
char *reply, size_t *reply_len,void (*msg_cb)(char *msg, size_t len));
msg_cb是一个回调函数,该参数的设置和W PAS中C/ S通信机制的设计有关。
从Client角度来看,它发送给W PAS的命令所对应的回复属于solicited event(有请
求的事件),而前面所提到的CTRL-EVENT事件(用于通知事件)对应为unsolicited
event(未请求的事件)。当Client在等待某个命令的回复时,W PAS同时可能有些通知事
件要发送给客户端,这些通知事件不是该命令的回复,所以不能通过wpa_ctrl_request的
reply参数返回。
为了防止丢失这些通知事件,wpa_cli设计了一个msg_cb回调用于客户端在等待命令回
复的时候处理那些unsolicited event。
这种一个函数完成两样完全不同的功能的设计实在有些特别,所以wpa_supplicant规
定只有打开通知事件监听功能的wpa_ctrl对象,才能在wpa_ctrl_request中通过msg_cb获
取通知事件。而打开通知事件监听功能相关的API如下所示。
// 打开通知事件监听功能
int wpa_ctrl_attach(struct wpa_ctrl *ctrl);
// 打开通知事件监听功能的wpa_ctrl对象能直接调用下面的函数来接收unsolicited event
int wpa_ctrl_recv(struct wpa_ctrl *ctrl, char *reply, size_t *reply_len);
如果客户端并不发送命令,而只是想接收Unsolicited event,可通过wpa_ctrl_recv
函数来达到此目的。
综上所述,单独使用wpa_ctrl_recv和wpa_ctrl_request都不方便。所以,一种常见的
用法是:客户端创建两个wpa_ctrl对象来简化自己的逻辑处理。
·一个打开了通知事件监听功能的wpa_ctrl对象将只通过wpa_ctrl_recv来接收通知事
件。
·另外一个wpa_ctrl专职用于发送命令和接收回复。由于没有调用wpa_ctrl_attach,
故它不会收到通知事件。
提示 下一章分析W ifiService时将见到这种创建两个wpa_ctrl对象的做法。
4.2.4 git的使用
W PAS难度较大的一个重要原因是其注释较少,很多变量的含义没有任何解释。笔者也
为此大伤脑筋。不得以,只能通过查看W PAS代码的历史版本来寻根溯源。经过实践,笔者
总结了利用git来查询W PAS历史版本信息的一些步骤,分别如下。
用git clone命令下载W PAS官方代码。
git clone git://w1.fi/srv/git/hostap.git
以下命令的含义是查询use_monitor在driver_nl80211.c中的变化情况。
git blame src/drivers/driver_nl80211.c|grep use_monitor
因为use_monitor定义于该文件中,所以用git blame查看。得到的结果如图4-3所
示。
图4-3 git blame结果
图4-3中的第一行显示了use_monitor最早出现的patch的情况,其对应的commit id
是a11241fa。接着,再通过命令"git log a11241fa"可查看当时的commit信息。
图4-4 git log结果
图4-4展示了a11241fa对应的commit消息。由于提交者一般会在该消息中添加注释性
内容,所以可通过研究这些内容来了解代码中某些变量的含义。
下面正式开始W PAS的代码分析之旅。首先分析W PAS的初始化流程。
4.3 wpa_supplicant初始化流程
Android系统中,W PAS启动是通过"setprop ctrl.start wpa_supplicant"来触发init
进程去fork一个子进程来完成的。W PAS在init配置文件中被定义为一个service。图4-5所
示为Note 2 init.smdk4x12.rc文件中关于wpa_supplicant的定义。
图4-5 init配置文件中的wpa_supplicant
图4-5中的黑框展示了wpa_supplicant的启动参数 ① 。其众多参数中,最重要的是通
过"-c"参数指定的W PAS启动配置文件(图4-5中,该配置文件全路径名
为/ data/ misc/ wifi/ wpa_supplicant.conf)。
提示 wpa_supplicant源代码中包含一个启动配置文件的模板,该文件对各项配置参
数都有说明。其文件路径为
external/ wpa_supplicant_8/ wpa_supplicant/ wpa_supplicant.conf。
Note 2中该配置文件的内容如图4-6所示。
图4-6 wpa_supplicant.conf文件内容
·ctrl_interface指明控制接口unix域socket的文件名。
·update_config表示如果W PAS运行过程中修改了配置信息,则需要把它们保存到此
wpa_supplicant.conf文件中。
·从device_name到config_method都和W PS设置有关。后续章节介绍其作用。
·p2p等选项和W i-Fi P2P有关。后续章介绍它们的作用。
·W PAS运行过程中得到的无线网络信息都会通过一个"network"配置项保存到此配置
文件中。如果该信息完整,一旦W PAS找到该无线网络就会尝试用保存的信息去加入它(这
也是为什么用户在settings中打开无线网络后,手机能自动加入周围某个曾经登录过的无线
网络的原因)。
·network项包括的内容非常多。图中第二个network项展示了该无线网络的ssid、密
钥管理方法(key management)、身份认证方法及密码等信息。network中的priority表
示无线网络的优先级。其作用是,如果同时存在多个可用的无线网络,W PAS优先选择
priority高的那一个。
下面正式进入W PAS的代码,先来看其入口函数main。
① 关于init.rc文件的解析及setprop的实现,读者可阅读《深入理解Android:卷Ⅰ》第
3章。
4.3.1 main函数分析
Android平台中,main函数定义于main.c中,代码如下所示。
[–>main.c::main]
int main(int argc, char *argv[])
{
int c, i;
struct wpa_interface *ifaces, *iface;
int iface_count, exitcode = -1;
struct wpa_params params;
struct wpa_global
global;
/
Android平台中,下面这个函数的实现在os_unix.c中。Android对其做了一些修改,主要是权
限方面的设置防止某些情况下被破解者利用权限漏洞以获取root权限。
*/
if (os_program_init())
return -1;
os_memset(¶ms, 0, sizeof(params));
params.wpa_debug_level = MSG_INFO;
iface = ifaces = os_zalloc(sizeof(struct wpa_interface));
…
iface_count = 1;
wpa_supplicant_fd_workaround(); // 输入输出重定向到/dev/null设备
for (;😉 { // 参数解析,由图4-3所知,Note 2中WPAS启动只使用了4个参数
c = getopt(argc, argv, “b:Bc:C:D🇩🇪f:g:hi:KLNo:O:p:P:qstuvW”);
if (c < 0)
break;
switch © {
…
case ‘c’:
// 指定配置文件名。注意,该参数赋值给了wpa_interface中的变量
iface->confname = optarg;
break;
…
case ‘D’:
// 指定driver名称。注意,该参数赋值给了wpa_interface中的变量
iface->driver = optarg;
break;
…
case ‘e’:
// 指定初始随机数文件,用于后续随机数的生成 ①
params.entropy_file = optarg; break;
…
case ‘i’:
iface->ifname = optarg; // 指定网络设备接口名,本例是"wlan0"
break;
…
}
}
exitcode = 0;
// 关键函数①:根据传入的参数,创建并初始化一个wpa_global对象
global = wpa_supplicant_init(¶ms);
…
for (i = 0; exitcode == 0 && i < iface_count; i++) {
…
// 关键函数②:WPAS支持操作多个无线网络设备,此处需将它们一一添加到WPAS中
// WPAS内部将初始化这些设备
if (wpa_supplicant_add_iface(global, &ifaces[i]) == NULL)
exitcode = -1;
}
// Android平台中,wpa_supplicant通过select或epoll方式实现多路I/O复用。相关解释见下文
if (exitcode == 0)
exitcode = wpa_supplicant_run(global);
wpa_supplicant_deinit(global);
…// 退出
return exitcode;
}
main函数中出现了几个重要的数据结构和两个关键函数。
注意 虽然W PAS代码遵循C语法,但笔者也将称结构体实例称为对象。
先来认识这几个重要数据结构,如图4-7所示。
图4-7 main函数中重要的数据结构
图4-7中:
·wpa_interface用于描述一个无线网络设备。该参数在初始化时用到。
·wpa_global是一个全局性质的上下文信息。它通过ifaces变量指向一个
wpa_supplicant对象(以后介绍wpa_supplicant时,读者将发现系统内的所有
wpa_supplicant对象将通过单向链表连接在一起。所以,严格意义上来说,ifaces变量指
向一个wpa_supplicant对象链表)。drv_priv包含driver wrapper所需要的全局上下文信
息。其drv_count代表当前编译到系统中的driver wrapper个数(详情见下文)。另
外,wpa_global有一个全局控制接口,如果设置该接口,其他wpa_interface设置的控制
接口将被替代。
·wpa_supplicant是W PAS的核心数据结构。一个interface对应有一个
wpa_supplicant对象,其内部包含非常多的成员变量(图4-7并未画出,下文详细介
绍)。另外,系统中所有wpa_supplicant对象都通过next变量链接在一起。
·ctrl_iface_global_priv是全局控制接口的信息,内部包含一个用于通信的socket句
柄。
提示 由于篇幅原因,笔者将根据情况略去数据结构中部分成员变量的介绍。
下面分析关键函数wpa_supplicant_init。
① 读者可阅读《深入理解Android:卷Ⅱ》3.3节以了解Android平台中更多和随机数有
关的知识。
4.3.2 wpa_supplicant_init函数分析
wpa_supplicant_init代码如下所示。
[–>wpa_supplicant.c::wpa_supplicant_init]
struct wpa_global * wpa_supplicant_init(struct wpa_params *params)
{
struct wpa_global
global;
int ret, i;
…
#ifdef CONFIG_DRIVER_NDIS
…// windows driver支持
#endif
#ifndef CONFIG_NO_WPA_MSG
// 设置全局回调函数,详情见下文解释
wpa_msg_register_ifname_cb(wpa_supplicant_msg_ifname_cb);
#endif / CONFIG_NO_WPA_MSG */
// 输出日志文件设置,本例未设置该文件
wpa_debug_open_file(params->wpa_debug_file_path);
…
ret = eap_register_methods();// ①注册EAP方法
…
global = os_zalloc(sizeof(*global)); // 创建一个wpa_global对象
… // 初始化global中的其他参数
wpa_printf(MSG_DEBUG, “wpa_supplicant v” VERSION_STR);
// ②初始化事件循环机制
if (eloop_init()) {…}
// 初始化随机数相关资源,用于提升后续随机数生成的随机性
// 这部分内容不是本书的重点,感兴趣的读者请自行研究
random_init(params->entropy_file);
// 初始化全局控制接口对象。由于本例中未设置全局控制接口,故该函数的处理非常简单,请读者自行阅读该函数
global->ctrl_iface = wpa_supplicant_global_ctrl_iface_init(global);
…
// 初始化通知机制相关资源,它和dbus有关。本例没有包括dbus相关内容,略
if (wpas_notify_supplicant_initialized(global)) {…}
// ③wpa_driver是一个全局变量,其作用见下文解释
for (i = 0; wpa_drivers[i]; i++)
global->drv_count++;
…
// 分配全局driver wrapper上下文信息数组
global->drv_priv = os_zalloc(global->drv_count * sizeof(void
));
…
return global;
}
wpa_supplicant_init函数的主要功能是初始化wpa_global以及一些与整个程序相关
的资源,包括随机数资源、eloop事件循环机制以及设置消息全局回调函数。
此处先简单介绍消息全局回调函数,一共有两个。
·wpa_msg_get_ifname_func:有些输出信息中需要打印出网卡接口名。该回调函数
用于获取网卡接口名。
·wpa_msg_cb_func:除了打印输出信息外,还可通过该回调函数进行一些特殊处
理,如把输出信息发送给客户端进行处理。
上述两个回调函数相关的代码如下所示。
[–>wpa_debug.c]
// wpa_msg_ifname_cb用于获取无线网卡接口名
// WPAS为其设置的实现函数为wpa_supplicant_msg_ifname_cb
// 读者可自行阅读此函数
static wpa_msg_get_ifname_func wpa_msg_ifname_cb = NULL;
void wpa_msg_register_ifname_cb(wpa_msg_get_ifname_func func){
wpa_msg_ifname_cb = func;
}
// WPAS中,wpa_msg_cb的实现函数是wpa_supplicant_ctrl_iface_msg_cb,它将输出信息发送给客户端
// 图4-2最后两行的信息就是由此函数发送给客户端的。而且前面的"<3>"也是由它添加的
static wpa_msg_cb_func wpa_msg_cb = NULL;
void wpa_msg_register_cb(wpa_msg_cb_func func){
wpa_msg_cb = func;
}
现在来看wpa_supplicant_init中列出的三个关键点,首先是eap_register_method函
数。
1.eap_register_methods函数
该函数本身非常简单,它主要根据编译时的配置项来初始化不同的eap方法。其代码如
下所示。
[–>eap_register.c::eap_register_methods]
int eap_register_methods(void)
{
int ret = 0;
#ifdef EAP_MD5 // 作为supplicant端,编译时将定义EAP_MD5
if (ret == 0)
ret = eap_peer_md5_register();
#endif / EAP_MD5
/
…
#ifdef EAP_SERVER_MD5 // 作为Authenticator端,编译时将定义EAP_SERVER_MD5
if (ret == 0)
ret = eap_server_md5_register();
#endif / EAP_SERVER_MD5 */
…
return ret;
}
如上述代码所示,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所示W PAS软件架构中的event loop模块
有关。
2.eloop_init函数及event loop模块
eloop_init函数本身特别简单,它仅初始化了W PAS中事件驱动的核心数据结构体
eloop_data。W PAS事件驱动机制的实现非常简单,它就是利用epoll(如果编译时设置了
CONFIG_ELOOP_POLL选项)或select实现了I/ O复用。
提醒 select(或epoll)是I/ O复用的重要函数,属于基础知识范畴。请不熟悉的读者
自行学习相关内容。
从事件角度来看,W PAS的事件驱动机制支持5种类型的event。
·read event:读事件,例如来自socket的可读事件。
·write event:写事件,例如socket的可写事件。
·exception event:异常事件,如果socket操作发生错误,则由错误事件处理。
·timeout event:定时事件,通过select的等待超时机制来实现定时事件。
·signal:信号事件,信号事件来源于Kernel。W PAS允许为一些特定信号设置处理函
数。
以上这些事件相关的信息都保存在eloop_data结构体中,如图4-9所示。
图4-8 eap_method数据结构
图4-9 eloop_data结构体
简单介绍一下eloop提供的事件注册API及eloop事件循环核心处理函数eloop_run。首
先是事件注册API函数,相关代码如下所示。
[–>eloop.h]
// 注册socket读事件处理函数,参数sock代表一个socket句柄。一旦该句柄上有读事件发生,则handler函数
// 将被事件处理循环(见下文eloop_run函数)调用
int eloop_register_read_sock(int sock, eloop_sock_handler handler,
void *eloop_data, void *user_data);
// 注册socket事件处理函数,具体是哪种事件(只能是读、写或异常)由type参数决定
int eloop_register_sock(int sock, eloop_event_type type,
eloop_sock_handler handler,void *eloop_data, void *user_data);
// 注册超时事件处理函数
int eloop_register_timeout(unsigned int secs, unsigned int usecs,
eloop_timeout_handler handler, void *eloop_data, void *user_data);
// 注册信号事件处理函数,具体要处理的信号由sig参数指定
int eloop_register_signal(int sig, eloop_signal_handler handler, void *user_data);
最后,向读者展示一下W PAS事件驱动机制的运行原理,其代码在eloop_run函数中,
如下所示。
[–>eloop.c::eloop_run]
void eloop_run(void)
{
fd_set *rfds, *wfds, *efds; // fd_set是select中用到的一种参数类型
struct timeval _tv;
int res;
struct os_time tv, now;
// 事件驱动循环
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_time(&now);
if (os_time_before(&now, &timeout->time))
os_time_sub(&timeout->time, &now, &tv);
else
tv.sec = tv.usec = 0;
_tv.tv_sec = tv.sec;
_tv.tv_usec = tv.usec;
}
// 将外界设置的读事件添加到对应的fd_set中
eloop_sock_table_set_fds(&eloop.readers, rfds);
…// 设置写、异常事件到fd_set中
// 调用select函数
res = select(eloop.max_sock + 1, rfds, wfds, efds,timeout ? &_tv : NULL);
if(res < 0) {…// 错误处理}
// 先处理信号事件
eloop_process_pending_signals();
// 判断是否有超时事件发生
timeout = dl_list_first(&eloop.timeout, struct eloop_timeout,list);
if (timeout) {
os_get_time(&now);
if (!os_time_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); // 处理超时事件
}
}
…// 处理读/写/异常事件。方法和下面这个函数类似
eloop_sock_table_dispatch(&eloop.readers, rfds);
…// 处理wfds和efds
}
out:
return;
}
eloop_run中的while循环是W PAS进程的运行中枢。不过其难度也不大。
下面来看wpa_supplicant_init代码中的第三个关键点,即wpa_drivers变量。
3.wpa_drivers数组和driver i/ f模块
wpa_drivers是一个全局数组变量,它通过extern方式声明于main.c中,其定义却在
drivers.c中,如下所示。
[–>drivers.c::wpa_drivers定义]
struct wpa_driver_ops
wpa_drivers[] =
{
#ifdef CONFIG_DRIVER_WEXT
&wpa_driver_wext_ops,
#endif / CONFIG_DRIVER_WEXT
/
#ifdef CONFIG_DRIVER_NL80211
&wpa_driver_nl80211_ops,
#endif / CONFIG_DRIVER_NL80211 */
…// 其他driver接口
}
wpa_drivers数组成员指向一个wpa_driver_ops类型的对象。wpa_driver_ops是
driver i/ f模块的核心数据结构,其内部定义了很多函数指针。而正是通过定义函数指针的
方法,W PAS能够隔离上层使用者和具体的driver。
注意 此处的driver并非通常意义所指的那些运行于Kernel层的驱动。读者可认为它
们是Kernel层wlan驱动在用户空间的代理模块。上层使用者通过它们来和Kernel层的驱动
交互。为了避免混淆,本书后续将用driver wrapper一词来表示W PAS中的driver。而
driver一词将专指Kernel里对应的wlan驱动。
另外,wpa_drivers数组包含多少个driver wrapper对象也由编译选项来控制(如代码
中所示的CONFIG_DRIVER_W EXT宏,它们可在android.cfg中被修改)。
此处先列出wpa_driver_nl80211_ops的定义。
[–>driver_nl80211.c::wpa_driver_nl80211_ops]
const struct wpa_driver_ops wpa_driver_nl80211_ops = {
.name = “nl80211”, // driver wrapper的名称
.desc = “Linux nl80211/cfg80211”, // 描述信息
.get_bssid = wpa_driver_nl80211_get_bssid, // 用于获取bssid
…
.scan2 = wpa_driver_nl80211_scan, // 扫描函数
…
.get_scan_results2 = wpa_driver_nl80211_get_scan_results,
// 获取扫描结果
…
.disassociate = wpa_driver_nl80211_disassociate, // 触发disassociation操作
.authenticate = wpa_driver_nl80211_authenticate, // 触发authentication操作
.associate = wpa_driver_nl80211_associate, // 触发association操作
// driver wrapper全局初始化函数,该函数的返回值保存在wpa_global成员变量drv_pri数组中
.global_init = nl80211_global_init,
…
.init2 = wpa_driver_nl80211_init, // driver wrapper初始化函数
…
#ifdef ANDROID // Android平台定义了该宏
.driver_cmd = wpa_driver_nl80211_driver_cmd,// 该函数用于处理和具体驱动相关的命令
#endif
};
本节介绍了main函数中第一个的关键点wpa_supplicant_init,其中涉及的知识有:几
个重要数据结构,如wpa_global、wpa_interface、eap_method、wpa_driver_ops等;
event loop的工作原理;消息全局回调函数和wpa_drivers等内容。
下面来分析main中第二个关键函数wpa_supplicant_add_iface。
4.3.3 wpa_supplicant_add_iface函数分析
wpa_supplicant_add_iface用于向W PAS添加接口设备。所谓的添加(add iface),
其实就是初始化这些设备。该函数代码如下所示。
[–>wpa_supplicant.c::wpa_supplicant_add_iface]
struct wpa_supplicant * wpa_supplicant_add_iface(struct wpa_global *global,
struct wpa_interface *iface)
{
struct wpa_supplicant *wpa_s;
struct wpa_interface t_iface;
struct wpa_ssid *ssid;
…
wpa_s = wpa_supplicant_alloc();
…
wpa_s->global = global;
t_iface =
iface;
…// 其他一些处理。本例未涉及它们
// wpa_supplicant_init_iface为重要函数,下面单独用一节来分析它
if (wpa_supplicant_init_iface(wpa_s, &t_iface)) {…}
…
// 通过dbus通知外界有新的iface加入。本例并未使用DBUS
if (wpas_notify_iface_added(wpa_s)) { …}
// 通过dbus通知外界有新的无线网络加入
for (ssid = wpa_s->conf->ssid; ssid; ssid = ssid->next)
wpas_notify_network_added(wpa_s, ssid);
/
还记得图4-7中wpa_global数据结构吗?wpa_global的ifaces变量指向一个
wpa_supplicant对象,而wpa_supplicant又通过next变量将自己链接到一个单向链表中。
*/
wpa_s->next = global->ifaces;
global->ifaces = wpa_s;
return wpa_s;
}
wpa_supplicant_add_iface的内容非常丰富,包括两个重要数据结构
(wpa_supplicant和wpa_ssid)以及一个关键函数wpa_supplicant_init_iface。由于这
些数据结构涉及较多背景知识,故本节先来介绍它们。
提示 wpa_supplicant_init_iface内容也比较丰富,本章将在4.3.4节中单独介绍。
1.wpa_ssid结构体
wpa_ssid用于存储某个无线网络的配置信息(如所支持的安全类型、优先级等)。它
其实是图4-6所示wpa_supplicant.conf中无线网络配置项在代码中的反映(conf文件中每
一个network项都对应一个wpa_ssid对象)。它的一些主要数据成员如图4-10所示。
图4-10 wpa_ssid数据结构
图4-10所示中的一些数据成员非常重要,下面分别介绍它们。
(1)安全相关成员变量及背景知识
和安全相关的成员变量如下所示。
1)passphrase:该变量只和W PA/ W PA2-PSK模式有关,用于存储我们输入的字符串
密码。而实际上,规范要求使用的却是图4-10中的psk变量。结合3.3.7节中关于key和
password的介绍可知,用户一般只设置字符串形式的password。而W PAS将根据它和ssid
进行一定的计算以得到最终使用的PSK。参考资料[3]中有PSK计算方法。
2)pairwise_cipher和group_cipher:这两个变量和规范中的cipher suite(加密套
件)定义有关。cipher suite用于指明数据收发两方使用的数据加密方法。
pairwise_cipher和group_cipher分别代表为该无线网络设置的单播和组播数据加密方法。
标准说明请阅读参考资料[4]。W PAS中的定义如下。
// 位于defs.h中
#define WPA_CIPHER_NONE BIT(0) // 不保护。BIT(N)是一个宏,代表1左移N位后的值
#define WPA_CIPHER_WEP40 BIT(1) // WEP40(即5个ASCII字符密码)
#define WPA_CIPHER_WEP104 BIT(2) // WEP104(即13个ASCII字符密码)
#define WPA_CIPHER_TKIP BIT(3) // TKIP
#define WPA_CIPHER_CCMP BIT(4) // CCMP
// 系统还定义了两个宏用于表示默认支持的加密套件类型:(位于config_ssid.h中)
#define DEFAULT_PAIRWISE (WPA_CIPHER_CCMP | WPA_CIPHER_TKIP)
#define DEFAULT_GROUP (WPA_CIPHER_CCMP | WPA_CIPHER_TKIP |
WPA_CIPHER_WEP104 | WPA_CIPHER_WEP40)
3)key_mgmt:该成员和802.11中的AKM suite相关。AKM(Authentication and
Key Managment,身份验证和密钥管理)suite定义了一套算法用于在Supplicant和
Authenticator之间交换身份和密匙信息。标准说明见参考资料[5],W PAS中定义的
key_mgmt可取值如下。
// 位于defs.h中
#define WPA_KEY_MGMT_IEEE8021X BIT(0) // 不同的AKM suite有对应的流程与算法。不详细介绍
#define WPA_KEY_MGMT_PSK BIT(1)
#define WPA_KEY_MGMT_NONE BIT(2)
#define WPA_KEY_MGMT_IEEE8021X_NO_WPA BIT(3)
#define WPA_KEY_MGMT_WPA_NONE BIT(4)
#define WPA_KEY_MGMT_FT_IEEE8021X BIT(5) // FT(Fast Transition)用于ESS中快速切换BSS
#define WPA_KEY_MGMT_FT_PSK BIT(6)
#define WPA_KEY_MGMT_IEEE8021X_SHA256 BIT(7) // SHA256表示key派生时使用SHA256做算法
#define WPA_KEY_MGMT_PSK_SHA256 BIT(8)
#define WPA_KEY_MGMT_WPS BIT(9)
// 位于config_ssid.h中
#define DEFAULT_KEY_MGMT (WPA_KEY_MGMT_PSK | WPA_KEY_MGMT_IEEE8021X)
// 默认的AKM suite
4)proto:代表该无线网络支持的安全协议类型。其可取值如下。
// 位于defs.h中
#define WPA_PROTO_WPA BIT(0)
#define WPA_PROTO_RSN BIT(1) // RSN其实就是WPA2
// 位于config_ssid.h中
#define DEFAULT_PROTO (WPA_PROTO_WPA | WPA_PROTO_RSN) // 默认支持两种协议
5)auth_alg:表示该无线网络所支持的身份验证算法,其可取值如下。
// 位于defs.h中
#define WPA_AUTH_ALG_OPEN BIT(0) // Open System,如果要使用WPA或RSN,必须选择它
#define WPA_AUTH_ALG_SHARED BIT(1) // Shared Key算法
#define WPA_AUTH_ALG_LEAP BIT(2) // LEAP算法,LEAP是思科公司提出的身份验证方法
#define WPA_AUTH_ALG_FT BIT(3) // 和FT有关,此处不详细介绍,读者可阅读参考资料[6]
6)eapol_flags:和动态W EP Key有关(本书不讨论,读者可阅读参考资料[7]),其
取值包括如下。
// 位于config_ssid.h中
#define EAPOL_FLAG_REQUIRE_KEY_UNICAST BIT(0)
#define EAPOL_FLAG_REQUIRE_KEY_BROADCAST BIT(1)
上述变量的取值将影响wpa_supplicant的处理逻辑。本章后续代码分析将见识到它们
的实际作用。
(2)其他成员变量及背景知识
图4-10中其他三个重要成员变量介绍如下。
1)proactive_key_caching:该变量和OPC(Opportunistic PMK Caching) ① 技术
有关。该技术虽还未正式被标准所接受,但很多无线设备厂商都支持它。其背景情况是,一
组AP和一个中心控制器(central controller)共同组建一个所谓的mobility zone(移动
区域)。zone中的所有AP都连接到此控制器上。当STA通过zone中的某一个AP(假设是
AP_0)加入到无线网络后,STA和AP0完成802.1X身份验证时所创建的PMKSA(假设是
PMKSA_0)将由controller发送到zone中的其他AP。其他AP将根据此PMSKA_0来生成
PMKSA_i。当STA切换到zone中的AP_i时,它将根据PMKSA_0计算PMKID_i(不熟悉
的读者请阅读3.3.7节RSNA介绍),并试图和AP_i重新关联(Reassociation)。如果此
AP_i属于同一个zone,因为之前它已经由controller发送的PMKSA_0计算出了
PMKSA_i,所以STA可避过802.1X认证流程而直接进入后续的(如4-W ay Handshake)
处理流程。802.1X验证的目的就是得到PMKSA,所以,如果AP_i已经有PMKSA_i,就无
须费时费力开展802.1X认证工作了。proactive_key_caching默认值为0,即不支持此功
能。另外,OPC功能需要AP支持。关于OPC的信息请阅读参考资料[8]和[9]。
2)disable:该变量取值为0(代表该无线网络可用)、1(代表该无线网络被禁止使
用,但可通过命令来启用它)、2(表示该无线网络和P2P有关)。
3)mode:wpa_ssid结构体内部还定义了一个枚举型变量,其可取值如图4-10底部所
示。此处要特别指出的是,基础结构型网络中,如果STA和某个AP成功连接的话,STA也
称为Managed STA(对应枚举值为W PAS_MODE_INFRA)。
2.wpa_supplicant结构体
wpa_supplicant结构体定义的成员变量非常多,图4-11列出了其中一部分内容。
图4-11 wpa_supplicant结构体
此处先解释几个比较简单的成员变量。
·drv_priv和global_drv_priv:W PAS为driver wrapper一共定义了两个上下文信
息。这是因为driver i/ f接口定义了两个初始化函数(以nl80211 driver为例,它们分别是
global_init和init2)。其中,global_init返回值为driver wrapper全局上下文信息,它将
保存在wpa_global的drv_priv数组中(见图4-7)。每个wpa_supplicant都对应有一个
driver wrapper对象,故它也需要保存对应的全局上下文信息。init2返回值则是driver
wrapper上下文信息,它保存在wpa_supplicant的driv_priv中。
·current_bss:该变量类型为wpa_bss。wpa_bss是无线网络在wpa_supplicant中
的代表。wpa_bss中的成员主要描述了无线网络的bssid、ssid、频率(freq,以MHz为单
位)、Beacon心跳时间(以TU为单位)、capability信息(网络性能,见3.3.5节定长字段
介绍)、信号强度等。wpa_bss的作用很重要,不过其数据结构相对比较简单,此处不介
绍。以后用到它时再来介绍。
现在,来看wpa_supplicant结构体中其他更有“料”的成员变量。
(1)安全相关成员变量及背景知识
wpa_supplicant也定义了一些和安全相关的成员变量。
·pairwise_cipher、group_cipher、key_mgmt、wpa_proto、
mgmt_group_cipher:这几个变量表示该wpa_supplicant最终选择的安全策略。其中
mgmt_group_cipher和IEEE 802.11w(定义了管理帧加密的规范)有关。为节约篇幅,
图4-11中仅列出pairwise_cipher一个变量。
·countermeasures:该变量名可译为“策略”,和TKIP的MIC(Message
Integrity Check,消息完整性校验)有关。因为TKIP MIC所使用的Michael算法在某些
情况下容易被攻破,所以规范特别定义了TKIP MIC countermeasures用于处理这类事
情。例如,一旦检测到60秒内发生两次以上MIC错误,则停止TKIP通信60秒。这部分内容
请阅读参考资料[10]和[11]。
(2)功能相关成员变量及背景知识
wpa_supplicant结构体中有一些成员变量和功能相关。
·sched_scan_timeout(还有一些相关变量未在图4-11中列出):该变量和计划扫描
(scheduled scan)功能有关。计划扫描即定时扫描,需要Kernel(版本必须大于3.0)的
W i-Fi驱动支持。启用该功能时,需要为驱动设置定时扫描的间隔(以毫秒为单位)。
·bgscan(还有其他相关成员变量未在图4-11中列出):该变量和后台扫描及漫游
(background scan and roaming)技术有关。当STA在ESS(假设该ESS由多个AP共同
构成)中移动时,有时候因为信号不好(例如STA离之前所关联的AP距离过远等),它需
要切换到另外一个距离更近(即信号更好)的AP。这个切换AP的工作就是所谓的漫游。为
了增强切换AP时的无缝体验(扫描过程中,STA不能收发数据帧。从用户角度来看,相当
于网络不能使用),STA可采用background scan(定时扫描一小段时间或者当网络空闲
时才扫描,这样可减少对用户正常使用的干扰)技术来监视周围AP的信号强度等信息。一
旦之前使用的AP信号强度低于某个阈值,STA则可快速切换到某个信号更强的AP。除了
background scan外,还有一种on-roam scan也能提升AP切换时的无缝体验。关于
background scan和roaming,请阅读参考资料[12]和[13]。
·gas:该变量是GAS(Generic Advertisement Service,通用广告服务)的小写,
和802.11u协议有关。该协议规定了不同网络间互操作的标准,其制定的初衷是希望W i-Fi
网络能够像运营商的蜂窝网络一样,方便终端设备接入。例如,人们用智能手机可搜索到数
十个、甚至上百个无线网络。在这种情况下如何选择正确的无线网络呢?802.11u协议使用
GAS和ANQP(Access Network Query Protocol,接入网络查询协议)来帮助设备自动
选择合适的无线网络。其中,GAS是MLME SAP中的一种(见规范6.3.71节),它使得
STA在通过认证前(prior to authentication)就可以向AP发送和接收ANQP数据包。
STA则使用ANQP协议向AP查询无线网络运营商的信息,然后STA根据这些信息来判断自
己可以加入哪一个运营商的无线网络(例如中国移动手机卡用户可以连接中国移动架设的无
线网络)。802.11u现在还不是特别完善,详细信息可阅读参考资料[14]和[15]。
·CONFIG_SME:该变量是一个编译宏,用于设置W PAS是否支持SME。我们在
3.3.6节“802.11 MAC管理实体”中曾介绍过SME(Station Management Entity)。如
果该功能支持,则driver wrapper可直接利用SME定义的SAP,而无须使用MLME的SAP
了。Android平台中如果定义了CONFIG_DRIVER_NL80211宏,则CONFIG_SME也将
被定义(参考drivers.mk文件)。不过SME的功能是否起作用,还需要看driver是否支
持。Galaxy Note 2 wlan driver不支持SME,故本书不讨论。
(3)wpa_states的取值
wpa_states的取值如下。
·W PA_DISCONNECTED:表示当前未连接到任何无线网络。
·W PA_INTERFACE_DISABLED:代表当前此wpa_supplicant所使用的网络设备
被禁用。
·W PA_INACTIVE:代表当前此wpa_supplicant没有可连接的无线网络。这种情况
包括周围没有无线网络,以及有无线网络,但是因为没有配置信息(如没有设置密码等)而
不能发起认证及关联请求的情况。
·W PA_SCANNING、W PA_AUTHENTICATING、W PA_ASSOCIATING:分别
表示当前wpa_supplicant正处于扫描无线网络、身份验证、关联过程中。
·W PA_ASSOCIATED:表明此wpa_supplicant成功关联到某个AP。
·W PA_4W AY_HANDSHAKE:表明此wpa_supplicant处于四次握手处理过程中。
当使用PSK(即W PA/ W PA2-Personal)策略时,STA收到第一个EAPOL-Key数据包则
进入此状态。当使用W PA/ W PA2-Enterprise方法时,当STA完成和RAIDUS身份验证后
则进入此状态。
·W PA_GROUP_HANDSHAKE:表明STA处于组密钥握手协议处理过程中。当STA
完成四次握手协议并收到组播密钥交换第一帧数据后即进入此状态(或者四次握手协议中携
带了GTK信息,也会进入此状态。详情见4.5.5节EAPOL-Key交换流程分析)。
·W PA_COMPLETED:所有认证过程完成,wpa_supplicant正式加入某个无线网
络。
介绍完上述几个重要数据结构后,下面将分析wpa_supplicant_add_iface中一个关键
函数wpa_supplicant_init_iface。
① 有些书上也叫Opportunisitic Key Caching,简写为OKC。
4.3.4 wpa_supplicant_init_iface函数分析
wpa_supplicant_init_iface内容非常多,我们将通过逐步展示代码段的方法,分五部
分介绍。
[–>wpa_supplicant.c::wpa_supplicant_init_iface代码段一]
static int wpa_supplicant_init_iface(struct wpa_supplicant *wpa_s,
struct wpa_interface *iface)
{
const char *ifname, *driver;
struct wpa_driver_capa capa;
if (iface->confname) {
…// CONFIG_BACKEND_FILE处理,此宏指明WPAS使用的配置项信息来源于文件
// Android定义了它
wpa_s->conf = wpa_config_read(wpa_s->confname);
}
…
由上述代码可知,init_iface初始化的第一个工作是解析运行时配置文件。其
中,wpa_s->confname的值为"/ data/ misc/ wifi/ wpa_supplicant.conf",解析函数是
wpa_config_read。
1.wpa_supplicant_init_iface分析之一
这个函数本身没有特别之处,仅是把配置文件中的信息转换成对应的数据结构。
[–>config_file.c::wpa_config_read]
struct wpa_config * wpa_config_read(const char *name)
{
FILE *f;
char buf[256], *pos;
int errors = 0, line = 0;
struct wpa_ssid *ssid, *tail = NULL, *head = NULL;
struct wpa_config *config; // 配置文件在代码中对应的数据结构
int id = 0;
config = wpa_config_alloc_empty(NULL, NULL);
…
f = fopen(name, “r”);
…
while (wpa_config_get_line(buf, sizeof(buf), f, &line, &pos)) {
if (os_strcmp(pos, “network={”) == 0) {
// 读取配置文件中的network项,并将其转化成一个wpa_ssid类型的对象
ssid = wpa_config_read_network(f, &line, id++);
…
// 根据图4-10所示,wpa_ssid通过next成员变量构成了一个单向链表
if (head == NULL) { head = tail = ssid;}
else { tail->next = ssid; tail = ssid;}
// network项属于配置文件的一部分,故wpa_ssid对象也包含在wpa_config对象中
if (wpa_config_add_prio_network(config, ssid)) {…}
…// CONFIG_NO_CONFIG_BLOBS,blob是配置文件中的一个字段,用于存储有些身
// 份认证算法需要用的证书之类的信息。本例没有使用blob配置项
// 解析其他项
} else if (wpa_config_process_global(config, pos, line) < 0) {…}
}
fclose(f);
config->ssid = head;
…
return config;
}
wpa_config和wpa_ssid这两个数据结构都是配置文件中的信息在代码中的反映。读者
可查看wpa_supplicant.conf配置模板文件来了解各个配置项的含义。
上述代码中,wpa_config_process_global的实现有一些特别,它通过宏的方式来定义
解析项及对应的解析函数。由于解析函数最终结果就是设置wpa_config中对应项的值,故
本章不讨论其细节,感兴趣的读者不妨自行阅读它们。
2.wpa_supplicant_init_iface分析之二
wpa_supplicant_init_iface函数代码段二如下所示。
[–>wpa_supplicant.c::wpa_supplicant_init_iface代码段二]
…// 接wpa_supplicant_init_iface代码段一
if (os_strlen(iface->ifname) >= sizeof(wpa_s->ifname)) {…}
// 将wpa_interface中的ifname复制到wpa_supplicant的ifname变量中
os_strlcpy(wpa_s->ifname, iface->ifname, sizeof(wpa_s->ifname));
…
// 下面这两个函数和EAPOL状态机相关,我们将在4.4节介绍
eapol_sm_notify_portEnabled(wpa_s->eapol, FALSE);
eapol_sm_notify_portValid(wpa_s->eapol, FALSE);
driver = iface->driver;
next_driver:
if (wpa_supplicant_set_driver(wpa_s, driver) < 0) return -1;
wpa_supplicant_set_driver将根据driver wrapper名(本例是"nl80211")找到
wpa_driver数组中nl80211指定的driver wrapper对象wpa_driver_nl80211_ops,然后调
用其global_init函数。直接来看global_init函数的实现。
提示 global_init函数将返回全局driver wrapper上下文信息,它保存在wpa_global
的drv_priv数组中。
(1)global_init函数分析
global_init是wpa_driver_ops结构体中的一个类型为函数指针的成员变量。nl80211
对应的driver wrapper将其设置为nl80211_global_init,代码如下所示。
[–>driver_nl80211.c::nl80211_global_init]
static void * nl80211_global_init(void)
{
struct nl80211_global *global;
struct netlink_config *cfg;
global = os_zalloc(sizeof(*global));
global->ioctl_sock = -1;
dl_list_init(&global->interfaces);
global->if_add_ifindex = -1;
cfg = os_zalloc(sizeof(
cfg));
…
cfg->ctx = global;
/
下面这三条语句用于创建netlink socket来接收来自内核的网卡状态变化事件(如UP、DORMANT、
REMOVED),然后通过eloop_register_read_sock注册一个netlink_recv函数用于处理接收
到的socket消息。
netlink_recv函数内部将根据消息的类别来调用newlink_cb和dellink_cb以处理网卡状态变
化事件。这两个回调函数处理比较简单,读者可在阅读完本章后再自行研究它们。
/
cfg->newlink_cb = wpa_driver_nl80211_event_rtm_newlink;
cfg->dellink_cb = wpa_driver_nl80211_event_rtm_dellink;
global->netlink = netlink_init(cfg);
// 将加入netlink中AF_NETLINK协议中的RTMGRP_LINK组播组
…
// nl80211利用netlink机制和wlan driver交互
if (wpa_driver_nl80211_init_nl_global(global) < 0) …// 错误处理
global->ioctl_sock = socket(PF_INET, SOCK_DGRAM, 0);
…
return global;
}
上面代码涉及一个比较重要的数据结构,即代表nl80211 driver wrapper全局上下文
信息的nl80211_global,其结构如图4-12所示。
图4-12 nl80211_global结构体
需要注意的是nl80211_global包含两个nl_handle对象。nl_handle的真实类型就是
libnl定义的nl_socket。其中,nl用于发送netlink消息,nl_event用于接收netlink消息。
这两个nl_handle对象的初始化由wpa_driver_nl80211_init_nl_global函数完成,马
上来看它。
(2)wpa_driver_nl80211_init_nl_global函数分析
wpa_driver_nl80211_init_nl_global是global_init的核心函数,其代码如下所示。
[–>driver_nl80211.c::wpa_driver_nl80211_init_nl_global]
static int wpa_driver_nl80211_init_nl_global(struct nl80211_global global)
{
// 此函数利用了第3章中介绍的API
int ret;
// 创建一个netlink回调对象
global->nl_cb = nl_cb_alloc(NL_CB_DEFAULT);
/
nl_create_handle返回值的类型为nl_handle,而nl_handle在driver_nl802.11c中
就是nl_socket(代码中的定义:#define nl_handle nl_sock)。
nl_create_handle内部调用genl_connect连接到内核对应的模块。注意,该函数最后的字符串参数
(如此处的"nl")仅用于输出调试信息。
/
global->nl = nl_create_handle(global->nl_cb, “nl”);
/
向netenlink中的"nl"模块查询"nl80211"模块的编号。注意,genl_ctrl_resolve函数本
来由libnl2定义,但driver_nl80211.c通过
#define genl_ctrl_resolve android_genl_ctrl_resolve
宏将其指向android_genl_ctrl_resolve。该函数内部通过发送查询消息来获取"nl80211"
模块的family值。请读者自行阅读android_genl_ctrl_resolve函数。
/
global->nl80211_id = genl_ctrl_resolve(global->nl, “nl80211”);
…
// 创建另外一个nl_sock对象,其用途是接收netlink消息
global->nl_event = nl_create_handle (global->nl_cb, “event”);
…
/
下面这几个函数的作用如下。
nl_get_multicast_id:先从nl80211模块中获得对应的组播组编号,如"scan"、"mlme"以及
“regulatory"组播组的编号。
nl_socket_add_membership:加入某个组播组。这样,当某个组播有消息发送时,nl_event就能收到了。
/
ret = nl_get_multicast_id(global, “nl80211”, “scan”);
ret = nl_socket_add_membership(global->nl_event, ret);
ret = nl_get_multicast_id(global, “nl80211”, “mlme”);
ret = nl_socket_add_membership(global->nl_event, ret);
ret = nl_get_multicast_id(global, “nl80211”, “regulatory”);
ret = nl_socket_add_membership(global->nl_event, ret);
nl_cb_set(global->nl_cb, NL_CB_SEQ_CHECK, NL_CB_CUSTOM,
no_seq_check, NULL);// 设置序列号检查函数为no_seq_check
nl_cb_set(global->nl_cb, NL_CB_VALID, NL_CB_CUSTOM,
process_global_event, global);// 设置netlink消息回调处理函数
/
将nl_event对应的socket注册到eloop中,回调函数为wpa_driver_nl80211_event_receive,
该函数内部将调用nl_recv_msg,而nl_recv_msg又会调用process_global_event。所以,我们只
要关注process_global_event就可以了。
*/
eloop_register_read_sock(nl_socket_get_fd(global->nl_event),
wpa_driver_nl80211_event_receive, global->nl_cb, global->nl_event);
return 0;
…
}
wpa_driver_nl80211_init_nl_global内容比较多,此处总结一下其工作内容。
·创建了两个nl_handle对象,分别是global->nl和gobal->event。nl_handle内部定
义一个socket句柄。所以,两个nl_handle等同于两个socket句柄。global->event用于接
收netlink消息。nl80211定义了几个组播组,此处选择加入其中的"scan”、
"mlme"和"regulatory"三个组播组,它们分别对应于扫描信息、mlme信息及管制信息。
wlan driver内部会往这三个组播发送相关的消息。这样,global->event就能收到它们。
·接着将global->event对应的socket注册到eloop读事件队列中。如此,内核发送的
netlink消息就能被wpa_driver_nl80211_event_receive处理。
wpa_driver_nl80211_event_receive内部将调用libnl API中的nl_recv_msg来接收消息,
而它又会触发最重要的process_global_event函数被调用。
·global->nl用来向wlan driver发送netlink消息。根据第3章对genlmsg的介绍,其
内部有一个变量用于指明family,而nl80211对应的family编号则保存在global-
nl80211_id中。
提示 根据笔者的心得,读者大可不必对libnl等进行深入细致的源码分析。对W PAS
的来说,仅了解libnl2 API的用法即可。
3.wpa_supplicant_init_iface分析之三
介绍完wpa_supplicant_set_driver后,现在回到wpa_supplicant_init_iface,继续
看第三段代码。
[–>wpa_supplicant.c::wpa_supplicant_init_iface代码段三]
…// 接wpa_supplicant_set_driver代码段
// 又是一个关键函数
wpa_s->drv_priv = wpa_drv_init(wpa_s, wpa_s->ifname);
…
// 设置driver参数,本例没有使用这一项功能
if (wpa_drv_set_param(wpa_s, wpa_s->conf->driver_param) < 0) {… }
// 从driver中获取网卡名
ifname = wpa_drv_get_ifname(wpa_s);
if (ifname && os_strcmp(ifname, wpa_s->ifname) != 0) {
// 如果不一致则替换配置文件中设置的网卡设备名
os_strlcpy(wpa_s->ifname, ifname, sizeof(wpa_s->ifname));
}
上一节初始化driver wrapper的全局上下文信息后(通过调用global_init来完成),
接着要处理的就是单个driver wrapper了。该工作由wpa_drv_init函数完成。其内部将调
用driver wrapper的init2函数(注意,如果driver wrapper定义了init2函数,init2将唯
一被调用,否则将调用其定义的init函数)。
直接来看driver_nl80211实现的init2函数,其代码如下所示。
[–>driver_nl80211.c::wpa_driver_nl80211_init]
static void * wpa_driver_nl80211_init(void *ctx, const char *ifname, void *global_priv)
{
struct wpa_driver_nl80211_data *drv;
struct rfkill_config *rcfg; struct i802_bss *bss;
…
drv = os_zalloc(sizeof(drv));
…
drv->global = global_priv;
drv->ctx = ctx; // ctx的真正类型是wpa_supplicant
bss = &drv->first_bss; bss->drv = drv;
os_strlcpy(bss->ifname, ifname, sizeof(bss->ifname));
drv->monitor_ifidx = -1; drv->monitor_sock = -1; drv->eapol_tx_sock = -1;
// ap_scan_as_station变量和hostapd有关
drv->ap_scan_as_station = NL80211_IFTYPE_UNSPECIFIED;
// ①下面两个关键函数见后文解释
if (wpa_driver_nl80211_init_nl(drv)) {…}
if (nl80211_init_bss(bss)) goto failed;
/
下面这个函数将读取/sys/class/net/wlan0/phy80211/name文件的内容,并将其保存到
wpa_driver_nl80211_data->phyname变量中。该文件存储了Wi-Fi物理设备的名称,如phy0等。
它由wifi wlan注册时动态生成,所以其值有可能变化。
注意,/sys/class/net/wlan0中的wlan0为无线网络设备名,它由wpa_supplicant -i参数指明。
*/
nl80211_get_phy_name(drv);
rcfg = os_zalloc(sizeof(*rcfg));
rcfg->ctx = drv;
os_strlcpy(rcfg->ifname, ifname, sizeof(rcfg->ifname));
// 和rfkill相关,见下文解释
rcfg->blocked_cb = wpa_driver_nl80211_rfkill_blocked;
rcfg->unblocked_cb = wpa_driver_nl80211_rfkill_unblocked;
drv->rfkill = rfkill_init(rcfg);
…
// 关键函数②
if (wpa_driver_nl80211_finish_drv_init(drv)) goto failed;
// 见下文关于PF_PACKET的解释
drv->eapol_tx_sock = socket(PF_PACKET, SOCK_DGRAM, 0);
if (drv->data_tx_status) { …}
if (drv->global) {
// 把自己加到nl80211_global中的interfaces链表中去
dl_list_add(&drv->global->interfaces, &drv->list);
drv->in_interface_list = 1;
}
return bss; // wpa_driver_nl80211_init返回的是一个i802_bss结构体对象
…
}
上述代码包含的知识点较多,涉及rfkill以及PF_PACKET背景知识,以及三个关键函
数,wpa_driver_nl80211_init_nl、nl80211_init_bss和
wpa_driver_nl80211_finish_drv_init。
(1)rfkill背景知识
rfkill代表radio frequency(RF)connector kill switch support,它是Kernel中的
一个子系统(subsystem)。其功能是控制系统中射频设备的电源(包括W i-Fi、GPS、
BlueTooth、FM等设备。注意,这些设备驱动只有把自己注册到rfkill子系统中后,rfkill
才能对它们起作用)的工作以避免浪费电力。rfkill有软硬两种方式来禁止(block)RF设
备。
·hard block:不能通过软件来重新启用RF设备。据观察,Android手机还没有hard
block功能。不过笔者猜测某些笔记本有这个功能。例如,笔者的Dell笔记本上有一个特殊
的开关,一旦把它关上,W i-Fi模块就不能工作 ① 。
·soft block:可以用软件来重新启用RF设备。
rfkill对用户空间提供了相应的控制接口,主要是通过/ dev/ rfkill设备文件来完成相关
操作。我们通过wpa_driver_nl80211_init中调用的一个名为rfkill_init的函数来认识如何
使用rfkill。该函数代码如下所示。
[–>rfkill.c::rfkill_init]
struct rfkill_data * rfkill_init(struct rfkill_config cfg)
{
/
rfkill_data 是WPAS自定义的一个数据结构,主要用于设置两个回调函数用于处理block
和unblock的情况。
由上面一段代码可知,这两个回调函数分别是wpa_driver_nl80211_rfkill_blocked和
wpa_driver_nl80211_rfkill_unblocked。
*/
struct rfkill_data *rfkill;
struct rfkill_event event; // rfkill_event代表rfkill事件
ssize_t len;
rfkill = os_zalloc(sizeof(rfkill));
rfkill->cfg = cfg;
// O_RDONLY标志表示driver_nl80211只读取rfkill事件,而不会去操作rfkill模块
rfkill->fd = open(“/dev/rfkill”, O_RDONLY);
…
// 设置I/O操作为非阻塞式
if (fcntl(rfkill->fd, F_SETFL, O_NONBLOCK) < 0) {…}
for (;😉 {// 读者知道为什么这里是一个for无限循环吗?
// 读取/dev/rfkill中已有的事件信息。rfkill事件信息保存在rfkill_event结构体中
len = read(rfkill->fd, &event, sizeof(event));
if (len < 0) {
if (errno == EAGAIN) break; // 无数据可读,则跳出循环
break; // 其他错误也跳出循环
}
…
/
rfkill_event的op变量代表rfkill事件的类型,目前可取值有RFKILL_OP_ADD(代表一
个设备添加到了rfkill子系统)、RFKILL_OP_DEL等。
rfkill_event的type变量代表该rfkill事件所对应设备的类型。目前可取值有RFKILL_
TYPE_WLAN(无线网卡设备)、RFKILL_TYPE_BLUETOOTH(蓝牙设备)等。
/
if (event.op != RFKILL_OP_ADD || event.type != RFKILL_TYPE_WLAN)
continue;
if (event.hard) { // 表示是否为hard block
rfkill->blocked = 1;
} else if (event.soft) { // 表示是否为soft block
rfkill->blocked = 1;
} // 如果hard和soft均未被设置,则表示该设备属于unblock状态,即设备允许被使用
}
// 为eloop注册一个读事件,一旦rfkill有新的事件到来,则eloop会触发rfkill_receive函数被调用
eloop_register_read_sock(rfkill->fd, rfkill_receive, rfkill, NULL);
return rfkill;
…// 错误处理
}
从上述代码可知,W PAS只是监控rfkill设备以获取发生在其上的rfkill_event,而它
并不操作rfkill以关闭或启用无线设备。
提示 关于rfkill更多的信息请阅读参考资料[16][17]。
(2)PF_PACKET背景知识
PF_PACKET有时也被称为AF_PACKET,是socket域(domain)中的一种,用于直
接在OSI/ RM的数据链路层(Data Link Layer)上收发数据。所以,通过AF_PACKET,
用户空间可直接实现在物理层之上的协议,如EAP和EAPOL等。
提醒 由3.3.1节可知,DLL层还可细分为LLC子层和MAC子层。
下面将通过一些具体代码段来展示PF_PAKCET的使用。
[AF_PACKET用法示例]
/
socket函数的第二个参数叫socket_type。AP_PACKET中可以使用SOCK_DGRAM和SOCK_RAW,二者
略有区别,主要体现在如何处理物理层地址信息上。使用AP_PACKET时,需要为数据包设置物理层地址,
它由结构体struct sockaddr_ll来表达。当socket_type设置为:
SOCK_RAM:用户接收到的数据包也将包含物理层地址,并且发送数据时,驱动将使用用户指定的物理层
地址来填充数据包。
SOCK_DGRAM:它比SOCK_RAW要高级一点。用户接收的数据包将不包括物理层地址信息,而用户发送时
指定的物理层地址也仅是一个参考,kernel会根据实际情况来填充一个更为合适的物理层地址。
另外,程序可以通过bind函数指定接收某个网卡设备上的数据包。
*/
int fd = socket(PF_PACKET, SOCK_DGRAM,htons(ETH_P_EAPOL));
// 最后一个参数代表EAPOL协议类型
struct sockaddr_ll ll; // sockaddr_ll结构体代表地址信息
memset(&ll, 0, sizeof(ll));
ll.sll_family = PF_PACKET; // 该变量必须被设置成AF_PACKET
ll.sll_ifindex = ifindex; // 网络设备的索引号
ll.sll_protocol = htons(ETH_P_EAPOL);
bind(fd, (struct sockaddr *) &ll, sizeof(ll));// 绑定到指定的网络设备
…// 其他处理
// 发送数据
struct sockaddr_ll ll2;// 目标地址
memset(&ll2, 0, sizeof(ll2));
ll.sll_family = AF_PACKET;
ll.sll_ifindex = ifindex
ll.sll_protocol = htons(ETH_P_EAPOL); // 帧类型,此处代表EAPOL帧
ll.sll_halen = ETH_ALEN; // 目标MAC地址长度
memcpy(ll.sll_addr, dst_addr, ETH_ALEN);// sll_addr用于表示目标物理层地址(即MAC地址)
// 发送EAPOL帧
ret = sendto(fd, buf, len, 0, (struct sockaddr *) &ll2,sizeof(ll2));
…
// 接收数据
struct sockaddr_ll ll3;
socklen_t fromlen;
memset(&ll3, 0, sizeof(ll3));
fromlen = sizeof(ll3);
int res = recvfrom(fd, buf, sizeof(buf), 0, (struct sockaddr *) &ll3, &fromlen);
关于PF_PACKET更为详细的信息,请读者通过man 7 packet查询Linux手册。接着
来看wpa_driver_nl80211_init中的三个重要函数,首先是wpa_driver_nl80211_init_nl和
nl80211_init_bss。
(3)wpa_driver_nl80211_init_nl与nl80211_init_bss函数分析
这两个函数都使用了libnl创建了回调对象,代码如下所示。
[–>driver_nl80211.c::wpa_driver_nl80211_init_nl]
static int wpa_driver_nl80211_init_nl(struct wpa_driver_nl80211_data *drv)
{
drv->nl_cb = nl_cb_alloc(NL_CB_DEFAULT);
…
nl_cb_set(drv->nl_cb, NL_CB_SEQ_CHECK, NL_CB_CUSTOM,no_seq_check, NULL);
nl_cb_set(drv->nl_cb, NL_CB_VALID, NL_CB_CUSTOM, process_drv_event, drv);
return 0;
}
static int nl80211_init_bss(struct i802_bss *bss)
{
bss->nl_cb = nl_cb_alloc(NL_CB_DEFAULT);
…
nl_cb_set(bss->nl_cb, NL_CB_SEQ_CHECK, NL_CB_CUSTOM,no_seq_check, NULL);
nl_cb_set(bss->nl_cb, NL_CB_VALID, NL_CB_CUSTOM, process_bss_event, bss);
return 0;
}
不过,它们仅创建了nl_cb对象,却并未创建nl_handle(即没有创建nl socket)。没
有和socket绑定,这些回调对象就不可能真正被用上。它们什么时候用呢?此处先提前介
绍一下使用它们的代码。
[–>driver_nl80211.c::nl80211_alloc_mgmt_handle]
static int nl80211_alloc_mgmt_handle(struct i802_bss *bss)
{
struct wpa_driver_nl80211_data *drv = bss->drv;
if (bss->nl_mgmt) {…/重复注册/return -1; }
bss->nl_mgmt = nl_create_handle(drv->nl_cb, “mgmt”); // 注意该函数的第一个参数
eloop_register_read_sock(nl_socket_get_fd(bss->nl_mgmt),
wpa_driver_nl80211_event_receive, bss->nl_cb, bss->nl_mgmt);
return 0;
}
static void wpa_driver_nl80211_event_receive(int sock, void *eloop_ctx, void *handle)
{
struct nl_cb *cb = eloop_ctx;
nl_recvmsgs(handle, cb); // cb是bss->nl_cb
}
注意,上述代码有一个非常奇怪的地方。bss->nl_mgmt创建时使用了drv->nl_cb对
象,该回调对象由wpa_driver_nl80211_init_nl创建,其对应的回调函数是
process_drv_event。nl_create_handle返回的实际上是一个nl_socket对象,其内部有一
个s_cb变量指向nl_create_handle的第一个参数(本例中即是drv->nl_cb)。注册到
eloop模块中的wpa_driver_nl80211_event_receive函数,在处理回调的时候却使用了
bss->nl_cb,该回调对象对应的是process_bss_event函数。
也就是说,上述函数一共使用了两个回调对象,一个是drv->nl_cb,另外一个是bss-
nl_cb。什么时候调用drv->nl_cb,什么时候调用bss->nl_cb呢?
根据笔者对比Android中libnl2和libnl2官方代码的结果,nl_recvmsgs将使用指定的
nl_cb对象进行回调(即它的第二个参数,本例中的bss->nl_cb),而
nl_recvmsgs_default将使用nl_socket中s_cb指定的回调对象(即本例中的drv-
nl_cb)。不过,Android的libnl2并没有nl_recvmsgs_default函数。所以,drv->nl_cb
实际上永远不会被用到。
注意 综合4.3.4节对wpa_driver_nl80211_init_nl_global的分析,W PAS中实际上真
正使用到的回调对象就是两个:一个是bss->nl_cb,对应的回调函数是
process_bss_event,另一个是global->nl_cb,对应的回调函数是
process_global_event。
另外,作为练习,请通过以下命令查看上一节提到的与drv->nl_cb和bss->nl_cb使用
有关的信息。
git blame src/ drivers/ driver_nl80211.c|grep process_drv_event
git show d6c9aab8
(4)wpa_driver_nl80211_finish_drv_init函数分析
wpa_driver_nl80211_init中的最后一个关键函数代码如下所示。
[–>driver_nl80211.c::wpa_driver_nl80211_finish_drv_init]
static int
wpa_driver_nl80211_finish_drv_init(struct wpa_driver_nl80211_data *drv)
{
struct i802_bss bss = &drv->first_bss;
int send_rfkill_event = 0;
drv->ifindex = if_nametoindex(bss->ifname);// 获取网卡设备的索引,属于netdevice编程范畴
drv->first_bss.ifindex = drv->ifindex;
#ifndef HOSTAPD // hostapd是另外一个程序,本书不讨论
if (drv->ifindex != drv->global->if_add_ifindex &&
/
①设置接口类型为NL80211_IFTYPE_STATION,见下文解释。注意,这个函数内容非常丰富,
其中包含很多和P2P相关的信息。本章暂时不考虑它。另外,此函数内部会调用到上一节提到的
nl80211_alloc_mgmt_handle。
/
wpa_driver_nl80211_set_mode(bss, NL80211_IFTYPE_STATION) < 0) {…}
/
linux_set_iface_flags通过ioctl方式启动ifname对应的网卡设备。
该函数使用了netdevice API,请读者回顾表2-2。
其使用的参数为SIOCSIFFLAGS和IFF_UP。
/
if (linux_set_iface_flags(drv->global->ioctl_sock, bss->ifname, 1)) {
// 注意,如果linux_set_iface_flags返回非0值(即启动设备失败)
// 要判断是不是rfkill禁止了该设备
if (rfkill_is_blocked(drv->rfkill)) {
…// 如果是因为rfkill原因导致设备被禁止,则需要通知wpa_supplicant
drv->if_disabled = 1;// 设置if_disabled为1,表示该设备被rfkill禁止了
send_rfkill_event = 1; // 该值表示需要设置WPAS的状态
} else {…}
}
// ②设置Wi-Fi设备工作状态为,IF_OPER_DORMANT,见下文解释
netlink_send_oper_ifla(drv->global->netlink, drv->ifindex,1, IF_OPER_DORMANT);
#endif / HOSTAPD /
// ③获取Wi-Fi设备的capability,见下文解释
if (wpa_driver_nl80211_capa(drv)) return -1;
// 通过ioctl方式获取指定网卡的MAC地址,也属于netdeivce编程范畴,回顾表2-2
if (linux_get_ifhwaddr(drv->global->ioctl_sock, bss->ifname,bss->addr)) return -1;
if (send_rfkill_event) {
/
添加一个超时任务,超时时间为0秒。超时处理函数为wpa_driver_nl80211_send_rfkill,该
函数内部将设置wpa_states为WPA_INTERFACE_DISABLED。
可参考4.3.3节了解WPA_INTER FACE_DISABLED状态。
/
eloop_register_timeout(0, 0, wpa_driver_nl80211_send_rfkill,drv, drv->ctx);
}
return 0;
}
wpa_driver_nl80211_finish_drv_init代码不长,但内容却比较丰富,先简单总结一
下其工作流程。
1)调用wpa_driver_nl80211_set_mode函数设置W i-Fi设备类型为
NL80211_IFTYPE_STATION。下文将详细介绍W i-Fi设备类型的知识。
2)调用linux_set_iface_flags通过netdevice API启用该W i-Fi设备。如果失败,则
需要判断该设备是否被rfkill block。
3)调用netlink_send_oper_ifla函数设置网卡的工作状态(Interface Operational
Status,IfOperStatus)为IF_OPER_DORMANT。关于IfOperStatus详情见下文解
释。
4)调用wpa_driver_nl80211_capa获取W i-Fi设备的处理能力(capability)。详情
见下文解释。
5)最后,调用linux_get_ifhwaddr获取W i-Fi设备的MAC地址,并判断是否需要设
置超时函数wpa_driver_nl80211_send_rfkill。
上述内容中有两个背景知识(设备类型以及工作状态)和一个重要函数
wpa_driver_nl80211_capa。此处先来认识设备类型。
一般而言,一块网络接口设备只有一个MAC地址,但现在许多设备都支持多个所谓的
虚拟设备(Virtual Interface),每一个虚拟设备都对应有一个虚拟MAC地址。例如,图
4-13所示为W i-Fi P2P中一种名为并发设备(Concurrent Device)的示意图。
图4-13 P2P Concurrent设备
图4-13中,位于中间的W i-Fi设备一方面以P2P Device的身份和左下角的另一个P2P
Device相连,另一方面又以STA的身份和右边的AP相连。P2P Device和STA的工作方式
不尽相同,怎么实现这种并发设备呢?解决方法就是通过这种虚拟设备的方式,使P2P
Device和STA分别使用不同的Virtual Interface和Virtual MAC。
提示 目前,市面上许多Android手机打开W i-Fi P2P功能后就必须关闭STA功能,而
笔者的Note 2就能做到P2P和STA同时工作。
NL80211定义了多种Virtual Interface类型,如下所示。
enum nl80211_iftype {
NL80211_IFTYPE_UNSPECIFIED,
NL80211_IFTYPE_ADHOC, // IBSS类型
NL80211_IFTYPE_STATION, // 基础结构型网络中的STA
NL80211_IFTYPE_AP, // 基础结构型网络中的AP
NL80211_IFTYPE_AP_VLAN, // 和VLAN有关,本书不讨论
NL80211_IFTYPE_WDS, // 无线桥接。本书不讨论
NL80211_IFTYPE_MONITOR, // 可接收无线网络所有的数据包,它提供类似AirPcap这样的功能
NL80211_IFTYPE_MESH_POINT, // Mesh网络节点,本书不讨论
NL80211_IFTYPE_P2P_CLIENT, // P2P相关,见后续章节
NL80211_IFTYPE_P2P_GO, // P2P相关,见后续章节
NUM_NL80211_IFTYPES,
NL80211_IFTYPE_MAX = NUM_NL80211_IFTYPES - 1
};
下面来看接口设备的工作状态IfOperStatus,其定义来自RFC2863。
[–>if.h]
enum {
IF_OPER_UNKNOWN, // 未知状态
// 下面这个状态表示因为系统缺乏该接口设备所依赖的模块(一般是硬件模块)而导致接口设备不能工作
IF_OPER_NOTPRESENT, //
IF_OPER_DOWN, // 接口不能工作
/
相比DOWN状态,LOWERLAEYDOWN指出了接口设备不能工作的原因是其所依赖的更低一层的设备不
能正常工作。
/
IF_OPER_LOWERLAYERDOWN,
IF_OPER_TESTING, // 接口处于测试状态中
/
接口处于休眠或暂停(pending)状态中。这种状态表示接口设备在等待某个事情的发生(例如上层
有数据要发送,则会将DORMANT状态设置为UP状态)。
*/
IF_OPER_DORMANT,
IF_OPER_UP, // 接口可工作
};
关于IfOperStatus,请读者阅读参考资料[18]。
最后,看关键函数wpa_driver_nl80211_capa,其功能是用于获取无线网络设备的
capability。代码如下所示。
[–>driver_nl80211.c::wpa_driver_nl80211_capa]
static int wpa_driver_nl80211_capa(struct wpa_driver_nl80211_data drv)
{
struct wiphy_info_data info;
// 发送netlink命令NL80211_CMD_GET_WIPHY来获取Wi-Fi设备的信息。下文将单独用一节来介绍此函数
if (wpa_driver_nl80211_get_info(drv, &info)) return -1;
drv->has_capability = 1;
/
drv->capa变量的类型是struct wpa_driver_capa,用于表示设备的capability,这些capa如下。
key_mgmt:该设备支持的密钥管理类型。默认支持WPA、WPA-PSK、WPA2和WPA2-PSK。
enc:支持的加密算法类型。默认支持WEP40、WEP104、TKIP和CCMP。
auth:支持的身份验证类型:默认支持Open System、Shared和LEAP。
/
drv->capa.key_mgmt = WPA_DRIVER_CAPA_KEY_MGMT_WPA | WPA_DRIVER_CAPA_KEY_MGMT_WPA_PSK |
WPA_DRIVER_CAPA_KEY_MGMT_WPA2 | WPA_DRIVER_CAPA_KEY_MGMT_WPA2_PSK;
drv->capa.enc = WPA_DRIVER_CAPA_ENC_WEP40 | WPA_DRIVER_CAPA_ENC_WEP104 |
WPA_DRIVER_CAPA_ENC_TKIP | WPA_DRIVER_CAPA_ENC_CCMP;
drv->capa.auth = WPA_DRIVER_AUTH_OPEN | WPA_DRIVER_AUTH_SHARED | WPA_DRIVER_AUTH_LEAP;
/
WPA_DRIVER_FLAGS_SANE_ERROR_CODES选项主要针对associate操作。当关联操作失败后,
如果driver支持该选项,则表明driver能处理失败之后的各种收尾工作(key、timeout等工作)。
否则,WPAS需要自己处理这些事情。
/
drv->capa.flags |= WPA_DRIVER_FLAGS_SANE_ERROR_CODES;
/
WPA_DRIVER_FLAGS_SET_KEYS_AFTER_ASSOC_DONE标志标明association成功后,Kernel
driver需要设置WEP key。这个标志出现的原因是由于Kernel API发生了变动,使得只能在关联
成功后才能设置key。
/
drv->capa.flags |= WPA_DRIVER_FLAGS_SET_KEYS_AFTER_ASSOC_DONE;
/
下面这两个标志表示Kernel中的driver是否能反馈EAPOL数据帧发送情况以及Deauthentication/
Disassociation帧发送情况(TX Report)。
/
drv->capa.flags |= WPA_DRIVER_FLAGS_EAPOL_TX_STATUS;
drv->capa.flags |= WPA_DRIVER_FLAGS_DEAUTH_TX_STATUS;
/
以下几个选项都和设备做AP使用有关,也就是和hostapd相关。此处简单介绍一下它们。
device_ap_sme表示AP集成了SME。读者还记得SME吗?3.3.6节曾介绍过。
/
drv->device_ap_sme = info.device_ap_sme;
/
poll_command_supported:hostapd需要判断STA是否还活跃,即心跳检测。检测方法是发送
null数据帧(即不带任何数据的无线MAC数据帧),如果STA还活跃的话,一定会回复ACK给AP(读
者还记得CSMA/CA机制吗?)。发送null数据帧的工作可以由Kernel driver完成,也可以由
hostapd来完成。如果Kernel driver支持poll_command_supported,hostapd只要发送
netlink命令NL80211_CMD_PROBE_CLIENT给Kernel驱动,所有工作就由Kernel驱动完成。
否则,hostapd需要自己构造一个null数据帧,然后再发送出去。
/
drv->poll_command_supported = info.poll_command_supported;
/
和WPA_DRIVER_FLAGS_EAPOL_TX_STATUS有关。如果wlan驱动支持的话,EAPOL帧TX
Report将通知给用户空间的driver wrapper,即此处的driver_nl80211。
/
drv->data_tx_status = info.data_tx_status;
/
use_monitor也和AP心跳检测STA有关。如果Kernel driver不支持poll_command_
supported的话,hostapd可通过创建一个NL80211_IFTYPE_MONITOR类型的接口设备用于监控
STA的活跃情况。
/
drv->use_monitor = !info.poll_command_supported;
if (drv->device_ap_sme && drv->use_monitor) {
// monitor_supported表示kernel driver是否支持创建NL80211_IFTYPE_MONITOR类
// 型的接口设备
if (!info.monitor_supported) drv->use_monitor = 0;
}
/
经过测试,Galaxy Note 2机器中上述变量取值情况如下。
device_ap_sme为1,poll_command_supported为0,data_tx_status为0,use_monitor为1,
capa.flags取值情况见下文。
*/
if (!drv->use_monitor && !info.data_tx_status)
drv->capa.flags &= ~WPA_DRIVER_FLAGS_EAPOL_TX_STATUS;
return 0;
}
Galaxy Note 2手机中得到的flags为0x2c0c0,它是如下几个选项的组合。
#define WPA_DRIVER_FLAGS_AP 0x00000040
#define WPA_DRIVER_FLAGS_SET_KEYS_AFTER_ASSOC_DONE 0x00000080
#define WPA_DRIVER_FLAGS_SANE_ERROR_CODES 0x00004000
#define WPA_DRIVER_FLAGS_OFFCHANNEL_TX 0x00008000
#define WPA_DRIVER_FLAGS_DEAUTH_TX_STATUS 0x00020000
上述wpa_driver_nl80211_capa函数中,先调用wpa_driver_nl80211_get_info函
数,从wlan driver获取设备信息,然后wpa_driver_nl80211_capa再做一些处理。
此处向读者展示一下wpa_driver_nl80211_get_info的内容,请读者关注其中和
nl80211用法有关的部分。
提醒 W PAS中类似wpa_driver_nl80211_get_info的函数还有很多,仅以
wpa_driver_nl80211_get_info为代表进行介绍。
(5)wpa_driver_nl80211_get_info函数分析
wpa_driver_nl80211_get_info代码如下所示。
[–>driver_nl80211.c::wpa_driver_nl80211_get_info]
static int wpa_driver_nl80211_get_info(struct wpa_driver_nl80211_data *drv,
struct wiphy_info_data *info)
{
struct nl_msg *msg;
os_memset(info, 0, sizeof(*info));
info->capa = &drv->capa;
msg = nlmsg_alloc();
…
// 构造一个NL80211_CMD_GET_WIPHY命令以获取设备信息
nl80211_cmd(drv, msg, 0, NL80211_CMD_GET_WIPHY);
// NL80211_CMD_GET_WIPHY命令需要携带ifindex参数以指明要查询哪个设备
NLA_PUT_U32(msg, NL80211_ATTR_IFINDEX, drv->first_bss.ifindex);
// 发送命令并等待回复,回复消息将由wiphy_info_handler函数处理
if (send_and_recv_msgs(drv, msg, wiphy_info_handler, info) == 0) return 0;
…
}
在driver_nl80211.c中,wpa_driver_nl80211_get_info函数非常具有典型性。当
driver wrapper和wlan driver通信时,需要构造一个nl_msg消息,然后往其中填写对应的
参数。发送该消息时,如果需要等待driver的回复,还可以设置一个回复消息处理函数用于
解析接收到的回复消息。
上述代码中,wiphy_info_handler就是这个回调函数。其内容非常长。不过,绝大部
分代码都是在解析netlink消息。因此,我们仅看其中与接口类型解析相关的代码片段即可
窥斑见豹。
[–>driver_nl80211.c::wiphy_info_handler]
static int wiphy_info_handler(struct nl_msg *msg, void *arg)
{
struct nlattr *tb[NL80211_ATTR_MAX + 1];
struct genlmsghdr *gnlh = nlmsg_data(nlmsg_hdr(msg));
struct wiphy_info_data *info = arg;
…
struct wpa_driver_capa *capa = info->capa;
static struct nla_policy
…// 其他信息解析
if (tb[NL80211_ATTR_SUPPORTED_IFTYPES]) {
struct nlattr nl_mode;
int i;
nla_for_each_nested(nl_mode, // 遍历netlink attribute信息
tb[NL80211_ATTR_SUPPORTED_IFTYPES], i) {
switch (nla_type(nl_mode)) {
case NL80211_IFTYPE_AP:// wlan driver支持设置接口类型为AP
capa->flags |= WPA_DRIVER_FLAGS_AP; // Galaxy Note 2支持此项
break;
…
case NL80211_IFTYPE_MONITOR:
info->monitor_supported = 1;
break;
}
}
}
…// 其他信息解析
return NL_SKIP;
}
关于driver_nl80211.c中nl80211使用最典型的wpa_driver_nl80211_get_info即介绍
到此,读者可阅读nl80211头文件来了解NL80211_CMD_GET_W IPHY和
NL80211_ATTR_SUPPORTED_IFTYPES相关的信息,其注释非常详尽。另外,读者可
阅读参考资料[19]以了解更多和nl80211命令相关的知识。
通过上面内容可知,wpa_supplicant_init_iface代码段三中最主要的函数是
wpa_drv_init,下面总结它的相关知识。
(6)wpa_drv_init相关知识总结
本节对wpa_drv_init函数进行了详细分析,其中涉及的两个重要数据结构如图4-14所
示。
图4-14 wpa_driver_nl80211_data和i802_bss结构体
图4-14中:
·wpa_driver_nl80211_data通过first_bss成员包含一个i802_bss结构体对象,而
i802_bss内部通过next指针构成一个单向链表。
·wpa_driver_nl80211_init最后返回的是一个i802_bss对象,它就是driver
wrapper上下文信息。i802_bss通过drv变量指向一个wpa_driver_nl80211_data对象。
提醒 根据前文的分析,global_init函数返回的是全局driver wrapper上下文信息。
对于nl80211 driver wrapper来说,这个全局上下文信息就是一个nl80211_global对象。
图4-15所示为wpa_drv_init中一些重要函数的调用流程。
图4-15 wpa_drv_init流程
图4-15所示函数较多,内容也比较丰富,请读者注意其中涉及的背景知识。
4.wpa_supplicant_init_iface分析之四
继续来看wpa_supplicant_init_iface函数,这次要分析的代码片段如下所示。
[–>wpa_supplicant.c::wpa_supplicant_init_iface代码段四]
…// 接wpa_drv_init
// ①初始化wpa上下文信息。见下文解释
if (wpa_supplicant_init_wpa(wpa_s) < 0) return -1;
// 设置wpa_s->wpa指向一个wpa_sm对象,下面这两个函数用于设置wpa_sm中的一些成员变量
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);
/
如果运行时配置文件(即wpa_supplicant.conf)设置了dot11RSNAConfigPMKLifetime、
dot11RSNAConfigPMKReauthThreshold和dot11RSNAConfigSATimeout,则使用配置文件中的值
来替换wpa_sm中的默认值。下文将详细介绍这个三个变量的含义。
*/
if (wpa_s->conf->dot11RSNAConfigPMKLifetime &&
wpa_sm_set_param(wpa_s->wpa, RSNA_PMK_LIFETIME,
wpa_s->conf->dot11RSNAConfigPMKLifetime)) {…}
…// 处理dot11RSNAConfigPMKReauthThreshold和dot11RSNAConfigSATimeout
// ②获取Wi-Fi设备的hardware特性
wpa_s->hw.modes = wpa_drv_get_hw_feature_data(wpa_s,&wpa_s->hw.num_modes,
&wpa_s->hw.flags);
// wpa_drv_get_capa函数已经见识过了,但这里出现了上一节没有介绍的新成员
if (wpa_drv_get_capa(wpa_s, &capa) == 0) {
// ③capability信息,见下文解释
wpa_s->drv_capa_known = 1;
// 笔者的Note 2中,capa.flags的值为0x2c0c0
wpa_s->drv_flags = capa.flags;
wpa_s->probe_resp_offloads = capa.probe_resp_offloads;
wpa_s->max_scan_ssids = capa.max_scan_ssids;
wpa_s->max_sched_scan_ssids = capa.max_sched_scan_ssids;
wpa_s->sched_scan_supported = capa.sched_scan_supported;
wpa_s->max_match_sets = capa.max_match_sets;
wpa_s->max_remain_on_chan = capa.max_remain_on_chan;
wpa_s->max_stations = capa.max_stations;
}
if (wpa_s->max_remain_on_chan == 0)
wpa_s->max_remain_on_chan = 1000;
上述代码片段共有三个关键点,分别如下。
·wpa_supplicant_init_wpa函数用于初始化wpa_sm相关的资源。
·wpa_drv_get_hw_feature_data函数用于获取hw特性。其中一些变量涉及较深的背
景知识。
·wpa_drv_get_capa是获取driver的capability。这个函数在上一节已经介绍过了,
但本节出现了一些新的capability信息。
(1)wpa_supplicant_init_wpa函数分析
wpa_supplicant_init_wpa函数代码并不复杂,主要完成以下两件事情。
·创建一个wpa_sm_ctx对象并填充其中的函数指针成员。
·初始化wpa_sm状态机。
代码如下所示。
[–>wpas_glue.c::wpa_supplicant_init_wpa]
int wpa_supplicant_init_wpa(struct wpa_supplicant *wpa_s)
{
#ifndef CONFIG_NO_WPA
struct wpa_sm_ctx *ctx;
ctx = os_zalloc(sizeof(ctx));
…
ctx->ctx = wpa_s;
ctx->msg_ctx = wpa_s;
ctx->set_state = _wpa_supplicant_set_state;
…// 其他成员变量设置
wpa_s->wpa = wpa_sm_init(ctx);
#endif / CONFIG_NO_WPA */
return 0;
}
wpa_sm_init的代码如下所示。
[–>wpa.c::wpa_sm_init]
struct wpa_sm * wpa_sm_init(struct wpa_sm_ctx *ctx)
{
struct wpa_sm *sm;
sm = os_zalloc(sizeof(sm));
dl_list_init(&sm->pmksa_candidates);
sm->renew_snonce = 1;
sm->ctx = ctx;
// 下面这三个MIB相关成员变量的解释见下文
sm->dot11RSNAConfigPMKLifetime = 43200;
sm->dot11RSNAConfigPMKReauthThreshold = 70;
sm->dot11RSNAConfigSATimeout = 60;
// 创建PMKSA缓存,用于存储PMKSA
sm->pmksa = pmksa_cache_init(wpa_sm_pmksa_free_cb, sm, sm);
…
return sm;
}
上述两段代码中涉及的函数指针暂且先略过,先介绍其中的几个重要数据结构,它们如
图4-16所示。
图4-16 wpa_sm_ctx和wpa_sm等结构体
图4-16显示了四个重要数据结构的内容。
·struct wpa_sm_ctx定义一些函数指针。这些函数的作用留待后续用到时再介绍。
·struct wpa_sm结构体名为状态机(SM代表State Machine),但和W PAS中其他
状态机比起来,它更像是一个存储状态的上下文信息。该结构体内部通过eapol变量指向一
个struct eapol_sm对象。4.4节将详细分析eapol_sm。
·struct rsn_pmksa_cache、struct rsn_pmksa_cache_entry与PMKSA缓存有关。
每一个rsn_pmksa_cache_entry代表一个PMKSA条目。注意,rsn_pmksa_cache_entry
中有一个名为aa的数组,其存储的是Authenticator的Address。一般情况下它和AP的
bssid相同。
PMKSA还和几个MIB选项有关,它们被定义成wpa_sm中的同名成员变量(数据类型
都是unsigned in),分别如下。
·dot11RSNAConfigPMKLifetime:表示每一个PMKSA条目的有效时间(单位为
秒),默认是43200秒。过了有效时间后,需要重新计算PMKSA。
·dot11RSNAConfigPMKReauthThreshold:用于指明PMKSA条目有效时间过去
百分之多少后,需要重新进行身份认证。默认是70% 。
·dot11RSNAConfigSATimeout:指明supplicant和Authenticator双方进行身份
验证的最长时间。默认是60秒。在此时间内没有完成身份验证,则认为验证失败。
·dot11RSNA4W ayHandshakeFailures:用于保存4-W ay Handshake失败的次
数。
下面来看代码中的第二个关键函数wpa_drv_get_hw_feature_data及相关的背景知
识。
(2)wpa_drv_get_hw_feature_data函数分析
该函数内部将通过wpa_driver_ops结构体中的get_hw_feature_data指针调用
driver_nl80211实现的wpa_driver_nl80211_get_hw_feature_data函数以获取wifi hw特
性。此处不讨论其函数实现,而是看看hw特性都有哪些内容。hw特性由数据结构
hostapd_hw_modes来表达,如图4-17所示。
图4-17 hostapd_hw_modes数据结构
wpa_drv_get_hw_feature_data返回的是一个hostapd_hw_modes数组,其内容已经
在图4-17中标记出来。这里展示一个实例,图4-18所示为修改W PAS后打印的Note 2 hw
特性的一部分。
图4-18 Note 2 dump信息
图4-18所示为hostapd_hw_modes数组中第三个元素的信息,它展示了硬件中和
802.11b相关的特性。共13个信道,以及每个信道的中心频率以及最大功率,支持四种传输
速率。
现在来看最后一个关键点。
(3)capability信息及含义
wpa_supplicant_init_wpa代码片段最后还显示了一些capability信息,它们的含义如
下。
·probe_resp_offloads:当设备做AP使用时(即运行hostapd),它需要发送Probe
Response帧以回复其他STA的Probe Request帧。Probe Response帧(或者AP发送的
Beacon帧)的内容需要hostapd来填充。这个变量用于指明哪些vendor specific的内容将
由W i-Fi驱动或者硬件去填充。目前NL80211.h通过枚举类型
nl80211_probe_resp_offload_support_attr来定义所能支持的协议,包括W PSv1、
W PSv2、P2P和802.11u。
·max_scan_ssids:一个Probe Request要么指定wildcard ssid以扫描周围所有的无
线网络,要么指定某个ssid以扫描特定无线网络。为了方便wpa_supplicant的使
用,driver新增了一个功能,使得上层可通过一次scan请求来扫描多个不同ssid的无线网
络。注意,此功能只是方便了W PAS内部的使用。由于规范定义的Probe Request帧只能携
带一个ssid参数。所以,上层即使想一次scan多个ssid,硬件实际上还是要为每一个ssid发
送一个Probe Request帧。
·max_sched_scan_ssids和sched_scan_supported:与计划扫描有关。
max_sched_scan_ssids和max_scan_ssids作用类似,是方便wpa_supplicant同时扫描多
个ssid而设置的。
·max_match_sets:使用计划扫描时,可以给驱动指定一个ssid过滤列表。只有扫描
结果符合ssid过滤列表的那些无线网络才会通知wpa_supplicant以开展后续处理。由于该
过滤功能可由W i-Fi硬件来完成,所以它可以节省一部分电力(即无须软件去执行过滤功
能)。
·max_remain_on_chan:该变量和off-channel transmition功能有关。该功能使得
W i-Fi硬件能在某个特定信道(channel)上保持awake状态一定时间用于传输某些MAC帧
(例如管理帧中的一种名为Action的帧)。该功能叫off-channel的原因是,STA实际上在
另一个信道(此channel叫on-channel)上和AP保持连接。举一个简单的例子,假设STA
和所关联的AP工作在2.4GHz第6频段。在某些时候,STA会转移到2.4GHz其他频段以接收
或处理其他STA(P2P的情况)或AP发送的MAC帧。上述例子中,6频段就是on-
channel,而其他频段则是off-channel。max_remain_on_chan变量用于指明STA在off-
channel中工作的最长时间,以毫秒为单位。为什么要限制off-channel时间呢?还是以上
述例子为例,STA和AP工作在第6频段,二者数据传输也是在第6频段。当STA转移到其他
频段时,它将无法接收第6频段所发送的数据。如果max_remain_on_chan时间过长,用户
将发现数据传输率大幅降低 ② 。
·max_stations:当手机做AP使用时(即无线网络接口设备的类型为
NL80211_IFTYPE_AP),该变量表示最多支持多少个STA与之关联。
下面接着分析wpa_supplicant_init_iface如下所示最后一部分代码。
5.wpa_supplicant_init_iface分析之五
wpa_supplicant_init_iface最后一段代码如下所示。
[–>wpa_supplicant.c::wpa_supplicant_init_iface代码段五]
// ①初始化driver wrapper模块最后一部分内容
if (wpa_supplicant_driver_init(wpa_s) < 0) return -1;
…// TDLS相关,本书不讨论
…// 设置country
// 初始化WPS相关模块,本章不讨论
if (wpas_wps_init(wpa_s)) return -1;
// ②初始化EAPOL模块。这部分内容4.4节介绍
if (wpa_supplicant_init_eapol(wpa_s) < 0) return -1;
wpa_sm_set_eapol(wpa_s->wpa, wpa_s->eapol);
// ③初始化ctrl i/f模块
wpa_s->ctrl_iface = wpa_supplicant_ctrl_iface_init(wpa_s);
…
wpa_s->gas = gas_query_init(wpa_s); // GAS相关,本书不讨论
#ifdef CONFIG_P2P
if (wpas_p2p_init(wpa_s->global, wpa_s) < 0) {…// P2P模块初始化,见第7章分析}
#endif / CONFIG_P2P */
// ④bss相关,详情见下文
if (wpa_bss_init(wpa_s) < 0) return -1;
return 0;// wpa_supplicant_init_iface终于成功返回
上述代码包括四个关键函数,其中第二个关键点和EAPOL模块相关,其内容4.4节再介
绍。
(1)wpa_supplicant_driver_init函数分析
先来看第一个关键函数wpa_supplicant_driver_init,代码如下所示。
[–>wpa_supplicant.c::wpa_supplicant_driver_init]
int wpa_supplicant_driver_init(struct wpa_supplicant *wpa_s)
{
static int interface_count = 0;
// 关键函数,见下文代码分析
if (wpa_supplicant_update_mac_addr(wpa_s) < 0) return -1;
if (wpa_s->bridge_ifname[0]) {…// 桥接相关内容,本书不讨论}
// 清除driver中保存的key相关的信息
wpa_clear_keys(wpa_s, NULL);
// 设置TKIP countermeasure值为0
wpa_drv_set_countermeasures(wpa_s, 0);
// 清空drive wrapper及driver中保存的pmkid信息。
wpa_drv_flush_pmkid(wpa_s);
// 设置wpa_supplicant结构体中的一些变量的初值
wpa_s->prev_scan_ssid = WILDCARD_SSID_SCAN;
wpa_s->prev_scan_wildcard = 0;
// 判断wpa_conf中是否有使能的网络
if (wpa_supplicant_enabled_networks(wpa_s->conf)) {
…// 当前配置文件中没有使能任何一个网络,故此段代码略去
} else // 设置状态为WPA_INACTIVE。该函数比较简单,请读者自行阅读
wpa_supplicant_set_state(wpa_s, WPA_INACTIVE);
return 0;
}
上述代码中唯一需要介绍的就是wpa_supplicant_update_mac_addr,因为它和图4-
1中的l2_packet模块初始化有关,其代码如下所示。
[–>wpa_supplicant.c::wpa_supplicant_update_mac_addr]
int wpa_supplicant_update_mac_addr(struct wpa_supplicant *wpa_s)
{
if (wpa_s->driver->send_eapol) { …// nl80211 driver wrapper没有定义该函数
} else if (!(wpa_s->drv_flags &
WPA_DRIVER_FLAGS_P2P_DEDICATED_INTERFACE)) {
// WPA_DRIVER_FLAGS_P2P_DEDICATED_INTERFACE和P2P有关,本例不支持该参数
l2_packet_deinit(wpa_s->l2); // 先释放之前创建的l2_packet模块
// 初始化l2_packet
wpa_s->l2 = l2_packet_init(wpa_s->ifname,
wpa_drv_get_mac_addr(wpa_s), // 获取接口的MAC地址
ETH_P_EAPOL,
// 收到的EAPOL及EAP帧将由此函数负责处理
wpa_supplicant_rx_eapol, wpa_s, 0);
…
} else { …}
// 将l2_packet_data中的own_addr内容复制到wpa_supplicant的own_addr成员变量
if (wpa_s->l2 && l2_packet_get_own_addr(wpa_s->l2, wpa_s->own_addr)) {…}
// 再把wpa_supplicant的own_addr复制到wpa_sm中的own_addr中
wpa_sm_set_own_addr(wpa_s->wpa, wpa_s->own_addr);
…
return 0;
}
l2_packet_init内部就是创建一个PF_PACKET域的socket。注意,l2_packet_init最
后一个参数为0,这样,socket的类型将是SOCK_DGRAM。l2_packet_init返回值类型为
l2_packet_data,其成员如图4-19所示。
图4-19 l2_packet_data成员
l2_packet_init通过eloop_register_read_sock函数为图4-19中的socket句柄fd注册
一个读事件回调函数l2_packet_receive,而该函数将接收socket数据,然后回调
rx_callback。该函数对于4-W ay Handshake非常重要,后文将详细介绍此处设置的回调
函数(wpa_supplicant_rx_eapol)。
下面来看第三个关键函数wpa_supplicant_ctrl_iface_init。
(2)wpa_supplicant_ctrl_iface_init函数分析
该函数内部将创建一个unix域socket,然后向eloop注册一个读事件处理函数。
Android平台对此函数进行了定制,主要是利用图4-3中init配置文件中wpa_supplicant的
socket选项。init在fork出一个wpa_supplicant子进程时将创建一个socket,并通过环境
变量传给wpa_supplicant子进程。
提示 对socket选项感兴趣的读者可阅读《深入理解Android:卷Ⅰ》3.2.3节“启动
Zygote”。
[–>ctrl_iface_unix.c::wpa_supplicant_ctrl_iface_init]
wpa_supplicant_ctrl_iface_init(struct wpa_supplicant *wpa_s)
{
struct ctrl_iface_priv *priv;
struct sockaddr_un addr;
…
priv = os_zalloc(sizeof(priv));
dl_list_init(&priv->ctrl_dst);
priv->wpa_s = wpa_s;
priv->sock = -1;
buf = os_strdup(wpa_s->conf->ctrl_interface);
…
#ifdef ANDROID // Android平台定义了此编译宏
// addr.sun_patch的值为wpa_wlan0。该值和图4-5中socket选项指定的值一样
os_snprintf(addr.sun_path, sizeof(addr.sun_path), “wpa_%s”,
wpa_s->conf->ctrl_interface);
priv->sock = android_get_control_socket(addr.sun_path);// 获取socket句柄
if (priv->sock >= 0)
goto havesock; // 直接跳转
#endif / ANDROID /
…
havesock:
#endif / ANDROID */
// 客户端发送命令都由wpa_supplicant_ctrl_iface_receive处理
eloop_register_read_sock(priv->sock, wpa_supplicant_ctrl_iface_receive,
wpa_s, priv);
// 读者还记得4.3.2节wpa_supplicant_init分析中提到的消息全局回调函数吗
wpa_msg_register_cb(wpa_supplicant_ctrl_iface_msg_cb);
os_free(buf);
return priv;
上述代码中,客户端发送的命令将由wpa_supplicant_ctrl_iface_receive函数处理。
提示 后文分析线路二中用户发送的W PAS命令时,就将直接分析此函数。
(3)wpa_bss_init函数分析
最后来看wpa_bss_init函数。
[–>bss.c::wpa_bss_init]
int wpa_bss_init(struct wpa_supplicant *wpa_s)
{
// bss和bss_id是wpa_supplicant结构体中的成员变量,它们通过链表的方式来保存wpa_bss信息
dl_list_init(&wpa_s->bss);
dl_list_init(&wpa_s->bss_id);
// 注册一个超时任务,超时时间为WPA_BSS_EXPIRATION_PERIOD,值为10秒
eloop_register_timeout(WPA_BSS_EXPIRATION_PERIOD, 0,wpa_bss_timeout, wpa_s, NULL);
return 0;
}
wpa_supplicant注册了一个定时任务用于定时更新其保存的wpa_bss信息,一旦某个
无线网络在一定时间内没有更新或使用,则需要从链表中把它去掉。
超时任务的函数代码如下。
[–>bss.c::wpa_bss_timeout]
static void wpa_bss_timeout(void *eloop_ctx, void timeout_ctx)
{
struct wpa_supplicant wpa_s = eloop_ctx;
// bss_expiration_age默认是1800秒
// 下面这个函数将更新wpa_bss链表以删除一些无用的wpa_bss对象
wpa_bss_flush_by_age(wpa_s, wpa_s->conf->bss_expiration_age);
eloop_register_timeout(WPA_BSS_EXPIRATION_PERIOD, 0,wpa_bss_timeout, wpa_s, NULL);
}
6.wpa_supplicant_add_iface流程总结
看到这里,读者一定会感慨,线路一走下来比较艰难。其中所调用函数之多、每个变量
背后含义之丰富都是初学者要面临的挑战。在此,通过图4-20展示
wpa_supplicant_add_iface中所涉及的几个重要函数的调用流程。
图4-20 wpa_supplicant_add_iface重要流程
图4-20中的第8个函数调用wpa_driver_nl80211_init的内容在图4-15中。请读者结合
这两个图来学习调用流程。
即使用了如此之多的笔墨,wpa_supplicant_init初始化所涉及的内容依然不能全部覆
盖到。下一节介绍非常重要的两个模块:EAP和EAPOL。
① 注意,此结论为笔者根据笔记本的表现形式进行的猜测。如有读者知道其工作原理不妨
与大家分享。
② max_remain_on_chan的官方解释可参考nl80211关于
NL80211_CMD_REMAIN_ON_CHANNEL的定义,其原文是"Request to remain awake
on the specified channel for the specified amount of time.This can be used to do
off-channel operations like transmit aPublic Action frame and wait for aresponse
while being associated to an AP on another channel"。请了解该功能的读者和大家分
享相关知识。
4.4 EAP和EAPOL模块
我们在第3章曾介绍过EAP和EAPOL方面的知识,它们主要和EAP/ EAPOL数据包格式
以及数据包交互流程有关。在此基础上,本节将进一步讨论W PAS中图4-1所涉及的EAP
State Machine和EAPOL State Machine这两个模块。
提示 虽然图4-1所示这两个模块名字中都带State Machine一词,但笔者更愿意称它
们为EAP模块和EAPOL模块,其原因我们后续将会见到。另外,为了行文方便,以后将用
SM代表State Machine。
本节先来介绍EAP模块,它和RFC4137协议有关。
4.4.1 EAP模块分析 [20]
RFC4137协议的全称是"State Machine for Extensible Authentication
Protocol(EAP)Peer and Authenticator",它描述了Peer端(即Supplicant端)和
Authenticator端通过状态机(State Machine)这种方式来实现EAP处理流程的具体步骤
和相关细节。本节将重点介绍Supplicant端SM的设计原理。为了行文方便,本节将使用
SUPP代替Supplicant。
1.Supplicant端SM设计原理
对状态机来说,最重要的是其状态切换图。RFC4137中SUPP SM状态切换如图4-21所
示。
图4-21的内容极为丰富,此处先介绍其中三个知识点。
·SUPP SM一共定义了13个状态,每个状态用一个框表示。框顶部所示为状态名,如
INTIALZE、IDLE等。
·每个状态都可以有自己的Entry Action(以后简称EA)。进入这个状态后,EA将
被执行。EA由状态框中状态名下边的伪代码(采用了类C++语法)表示。以FAILURE状态
为例,当状态机进入该状态后,将执行"eapFail=TRUE"伪代码,eapFail是SUPP SM定义
的变量,下文将详细介绍图中涉及的变量和相关函数。
·图中的UCT代表Unconditional Transition,即无条件状态转换。以DISCARD状
态和IDLE状态为例,由于UCT的存在,当SUPP SM在DISCARD状态中执行完其EA后,
将直接转换到IDLE状态。
对一个状态机而言,其状态的转换是因为外界条件发生变化导致。在规范中,这些外界
条件由变量来表达。图4-21中出现了很多变量和EA所包括的一些函数,它们都由RFC4137
文档定义。了解它们的作用对真正理解SUPP SM有直接和重要帮助。接下来的章节就将介
绍这些变量和函数。
图4-21 SUPP SM状态切换
图4-22 RFC4137 SUPP SM模块划分
RFC4137将和SUPP SM相关的模块分为三层,如图4-22所示。图中最底层是Lower
Layer(LL),这一层的作用是接收和发送EAP包。位于中间的SUPP SM层实现了
Supplican状态机。最上层是EAP Method(EM)层,它实现了具体的EAP方法。
SUPP SM将与EM层和LL层交互。一个最典型的交互例子就是LL收到EAP数据包后,
将该数据包交给SUPP SM层去处理。如果该EAP包需要EM层处理(例如具体的验证算法
需要EM完成),则SUPP SM层将该包交给EM。EM处理完的结果将由SUPP SM转交给LL
去发送。
提示 RFC4137中,三层之间交互的手段可以是设置变量,或者是调用函数。
先来看SUPP SM与LL交互时所使用的变量。
(1)LL层和SUPP SM层交互变量
LL层和SUPP SM层的交互比较简单,主要包括三个步骤。
1)LL层收到EAP数据包后,将其保存在eapReqData变量中,然后设置eapReq变量
为TRUE。这个变量的改变对SUPP SM层来说是一个触发信号(signal)。SUPP SM可能
会发生状态转换。
2)SUPP SM层从eapReqData中取出数据后进行处理。如果有需要回复的数据,则设
置eapResp值为TRUE,否则设置eapNoResp值为TRUE。回复数据存储在eapRespData
中。LL层将发送此回复包。
3)如果SUPP SM完成身份验证后,它将设置eapSuccess或eapFailure变量以告知LL
层其验证结果。eapSuccess为TRUE,表明验证成功。eapFailure为TRUE,则验证失
败。
上述描述中所涉及的变量及其类型如表4-1所示。注意,此处的数据类型属于伪代码。
提示 在W PAS中,LL层并非那些直接利用socket进行数据收发的模块,而是EAPOL
模块。EAP和EAPOL模块的关系将留待下一节再介绍。
表4-1中altAccept和altReject两个变量的命名非常晦涩难懂。RFC4137指出这两个变
量的定义在RFC3748中。实际上,RFC3748从头至尾都没有出现过这两个变量。经过仔细
研究,笔者发现http:/ / lists.frascone.com/ pipermail/ eap/ msg02578.html对此有一个说
法,内容如下。
这两个变量取名为lowerLayerSuccess和lowerLayerFailure更合适,它们用于通知
LL层Success或Failure信息。结合上述资料,笔者查阅了RFC3748 7.12节,在802.11网
络中,Indicatioin(通知)Success和Failure的可能场景如下。
·当supplicant收到Disassociate帧或者Deauthenticate帧时,表示
lowerLayerFailure。
·当收到4-W ay Handshake第一个Message时,表示lowerLayerSuccess。
规范阅读提示 RFC4137中,上述变量还可分为两种类型。
1)由LL层暴露给SUPP SM层的变量(Variables from Lower Layer to Peer)。它
们从表4-1中的eapReq开始,到altReject结束。原则上,LL层和SUPP SM层都可以修改
这些变量。
2)由SUPP SM层暴露给LL层的变量(Variables from Peer to Lower Layer)。它
们从表4-1中的eapResp开始,到eapKeyAvailable结束。原则上,LL层和SUPP SM层都
可以修改这些变量。另外,这些变量也可由SUPP SM层暴露给EM层来使用。
接着来看SUPP SM层和EM层交互变量。
(2)SUPP SM层和EM层交互变量
SUPP SM层和EM层的交互也是通过变量来完成的。这些变量如表4-2所示。
注意,methodState和decision的值由具体的认证方法(即Method)来确定。
提示 本书不讨论所有EAP方法的具体实现。感兴趣的读者可以深入研究EAP模块。
不过对EAP SUPP SM来说,methodState和decision的取值情况才是最重要的,因为它
们会直接影响SUPP SM的状态切换。
(3)SUPP SM其他变量和处理函数
RFC4137还为SUPP SM还定义了其他一些变量(Peer State Machine Local
Variables)及函数。变量定义见表4-3。
表4-4所示为SUPP SM处理函数的定义。
注意,表4-4中用伪代码展示了这些函数的使用案例,它们并不遵守C++语法。例如:
(rxReq,rxSuccess,rxFailure,reqId,reqMethod)=parseEapReq(eapReqData)
等号左边为parseEapReq函数的返回值,等号右边括号中的"eapReqData"为
parseEapReq的输入参数。如果使用案例中没有等号,则表示该函数无返回值(具体实现
时,可设置该函数的返回值为void)。
注意 图4-21中还包含一些其他函数,奇怪的是规范中并没有列举它们。不过,相信
读者很容易理解这些数作用,此处不详述。现在,读者能看懂图4-21所示的状态图了吗?
(4)SUPP SM定义的状态
SUPP SM定义的13个状态如表4-5所示。
前面展示了RFC4137中和SUPP SM相关的内容。笔者觉得这个状态机定义太过烦琐。
不过,本着简单、明了并且没有歧义的原则,这种做法似乎又无可厚非。从笔者经验来看,
读者只要能看懂图4-21所示SUPP SM状态切换图即算掌握了EAP模块的精髓了。W PAS中
EAP SUPP SM该如何实现呢?请看下节。
2.EAP SUPP SM代码分析
上文中曾提到过,RFC4137定义的状态机非常烦琐,而具体实现可以根据情况进行裁
剪。不过,W PAS中的EAP SUPP SM却较为严格得遵循了RFC4137。先来看其定义的数
据类型和数据结构。
(1)相关数据结构与数据类型
图4-23所示为EAP SUPP SM定义的枚举变量类型。
图4-23 EAP SUPP SM枚举类型定义
图4-23中,左图定义了EapDecision、EapMethodState、Boolean、
eapol_bool_var和eapol_int_var枚举类型,它们都和上节介绍的变量及类型有关。右图定
义了EapType枚举变量,代表不同的EAP Method。
图4-24所示为W PAS中和SUPP SM相关的数据结构。
图4-24 EAP SUPP SM相关数据结构定义
图4-24中,eap_sm是RFC4137 EAP SUPP SM的代表。从其成员变量的命名可知,
它几乎完全是按照RFC4137来实现的。eap_sm定义了一个名为EAP_state的枚举类型成员
变量。
eap_sm通过m成员变量指向一个eap_method链表。eap_method是一个由next指针
链接起来的单向链表,每一个eap_method对象代表一种具体的EAP Method(EAP
Method的注册请回顾4.3.2节“eap_register_methods函数分析”)。eap_method最重
要的是其处理函数,W PAS对其略有修改。例如,process函数实际上完成了表4-4中
m.check、m.process和m.buildResp的功能。
eap_method中,process函数第三个参数的类型是eap_method_ret,代表一个
eap_method_ret对象。由图4-24可知,它包括了ignore、methodState、decision以及
allowNotifications变量。
eap_sm的eapol_cb对象指向一个eapol_callbacks对象,它是LL层的代表。不
过,eapol_callbacks的定义看起来和RFC4137关系不大。
注意 图4-24中eap_sm第一个成员变量EAP_State是一个枚举类型,其枚举值就是图
4-21中SUPP SM的各个状态。
EAP SUPP SM初始化时,eap_sm的eapol_callbacks被设置为eapol_cb对象。代码
如下所示。
[–>eapol_supp_sm.c::eapol_cb定义]
static struct eapol_callbacks eapol_cb = {
eapol_sm_get_config,eapol_sm_get_bool,eapol_sm_set_bool,eapol_sm_get_int,
eapol_sm_set_int,eapol_sm_get_eapReqData,eapol_sm_set_config_blob,
eapol_sm_get_config_blob,eapol_sm_notify_pending,eapol_sm_eap_param_needed,
eapol_sm_notify_cert
};
上述函数相关代码我们留待碰到它们时再介绍。
(2)W PAS状态机通用宏
W PAS中有许多状态机,所以它定义了一些通用宏来帮助实现状态机相关的代码。这些
宏的定义如下所示。
[–>state_machine.h]
/
定义一个状态的EA,它是一个函数声明。
而STATE_MACHINE_DATA也是一个宏。对于EAP SSM来说,其类型是struct eap_sm。
global代表触发该状态的原因是否为UCT。
*/
#define SM_STATE(machine, state)
static void sm_ ## machine ## _ ## state ## Enter(STATE_MACHINE_DATA sm,int global)
// 每个状态进入后执行的一段代码。一般是打印一些信息,并设置新的状态
#define SM_ENTRY(machine, state)
if (!global || sm->machine ## state != machine ## _ ## state) {
sm->changed = TRUE; \ // changed变量用于记录状态机的状态是否发生变化
wpa_printf(MSG_DEBUG, STATE_MACHINE_DEBUG_PREFIX ": " #machine
" entering state " #state);
}
sm->machine ## state = machine ## _ ## state; // 设置状态机的状态
// SM_ENTER宏对应一次函数调用,调用的是SM_STATE宏定义的函数
#define SM_ENTER(machine, state)
sm ## machine ## _ ## state ## Enter(sm, 0)
// 对应一次函数调用,表示因UCT而直接进入某个状态
#define SM_ENTER_GLOBAL(machine, state)
sm ## machine ## _ ## state ## Enter(sm, 1) // 这个函数由SM_STATE宏声明
// 运行状态机。该宏定义一个函数
#define SM_STEP(machine)
static void sm ## machine ## Step(STATE_MACHINE_DATA *sm)
// 该宏对应一次函数调用,即sm##machine_Step(sm),sm参数由调用函数内声明
#define SM_STEP_RUN(machine) sm ## machine ## _Step(sm)
下面通过SUPP SM的实现代码来认识下上述通用宏的用法。
(3)EAP SUPP SM的实现
SUPP SM的状态比较多,此处仅列举DISABLED状态的实现代码以帮助读者理解通用
宏的作用。对状态机来说,其状态对应的EA非常重要。如下代码所示为DISABLED状态对
应的EA。根据上节对通用宏的介绍,EA由SM_STATE宏来定义。
[–>eap.c::SM_STATE(EAP,DISABLED)]
/
该宏对应的代码是
static void sm_EAP_DISABLED_Enter(STATE_MACHINE_DATA *sm,int global)
/
SM_STATE(EAP, DISABLED)
{
SM_ENTRY(EAP, DISABLED); // 每个状态的EA都会执行SM_ENTRY代码段
/
SM_ENTRY宏对应的代码是:
if (!global || sm->EAP_state != EAP_DISABLED) {
sm->changed = TRUE;
wpa_printf(MSG_DEBUG, STATE_MACHINE_DEBUG_PREFIX ": " “EAP”
" entering state " “DISABLED”); // 这段日志对了解SUPP SM当前处于哪个状态非常重要
}
// EAP_state是eap_sm中成员变量。读者可参考图4-24
sm->EAP_state = EAP_DISABLED;
/
sm->num_rounds = 0;
}
SM_STATE只是定义了状态机某个状态的EA,那么状态机是如何运作的呢?根据图4-
21以及前文所述,状态机的状态切换主要是通过判断条件是否满足来完成。SM_STEP定义
的函数就是用于检查状态机的这些条件变量,然后根据情况进行状态转换的。SUPP SM的
SM_STEP宏对应的代码如下所示。
[–>eap.c::SM_STEP(EAP)]
/
SM_STEP宏对应的函数定义为:
static void sm_EAP_Step(STATE_MACHINE_DATA *sm)
/
SM_STEP(EAP)
{
/
对应UCT的处理。eapol_get_bool函数将调用eapol_callbacks对象中的eapol_sm_get
bool函数其内部返回eap_sm中eapRestart成员变量的值。
/
if (eapol_get_bool(sm, EAPOL_eapRestart) &&
eapol_get_bool(sm, EAPOL_portEnabled))
/
调用由SM_STATE(EAP,INITIALZE)定义的函数,以进入EAP_INITIALIZE状态。
GLOBAL的意思是UCT。请读者注意此处的判断条件:如果eap_sm的eapRestart和
portEnabled成员变量都为true,则直接进入INITIALIZE状态。它完全和图4-21
SUPP SM状态图一样。
*/
SM_ENTER_GLOBAL(EAP, INITIALIZE);
else if (!eapol_get_bool(sm, EAPOL_portEnabled) || sm->force_disabled)
SM_ENTER_GLOBAL(EAP, DISABLED);
else if (sm->num_rounds > EAP_MAX_AUTH_ROUNDS) {
if (sm->num_rounds == EAP_MAX_AUTH_ROUNDS + 1) {
…// 有一些EAP方法在认证错误时会有很多消息往来,WPAS对此做了一个限制
// 一旦这些错误消息往来超过50次(由EAP_MAX_AUTH_ROUDS),则直接进入FAILURE状态
sm->num_rounds++;
SM_ENTER_GLOBAL(EAP, FAILURE); // GLOBAL代表UCT的情况
}
} else eap_peer_sm_step_local(sm); // 对应其他非UCT的情况
}
此处简单看一下eap_peer_sm_step_local的代码。
[–>eap.c::eap_peer_sm_step_local]
static void eap_peer_sm_step_local(struct eap_sm *sm)
{
switch (sm->EAP_state) {
…
case EAP_IDLE:
// 图4-21中,idle状态可依据条件不同而跳转到其他多个状态
// 下面这个函数用于选择目标状态及跳转到它
eap_peer_sm_step_idle(sm);// 根据图4-21的idle状态跳转,读者能想象出该函数的代码实现吗
break;
case EAP_RECEIVED:
eap_peer_sm_step_received(sm);
break;
case EAP_GET_METHOD:
if (sm->selectedMethod == sm->reqMethod)
SM_ENTER(EAP, METHOD); // 直接进入METHOD状态
else
SM_ENTER(EAP, SEND_RESPONSE); // 直接进入SEND_RESPONSE状态
break;
…
}
eap_peer_sm_step_local用于处理那些非UCT导致的状态切换。
提示 EAP SUPP SM的代码虽不复杂,但由于SUPP SM状态和触发条件(即定义的
那些变量)太多,想通过看代码去跟踪SUPP SM间的状态跳转是一件非常困难的事情。相
比而言,图4-21比代码要直观,更加容易把注意力集中在目标状态以及它对应的EA上。另
外,SM_STATE代码段中包含的那段wpa_printf输出将告知EAP模块当前的状态。读者以
后在分析W PAS日志时千万要注意。
3.EAP SUPP SM总结
EAP SUPP SM基于RFC4137而实现,其内部变量的定义以及状态切换逻辑都来源于
规范。以笔者的经验来看,掌握RFC4137是理解W PAS中EAP SUPP SM实现的基石。另
外,对具体的处理逻辑而言,SUPP SM最重要的内容还是各个状态对应的EA。正如前文所
述,图4-21对SUPP SM的运行极为重要,希望读者认真学习。
另外,对W PAS中状态机的实现来说,SM_STATE用于定义某个状态的EA(即一个函
数)。每个EA都会执行SM_ENTRY宏定义的一段代码。SM_ENTER和
SM_ENTER_GLOBAL宏用于调用SM_STATE定义的函数。GLOBAL代表UCT的情况。
SM_STEP宏用于运行整个状态机。请读者注意它和SM_ENTER的区别。SM_ENTER宏将
直接调用某个指定状态(由SM_ENTER宏的参数决定)的EA,而SM_STEP则将根据SM
中的变量情况来决定下一个要跳转的状态,然后调用它的SM_ENTER。
下面来看EAPOL模块的实现。
4.4.2 EAPOL模块分析 [21]
同EAP模块类似,EAPOL模块的实现参考了另外一个规范,即IEEE 802.1X。
注意 参考IEEE 802.1X 2004版规范的主要原因是,W PAS中EAPOL模块也基于该版
本的规范。另外,笔者比较了2004版和2010版的802.1X,发现2004版的内容组织相对清晰
易读。
在介绍802.1X前,先来看其描述的EAP和EAPOL之间的关系,如图4-25所示。
图4-25 EAP和EAPOL的关系
根据上一节对EAP SUPP SM的介绍,读者会发现图4-25中所示的eapResp、
eapSuccess等变量就是RFC4137中定义的用于LL层和EAP SUPP SM层交互的变量。很明
显,802.1X模块(在W PAS中,它就是EAPOL模块)是EAP SUPP的LL层(参考图4-
22)。
另外一个可能会让读者感到惊奇的是,802.1X规范为EAPOL Supplicant定义了5个不
同的状态机,分别如下。
·Port Timers SM:Port超时控制状态机。Port的概念请参考3.3.7节802.1X介绍。
·Supplicant PAE SM:PAE是Port Access Entitiy的缩写。该状态机用于维护
Port的状态。
·Supplicant Backend SM:规范并没有明示该状态机的作用。但笔者觉得它主要用
于给Authenticator发送EAPOL回复消息。
·The Key Receiver SM:用于处理Key(指EAPOL-Key帧)相关流程的状态机。
·The Supplicant Key Transmit SM:该状态机非必选项,所以W PAS未实现它。
说实话,EAPOL Supplicant定义5个状态机确实有些复杂。主要原因是这5个状态机
相互之间都有关联,这些关联体现在它们可能都受同一个变量的影响,从而导致各自的状态
发生变化。例如Port Timers SM修改了一些变量后,就有可能使得其他状态机的状态发生
变化。规范中把这些变量成为全局变量(Global Varaibles)。
规范阅读提示
1)除了SUPP包含的这五个状态机外,规范还为Authenticator定义了四个状态机。
Authenticator也需要实现Port Timers SM和The Key Receiver SM。
2)规范中将这些状态机统称为PACP(Port Access Control Protocol)State
Machine。
下面,先来认识这些全局变量。
1.EAPOL SUPP全局变量
802.1X定义了一些全局变量,它们被多个状态机使用。这些全局变量的定义如表4-6所
示。
注意,表4-6省略了部分和Authenticator相关的全局变量。另外,规范还定义了一些
全局超时变量,它们将在Port Timers SM中介绍。
2.SUPP PACP状态机
(1)Port Timers SM
Port Timers SM(PT SM)对应的状态切换如图4-26所示。
PT SM的功能比较简单,就是每一秒触发一次以从ONE_SECOND状态进入TICK状
态。TICK状态的EA中,它将递减(图4-26中的dec函数)某些变量的值。
图4-26 PT SM状态切换
注意,PT SM在SUPP和AUTH两端都有。所以,图4-26中的一些变量只用于AUTH
端。这些变量的含义如表4-7所示。
虽然规范定义了PT SM,但W PAS中,PT SM的功能并不是通过状态机宏来实现的,
而仅仅是向eloop模块注册了一个超时时间为1秒的函数eapol_port_timers_tick,其代码
如下所示。
[–>eapol_supp_sm.c::eapol_port_timers_tick]
static void eapol_port_timers_tick(void *eloop_ctx, void *timeout_ctx)
{
struct eapol_sm *sm = timeout_ctx;
if (sm->authWhile > 0) {// 处理authWhile
sm->authWhile–;
if (sm->authWhile == 0)
wpa_printf(MSG_DEBUG, “EAPOL: authWhile --> 0”);
}
// 处理heldWhile,startWhen,idleWhile(idleWhile见表4-1)
…
if (sm->authWhile | sm->heldWhile | sm->startWhen | sm->idleWhile) {
// 重新注册超时处理函数,相当于切换到图4-26中的ONE_SECOND状态
eloop_register_timeout(1, 0, eapol_port_timers_tick, eloop_ctx, sm);
} else {
sm->timer_tick_enabled = 0;
}
eapol_sm_step(sm);// 处理其他状态机的状态切换,此函数内容下文会介绍
}
上述代码中,eapol_port_timers_tick除了递减相关变量外,最后还需要调用
eapol_sm_step函数以判断其他状态机是否需要切换状态。这是PT SM和其他状态机联动
的关键纽带,而这个纽带在规范中并不能直接体现出来(规范中,PT SM只是修改某些变
量,至于其他状态机到底怎么被触发,则没有说明。而eapol_port_timers_tick函数修改
完变量后,直接调用eapol_sm_step函数完成了对其他状态机的检查)。
下面来看第二个状态机The Key Receiver SM。
(2)The Key Receiver SM
图4-27所示为The Key Receiver SM(以后简称TKR SM)状态切换图。主要有两点
值得关注。
图4-27 TKR SM状态切换
·TKR SM包含两个状态。第一个是NO_KEY_RECEIVE状态。当rxKey(boolean型
变量,当Supplicant收到EAPOL Key帧后,该值为TRUE)变为TRUE时,TKR进入
KEY_RECEIVE状态。
·TKR在KEY_RECEIVE状态时需要调用processKey函数处理EAPOL Key消息。
W PAS中,TKR的代码也非常简单,如下所示。
[–>eapol_supp_sm.c::TRK SM相关函数]
SM_STATE(KEY_RX, NO_KEY_RECEIVE)
{
SM_ENTRY(KEY_RX, NO_KEY_RECEIVE);
}
SM_STATE(KEY_RX, KEY_RECEIVE)
{
SM_ENTRY(KEY_RX, KEY_RECEIVE);
eapol_sm_processKey(sm); // 对应图4-27所示的processKey函数
sm->rxKey = FALSE;
}
SM_STEP(KEY_RX) // TKR状态机状态切换函数
{
if (sm->initialize || !sm->portEnabled)
SM_ENTER_GLOBAL(KEY_RX, NO_KEY_RECEIVE); // 直接进入NO_KEY_RECEIVE状态
switch (sm->KEY_RX_state) {
case KEY_RX_UNKNOWN:
break;
case KEY_RX_NO_KEY_RECEIVE:
if (sm->rxKey)
SM_ENTER(KEY_RX, KEY_RECEIVE);
break;
case KEY_RX_KEY_RECEIVE:
if (sm->rxKey)
SM_ENTER(KEY_RX, KEY_RECEIVE);
break;
}
}
TKR SM的代码非常简单,此处不详述。下面来看PAE SM。
(3)PAE SM
PAE SM比较复杂,其状态切换如图4-28所示。
图4-28 PAE SM状态切换
图4-28中涉及的变量定义见表4-8。
图4-28还包括两个函数。
·txStart:用于发送EAPOL-Start消息给Authenticator。
·txLogoff:用于发送EAPOL-Logoff消息给Authenticator。
提示 PAE SM中的状态虽然较多,但笔者觉得它们的划分似乎并无泾渭分明的根据。
另外,规范对它们的描述也仅是说明满足什么条件将进入什么状态。至于为什么划分这么多
状态也没有太多可参考的依据。所以,读者也不必拘泥于求根究底了,只要把握图4-28即
可。
W PAS中,PAE SM相关的代码也比较简单,此处仅看LOGOFF状态的EA,如下所
示。
[–>eapol_supp_sm.c::SM_STATE(SUPP_PAE,LOGOFF)]
SM_STATE(SUPP_PAE, LOGOFF) // 状态机名为SUPP_PAE,状态名为LOGOFF
{
SM_ENTRY(SUPP_PAE, LOGOFF);
eapol_sm_txLogoff(sm); // 对应图4-28中的txLogoff函数
sm->logoffSent = TRUE;
sm->suppPortStatus = Unauthorized;
// 这个函数内部将通过Nl80211 API设置WLAN Driver的状态
// 属于EAPOL模块和WPAS中其他模块的交互处理
eapol_sm_set_port_unauthorized(sm);
}
(4)Backend SM
Backend SM(BE SM)的状态转换如图4-29所示。
需要介绍和BE SM相关的变量authPeriod,它和authW hile(见表4-7)有关,默认
值为30秒。
图4-29 BE SM状态切换
BE SM包含如下几个重要函数。
·abortSupp:停止认证工作,释放相关的资源。
·getSuppResp:这个函数本意是用来获取EAP Response信息的,然后用
txSuppResp函数发送出去。但W PAS中,该函数没有包括任何有实质意义的内容。
·txSuppResp:发送EAPOL-Packet包给Authenticator。
BE SM的部分代码如下所示。
[–>eapol_supp_sm.c::SM_STATE(SUPP_BE,REQUEST)]
SM_STATE(SUPP_BE, REQUEST) // REQUEST状态对应的EA
{
SM_ENTRY(SUPP_BE, REQUEST);
sm->authWhile = 0;
sm->eapReq = TRUE;
eapol_sm_getSuppRsp(sm); // 此函数内部并无任何有实质意义的内容,读者不妨自行阅读它
}
提示 前面几节介绍了802.1X中SUPP PACP几个状态机相关的知识。相比EAP SUPP
SM而言,虽然PACP状态机的个数增加了不少,但每个状态机包含的状态却少了许多,所
以PACP状态机反而容易理解。
有了理论知识后,马上来看EAPOL SUPP模块中的几个重要数据结构和函数。
3.EAPOL SUPP代码分析
图4-23和图4-24介绍了EAPOL和EAP模块的关系,那么EAPOL和W PAS其他模块是
什么关系呢?相关数据结构如图4-30所示。
图4-30 W PAS中EAPOL/ EAP模块数据结构
由图4-30可知,W PAS定义了一个数据结构eapol_sm来存储和PACP状态机相关的内
容。其内部定义了三个状态机(TKR SM、PAE SM和BE SM)各自的状态信息(由三个
状态枚举值表达)、相关变量等。EAPOL模块和W PAS中重要模块wpa_supplicant的交互
接口是通过结构体eapol_ctx来定义的。EAPOL模块通过eap变量指向EAP模块的代表
eap_sm结构体。
提示 eapol_sm和eapol_ctx实际包含的成员变量非常多,此处仅列举其中一部分。
虽然W PAS包括EAPOL和EAP两个模块,但W PAS其他模块一般只和EAPOL模块交
互。至于EAP模块,它的操作(例如EAP的初始化以及EAP SUPP SM的运作)则由
EAPOL模块来触发。
(1)EAPOL模块的初始化
先来看EAPOL和EAP模块的初始化函数,由wpa_supplicant_init_eapol函数完成,
代码如下所示。
[–>wpas_glue.c::wpa_supplicant_init_eapol]
int wpa_supplicant_init_eapol(struct wpa_supplicant *wpa_s)
{
#ifdef IEEE8021X_EAPOL
struct eapol_ctx *ctx;
ctx = os_zalloc(sizeof(ctx));
…
ctx->ctx = wpa_s;
ctx->msg_ctx = wpa_s;
ctx->eapol_send_ctx = wpa_s;
ctx->preauth = 0;
ctx->eapol_done_cb = wpa_supplicant_notify_eapol_done;
ctx->eapol_send = wpa_supplicant_eapol_send;
…// 其他eapol_ctx成员变量的初始化
ctx->wps = wpa_s->wps;
ctx->eap_param_needed = wpa_supplicant_eap_param_needed;
ctx->port_cb = wpa_supplicant_port_cb;
ctx->cb = wpa_supplicant_eapol_cb;
ctx->cert_cb = wpa_supplicant_cert_cb;
ctx->cb_ctx = wpa_s;
wpa_s->eapol = eapol_sm_init(ctx); // 初始化EAPOL模块
…
#endif / IEEE8021X_EAPOL */
return 0;
}
wpa_supplicant_init_eapol首先设置eapol_ctx对象,然后调用eapol_sm_init来完
成EAPOL模块的初始化。eapol_sm_init的代码如下所示。
[–>eapol_supp_sm.c::eapol_sm_init]
struct eapol_sm *eapol_sm_init(struct eapol_ctx *ctx)
{
struct eapol_sm *sm;
struct eap_config conf;
sm = os_zalloc(sizeof(*sm)); // EAPOL对应的状态机信息
…
sm->ctx = ctx;// eapol_ctx是WPAS中EAPOL模块和其他模块交互的接口
sm->portControl = Auto;
sm->heldPeriod = 60;
sm->startPeriod = 30;
sm->maxStart = 3;
sm->authPeriod = 30;
os_memset(&conf, 0, sizeof(conf));
conf.opensc_engine_path = ctx->opensc_engine_path;
…
conf.wps = ctx->wps;
// 初始化EAP Supplicant SM相关资源
sm->eap = eap_peer_sm_init(sm, &eapol_cb, sm->ctx->msg_ctx, &conf);
…
// 先设置initialize变量为TRUE,然后初始化相关状态
sm->initialize = TRUE;
eapol_sm_step(sm);
sm->initialize = FALSE; // 设置为FALSE,再初始化相关状态
eapol_sm_step(sm);
sm->timer_tick_enabled = 1;
eloop_register_timeout(1, 0, eapol_port_timers_tick, NULL, sm);
return sm;
}
在eapol_sm_init代码中:
1)先通过调用eap_peer_sm_init初始化EAP SUPP SM相关资源。
2)然后完成EAPOL PACP三个状态机的初始化工作。初始化的方法很简单,即先设
置initialize为TRUE,然后执行eapol_sm_step函数(该函数代码见下文,其主要目的是
根据条件以跳转到下一个状态。initialize为TRUE时,将触发一些状态的EA被调用,从而
某些变量的初值将被设定)。然后设置initialize为FALSE后,再度执行eapol_sm_step函
数(这样,对应状态的EA也将被执行,从而剩余变量的初值将被设定)。
3)最后通过注册一个eloop超时任务实现了PT SM。
(2)状态机的联动
根据前面的介绍,EAPOL和EAP一共有四个状态机,它们到底是怎么联动的呢?答案
就在eapol_sm_step中。eapol_sm_step的代码如下所示。
[–>eapol_supp_sm.c::eapol_sm_step]
void eapol_sm_step(struct eapol_sm sm)
{
int i;
/
笔者一直很好奇EAPOL和EAP中的四个状态机是怎么联动的。通过下面的代码可知,
根据EAPOL和EAP的关系,首先要运行EAPOL中的三个状态机(Port Timers SM由eloop定时任务
来实现),分别是SUPP_PAE、KEY_RX和SUPP_BE。然后执行EAP SUPP SM。如果changed变量
为TRUE,表示状态发生了切换。由于每个状态对应的EA又有可能改变其中一些变量从而引起其他状
态机状态发生变化,所以,这里有一个for语句来循环处理状态切换,直到四个状态机都没有状态切
换为止。一般情况下,for循环应该是一个无限循环,但此次通过100来控制循环次数,是为了防止
某些情况下状态机陷入死循环而不能退出(这也说明规范中定义的SM在联动时可能有逻辑错误)。
/
for (i = 0; i < 100; i++) {
sm->changed = FALSE;
SM_STEP_RUN(SUPP_PAE);
SM_STEP_RUN(KEY_RX);
SM_STEP_RUN(SUPP_BE);
if (eap_peer_sm_step(sm->eap)) // eap_peer_sm_step返回非零,表示状态有变化
sm->changed = TRUE;
if (!sm->changed) break; // 如果没有状态变化,则跳出循环
}
/
运行超过100次,需要重新启动EAPOL模块状态机运行,eapol_sm_step_timeout将重新调用
eapol_sm_step函数。
/
if (sm->changed) {
eloop_cancel_timeout(eapol_sm_step_timeout, NULL, sm);
eloop_register_timeout(0, 0, eapol_sm_step_timeout, NULL, sm);
}
/
cb_status是一个枚举类型的变量,可取值有EAPOL_CB_IN_PROGRESS, EAPOL_CB_SUCCESS和
EAPOL_CB_FAILURE。
/
if (sm->ctx->cb && sm->cb_status != EAPOL_CB_IN_PROGRESS) {
int success = sm->cb_status == EAPOL_CB_SUCCESS ? 1 : 0;
/
该值在PAE AUTHENTICATED状态中被置为EAPOL_CB_SUCCESS,表示认证成功。在PAE
HELD状态被置为EAPOL_CB_FAILURE,表示认证还未成功。
*/
sm->cb_status = EAPOL_CB_IN_PROGRESS;
// 回调通知WPAS,真实的函数是wpa_supplicant_eapol_cb,这个函数以后介绍
sm->ctx->cb(sm, success, sm->ctx->cb_ctx);
}
}
W PAS中状态机联动代码的实现非常巧妙,它通过循环来处理各个状态机的状态变换,
直到四个状态机都稳定为止。
注意 初始化结束后,各个状态机的状态为:SUPP_PAE为DISCONNECTED状态、
KEY_RX为NO_KEY_RECEIVE状态、SUPP_BE为IDLE状态、EAP_SM为DISABLED状
态。
至此,对FRC4137和IEEE 802.1X-2004协议中EAP Supplicant和EAPOL
Supplicant涉及的状态机进行了详细介绍。
在具体实现中,W PAS实现的EAPOL和EAP状态机较为严格得遵循了这两个文档。所
以,读者只要理解了协议中的状态切换和相关变量,则能轻松理解W PAS的实现。反之,如
果仅单纯从代码入手,EAPOL/ EAP状态机的代码将会非常难以理解。
关于EAPOL/ EAP状态机相关的知识就介绍到这,以后碰到具体代码时,读者根据状态
切换图直接进入某个状态中去看其处理函数(即EA)。
提示 本章第二条分析路线使用的目标AP采用W PA2-PSK作为认证算法,故后续章节
不会涉及太多和EAPOL及EAP相关的代码分析。感兴趣的读者可在本节基础上,自行搭建
RAIDUS服务器来研究W PAS中EAPOL/ EAP的工作过程。
4.5 wpa_supplicant连接无线网络分析
本节将介绍第二条分析路线,即通过命令行发送命令的方式触发wpa_supplicant进行
相关工作,使手机加入一个利用W PA-PSK进行认证的无线网络。以笔者的Note 2为例,
整个过程用到的命令如下所示。
[命令示例]
adb root #获取手机root用户权限。只有root被破解的手机才能成功
adb shell #登录手机shell
#笔者事先已编译wpa_cli并将其放到/system/bin目录中。这个命令用于启动wpa_cli,-i参数指明unix域控制
#socket文件名,它应该和wpa_supplicant启动时设置的控制接口文件名一致
wpa_cli -iwlan0 #该命令执行后,将进入wpa_cli进程,后续操作都在此进程中开展
#发送ADD_NETWORK命令给wpa_supplicant,它将返回一个新网络配置项的编号
#请参考4.3.3节wpas_ssid结构体介绍
ADD_NETWORK #假设wpa_supplicant返回的新网络配置项编号为0
SET_NETWORK 0 ssid “Test” #设置0号网络的ssid为"Test"
SET_NETWORK 0 key_mgmt WPA-PSK #设置0号网络的key_mgmt为"WPA-PSK"
SET_NETWORK 0 psk “12345Test” #设置0号网络的psk为"12345Test"
ENABLE_NETWORK 0 #使能0号网络,它将触发wpa_supplicant扫描、关联等一系列操作直到加入无线网络"Test"
CTRL+C #退出wpa_cli
dhcpcd wlan0 #启动dhcpd,wlan0为无线接口设备名。dhcpcd可为手机从AP那获取一个IP地址
dhcpcd成功执行后,手机将从AP那分配到一个IP地址。至此,手机就可以使
用"Test"无线网络了。
注意 上述命令执行前有几个注意事项。
1)先要在Settings中开启无线网络。这个操作完成了wlan驱动及相应固件加载的工
作。该工作实际上由netd来完成,而wpa_cli无法完成它。
2)开启无线网络后,W ifiService和wpa_supplicant都开始工作了。为了避免
W ifiService的干扰,可以把Settings中的那些已知的无线网络信息都清除。
3)由于wpa_supplicant支持多个客户端,所以wpa_cli可以和W ifiService共同工
作。只要不操作Settings中无线网络相关的选项,W ifiService就不会干扰wpa_cli。
4)然后按上述步骤执行wpa_cli。
根据前文所述,所有来自客户端的命令都由wpa_supplicant_ctrl_iface_receive函数
处理(参考4.3.4节)。该函数代码非常简单,就是根据客户端发送的命令进行对应处理。
[–>ctrl_iface_unix.c::wpa_supplicant_ctrl_iface_receive]
static 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_un from;
socklen_t fromlen = sizeof(from);
char *reply = NULL; size_t reply_len = 0; int new_attached = 0;
res = recvfrom(sock, buf, sizeof(buf) - 1, 0,(struct sockaddr *) &from, &fromlen);
…
buf[res] = ‘\0’;
// 客户端第一次和WPAS连接时,需要发送"ATTACH"命令
if (os_strcmp(buf, “ATTACH”) == 0) {
…// 略过相关处理
}…// “DETACH"和"LEVEL"命令处理
else {
#if defined(CONFIG_P2P) && defined(ANDROID_P2P)
…// P2P处理。虽然WPAS编译时打开了CONFIG_P2P和ANDROID_P2P
// 但本章不讨论P2P相关的内容
#endif
// 大部分的命令处理都在wpa_supplicant_ctrl_iface_process函数中
reply = wpa_supplicant_ctrl_iface_process(wpa_s, buf,&reply_len);
}
if (reply) {// 回复客户端
sendto(sock, reply, reply_len, 0, (struct sockaddr ) &from,fromlen);
os_free(reply);
} …
/
Client成功ATTACH后,将通知EAPOL模块。因为有些认证流程需要用户的参与(例如输入密码之类的),
所以当客户端连接上后,EAPOL模块将判断是否需要和客户端交互。读者可阅读
eapol_sm_notify_ctrl_attached函数。
*/
if (new_attached)
eapol_sm_notify_ctrl_attached(wpa_s->eapol);
}
如上述代码所示,绝大部分命令都由wpa_supplicant_ctrl_iface_process函数处理。
下面将按顺序来分析其处理ADD_NETW ORK、SET_NETW ORK以及
ENABLE_NETW ORK的代码。
4.5.1 ADD_NETWORK命令处理
[–>ctrl_iface.c::wpa_supplicant_ctrl_iface_process]
char * wpa_supplicant_ctrl_iface_process(struct wpa_supplicant *wpa_s,char *buf,
size_t *resp_len)
{
char *reply;
const int reply_size = 4096;
int ctrl_rsp = 0;
int reply_len;
…
reply = os_malloc(reply_size);
…
// 开始命令处理
…
else if (os_strcmp(buf, “ADD_NETWORK”) == 0) {
reply_len = wpa_supplicant_ctrl_iface_add_network( wpa_s, reply, reply_size);
}else if
…// 其他命令处理
if (reply_len < 0) {// 命令处理出错
os_memcpy(reply, “FAIL\n”, 5);
reply_len = 5;
}
…
*resp_len = reply_len;
return reply;
}
ADD_NETW ORK的真正处理在wpa_supplicant_ctrl_iface_add_network函数中,
其代码如下所示。
[–>ctrl_iface.c::wpa_supplicant_ctrl_iface_add_network]
static int wpa_supplicant_ctrl_iface_add_network(struct wpa_supplicant *wpa_s,
char *buf, size_t buflen)
{
struct wpa_ssid ssid;
int ret;
/
wpa_config_add_network返回一个wpa_ssid对象,读者还记得它吗?wpa_ssid是无线网络
配置项在WPAS中的反映(请参考4.3.3节wpa_ssid结构体介绍)。wpa_config_add_network
内部就是分配一个wpa_ssid对象,然后将其保存到一个链表中。注意,wpa_config是wpa_supplicant.
conf在代码中的代表。所以,此处添加的无线网络信息将会保存到配置文件中,以备下次使用。
*/
ssid = wpa_config_add_network(wpa_s->conf);
…
wpas_notify_network_added(wpa_s, ssid);
ssid->disabled = 1; // disabled为1表示该无线网络未启用,需要通过ENABLE_NETWORK来
启动它
// 设置该无线网络的默认配置项
wpa_config_set_network_defaults(ssid);
// 返回该网络的编号(由wpa_ssid的id变量表示。它在wpa_config_add_network函数中被赋值)
ret = os_snprintf(buf, buflen, “%d\n”, ssid->id);
…
return ret;
}
上述代码比较简单,就是分配一个wpa_ssid对象,然后设置它的一些默认属性。整个
函数返回该wpa_ssid对象的id,即它在链表中的顺序。
wpa_ssid的默认属性对后续流程有一些影响,默认属性都是什么呢?来看看
wpa_config_set_network_defaults函数,代码如下所示。
[–>config.c::wpa_config_set_network_defaults]
void wpa_config_set_network_defaults(struct wpa_ssid ssid)
{
// 设置proto、pairwise_cipher、group_cipher以及key_mgmt的信息,读者还记得这些变量的含义吗
// 请参考4.3.3节安全相关成员变量及背景知识介绍
ssid->proto = DEFAULT_PROTO;
ssid->pairwise_cipher = DEFAULT_PAIRWISE;
ssid->group_cipher = DEFAULT_GROUP;
ssid->key_mgmt = DEFAULT_KEY_MGMT;
#ifdef IEEE8021X_EAPOL
ssid->eapol_flags = DEFAULT_EAPOL_FLAGS; // EAP相关变量,见下文解释
ssid->eap_workaround = DEFAULT_EAP_WORKAROUND;
ssid->eap.fragment_size = DEFAULT_FRAGMENT_SIZE;
#endif / IEEE8021X_EAPOL /
#ifdef CONFIG_HT_OVERRIDES
…// 和802.11n有关,本书不涉及
#endif / CONFIG_HT_OVERRIDES */
}
上述代码中出现了三个和EAPOL相关的变量,此处简单介绍一下。
(1)eapol_flags
它和动态W EP key有关。只适用于非W PA安全环境中,可取值有三个,分别如下。
1:代码中定义为BIT(0),表示需要为单播数据传输使用动态W EP Key,对应宏为
EAPOL_FLAG_REQUIRE_KEY_UNICAST。
2:代码中定义为BIT(1),表示需要为组播数据传输使用动态W EP Key,对应宏为
EAPOL_FLAG_REQUIRE_KEY_BROADCAST。
3:单播和组播都使用动态W EP Key,对应宏为DEFAULT_EAPOL_FLAGS。
(2)eap_workaround
身份认证方法多种多样,而有些AS(Authenticator服务器)并不严格遵守规范。该
变量表示碰到这种情况时,W PAS是否可以采取“绕”(workaround本意是“变通”)过
去的方式来对待这些AS。由于这种不严格的情况非常普遍,所以该值默认是1。
(3)fragment_size
该变量和EAPOL消息分片大小有关。默认的DEFAULT_FRAGMENT_SIZE大小为
1398,表示EAPOL消息只要不超过这个大小,就不用对其进行分片。
“ADD_NETW ORK"命令比较简单,它最终将返回给客户端对应的无线网络配置的编
号。在本例中,它是0。
下面来看客户端通过"SET_NETW ORK"为该无线网络配置项设置参数的处理过程。
4.5.2 SET_NETWORK命令处理
SET_NETW ORK对应的命令处理函数为wpa_supplicant_ctrl_iface_set_network,
其代码如下所示。
[–>ctrl_iface.c::wpa_supplicant_ctrl_iface_set_network]
static int wpa_supplicant_ctrl_iface_set_network(
struct wpa_supplicant *wpa_s, char *cmd)
{
int id;
struct wpa_ssid *ssid;
char *name, *value;
// SET_NETWORK的参数是: “ ”
name = os_strchr(cmd, ’ '); *name++ = ‘\0’; // 获取name
value = os_strchr(name, ’ '); value++ = ‘\0’; // 获取value
id = atoi(cmd); // 获取id
…
// 从wpa_config中的无线网络配置列表中找到对应编号的无线网络配置项
ssid = wpa_config_get_network(wpa_s->conf, id);
…
/
为该网络设置对应的配置值。wpa_config_set函数的具体实现与4.3.4节"wpa_supplicant_
init_iface分析之一"介绍的wpa_config_process_global函数类似,其内部也是通过定义
一些宏和数组来完成配置项的设置,不讨论其细节。就本例而言,当三个SET_NETWORK命令处理
完毕时,wpa_ssid的
ssid=“Test”、key_mgmt=WPA_KEY_MGMT_PSK、passphrase=“12345Test”。
注意:虽然在命令行中设置的是psk=“12345Test”,但实际上密码值将保存在passphrase变量中。
*/
if (wpa_config_set(ssid, name, value, 0) < 0) {…}
// 清空对应的PMKSA缓存信息。wpa_s->wpa指向一个wpa_sm对象
wpa_sm_pmksa_cache_flush(wpa_s->wpa, ssid);
if (wpa_s->current_ssid == ssid || wpa_s->current_ssid == NULL)
eapol_sm_invalidate_cached_session(wpa_s->eapol);
if ((os_strcmp(name, “psk”) == 0 && value[0] == '”’ && ssid->ssid_len) ||
(os_strcmp(name, “ssid”) == 0 && ssid->passphrase))
wpa_config_update_psk(ssid);// 将字符串形式的passphrase转成key,见下文介绍
else if (os_strcmp(name, “priority”) == 0)
wpa_config_update_prio_list(wpa_s->conf);
return 0;
}
我们在3.3.7节的开头部分曾介绍过Key和Passphrase的区别。一般而
言,Passphrase(也叫Password)表现为human-readable的字符串,而Key则一般是二
进制或十六进制的数据。STA和AP交互的是Key,而用户设置的是Passphrase。所以上述
代码中需要将Passphrase转换成Key,这是通过wpa_config_update_psk函数来完成的。
其代码如下所示。
[–>config.c::wpa_config_update_psk]
void wpa_config_update_psk(struct wpa_ssid *ssid)
{
#ifndef CONFIG_NO_PBKDF2 // 本例支持该宏,如果没有它的话,用户只能输入十六进制的Key
// 对用户设置的psk和ssid进行hash计算,最终的结果作为真正的Pre-Shared Key
pbkdf2_sha1(ssid->passphrase,(char ) ssid->ssid, ssid->ssid_len,
4096,ssid->psk, PMK_LEN);
ssid->psk_set = 1;
#endif / CONFIG_NO_PBKDF2 */
}
SET_NETW ORK命令处理的介绍到此为止。下面来看最后一个关键命令
ENABLE_NETW ORK的处理流程。
4.5.3 ENABLE_NETWORK命令处理
ENABLE_NETW ORK命令由wpa_supplicant_ctrl_iface_enable_network进行处
理,其代码如下所示。
[–>ctrl_iface.c::wpa_supplicant_ctrl_iface_enable_network]
static int wpa_supplicant_ctrl_iface_enable_network(struct wpa_supplicant *wpa_s,
char *cmd)
{
int id;
struct wpa_ssid *ssid;
if (os_strcmp(cmd, “all”) == 0) { // 使能所有无线网络
ssid = NULL;
} else {
id = atoi(cmd); // 本例中的id为0
ssid = wpa_config_get_network(wpa_s->conf, id); // 找到id为0的无线网络配置对象
…
// 在前面ADD_NETWORK中,disabled为1,表示还没有使能它。disable为2的情况和P2P有关
if (ssid->disabled == 2) {…}
}
wpa_supplicant_enable_network(wpa_s, ssid);
return 0;
}
来看wpa_supplicant_enable_network,其代码如下所示。
[–>wpa_supplicant.c::wpa_supplicant_enable_network]
void wpa_supplicant_enable_network(struct wpa_supplicant *wpa_s, struct wpa_ssid *ssid)
{
struct wpa_ssid *other_ssid;
int was_disabled;
if (ssid == NULL) {
…// 处理ENABLE_NETWORK all的情况
} else if (ssid->disabled && ssid->disabled != 2) {
if (wpa_s->current_ssid == NULL) {// WPAS当前没有活跃的无线网络,所以current_ssid为空
wpa_s->reassociate = 1; // 注意这个变量的值
// ADD_NETWORK只是添加了一个无线网络配置项
// 接下来要发起扫描工作以和对应的无线网络进行交互
// 下面这个函数将发起scan操作。后面两个0代表时间。详情见下节分析
wpa_supplicant_req_scan(wpa_s, 0, 0);
}
was_disabled = ssid->disabled;
ssid->disabled = 0; // 设置disabled为0
if (was_disabled != ssid->disabled)
wpas_notify_network_enabled_changed(wpa_s, ssid);
}
}
正如代码中注释所说,ADD_NETW ORK不过是为W PAS添加了一个无线网络配置项罢
了。该无线网络是否存在?通过SET_NETW ORK配置的信息是否正确?这些问题的解答首
先从无线网络扫描开始。
1.无线网络扫描流程分析
ENABLE_NETW ORK将发起无线网络扫描请求,这是由wpa_supplicant_req_scan
完成的,其代码如下所示。
[–>scan.c::wpa_supplicant_req_scan]
void wpa_supplicant_req_scan(struct wpa_supplicant *wpa_s, int sec, int usec)
{
#ifndef ANDROID // Android平台上,该宏被定义
…// 不讨论非Android平台上的代码
#endif
eloop_cancel_timeout(wpa_supplicant_scan, wpa_s, NULL);
// 在本例中,sec和usec都是0,所以wpa_supplicant_scan将很快得到执行。该函数是扫描的核心代码
eloop_register_timeout(sec, usec, wpa_supplicant_scan, wpa_s, NULL);
}
wpa_supplicant_scan是无线网络扫描的核心函数,其代码比较复杂,我们分段来
看。
(1)wpa_supplicant_scan分析之一
这一段代码主要和scan请求的参数准备有关。
[–>scan.c::wpa_supplicant_scan代码段一]
static void wpa_supplicant_scan(void *eloop_ctx, void *timeout_ctx)
{
struct wpa_supplicant *wpa_s = eloop_ctx;
struct wpa_ssid *ssid; int scan_req = 0, ret;
struct wpabuf extra_ie; // 用于存储Information Element信息
struct wpa_driver_scan_params params; // 发给驱动的scan请求命令
// 用于记录一个scan请求能包含多少个ssid。请参考4.3.4节关于capability的介绍
size_t max_ssids;
enum wpa_states prev_state;
// wpa_state取值为WPA_INACTIVE,由4.3.4节wpa_supplicant_driver_init函数中的代码设置
if (wpa_s->wpa_state == WPA_INTERFACE_DISABLED) { return;}
// disconnected为0,scan_req为1,都是wpa_supplicant构造时的默认值
if (wpa_s->disconnected && !wpa_s->scan_req) { …}
/
搜索wpa_config中所有的无线网络配置项,看其中是否有使能的无线网络。本例中,在扫描之前,
已经将目标wpa_ssid的disabled变量置为0,这样,下面这个函数调用将返回非0值,使得整
个if判断为假。
/
if (!wpa_supplicant_enabled_networks(wpa_s->conf) && !wpa_s->scan_req) {…}
/
ap_scan是一个很有意思的参数,它和AP扫描和选择有关,默认值为1。值为1:表示WPAS来完成
AP扫描和选择的绝大部分工作(包括关联、EAPOL认证等工作)。值为0:表示驱动完成AP扫描和选
择的工作。这种驱动比较少见,笔者未能找到关于WPA_DRIVER_FLAGS_WIRED标志的合理解释,有
知晓的读者不妨和大家分享一下相关知识。值为2:和0类似,不过在NDIS(Windows上的网络设备
驱动)中用得较多。
/
if (wpa_s->conf->ap_scan != 0 && (wpa_s->drv_flags & WPA_DRIVER_FLAGS_WIRED)){…}
if (wpa_s->conf->ap_scan == 0) {// 如果驱动能完成大部分工作的话,WPAS的工作量将大大减少
wpa_supplicant_gen_assoc_event(wpa_s);
return; // 无须后面的流程
}
…// CONFIG_P2P:P2P相关,本章不讨论
if (wpa_s->conf->ap_scan == 2)
max_ssids = 1;
else {
max_ssids = wpa_s->max_scan_ssids; // 一个scan请求能包含多少个ssid
if (max_ssids > WPAS_MAX_SCAN_SSIDS)
max_ssids = WPAS_MAX_SCAN_SSIDS;
}
scan_req = wpa_s->scan_req; // scan_req为1
wpa_s->scan_req = 0; // scan_req被置为0
os_memset(¶ms, 0, sizeof(params));
// 初始化scan请求的参数,其类型为wpa_driver_scan_params
根据第3章关于无线网络扫描的介绍,一个Probe Request要么指定wildcard ssid以扫
描周围所有的无线网络,要么指定某个ssid以扫描特定无线网络。为了方便W PAS的使
用,wlan driver新增了一个功能,使得上层可通过一次scan请求来扫描多个不同ssid的无
线网络。一个scan请求在代码中对应的数据结构就是wpa_driver_scan_params。而
wpa_supplicant_scan最重要的工作就是准备好这个请求。
(2)wpa_supplicant_scan分析之二
接着来看代码段二。
[–>scan.c::wpa_supplicant_scan代码段二]
…// 接上段代码
prev_state = wpa_s->wpa_state; // 此时的wpa_state是WPA_INACTIVE
if (wpa_s->wpa_state == WPA_DISCONNECTED || wpa_s->wpa_state == WPA_INACTIVE)
wpa_supplicant_set_state(wpa_s, WPA_SCANNING);// 设置WPAS状态为WPA_SCANNING
/
connect_without_scan指向一个wpa_ssid对象。它对应的应用场景是:WPAS事先通过某种方
式(例如后续章节将要介绍的WPS)已经知道要连接的无线网络了,所以此处就无须扫描,仅关联它即可。
/
if (scan_req != 2 && wpa_s->connect_without_scan) {
for (ssid = wpa_s->conf->ssid; ssid; ssid = ssid->next) {
if (ssid == wpa_s->connect_without_scan) break;
}
wpa_s->connect_without_scan = NULL;
if (ssid) {
wpa_supplicant_associate(wpa_s, NULL, ssid); // 关联到目标网络
return;
}
}
// 搜索wpa_config中的所有无线网络配置项,看看哪些需要包含到这次scan请求中
ssid = wpa_s->conf->ssid;
/
prev_scan_ssid用于记录上一次scan请求的最后一个ssid。它对应了如下的应用场景。
假设scan请求一次只能携带2个ssid,如果要扫描wpa_config中配置的全部网络项(假设是4个),
则需要发起两次scan请求。所以,当prev_scan_ssid上一次扫描的并非全部无线网络的话(由
wildcardssid来判断),则此处要接着扫描之前没有扫描的那些无线网络。
以本例而言,prev_scan_ssid初始值是WILDCARD_SSID_SCAN(其值为1)。
*/
if (wpa_s->prev_scan_ssid != WILDCARD_SSID_SCAN) {
while (ssid) {
if (ssid == wpa_s->prev_scan_ssid) {
ssid = ssid->next;
break;
}
ssid = ssid->next;
}
}
if (scan_req != 2 && wpa_s->conf->ap_scan == 2) {
…// 不考虑这种情况
#ifndef ANDROID
…
#endif
} else {
struct wpa_ssid *start = ssid, tssid;
int freqs_set = 0;
if (ssid == NULL && max_ssids > 1)
ssid = wpa_s->conf->ssid;
while (ssid) {
/
有一些AP被设置为hidden ssid。即它不响应wildcard ssid扫描的Probe Request,
同时,自己发送的Beacon帧也不携带ssid信息。这样,只有知道ssid的STA才能和这
些AP连接上,其安全性略有提高。scan_ssid就是用来判断此无线网络是否需要指明ssid。
本例中的"Test"无线网络没有隐藏 ssid,所以scan_ssid值为0。否则需要通过SET_
NETWORK 0 scan_ssid 1来设置它。
/
if (!ssid->disabled && ssid->scan_ssid) {
// 把ssid信息加到params的ssids数组中
params.ssids[params.num_ssids].ssid = ssid->ssid;
params.ssids[params.num_ssids].ssid_len = ssid->ssid_len;
params.num_ssids++;
// 如果本次scan请求的ssid个数已经达到driver能支持的最大数,则跳出循环
if (params.num_ssids + 1 >= max_ssids) break;
}
ssid = ssid->next;
if (ssid == start)
break;
if (ssid == NULL && max_ssids > 1 && start != wpa_s->conf->ssid)
ssid = wpa_s->conf->ssid;
}
/
处理扫描时的频率选择。如果已经知道目标无线网络的工作信道,可以直接设定频率参数以
优化扫描过程。否则,无线网卡将尝试在各个信道上搜索目标无线网络。本例没有使用频率参数。
/
for (tssid = wpa_s->conf->ssid; tssid; tssid = tssid->next) {
if (tssid->disabled) continue;
if ((params.freqs || !freqs_set) && tssid->scan_freq) {
int_array_concat(¶ms.freqs,tssid->scan_freq);
} else {
os_free(params.freqs);
params.freqs = NULL;
}
freqs_set = 1;
}
int_array_sort_unique(params.freqs); // 对所有频率参数进行升序排序
}
if (ssid && max_ssids == 1) { // 如果scan请求最多只能包含一个ssid
if (!wpa_s->prev_scan_wildcard) {
params.ssids[0].ssid = NULL; // 扫描wildcast ssid
params.ssids[0].ssid_len = 0;
wpa_s->prev_scan_wildcard = 1;
} else {
wpa_s->prev_scan_ssid = ssid;
wpa_s->prev_scan_wildcard = 0;
}
} else if (ssid) {
wpa_s->prev_scan_ssid = ssid;
params.num_ssids++;
} else {
wpa_s->prev_scan_ssid = WILDCARD_SSID_SCAN;
params.num_ssids++;
}
// 对频率参数进行修改,和P2P以及WPS有关,本章略过它们
wpa_supplicant_optimize_freqs(wpa_s, ¶ms);
// 是否需要携带附件的IE信息。主要用在WPS等情况,本章略过它们
extra_ie = wpa_supplicant_extra_ies(wpa_s, ¶ms);
if (params.freqs == NULL && wpa_s->next_scan_freqs) {
params.freqs = wpa_s->next_scan_freqs;
} else os_free(wpa_s->next_scan_freqs);
wpa_s->next_scan_freqs = NULL;
/
scan请求可以设置一个过滤条件,扫描完毕后,driver wrapper会过滤掉那些不符合条件的无线
网络。注意,filter_ssids用来保存那些不能被过滤的无线网络ssid。即,扫描到的无线网络不在
filter_ssids中时,它将被过滤掉。过滤的代码在driver_nl80211.c nl80211_scan_filtered
函数中,其调用之处在同一文件里的bss_info_handler函数中。
/
params.filter_ssids = wpa_supplicant_build_filter_ssids(wpa_s->conf,
¶ms.num_filter_ssids);
if (extra_ie) {
params.extra_ies = wpabuf_head(extra_ie);
params.extra_ies_len = wpabuf_len(extra_ie);
}
#ifdef CONFIG_P2P
…
#endif / CONFIG_P2P */
上述wpa_supplicant_scan代码段主要展示了如何填写扫描请求参数,复杂之处在于
其对细节的处理。下面来看最后一个代码段。
(3)wpa_supplicant_scan分析之三
当scan请求的参数准备好后,wpa_supplicant_scan将直接向driver wrapper发起
scan请求。
[–>scan.c::wpa_supplicant_scan代码段三]
// 调用driver wrapper的scan2函数。下文将直接分析driver_nl80211的scan2函数
ret = wpa_supplicant_trigger_scan(wpa_s, ¶ms);
// 释放分配的资源
wpabuf_free(extra_ie);
os_free(params.freqs);
os_free(params.filter_ssids);
if (ret) {…// 错误处理}
}
对于driver_nl80211来说,对应的函数是wpa_driver_nl80211_scan,马上来看其代
码。
[–>driver_nl80211.c::wpa_driver_nl80211_scan]
static int wpa_driver_nl80211_scan(void *priv, struct wpa_driver_scan_params *params)
{
struct i802_bss *bss = priv; // i802_bss是driver wrapper的上下文信息
struct wpa_driver_nl80211_data *drv = bss->drv;
// 获取wpa_driver_nl80211_data对象
int ret = 0, timeout;
struct nl_msg *msg, *ssids, *freqs, rates;
size_t i;
drv->scan_for_auth = 0;
msg = nlmsg_alloc();ssids = nlmsg_alloc();freqs = nlmsg_alloc();
rates = nlmsg_alloc();
// 这个函数的主要功能是将wpa_driver_scan_params参数转换成对应的netlink command
os_free(drv->filter_ssids);
drv->filter_ssids = params->filter_ssids;
params->filter_ssids = NULL;
drv->num_filter_ssids = params->num_filter_ssids;
nl80211_cmd(drv, msg, 0, NL80211_CMD_TRIGGER_SCAN);
NLA_PUT_U32(msg, NL80211_ATTR_IFINDEX, drv->ifindex); // 指定网卡设备编号
for (i = 0; i < params->num_ssids; i++) // 填充ssid信息
NLA_PUT(ssids, i + 1, params->ssids[i].ssid_len,params->ssids[i].ssid);
if (params->num_ssids) nla_put_nested(msg, NL80211_ATTR_SCAN_SSIDS, ssids);
if (params->extra_ies) // 填充附加IE信息
NLA_PUT(msg, NL80211_ATTR_IE, params->extra_ies_len,params->extra_ies);
if (params->freqs) { // 填充频率信息
for (i = 0; params->freqs[i]; i++) NLA_PUT_U32(freqs, i + 1, params->freqs[i]);
nla_put_nested(msg, NL80211_ATTR_SCAN_FREQUENCIES, freqs);
}
if (params->p2p_probe) {…// P2P相关}
/
发送请求给wlan驱动。返回值只是表示该命令是否正确发送给了驱动。扫描结束事件将通过
driver event返回给WPAS。下文将分析如何处理扫描结束事件。
/
ret = send_and_recv_msgs(drv, msg, NULL, NULL);
msg = NULL;
if (ret) { …// 错误处理}
timeout = 10;
/
一般情况下,driver完成扫描后需要通知WPAS一个scan complete事件。如果驱动不通知的话,
WPAS就会自己去查询driver以获取扫描到的无线网络信息。如何知晓driver是否会通知该事件呢?
WPAS中是通过scan_complete_events变量来判断的。值得指出的是,该变量的取值是测试出来的。
即scan_complete_events初始值为0。如果扫描后收到了scan complete事件,该值将被
修改为1。由于本例中,该变量是第一次碰到,所以其值为0。
*/
if (drv->scan_complete_events) timeout = 30;
eloop_cancel_timeout(wpa_driver_nl80211_scan_timeout, drv, drv->ctx);
// 本例中,timeout为10秒
eloop_register_timeout(timeout, 0, wpa_driver_nl80211_scan_timeout, drv, drv->ctx);
…
return ret;
}
就本例而言,ENABLE_W ORK命令处理的第一步就是要扫描周围的无线网络。至于目
标无线网络是否存在,则属于扫描结果处理流程的工作了。
(4)无线网络扫描流程总结
图4-31所示为触发扫描功能的流程图。
图4-31 触发扫描的流程图
图4-31所示函数中,wpa_supplicant_scan的内容较为丰富,其中有很多细节内容。
读者初次学习时,可以先不考虑这些细节,只要把握图4-31中的函数调用流程即可。
马上来看扫描结果的处理流程。
2.扫描结果处理流程分析
在上一节的扫描请求中,driver_nl80211发送了NL80211_CMD_TRIGGER_SCAN命
令给wlan driver以通知它开始扫描周围的无线网络。当wlan driver完成此任务后,它将向
4.3.4节中wpa_driver_nl80211_init_nl_global函数中注册的三个netlink组播之一
的"scan"组播地址发送结果。而driver_nl80211对应处理来自wlan driver netlink消息处
理回调函数为process_global_event,而它最终又会调用do_process_drv_event进行处
理。所有,直接来看do_process_drv_event中和扫描结果相关的代码即可。
[–>driver_nl80211.c::do_process_drv_event]
static void do_process_drv_event(struct wpa_driver_nl80211_data *drv,
int cmd, struct nlattr *tb)
{
/
ap_scan_as_station和hostapd有关,该值默认等于NL80211_IFTYPE_UNSPECIFIED。读
者可参考4.3.4节"wpa_supplicant_init_iface分析之三"中对wpa_driver_nl80211_init
的分析。
/
if (drv->ap_scan_as_station != NL80211_IFTYPE_UNSPECIFIED &&
(cmd == NL80211_CMD_NEW_SCAN_RESULTS ||cmd == NL80211_CMD_SCAN_ABORTED)) {…}
switch (cmd) {
// driver wrapper发送NL80211_CMD_TRIGGER_SCAN命令后,wlan driver也会回复同名的一个消息
case NL80211_CMD_TRIGGER_SCAN:
wpa_printf(MSG_DEBUG, “nl80211: Scan trigger”);
break;
…// 其他消息处理
case NL80211_CMD_NEW_SCAN_RESULTS:
// 收到来自driver的scan回复,所以设置scan_complete_events变量为1
drv->scan_complete_events = 1;
// 取消超时任务
eloop_cancel_timeout(wpa_driver_nl80211_scan_timeout, drv, drv->ctx);
/
图4-1所示的WPAS软件结构中曾提到driver wrapper将通过发送driver event的方式
触发WPAS其他模块进行对应处理。下面这个函数即是用来处理和发送与scan相关的driver
event的。其详情见下文。
*/
send_scan_event(drv, 0, tb);
break;
…
}
return
}
上述代码中的send_scan_event将解析来自wlan driver的扫描完毕事件,然后再向
W PAS发送driver event,其代码如下所示。
[–>driver_nl80211.c::send_scan_event]
static void send_scan_event(struct wpa_driver_nl80211_data *drv, int aborted,
struct nlattr *tb[])
{
// 代表driver event的数据结构
// 它是一个联合体,包含了assoc_info、auth_info、scan_info等较多内容
union wpa_event_data event;
struct nlattr *nl;
int rem;
// 扫描信息,包含频率数组(int类型的数组)以及wpa_driver_scan_ssid数组
// 请读者特别注意scan_info不是扫描结果
struct scan_info info;
#define MAX_REPORT_FREQS 50
int freqs[MAX_REPORT_FREQS];
int num_freqs = 0;
/
scan_for_auth变量和wlan driver有关,它描述了如下的应用场景。
当WPAS发起认证(authentication)操作时,它将向driver发送NL80211_CMD_
AUTHENTICATE命令。driver可能因为某种原因(超时等),其内部存储的目标BSS(代表目标无
线网络)信息失效。这时,它将返回ENOENT给WPAS。正常情况下,WPAS应该重新扫描。但为了加
快这个流程,WPAS可以单独扫描目标无线网络(因为WPAS还是保存了目标无线网络的信息,例如
频道等),然后再发起认证操作。从笔者角度来看,该场景对应的问题是wlan driver没有目标
BSS信息,而WPAS有目标BSS信息。WPAS和wlan driver是两个不同模块,二者的信息并不能
总是保持一致。既然WPAS有目标BSS信息,那么可以通过更加快捷的方法让wlan driver也得
到这个信息(通过在指定频道到扫描目标BSS),从而加快整个流程(从WPAS角度来看,用户加入无
线网络的流程已经进行到Authentication阶段,此时再退回到重新扫描的阶段实在有些麻烦)。
关于scan_for_auth的官方解释,请通过前面介绍的git blame命令获取commit message,
方法是先通过"git blame ./src/drivers/driver_nl80211.c | grep scan_for_auth”
获得和scan_for_auth相关的commit号,然后再用git log commit号查看。
git blame的用法请参考4.2.4节。
*/
if (drv->scan_for_auth) {…}
os_memset(&event, 0, sizeof(event));
info = &event.scan_info;
info->aborted = aborted;
if (tb[NL80211_ATTR_SCAN_SSIDS]) {// 遍历NL80211_ATTR_SCAN_SSIDS属性
nla_for_each_nested(nl, tb[NL80211_ATTR_SCAN_SSIDS], rem) {
struct wpa_driver_scan_ssid *s = &info->ssids[info->num_ssids];
s->ssid = nla_data(nl); s->ssid_len = nla_len(nl);
info->num_ssids++;
if (info->num_ssids == WPAS_MAX_SCAN_SSIDS) break;
}
}
if (tb[NL80211_ATTR_SCAN_FREQUENCIES]) {// 获取netlink消息中的频率信息
nla_for_each_nested(nl, tb[NL80211_ATTR_SCAN_FREQUENCIES], rem) {
freqs[num_freqs] = nla_get_u32(nl);
num_freqs++;
if (num_freqs == MAX_REPORT_FREQS - 1) break;
}
info->freqs = freqs;
info->num_freqs = num_freqs;
}
// wpa_supplicant_event被driver event模块用来发送driver事件给WPAS
wpa_supplicant_event(drv->ctx, EVENT_SCAN_RESULTS, &event);
}
上述代码中请注意struct scan_info的作用,它定义于联合体wpa_event_data中,包
含了本次扫描请求扫描了哪些SSID、对应的频率等。
提示 scan_info不是scan后得到的结果信息,而是代表驱动处理scan请求时的一些处
理信息。对于那些不支持通知scan complete事件的driver而言,scan_info就没法获得。
例如,超时扫描处理wpa_driver_nl80211_scan_timeout中调用wpa_supplicant_event
时就没有scan_info。scan_info到底有什么作用呢?后文将详细介绍它。
wpa_supplicant_event是driver wrapper向W PAS通知driver event的接口函数,其
代码如下所示。
[–>events.c::wpa_supplicant_event]
void wpa_supplicant_event(void *ctx, enum wpa_event_type event,
union wpa_event_data *data)
{
struct wpa_supplicant *wpa_s = ctx;
u16 reason_code = 0;
int locally_generated = 0;
…
switch (event) {
…// driver event总类非常多,现在只分析EVENT_SCAN_RESULT即可
case EVENT_SCAN_RESULTS:
wpa_supplicant_event_scan_results(wpa_s, data);
break;
…
}
}
代码很简单,下面直接来分析此处的目标函数
wpa_supplicant_event_scan_results。
(1)wpa_supplicant_event_scan_results函数分析
函数代码如下。
[–>events.c::wpa_supplicant_event_scan_results]
static void wpa_supplicant_event_scan_results(struct wpa_supplicant *wpa_s,
union wpa_event_data *data)
{
const char *rn, *rn2;
struct wpa_supplicant ifs;
// 笔者编译的WPAS实际上打开了ANDROID_P2P宏,但本章不讨论P2P相关内容
#ifdef ANDROID_P2P
if (_wpa_supplicant_event_scan_results(wpa_s, data, 0) < 0)
#else
if (_wpa_supplicant_event_scan_results(wpa_s, data) < 0)
#endif
return;
/
get_radio_name函数和4.3.4节"wpa_supplicant_init_iface分析之三"中提到的
/sys/class/net/wlan0/phy80211/name文件内容有关。driver_nl80211实现了这个函数,
它将返回上面这个文件的内容。
/
if (!wpa_s->driver->get_radio_name) return;
/
下面这段代码的作用主要是看虚拟接口设备(参考4.3.4节中对Virtual Interface的介绍)
是不是使用了同一个物理设备。如果使用了同一个物理设备,则这个虚拟设备获得的扫描结果可以
和其他虚拟设备共享(避免重复扫描)。本章不对其中细节进行介绍。
/
rn = wpa_s->driver->get_radio_name(wpa_s->drv_priv);
/
global指向wpa_global对象,ifaces是wpa_global的成员变量,指向一个wpa_supplicant
对象的队列,可参考图4-7和图4-11。在WPAS中,每一个wpa_supplicant对象会和一个虚拟接口
设备关联。
*/
for (if s = wpa_s->global->ifaces; ifs; ifs = ifs->next) {
if (ifs == wpa_s || !ifs->driver->get_radio_name)
continue;
rn2 = ifs->driver->get_radio_name(ifs->drv_priv);
if (rn2 && os_strcmp(rn, rn2) == 0) {// 比较两个虚拟设备对应的物理设备是否为同一个
#ifdef ANDROID_P2P
…
#else // 和其他虚拟设备分享扫描结果
_wpa_supplicant_event_scan_results(ifs, data);
#endif
}
}
}
上述代码中,_wpa_supplicant_event_scan_results是核心处理函数并且内容较多,
所以下面将分段研究它。
(2)_wpa_supplicant_event_scan_results分析之一
先来看第一个代码段,代码如下所示。
[–>events.c::wpa_supplicant_event_scan_results代码段一]
#ifdef ANDROID_P2P
static int wpa_supplicant_event_scan_results(struct wpa_supplicant *wpa_s,
union wpa_event_data *data, int suppress_event)
#else
static int wpa_supplicant_event_scan_results(struct wpa_supplicant *wpa_s,
union wpa_event_data *data)
#endif
{
struct wpa_bss *selected; struct wpa_ssid *ssid = NULL;
struct wpa_scan_results scan_res;// 这才是真正的扫描结果
int ap = 0;
…
wpa_supplicant_notify_scanning(wpa_s, 0);
…// 和P2P相关,本章不讨论
// 获得无线网络扫描结果。就本例而言,前面得到的scan_info信息将传递到下面这个函数
scan_res = wpa_supplicant_get_scan_results(wpa_s,data ? &data->scan_info : NULL, 1);
if (scan_res == NULL) {…// 扫描结果为空,重新发起扫描}
#ifndef CONFIG_NO_RANDOM_POOL
/
把扫描结果中得到的无线网络频率、信号强度等信息取出来,然后写到图4-1中的crpto模块。
这样,能增加随机数生成的随机性。WPAS中nonce的生成都会利用随机数生成器。感兴趣的读者
不妨自行研究相关内容。
/
…
#endif / CONFIG_NO_RANDOM_POOL */
wpa_supplicant_get_scan_results比较重要,我们通过代码来介绍它。
[–>scan.c::wpa_supplicant_get_scan_results]
struct wpa_scan_results * wpa_supplicant_get_scan_results(struct wpa_supplicant *wpa_s,
struct scan_info *info, int new_scan)
{
struct wpa_scan_results *scan_res;
size_t i;
int (*compar)(const void *, const void ) = wpa_scan_result_compar;
/
调用driver interface的get_scan_results2函数,对nl80211来说,其真实函数是wpa_driver
nl80211_get_scan_results,它将向wlan driver发送NL80211_CMD_GET_SCAN命令以获取
扫描结果。另外,wpa_driver_nl80211_get_scan_results中还涉及一个比较有意思的函数
wpa_driver_nl80211_check_bss_status。其作用请读者利用前面介绍的git blame方法来查询。
/
scan_res = wpa_drv_get_scan_results2(wpa_s);
if (scan_res == NULL) {return NULL;} //
#ifdef CONFIG_WPS
…// WPS相关
#endif / CONFIG_WPS /
/
对扫描结果进行排序,排序函数是wpa_scan_result_compar,它将根据无线网络的信噪比(SNR)
进行排序信噪比越高,无线网络信号越强。注意,除了SNR外,wpa_scan_result_compar还有别
的比较条件,感兴趣的读者可行阅读该函数。
*/
qsort(scan_res->res, scan_res->num, sizeof(struct wpa_scan_res ),compar);
…
/
更新WPAS中保存的那些bss信息(即无线网络信息,每一个无线网络由wpa_bss表示)。我们在4.3.4节中
曾经介绍过和wpa_bss相关的知识。WPAS维护了一个wpa_bss链表,每次扫描时都可能更新这个链表
(添加新扫描得到的wpa_bss、删除某些老旧的wpa_bss)。
/
wpa_bss_update_start(wpa_s);
for (i = 0; i < scan_res->num; i++)
wpa_bss_update_scan_res(wpa_s, scan_res->res[i]);
/
这里用上了scan_info。那些没有包含在本次scan_info中的wpa_bss不用更新。结合4.5.3节
“wpa_supplicant_scan分析之二”中提到的prev_scan_ssid的作用,能想到为什么不更新那些
没有包含在scan_info中的wpa_bss吗?
/
wpa_bss_update_end(wpa_s, info, new_scan);
return scan_res;
}
图4-32所示为扫描结果对应的数据结构。
图4-32 wpa_scan_res相关数据结构
(3)_wpa_supplicant_event_scan_results分析之二
接着来看_wpa_supplicant_event_scan_results下一个代码片段。
[–>events.c::_wpa_supplicant_event_scan_results代码段二]
…// 接_wpa_supplicant_event_scan_results代码片段一
/
如果wpa_supplicant对扫描结果有特殊处理,则调用scan_res_handler对应的函数处理。
目前只有P2P情况下scan_res_handler才起作用。
/
if (wpa_s->scan_res_handler) { …return 0;}
…// hostapd的情况,本书不讨论
#ifdef ANDROID_P2P
if(!suppress_event)
#endif
{ …// 通知客户端,扫描结束 }
wpas_notify_scan_done(wpa_s, 1);
if ((wpa_s->conf->ap_scan == 2 && !wpas_wps_searching(wpa_s))) {}
if (wpa_s->disconnected) {…}
/
还记得4.5.3节“wpa_supplicant_scan分析之一”中的ap_scan变量吗?下面这个
wpas_driver_bss_selection函数判断是否由driver来控制无线网络的选择。显然,
本例中它将返回0值。不过,由于本例不支持bgscan(由CONFIG_BGSCAN宏控制),
bgscan_notify_scan返回0。
这样,下面这个if判断失败。
/
if (!wpas_driver_bss_selection(wpa_s)&&bgscan_notify_scan(wpa_s, scan_res) == 1) {
wpa_scan_results_free(scan_res);
return 0;
}
// 从扫描结果中选择一个合适的无线网络
// 注意,在本例中,下面这个函数将返回目标无线网络对应的wpa_bss对象
selected = wpa_supplicant_pick_network(wpa_s, scan_res, &ssid);
上面这段代码相对简单。注意最后一句代码中调用的wpa_supplicant_pick_network
函数。它将根据scan_res(扫描结果)、wpa_bss(代表一个真实BSS的信息)和
wpa_ssid(代表用户设置的某个无线网络配置项)的匹配情况来选择合适的无线网络。匹
配检查包含很多内容,例如ssid是否匹配、安全设置是否匹配、速率是否匹配等。最终,该
函数返回一个被选中的目标无线网络的wpa_bss对象。
提示 wpa_supplicant_pick_network函数实际上检查的项非常多。由于篇幅问题,
本章不能一一道来,感兴趣的读者请仔细研究。
(4)_wpa_supplicant_event_scan_results分析之三
接着来看_wpa_supplicant_event_scan_results的第三段代码。
[–>events.c::_wpa_supplicant_event_scan_results代码段三]
…// 接代码段二
if (selected) { // 本例中返回的selected无线网络wpa_bss对象代表目标"Test"无线网络
int skip;
/
判断是否需要漫游。简单来说,漫游表示需要切换无线网络。对本例而言,由于之前没有和AP关联,
所以此处是需要漫游的(即需要切换无线网络)。
wpa_supplicant_need_to_roam中还包括对同一个ESS中如何选择更合适的BSS有一些
简单的判断,主要是根据信号强度来选择。
*/
skip = !wpa_supplicant_need_to_roam(wpa_s, selected, ssid,scan_res);
wpa_scan_results_free(scan_res);
if (skip) {// 无须切换网络,所以没有太多要做的工作
wpa_supplicant_rsn_preauth_scan_results(wpa_s);
return 0;
}
// 关联至目标无线网络。我们下一节再分析它
if (wpa_supplicant_connect(wpa_s, selected, ssid) < 0) {…}
// 预认证(Pre-Authentication)处理,和802.11中的Fast Transition有关,本书不讨论
wpa_supplicant_rsn_preauth_scan_results(wpa_s);
} else { …// 没有匹配的无线网络情况的处理 }
return 0;
}
到此为止,扫描结果的处理基本完毕,剩下的工作就是通过wpa_supplicant_connect
向目标AP发起关联等请求以加入"Test"无线网络。
在介绍wpa_supplicant_connect之前,先总结一下扫描结果处理流程。
(5)扫描结果处理流程总结
扫描结果的处理流程相对比较复杂,如图4-33所示。其中有几个函数包含一些非常重
要的细节,读者在研究过程中要特别注意。
·wpa_supplicant_pick_nework:在扫描结果中找到一个最佳的无线网络。
·wpa_supplicant_need_to_roam:判断是否需要切换网络。
·wpa_supplicant_rsn_preauth_scan_results:更新PMKSA缓存信息。其工作和
Pre-Authentication有关。不过Pre-Authenticaton真正的实现也需要AP的支持。
图4-33 扫描处理流程
图4-34所示为笔者通过AirPcap截获的目标AP发送的Probe Response帧。
图4-34 Probe Response帧内容
请读者注意图4-34中所示的RSN信息,它描述了AP所支持的RSN功能。下节介绍
W PAS如何关联到目标无线网络时将用到它。
3.关联无线网络处理流程分析
关联无线网络处理的流程从wpa_supplicant_connect开始,其代码如下所示。
[–>events.c::wpa_supplicant_connect]
int wpa_supplicant_connect(struct wpa_supplicant *wpa_s,struct wpa_bss *selected,
struct wpa_ssid ssid)
{
// WPS相关,本章不讨论
if (wpas_wps_scan_pbc_overlap(wpa_s, selected, ssid)) {…}
/
4.5.3节介绍的wpa_supplicant_enable_network函数中,reassociate值被设置为1。
当WPAS处于ASSOCIATING状态时,wpa_s->pending_bssid用于存储目标网络的BSSID。
*/
if (wpa_s->reassociate || (os_memcmp(selected->bssid, wpa_s->bssid, ETH_ALEN) != 0 &&
((wpa_s->wpa_state != WPA_ASSOCIATING && wpa_s->wpa_state != WPA_AUTHENTICATING) ||
os_memcmp(selected->bssid, wpa_s->pending_bssid, ETH_ALEN) != 0))) {
// 和EAP-SIM/AKA认证方法有关。在此处初始化相关资源。本章不讨论
if (wpa_supplicant_scard_init(wpa_s, ssid)) {
wpa_supplicant_req_new_scan(wpa_s, 10, 0);
return 0;
}
wpa_supplicant_associate(wpa_s, selected, ssid);// 发起关联操作
} else {…}
return 0;
}
就本例而言,wpa_supplicant_connect最重要的工作就是触发STA发起关联操作。关
联操作通过wpa_supplicant_associate函数来完成。这部分代码比较复杂,我们分段来
看。
(1)wpa_supplicant_associate分析之一
和发起无线扫描请求一样,wpa_supplicant_associate函数主要目的就是填充一个用
于向wlan driver发起关联请求的struct wpa_driver_associate_params类型的对象,然
后调用driver interface对应的接口函数。由于关联时考虑的因素非常多,所以对应的处理
也比较烦琐。本节先介绍第一段内容。
[–>wpa_supplicant.c::wpa_supplicant_associate代码段一]
void wpa_supplicant_associate(struct wpa_supplicant *wpa_s,
struct wpa_bss *bss, struct wpa_ssid ssid)
{
u8 wpa_ie[200]; size_t wpa_ie_len;
int use_crypt, ret, i, bssid_changed;
int algs = WPA_AUTH_ALG_OPEN; // 认证方法
enum wpa_cipher cipher_pairwise, cipher_group;// 单播数据和组播数据加密方法
struct wpa_driver_associate_params params; // 此函数主要目的是正确填充params的内容
int wep_keys_set = 0; struct wpa_driver_capa capa;
int assoc_failed = 0; struct wpa_ssid old_ssid;
#ifdef CONFIG_HT_OVERRIDES
…// 802.11n相关的内容
#endif / CONFIG_HT_OVERRIDES /
#ifdef CONFIG_IBSS_RSN
…// IBSS相关内容
#endif / CONFIG_IBSS_RSN /
#ifdef ANDROID_P2P
int freq = 0;
#endif
if (ssid->mode == WPAS_MODE_AP || ssid->mode == WPAS_MODE_P2P_GO ||
ssid->mode == WPAS_MODE_P2P_GROUP_FORMATION) { … return; }
#ifdef CONFIG_TDLS
…// TDLS是WFA定义的另外一项规范。本书不讨论
#endif / CONFIG_TDLS /
/
我们曾在4.3.3节功能相关成员变量及背景知识中曾介绍过CONFIG_SME相关的信息。
Galaxy Note 2不支持WPA_DRIVER_FLAGS_SME参数。
/
if ((wpa_s->drv_flags & WPA_DRIVER_FLAGS_SME) && ssid->mode == IEEE80211_MODE_INFRA) {
sme_authenticate(wpa_s, bss, ssid);
return;
}
os_memset(¶ms, 0, sizeof(params));
wpa_s->reassociate = 0;
// wpas_driver_bss_selection于判断是否由wlan driver来完成无线网络选择,本例它返回FALSE
if (bss && !wpas_driver_bss_selection(wpa_s)) {
#ifdef CONFIG_IEEE80211R
…// 802.11R相关
#endif / CONFIG_IEEE80211R /
bssid_changed = !is_zero_ether_addr(wpa_s->bssid);
os_memset(wpa_s->bssid, 0, ETH_ALEN);// 设置wpa_supplicant bssid数组成员值为全0
// 将BSSID复制到wpas->pending_bssid中
os_memcpy(wpa_s->pending_bssid, bss->bssid, ETH_ALEN);
if (bssid_changed)
wpas_notify_bssid_changed(wpa_s);
#ifdef CONFIG_IEEE80211R
…// 和802.11R有关
#endif / CONFIG_IEEE80211R /
#ifdef CONFIG_WPS
} else if (…// WPS相关) {
…
#endif / CONFIG_WPS /
} else os_memset(wpa_s->pending_bssid, 0, ETH_ALEN);
// 取消计划扫描和普通扫描任务
wpa_supplicant_cancel_sched_scan(wpa_s);
wpa_supplicant_cancel_scan(wpa_s);
/
清空上一次association时使用的WPA/RSN IE信息,这些信息保存在wpa_supplicant
对象的assoc_wpa_ie(类型为u8)对应的buffer中。
/
wpa_sm_set_assoc_wpa_ie(wpa_s->wpa, NULL, 0);
如果不考虑各种编译宏选项(对W PS、802.11R支持),上述代码段还算比较简单。
(2)wpa_supplicant_associate分析之二
接着来看代码段二。
[–>wpa_supplicant.c::wpa_supplicant_associate代码段二]
…// 接代码段一
#ifdef IEEE8021X_EAPOL
// 本例中,为目标无线网络配置的key_mgmt为SET_NETWORK 0 key_mgmt WPA-PSK
// 通过"SET_NETWORK 0 key_mgmt WPA-PSK"命令来完成
if (ssid->key_mgmt & WPA_KEY_MGMT_IEEE8021X_NO_WPA) {
…// 处理key_mgmt为非WPA的情况}
#endif / IEEE8021X_EAPOL /
/
auth_alg为认证方法,可取值有WPA_AUTH_ALG_OPEN、WPA_AUTH_ALG_SHARED等。根据第3章
对WPA的介绍,如果要使用WPA的话,在和AP关联时必须使用Open System(即WPA_AUTH_ALG_OPEN)。
如果没有设置该值,其值默认为0。
/
if (ssid->auth_alg) {…}
/
下面这个if判断很重要,请读者注意。
wpa_bss_get_vendor_ie函数用于获取wpa_bss中的和vendor相关的IE,此处IE对应的标志是
WPA_IE_VENDOR_TYPE(该值被定义为:#define WPA_IE_VENDOR_TYPE 0x0050f201)。
读者可参考图4-34中Probe Response帧中最后一个IE项(Microsoft:WPA IE,
因为MS的OUI是00-50-f2)。注意:该OUI也供WFA定义的几种规范使用。
wpa_bss_get_ie(bss, WLAN_EID_RSN)) 用于获取RSN IE。图4-34中也包含RSN IE。
wpa_key_mgmt_wpa(ssid->key_mgmt)用于判断无线网络配置时设置的key_mgmt是否
和WPA相关(本例中,key_mgmt被设为WPA-PSK,它属于WPA的一种)。
简而言之,下面这个if条件就是判断目标无线网络是否支持WPA/RSN功能,并且无线网络配置项
是否也设置key_mgmt与WPA相关。很显然,本例满足下面这个if条件。
/
if (bss && (wpa_bss_get_vendor_ie(bss, WPA_IE_VENDOR_TYPE) ||
wpa_bss_get_ie(bss, WLAN_EID_RSN)) && wpa_key_mgmt_wpa(ssid->key_mgmt)) {
int try_opportunistic;
/
ssid->proto默认值为DEFAULT_PROTO,它由4.5.1节中ADD_NETWORK命令处理的
wpa_config_set_network_defaults函数完成。proactive_key_caching默认值为0。
/
try_opportunistic = ssid->proactive_key_caching && (ssid->proto & WPA_PROTO_RSN);
/
下面这个函数用于从pmksa缓存中取出current_ssid对应的pmkid cache项(类型为rsn
pmksa_cache),然后将其赋值给wpa_sm中的cur_pmksa变量中。此时我们还没有pmksa
缓存信息,故if条件失败。
/
if (pmksa_cache_set_current(wpa_s->wpa, NULL, bss->bssid,wpa_s->current_ssid,
try_opportunistic) == 0){
// 设置eapol_sm的cached_pmk为1(由该函数第二个参数决定),表示要使用pmksa
eapol_sm_notify_pmkid_attempt(wpa_s->eapol, 1);
}
wpa_ie_len = sizeof(wpa_ie);
/
wpa_supplicant_set_suites函数比较繁琐,其目的是生成一个用于关联请求的IE信息。
这些信息包括:group_cipher、pairwise_cipher、key_mgmt。信息的选择需要考虑AP的情况,
即图4-34中AP包含的RSN和WPA IE。最终的选择如下:
proto=WPA_PROTO_RSN、group_cipher=pairwise_cipher=WPA_CIPHER_CCMP
key_mgmt=WPA_KEY_MGMT_PSK
感兴趣的读者不妨自行阅读wpa_supplicant_set_suites。
/
if (wpa_supplicant_set_suites(wpa_s, bss, ssid,wpa_ie, &wpa_ie_len)) {…}
} else if (wpa_key_mgmt_wpa_any(ssid->key_mgmt)) {
…
#ifdef CONFIG_WPS
…// WPS处理
#endif / CONFIG_WPS /
} else {…}
…// P2P和interworking相关的处理
// 清除wlan driver中的key设置
wpa_clear_keys(wpa_s, bss ? bss->bssid : NULL);
use_crypt = 1;
/
将WPAS中定义的数据类型转换成driver wrapper使用的数据类型。例如,WPAS使用WPA_CIPHER_CCMP,
而driver wrapper对应的值为CIPHER_CCMP。
/
cipher_pairwise = cipher_suite2driver(wpa_s->pairwise_cipher);
cipher_group = cipher_suite2driver(wpa_s->group_cipher);
if (wpa_s->key_mgmt == WPA_KEY_MGMT_NONE ||
wpa_s->key_mgmt == WPA_KEY_MGMT_IEEE8021X_NO_WPA) {…}
if (wpa_s->key_mgmt == WPA_KEY_MGMT_WPS) use_crypt = 0;
#ifdef IEEE8021X_EAPOL
if (wpa_s->key_mgmt == WPA_KEY_MGMT_IEEE8021X_NO_WPA) {
…// 选择单播和组播数据加密方法}
#endif / IEEE8021X_EAPOL /
if (wpa_s->key_mgmt == WPA_KEY_MGMT_WPA_NONE) {…}
// 设置wpa_sm的状态为WPA_ASSOCIATING
wpa_supplicant_set_state(wpa_s, WPA_ASSOCIATING);
这段代码主要是根据AP的情况选择合适的加密方法及认证方法。虽然目的很简单,但
判断条件以及相关函数却比较烦琐,而且还有相当一部分代码是针对P2P、W PS等不同情况
的处理。建议读者先了解其目的,以后根据工作需要再来研读其中的细节。
(3)wpa_supplicant_associate分析之三
下面来看wpa_supplicant_associate最后一段代码。
[–>wpa_supplicant.c::wpa_supplicant_associate代码段三]
…// 接代码段二
if (bss) { // 填充struct wpa_driver_associate_params中的信息
params.ssid = bss->ssid;
params.ssid_len = bss->ssid_len;
if (!wpas_driver_bss_selection(wpa_s)) {
params.bssid = bss->bssid;
params.freq = bss->freq;
}
}…// 其他处理
// 填充参数
params.wpa_ie = wpa_ie; params.wpa_ie_len = wpa_ie_len;
params.pairwise_suite = cipher_pairwise; params.group_suite = cipher_group;
params.key_mgmt_suite = key_mgmt2driver(wpa_s->key_mgmt);
params.wpa_proto = wpa_s->wpa_proto;params.auth_alg = algs;
params.mode = ssid->mode;
for (i = 0; i < NUM_WEP_KEYS; i++) {…// 设置wep key相关信息}
params.wep_tx_keyidx = ssid->wep_tx_keyidx;
…
/
设置wlan driver是否丢弃未加密数据包。注意,在通过RSN身份验证前,EAPOL 4-Way Handshake等
EAPOL数据是没有加密的,所以这个变量只针对非EAPOL/EAP数据包。本例中use_crpyt为1。
/
params.drop_unencrypted = use_crypt;
#ifdef CONFIG_IEEE80211W
…// 802.11w相关
#endif / CONFIG_IEEE80211W /
params.p2p = ssid->p2p_group;
/
uapsd为Unscheduled Automatic Power Save Delivery的缩写,也称为WMM power save,
属于WFA定义的一种标准,和节电有关。
/
if (wpa_s->parent->set_sta_uapsd)
params.uapsd = wpa_s->parent->sta_uapsd;
else
params.uapsd = -1;
#ifdef ANDROID_P2P
…// P2P相关
#endif
// 调用driver interface的associate函数以发起关联请求。该函数非常重要,下一节介绍
ret = wpa_drv_associate(wpa_s, ¶ms);
if (ret < 0) {…// 错误处理}
if (wpa_s->key_mgmt == WPA_KEY_MGMT_WPA_NONE) {…// 其他处理
#ifdef CONFIG_IBSS_RSN
} else if (…) {…// IBSS相关
#endif / CONFIG_IBSS_RSN */
} else {
int timeout = 60;// 设置一个超时时间,身份认证必须在60秒内完成
if (assoc_failed) timeout = ssid->mode == WPAS_MODE_IBSS ? 10 : 5;
else if (wpa_s->conf->ap_scan == 1) // 对本例而言,timeout最终取值为10秒
timeout = ssid->mode == WPAS_MODE_IBSS ? 20 : 10;
// 设置一个超时任务:对应函数为wpa_supplicant_timeout,用于处理身份认证超时的情况
wpa_supplicant_req_auth_timeout(wpa_s, timeout, 0);
}
…
if (wpa_s->current_ssid && wpa_s->current_ssid != ssid)
eapol_sm_invalidate_cached_session(wpa_s->eapol);// 设置上一次eapol session无效
old_ssid = wpa_s->current_ssid;
wpa_s->current_ssid = ssid;// 设置wpa_supplicant对象的current_ssid和current_bss变量
wpa_s->current_bss = bss;
// 下面这个函数实际上是将加密/身份验证信息设置到wpa_sm对应的变量中去
wpa_supplicant_rsn_supp_set_config(wpa_s, wpa_s->current_ssid);
// 配置eapol sm和eap sm。其中:portControl被置为AUTO
// eapSuccess=altAccept=eapFail=altReject=FALSE。这个过程没有状态发生变化
wpa_supplicant_initiate_eapol(wpa_s);
if (old_ssid != wpa_s->current_ssid)
wpas_notify_network_changed(wpa_s);
}
至此,wpa_supplicant_associate函数就分析完毕。读者如果还记得第3章关于STA
加入AP的步骤的话,可能会有一些疑惑。STA加入AP的顺序是先发送Authentication请
求,然后再发送Association请求。但此处仅调用了wpa_drv_associate函数,似乎忽略了
Authentication这一步。该问题的回答将放在下一节,直接来看driver nl80211对应的
wpa_driver_nl80211_associate函数。
(4)wpa_driver_nl80211_associate函数分析
代码如下所示。
[–>driver_nl80211.c::wpa_driver_nl80211_associate]
static int wpa_driver_nl80211_associate(void *priv,
struct wpa_driver_associate_params *params)
{
struct i802_bss *bss = priv;
struct wpa_driver_nl80211_data *drv = bss->drv;
int ret = -1; struct nl_msg msg;
// 处理AP和IBSS的情况
if (params->mode == IEEE80211_MODE_AP) return wpa_driver_nl80211_ap(drv, params);
if (params->mode == IEEE80211_MODE_IBSS)
return wpa_driver_nl80211_ibss(drv, params);
// 目前大部分手机不支持WPA_DRIVER_FLAGS_SME
if (!(drv->capa.flags & WPA_DRIVER_FLAGS_SME)) {
enum nl80211_iftype nlmode = params->p2p ? // 本例对应的是非p2p情况
NL80211_IFTYPE_P2P_CLIENT : NL80211_IFTYPE_STATION;
// 设置设备类型,可参考4.3.4节wpa_driver_nl80211_finish_drv_init函数分析
if (wpa_driver_nl80211_set_mode(priv, nlmode) < 0) return -1;
return wpa_driver_nl80211_connect(drv, params);
}
/
注意:根据第3章对STA加入AP操作的描述,STA需要首先发送authentication请求,然后再发送
association请求。在“wpa_supplicant_associate分析之一”中,有一个sme_authenticate
函数,它将发起authentication请求。对于不支持SME的wlan driver来说,WPAS只要向wlan driver
发送connect命令即可完成association和authentication这两步。这种实现方式和wlan driver
以及cfg80211有关。本书不拟对此展开详细讨论。读者可在wpa_supplicant官方源码中利用
"git log a8c5b43a"命令查看相关信息。
*/
…// 对于支持SME的wlan driver,则需要发送NL80211_CMD_ASSOCIATE命令
}
来看wpa_driver_nl80211_connect函数,其代码如下所示。
[–>driver_nl80211.c::wpa_driver_nl80211_connect]
static int wpa_driver_nl80211_connect(struct wpa_driver_nl80211_data *drv,
struct wpa_driver_associate_params *params)
{
struct nl_msg *msg; enum nl80211_auth_type type;
int ret = 0; int algs;
msg = nlmsg_alloc();
nl80211_cmd(drv, msg, 0, NL80211_CMD_CONNECT);// 构造一个NL80211_CMD_CONNECT命令
NLA_PUT_U32(msg, NL80211_ATTR_IFINDEX, drv->ifindex);// 设置本次命令对应的网络接口
// 设备索引号填充bssid信息
if (params->bssid) NLA_PUT(msg, NL80211_ATTR_MAC, ETH_ALEN, params->bssid);
if (params->freq) {…// 填充频率信息}
if (params->ssid) {// 填充ssid信息
NLA_PUT(msg, NL80211_ATTR_SSID, params->ssid_len,params->ssid);
os_memcpy(drv->ssid, params->ssid, params->ssid_len);// 将ssid信息保存起来
drv->ssid_len = params->ssid_len;
}
…
if (params->pairwise_suite != CIPHER_NONE) {
…// 处理pairwise_cipher取值情况
NLA_PUT_U32(msg, NL80211_ATTR_CIPHER_SUITES_PAIRWISE, cipher);
}
…// 其他各项参数处理
// 向wlan driver发送NL80211_CMD_CONNECT命令
ret = send_and_recv_msgs(drv, msg, NULL, NULL);
msg = NULL;
if (ret) {…// 错误处理}
ret = 0;
…
return ret;
}
至此,W PAS就把CONNECT请求发给了驱动,驱动将完成Authentication帧和
Association Request帧的处理。
和scan请求类似,关联无线网络处理流程主要是为了填充一个
wpa_driver_associate_params类型的对象。不过该对象中各个参数的设置颇有来头,需
要对802.11规范有相当了解才可以做到正确无误。
(5)关联无线网络处理流程总结
来看一下关联无线网络处理流程所涉及的几个重要函数调用,如图4-35所示。
图4-35 wpa_supplicant_connect流程
图4-35中最复杂的就是wpa_supplicant_associate函数,希望读者能结合代码进行阅
读。
W PAS成功调用wpa_supplicant_associate后,将等待NL80211_CMD_CONNECT
命令的处理结果。该结果由wlan driver通过NL80211_CMD_CONNECT类型的消息返回
给driver wrapper。下面一节将分析W PAS如何处理该消息。
4.关联事件处理流程分析
结合上节所述内容,W PAS中的driver wrapper将收到来自wlan driver的
NL80211_CMD_CONNECT命令,其对应的处理代码如下所示。
[–>driver_nl80211.c::do_process_drv_event]
static void do_process_drv_event(struct wpa_driver_nl80211_data *drv, int cmd,
struct nlattr *tb)
{
// 曾在4.5.3节“扫描结果处理流程分析”中见过该函数处理NL80211_CMD_NEW_SCAN_RESULTS
…
switch (cmd) {
…
case NL80211_CMD_CONNECT:// wlan driver返回该命令的处理结果
case NL80211_CMD_ROAM:
/
调用mlme_event_connect进行处理,其中NL80211_ATTR_STATUS_CODE属性保存了AP的处理结果
(即Association请求的处理结果)、NL80211_ATTR_MAC属性保存了AP的MAC地址(就是bssid)、
NL80211_ATTR_REQ_IE属性保存了Association Request请求时包含的IE(STA发送的IE)、
NL80211_ATTR_RESP_IE属性保存了Association Response帧包含的IE信息(AP发送的IE)。
*/
mlme_event_connect(drv, cmd, tb[NL80211_ATTR_STATUS_CODE],
tb[NL80211_ATTR_MAC], tb[NL80211_ATTR_REQ_IE], tb[NL80211_ATTR_RESP_IE]);
…
}
当wlan driver返回NL80211_CMD_CONNECT命令时,其真正的处理函数实际上是
mlme_event_connect,此函数代码如下所示。
[–>driver_nl80211.c::mlme_event_connect]
static void mlme_event_connect(struct wpa_driver_nl80211_data *drv,
enum nl80211_commands cmd, struct nlattr *status, struct nlattr *addr,
struct nlattr *req_ie,struct nlattr resp_ie)
{
/
driver wrapper通知WPAS其他模块时候使用的事件类型。我们曾在4.5.3节“扫描结果处理流程分析”
中对send_scan_event函数分析时见过。
*/
union wpa_event_data event;
if (drv->capa.flags & WPA_DRIVER_FLAGS_SME) {…// wlan driver支持SME时的处理}
os_memset(&event, 0, sizeof(event));
if (cmd == NL80211_CMD_CONNECT && nla_get_u16(status) != WLAN_STATUS_SUCCESS) {
…// 关联AP失败的处理
wpa_supplicant_event(drv->ctx, EVENT_ASSOC_REJECT, &event);
return;
}
drv->associated = 1;
if (addr)// 保存bssid
os_memcpy(drv->bssid, nla_data(addr), ETH_ALEN);// 保存bssid信息
if (req_ie) {// 保存Association Request ie信息
event.assoc_info.req_ies = nla_data(req_ie);
event.assoc_info.req_ies_len = nla_len(req_ie);
}
if (resp_ie) {// 保存Association Response ie信息
event.assoc_info.resp_ies = nla_data(resp_ie);
event.assoc_info.resp_ies_len = nla_len(resp_ie);
}
// 通过发送NL80211_GET_SCAN命令获取STA的工作频率
event.assoc_info.freq = nl80211_get_assoc_freq(drv);
// 重要函数,见下文分析
wpa_supplicant_event(drv->ctx, EVENT_ASSOC, &event);
}
wpa_supplicant_event中处理EVENT_ASSOC消息的是
wpa_supplicant_event_assoc,下面将分段介绍它。
(1)wpa_supplicant_event_assoc分析之一
代码如下所示。
[–>events.c::wpa_supplicant_event_assoc代码段一]
static void wpa_supplicant_event_assoc(struct wpa_supplicant *wpa_s,
union wpa_event_data data)
{
u8 bssid[ETH_ALEN]; int ft_completed;
int bssid_changed; struct wpa_driver_capa capa;
…// CONFIG_AP处理
// 判断Fast Transition是否完成,由于本例没有设置CONFIG_80211R宏,所以下面这个函数返回0
ft_completed = wpa_ft_is_completed(wpa_s->wpa);
/
在4.5.3节“wpa_supplicant_associate分析之一”中,wpa_supplicant对象的assoc_wpa_ie
被清空。此处需要保存这些IE信息。wpa_supplicant_event_associnfo比较烦琐,主要是更新RSN/WPA
IE信息,请感兴趣的读者自行阅读。
/
if (data && wpa_supplicant_event_associnfo(wpa_s, data) < 0) return;
wpa_supplicant_set_state(wpa_s, WPA_ASSOCIATED);// 设置wpa_sm装为WPA_ASSOCIATED
// 从driver wrapper中获得bssid信息
if (wpa_drv_get_bssid(wpa_s,bssid)>=0&&os_memcmp(bssid,wpa_s->bssid,ETH_ALEN)!=0){
random_add_randomness(bssid, ETH_ALEN); // 和随机数相关
bssid_changed = os_memcmp(wpa_s->bssid, bssid, ETH_ALEN);
os_memcpy(wpa_s->bssid, bssid, ETH_ALEN); // 设置bssid
os_memset(wpa_s->pending_bssid, 0, ETH_ALEN); // 清空pending_bssid
if (bssid_changed)
wpas_notify_bssid_changed(wpa_s);
// 和动态WEP Key有关,本书不讨论
if (wpa_supplicant_dynamic_keys(wpa_s)&&!ft_completed)wpa_clear_keys(wpa_s, bssid);
/
就本例而言(ap_scan为1,并且current_ssid不为空),下面这个函数作用不大。对其他情况而言,
该函数负责的工作我们在前面都见识过了。
/
if (wpa_supplicant_select_config(wpa_s) < 0) {…// 错误处理}
/
这个if条件对应的代码段让笔者非常困惑,因为在4.5.3节“wpa_supplicant_associate
分析之三”的最后已经为wpa_s->current_bss赋值了。此处为何又再赋值?通过笔者实测,bss
和current_bss的值是相同的。添加这段代码的人也没有说明为什么(读者不妨参考“git show
8f770587”命令的结果)。有知晓其来龙去脉的读者不妨和大家分享相关知识。
*/
if (wpa_s->current_ssid) {
struct wpa_bss bss = NULL;
bss = wpa_supplicant_get_new_bss(wpa_s, bssid);
if (!bss) {
wpa_supplicant_update_scan_results(wpa_s);
bss = wpa_supplicant_get_new_bss(wpa_s, bssid);
}
if (bss)
wpa_s->current_bss = bss;
}
if (wpa_s->conf->ap_scan==1&&wpa_s->drv_flags&WPA_DRIVER_FLAGS_BSS_SELECTION){
…// wlan driver支持BSS SELECTION功能时对应的处理
}
}
wpa_supplicant_event_assoc函数的历史比较悠久(根据git commit信息,它从
0.6.3版本开始就存在。而实际历史可能还要更久,因为W PAS从0.6.3版本后才开始使用git
来管理代码)。在笔者研究的W PAS相关代码中,wpa_supplicant_event_assoc函数算是
相当复杂的了。其中一个主要原因是该函数内部有很多细节需要考虑,有些细节甚至是为了
解决某个bug。对于初学者而言,笔者建议可只关注该函数涉及的主要流程。
(2)wpa_supplicant_event_assoc分析之二
接下来分析该函数最后一段代码。
[–>events.c::wpa_supplicant_event_assoc代码段二]
…// 接代码段一
#ifdef CONFIG_SME
…// 支持SME的情况
#endif / CONFIG_SME /
if (wpa_s->current_ssid) {
/
初始化SIM/USIM卡。根据代码中的注释:在ap_scan为1的情况下,该函数在此之前就被调用过。
而其他情况下,需要在此处调用它。在4.5.3节关联无线网络处理流程分析中见过此函数。
/
wpa_supplicant_scard_init(wpa_s, wpa_s->current_ssid);
}
wpa_sm_notify_assoc(wpa_s->wpa, bssid);
// 初始化wpa_sm中和后续EAPOL-Key交换相关联的变量
/
wpa_s->l2在4.3.4节wpa_supplicant_driver_init函数分析中通过l2_packet_init函数调用被赋值。
所以下面这个if条件为真。对Linux平台来说,l2_packet_notify_auth_start
实际上是一个空函数。
/
if (wpa_s->l2) l2_packet_notify_auth_start(wpa_s->l2);
if (!ft_completed) {// 设置EAPOL相关外部变量
eapol_sm_notify_portEnabled(wpa_s->eapol, FALSE);
eapol_sm_notify_portValid(wpa_s->eapol, FALSE);
}
// 本例采用的就是WPA-PSK认证算法
if (wpa_key_mgmt_wpa_psk(wpa_s->key_mgmt) || ft_completed)
eapol_sm_notify_eap_success(wpa_s->eapol, FALSE);
// 设置EAPOL模块中的portEnabled为TRUE,将状态机一系列动作。详情见下文
eapol_sm_notify_portEnabled(wpa_s->eapol, TRUE);
wpa_s->eapol_received = 0;
if (…){…}
} else if (!ft_completed) {
wpa_supplicant_req_auth_timeout(wpa_s, 10, 0);// 重新注册一个认证超时任务
}
wpa_supplicant_cancel_scan(wpa_s);// 取消scan任务
…// 处理wlan driver支持WPA_DRIVER_FLAGS_4WAY_HANDSHAKE功能以及ft_complete
为1时的情况
if (wpa_s->pending_eapol_rx) {
/
pending_eapol_rx变量对应如下的应用场景。
有时候在WPAS收到来自wlan driver的EVENT_ASSOC事件之前(即wpa_supplicant_event
assoc被调用之前),AP就发送EAPOL消息过来(后文会分析相关代码)以触发认证流程。而由于
WPAS还未处理EVENT_ASSOC事件。所以,EAPOL消息会先保存到pending_eapol_rx中,直到
wpa_supplicant_event_assoc函数中再来处理(即收到了EVENT_ASSOC事件)。笔者测试过
程中发现有些AP会出现这种情况。
*/
}
…// WEP的情况
…// IBSS的情况
}
wpa_supplicant_event_assoc函数终于分析完毕。正如上一节最后所提到的那样,该
函数实际上非常复杂,细节内容很难在本章篇幅内一一覆盖。同时,对初学者来说,流程比
细节更重要,所以可先略过这些细节。
上述代码中,eapol_sm_notify_portEnabled被调用以设置portEnable为TRUE。
EAPOL模块包含众多SM,它们是否会因为这个变量的变化而随之发生状态改变呢?来看下
节。
(3)eapol_sm_notify_portEnabled函数
eapol_sm_notify_portEnabled的代码如下所示。
[–>eapol_supp_sm.c::eapol_sm_notify_portEnabled]
void eapol_sm_notify_portEnabled struct eapol_sm sm, Boolean enabled)
{
…
sm->portEnabled= enabled;// 设置portEnabled变量为TRUE
eapol_sm_step(sm); // 状态机联动。这部分代码在4.4.2节介绍“状态机联动”时出现过
}
eapol_sm_step将依次更新SUPP_PAE、KEY_RX、SUPP_BE和EAP_SM状态信息。
在该函数执行之前,这四个状态机的状态依次如下。
·SUPP_PAE为DISCONNECTED状态;
·KEY_RX为NO_KEY_RECEIVE状态;
·SUPP_BE为IDLE状态;
·EAP_SM为DISABLED状态。
根据代码(eapol_supp_sm.c中SM_STEP(SUPP_PAE))以及图4-28可
知,SUPP_PAE下一个要进入的状态是CONNECTING,其EA(Entry Aciton)代码如
下。
[–>eapol_supp_sm.c::SM_STATE(SUPP_PAE,CONNECTING)]
SM_STATE(SUPP_PAE, CONNECTING)
{
// SUPP_PAE_state此时的值为SUPP_PAE_DISCONNECTED,故send_start为0
int send_start = sm->SUPP_PAE_state == SUPP_PAE_CONNECTING;
SM_ENTRY(SUPP_PAE, CONNECTING);
if (send_start) {
sm->startWhen = sm->startPeriod;
sm->startCount++;
} else {
#ifdef CONFIG_WPS
sm->startWhen = 1;
#else / CONFIG_WPS /
sm->startWhen = 3; // 设置startWhen值为3
#endif / CONFIG_WPS */
}
eapol_enable_timer_tick(sm); // 使能Port Timers SM
sm->eapolEap = FALSE;
if (send_start) eapol_sm_txStart(sm); // 发送EAPOL Start包。此时该函数不会被调用
}
读者可参考代码以及状态机示意图以了解其他状态机的切换。该函数执行完毕后,各个
状态机的变化如下。
·SUPP_PAE进入CONNECTING状态;
·KEY_RX不变(NO_KEY_RECEIVE);
·SUPP_BE进入IDLE状态;
·EAP_SM不变(DISABLED状态)。
注意 根据图4-21关于EAP SUPP_SM的描述,当portEnabled值为TRUE时,应该
从DISABLED状态切换至INITIALIZE状态。不过,在4.5.3
节“wpa_supplicant_associate分析之三”中曾调用过wpa_supplicant_initiate_eapol
函数。在该函数中,由于本例使用的认证算法是W PA-PSK,所以force_disabled变量为
TRUE,导致EAP SUPP SM不能转换至INITIALIZE状态。(参考eap.c::
SM_STEP(EAP)函数中第一个else if判断。)
(4)关联事件处理流程总结
关联事件处理流程中涉及的重要函数调用如图4-36所示。注
意,wpa_supplicant_event_assoc中略去了部分细节代码。请读者清楚图中的几个重要函
数后,再根据本节所述内容研究这些细节。
至此,W PAS已经和目标AP完成了Association操作,接来下将进入802.1X身份认证
流程。根据图3-44所示,接下来的工作将是4-W ay Handshake和Group Key Handshake
的处理。
图4-36 NL80211_CMD_CONNECT处理流程
5.EAPOL-Key交换流程分析
对本例而言,本节所说的EAPOL-Key交换包括4-W ay Handshake和Group Key
Handshake过程(注意,由于采用的是PSK认证方式,故本节分析的交互流程将不涉及
STA和Authenticator Server开展的身份认证的流程)。
首先发起的是4-W ay Handshake Key交换。对于W PA-PSK认证方法来说,STA不会
发送EAPOL-Start消息给AP。根据笔者的测试,完成关联操作后,Galaxy Note 2会发送
一个Null function(没有实际数据)的数据包给AP(如图4-37所示),而AP接收到该消
息后发现STA还未通过认证,所以它就会被触发以开始4-W ay Handshake流程(参考图3-
46的左图)。
图4-37 Null function数据包
由图4-37可知,Galaxy Note 2向AP发送一个Null function数据包后,AP就开始了
4-W ay Handshake流程。它首先发送一个EAPOL帧给STA(图4-37中"Message 1 of
4"这一项)。我们在4.3.4节的最后曾提到l2_packet模块(参考图4-1)用来接收PACKET
类型socket数据的函数是wpa_supplicant_rx_eapol。实际上,该函数就是W PAS中用来
接收EAP/ EAPOL数据包的。所以,wpa_supplicant_rx_eapol将处理AP发送过来的
EAPOL帧。马上来看此函数,代码如下所示。
[–>wpa_supplicant.c::wpa_supplicant_rx_eapol]
void wpa_supplicant_rx_eapol(void *ctx, const u8 *src_addr,
const u8 *buf, size_t len)
{
struct wpa_supplicant wpa_s = ctx;
/
读者还记得4.5.3节“wpa_supplicant_event_assoc分析之二”最后关于pending_eapol_rx
的解释吗?当wpa_state状态不为WPA_ASSOCIATED的时候,如果收到AP发来的数据包,则先保存
起来,然后留待wpa_supplicant_event_assoc中去处理。
/
if (wpa_s->wpa_state < WPA_ASSOCIATED) {
wpabuf_free(wpa_s->pending_eapol_rx);
wpa_s->pending_eapol_rx = wpabuf_alloc_copy(buf, len);// 复制数据
if (wpa_s->pending_eapol_rx) {
os_get_time(&wpa_s->pending_eapol_rx_time);
os_memcpy(wpa_s->pending_eapol_rx_src, src_addr,ETH_ALEN);
}
return;
}
…// CONFIG_AP处理
if (wpa_s->key_mgmt == WPA_KEY_MGMT_NONE) { return; }
/
eapol_received用于记录收到的EAPOL/EAP包个数。初次进来,需要设置认证超时任务(这个
任务已经设置过多次了,不过wpa_supplicant_req_auth_timeout会先取消上一次设置的认证超时
任务,然后再设置新的)。
/
if (wpa_s->eapol_received == 0 &&
(!(wpa_s->drv_flags & WPA_DRIVER_FLAGS_4WAY_HANDSHAKE) ||
!wpa_key_mgmt_wpa_psk(wpa_s->key_mgmt) || wpa_s->wpa_state != WPA_COMPLETED) &&
(wpa_s->current_ssid == NULL || wpa_s->current_ssid->mode != IEEE80211_MODE_IBSS)
) {
wpa_supplicant_req_auth_timeout(wpa_s,
(wpa_key_mgmt_wpa_ieee8021x(wpa_s->key_mgmt) ||
wpa_s->key_mgmt == WPA_KEY_MGMT_IEEE8021X_NO_WPA ||
wpa_s->key_mgmt == WPA_KEY_MGMT_WPS) ? 70 : 10, 0);
}
wpa_s->eapol_received++;
if (wpa_s->countermeasures) { return; }
…// CONFIG_IBSS_RSN处理
os_memcpy(wpa_s->last_eapol_src, src_addr, ETH_ALEN);
/
对于非PSK认证方法(如802.1X认证),EAPOL/EAP帧由eapol_sm_rx_eapol进行处理。
另外,与四次握手和组播密钥交换相关的EAPOL-Key帧也不在eapol_sm_rx_eapol中处理。
/
if (!wpa_key_mgmt_wpa_psk(wpa_s->key_mgmt) &&
eapol_sm_rx_eapol(wpa_s->eapol, src_addr, buf, len) > 0) return;
/
driver_nl80211没有实现poll函数。该函数的目的是为了从驱动中获取Association过程中涉及
的IE信息对于nl80211来说,关联完成后,我们就已经收到了这些信息,所以不需要再次获取它们了。
*/
wpa_drv_poll(wpa_s);
if (!(wpa_s->drv_flags & WPA_DRIVER_FLAGS_4WAY_HANDSHAKE))
wpa_sm_rx_eapol(wpa_s->wpa, src_addr, buf, len);// 关键函数。见下文分析
else if (wpa_key_mgmt_wpa_ieee8021x(wpa_s->key_mgmt)) {
eapol_sm_notify_portValid(wpa_s->eapol, TRUE);
}
}
wpa_sm_rx_eapol是本节的重点。在介绍之前,先来介绍EAPOL-Key交换的背景知
识。一方面对3.3.7节RSNA介绍的补充,另一方面该背景知识对我们理解
wpa_sm_rx_eapol函数也有重要帮助。
(1)背景知识介绍 [22][23]
由3.3.7节可知,RSNA过程中有两组Key需要交换和派生。
·PTK(Pairwise Transient Key,成对临时密钥)用于加解密单播数据。它从
PMK(Pairwise Master Key)派生(derive)而来。对于SOHO网络环境来说,PMK就
是PSK。它由用户设置的passphrase扩展而来(请读者参考4.5.2节中的
wpa_config_update_psk)。AP和STA需要通过4-W ay Handshake协议交换和派生
PTK。
·GTK(Group Transient Key,组临时密钥)用于加解密组播数据。它由
GMK(Group Master Key,其来源由AP或Authenticator Server决定)派生而来。AP
和STA通过Group Key Handshake协议交换和派生GTK。Group Key Handshake必须在
完成4-W ay Handshake之后才能开展。
提示 Group Key Handshake对应的场景比较特殊。对组播数据而言,AP和所有和
它关联的STA使用同一个GTK。不过,如果中途有STA离开(取消和AP的关联)的话,从
安全角度考虑,AP和剩下的STA之间最好更换一个新的GTK。AP将根据情况来触发
Group Key Handshake流程。另外,AP可通过4-W ay Handshake告知STA其当前使用
的GTK。这种情况下,AP和STA之间无须开展Group Key Handshake流程。
下面,通过实例来介绍4-W ay Handshake涉及的四次帧交换以及其中包含的数据信
息。
第一个EAPOL-Key帧(以后简称Message A)由AP发送给STA,其内容如图4-38所
示。Key Descriptor Type及以下的内容属于Key Descriptor。Key Descriptor用来描述
EAPOL-Key帧的Key信息,由多个字段组成。
·第一个字段是Key Descriptor Type,目前取值有三个,分别是RC4、W PA和
RSN(即W PA2),802.11中可使用后两个。
·Key Information字段(2字节)包含多个标志位。下文将一一介绍它们。
·Key Length字段(2字节)表示PTK密钥的长度。对CCMP来说,它是16字节(128
位),而对TKIP来说,其长度是32字节(256位)。读者可参考图3-47中的CCMP-TK、
TKIP-TK和TKIP-MIC Key。CCMP和TKIP长度不一致的原因是CCMP可用一个Key完
成数据加解密和MIC校验,而TKIP使用不同的Key来完成加解密以及MIC校验。
·Replay Counter字段(8字节)和防止重放攻击有关。一个简单的应用场景为STA
之前收到过Replay Counter为1的包。假如它又收到一个Replay Counter为0的包,则认
为发生了重放攻击,STA将丢弃Replay Counter为0的包。对Message A来说,该值必须
为0。
·Key Nonce字段(32字节)用于存储Nonce值。对Message A来说,此Nonce值由
AP生成,所以也叫ANonce。
·Key IV字段(16字节)表示初始向量,用于Key生成。Message A中该字段为全0。
·Key RSC字段(8字节)是Key Receive Sequence Counter的缩写,也和重放攻击
有关。该字段用于四次握手的第三帧以及组播Key握手的第一帧中。
·Key ID字段(8字节)对W PA/ RSN来说,该字段未使用。
·Key MIC字段(可变字节长度)表示存储MIC数据,其长度和具体的算法有关。笔
者查询了802.11文档 [22] ,规范中列出的几种算法对应的MIC长度都是16字节。
·Key Data Length字段(2字节)表示Key Data长度。图4-38没有携带Key Data,
所以该项为0。如果该项不为0,Key Descriptor还将在Key Data Length后添加一个"Key
Data"项。
图4-38 Message A的内容
图4-38中,Key Information字段的内容如图4-39所示。
图4-39 Key Information字段
如图4-39所示:
·Key Descriptor Version:用于指示Key Descriptor的版本号。当值为2时,表示
Key Descriptor的Key Data用AES加密,而Key MIC由HMAC-SHA1算法计算而来。
·Key Type:值为1,表示该帧用于PTK派生,值为0表示该帧用于GTK派生。
·Reserved:该字段必须为0(图4-38中,该字段也被称为Key Index)。
·Install:当Key Type为1时,Install值为1表示STA需要安装PTK(将PTK传给驱
动,下文源码分析时将看到相关函数)。Key Type为0时,Install必须为0。
·Key ACK:AP发送EAPOL-Key帧给STA时,如果需要STA发送回复数据,则设置
其为1。
·Key MIC:如果EAPOL-Key帧包含MIC信息,其值被设为1,否则为0。
·Secure:当STA或AP派生出了PTK或GTK后,STA或AP发出的EAPOL-Key帧将
该值设为1。
·Error:使用TKIP时,如果STA检查到MIC错误,则设置该值为1。对于其他情况下
的MIC错误,该值和Request都必须被设为1。
·Request:STA请求AP发起4-W ay Handshake(Key Type同时被设为1)或者
Group Key Handshake(Key Type同时被设为0)流程时,其值被设为1。或者和Error都
被设为1以报告MIC错误。
·Encrypted Key Data:表示Key Data是否加密。
·SMK:Station-to-station link Master Key的缩写,是另外一种Key交换协议。
本书不讨论。
同上述介绍,相信读者对Key Descriptor有了一定的了解。现在马上来介绍四次握手
协议中每个EAPOL-Key帧所包含的内容以及接收方的处理逻辑。
Message A由AP发送给STA,其内容如下。
·Key Info:Error位为0,因为这是第一帧数据。Secure位为0表示该帧没有加密的
数据。Key MIC为0表示该帧不包含MIC数据。Key ACK位为1表示AP要求STA回复此
帧。Key Install为0表示现在还无法安装PTK。Key Type设为1表示当前是Pairwise密钥
派生。本例中,Key Description Version值为2。
·Replay Counter:本例中它被设为1。STA需要保存这个值以检测重放攻击。
·Key Nonce:AP生成的随机数,也叫ANonce。
·由于本帧不包含Key信息,所以其他字段都设为0。
提醒 802.11规范中指出,Message A的Key Data可以携带PMKID信息(包含在
RSN IE或其他厂商自定义的IE中)。不过本例中,Message A没有携带它。
STA收到Message A的处理逻辑如下。
1)生成一个Nonce,也叫SNonce。
2)派生PTK。
3)构造第二个EAPOL-Key帧,称为Message B。
图4-40为Message B的截图。
图4-40 Message B的内容
如图4-40所示:
·由Key Info的设置可知该帧包括MIC数据(Key MIC位为1)。
·Replay Counter的值必须等于Message A中的Replay Counter。
·Key MIC是对整个EAPOL-Key帧进行计算而来,计算方法由Key Descriptor
Version指定(图4-40中的HMAC-SHA1 MIC方法)。
·Key Data包含一个RSN Information Element。该RSN IE来自STA之前和AP在关
联操作时获得的RSN IE。规范没有说明为何此处要包含RSN IE。不过[23]倒是有一句简单
的说法。即为了防止STA中途更改安全参数。
AP收到Message B的处理如下。
1)派生PTK。
2)检查Message B的MIC值是否正确。如果发现MIC错误,则丢弃(而且是
silently)Message B。
这种情况下,4-W ay Handshake流程已经被中断。如果MIC正确,AP将构造第三个
EAPOL-Key帧,此处称为Message C,其内容如图4-41所示。
图4-41 Message C的内容
由图4-41可知:
·Install为1,表示STA收到该帧后就可以安装PTK了。Secure为1表示AP已经派生
了PTK。Encrpted Key Data为1,表示Key Data被加密了。
·Replay Counter为2,比前面的值增加1。
·Key Nonce值和Message A的一样。
·MIC对EAPOL-Key整个进行计算得来。
·Key IV为0。注意,如果Key Descriptor Version为2时,该字段必须为0。否则可
以是其他随机数。
·Key Data由PTK加密后而来。其解密后的内容包括AP在Probe Response或
Beacon帧包含RSN IE信息,另外还有可能包括GTK信息。如果有GTK的话,4-W ay
Handshake完毕后就无须开展Group Key Handshake流程。
STA收到Message C的处理如下。
1)检查Replay Counter,计算MIC以及利用自己的PTK解密Key Data以获取RSN
IE。
2)如果Message C包含GTK信息,则取出GTK。注意,GTK以及和Key相关的信息
存储在KDE(Key Data Element)的IE中。关于KDE和GTK的格式,请读者继续阅读下
文。
3)构造并发送最后一个EAPOL-Key帧,称为Message D。
4)为Driver安装PTK。
Message D的内容如图4-42所示。由图可知,Replay Counter和Message C一样。
MIC通过对EAPOL-Key整个进行计算得来。
AP收到Message D的处理如下。
1)再次计算MIC,如果正确则为driver安装PTK。
2)更新Replay Counter,如果以后需要更新Key,则使用一个不同的Replay
Counter。
至此,4-W ay Handshake成功完成,而AP和STA以后的单播数据将全部通过PTK进
行加密。这就使得我们无法通过AirPcap解析Group Key Handshake数据包。关于Group
Key Handshake的流程请看参考资料[22][23]。
图4-42 Message D的内容
根据前文所述,AP在Message C中可以携带GTK信息。这样4-W ay Handshake完毕
后,STA无须开展Group Key Handshake。
注意 经过测试,有些AP每次在4-W ay Handshake完毕后就发起Group Key
Handshake,而有些AP在Message C中直接包含了GTK,这样就避免了Group Key
Handshake。接下来的代码分析以后一种情况为目标。
另外,请注意图3-47,该图展示了PTK的组成。以CCMP为例,PTK包含三个部分,
分别是KCK、KEK和TK。其中KCK用来处理MIC字段,KEK用来处理Key Data字段,而
TK则用于握手协议完毕后的数据加密。
介绍完背景知识后,马上来研究wpa_sm_rx_eapol函数,它是不是遵守了规范所描述
的流程呢?
(2)wpa_sm_rx_eapol函数
在分析代码之前,先介绍两个重要的数据结构,如图4-43所示。
·struct ieee802_1x_hdr为EAPOL帧头信息。
·struct wpa_eapol_key为EAPOL-Key帧的数据信息。
图4-43 EAPOL-Key帧对应的数据结构
wpa_sm_rx_eapol代码如下所示。
[–>wpa.c::wpa_sm_rx_eapol]
int wpa_sm_rx_eapol(struct wpa_sm *sm, const u8 *src_addr,
const u8 *buf, size_t len)
{
size_t plen, data_len, extra_len;
struct ieee802_1x_hdr *hdr; struct wpa_eapol_key *key;
u16 key_info, ver; u8 *tmp; int ret = -1;
struct wpa_peerkey *peerkey = NULL;
…// 参数检查
tmp = os_malloc(len); os_memcpy(tmp, buf, len);
hdr = (struct ieee802_1x_hdr *) tmp;
key = (struct wpa_eapol_key *) (hdr + 1);
plen = be_to_host16(hdr->length);
data_len = plen + sizeof(hdr);
// 检查EAPOL-Key帧的类型
if (hdr->version < EAPOL_VERSION) { }
if (hdr->type != IEEE802_1X_TYPE_EAPOL_KEY)// 本函数只处理EAPOL-Key帧
{ ret = 0; goto out;}
if (key->type != EAPOL_KEY_TYPE_WPA && key->type != EAPOL_KEY_TYPE_RSN) {
ret = 0; goto out; // 只处理key类型为WPA和RSN的情况
}
/
通知lowerLayerSuccess。该函数最后一个参数表示此次调用是否来自EAPOL模块内部,
由于wpa_sm_rx_eapol并非EAPOL模块内部,所以该参数为0。
/
eapol_sm_notify_lower_layer_success(sm->eapol, 0);
// 取出Key Information字段,并保存到key_info变量中
key_info = WPA_GET_BE16(key->key_info);
ver = key_info & WPA_KEY_INFO_TYPE_MASK; // 获取key descriptor version
if (ver != WPA_KEY_INFO_TYPE_HMAC_MD5_RC4 && ver !=
WPA_KEY_INFO_TYPE_HMAC_SHA1_AES)
goto out; // 根据规范的要求做相应处理
…// CONFIG_IEEE80211R和CONFIG_IEEE80211W的处理
// 加密算法兼容性检查
if (sm->pairwise_cipher==WPA_CIPHER_CCMP&&ver!=
WPA_KEY_INFO_TYPE_HMAC_SHA1_AES){
// 下面这个if判断用于检查组播数据加密设置是否正确
if(sm->group_cipher!=WPA_CIPHER_CCMP&&!(key_info&WPA_KEY_INFO_KEY_TYPE)){
…// 打印一句兼容性警告
} else goto out;
}
…// CONFIG_PEERKEY处理。PeerKey对应peerkey handshake流程,它和IEEE802.11e DLS有关
// 检查Replay Counter,如果收到的Replay Counter比之前接收的值小,则丢弃该帧
if (!peerkey && sm->rx_replay_counter_set && os_memcmp(key->replay_counter,
sm->rx_replay_counter, WPA_REPLAY_COUNTER_LEN) <= 0) goto out;
// STA收到的EAPOL-Key帧必须设置ACK位或SMK位为1
if (!(key_info & (WPA_KEY_INFO_ACK | WPA_KEY_INFO_SMK_MESSAGE)) ) goto out;
// 只有STA向AP发送的EAPOL-Key帧才能设置Request位
if (key_info & WPA_KEY_INFO_REQUEST) goto out;
/
MIC位不为0,则需要解析MIC数据。对STA来说,只有Message C会携带MIC信息。
wpa_supplicant_verify_eapol_key_mic比较简单,请读者自行阅读它。其功能是:STA
根据KCK计算MIC,然后将其和接收到的MIC进行比较。如果MIC值检查正常,则PTK正确。
*/
if ((key_info & WPA_KEY_INFO_MIC) && !peerkey &&
wpa_supplicant_verify_eapol_key_mic(sm, key, ver, tmp, data_len)) goto out;
extra_len = data_len - sizeof(*hdr) - sizeof(*key);
if (WPA_GET_BE16(key->key_data_length) > extra_len) goto out;// 参数检查
extra_len = WPA_GET_BE16(key->key_data_length);
// Encrpted Key Data位为1,表示该帧包含加密数据,需要利用KEK进行解密
// 该函数也比较简单,请读者自行阅读
if (sm->proto == WPA_PROTO_RSN && (key_info & WPA_KEY_INFO_ENCR_KEY_DATA)) {
if (wpa_supplicant_decrypt_key_data(sm, key, ver)) goto out;
extra_len = WPA_GET_BE16(key->key_data_length);
}
// Key Info Type值为1,表示为Pairwise Key。值为0表示Group Key
if (key_info & WPA_KEY_INFO_KEY_TYPE) {
if (key_info & WPA_KEY_INFO_KEY_INDEX_MASK) goto out;
if (peerkey) { peerkey_rx_eapol_4way(sm, peerkey, key, key_info, ver);
} else if (key_info & WPA_KEY_INFO_MIC) {// 对STA而言,只有Message C包含MIC信息
wpa_supplicant_process_3_of_4(sm, key, ver);// 处理Message C
} else {// 处理Message A
wpa_supplicant_process_1_of_4(sm, src_addr, key, ver);
}
} else if (key_info & WPA_KEY_INFO_SMK_MESSAGE) {// SMK的情况
peerkey_rx_eapol_smk(sm, src_addr, key, extra_len, key_info, ver);
} else {// Group Key Handshake处理
if (key_info & WPA_KEY_INFO_MIC) {// 组播密钥交换。本章不讨论此函数
wpa_supplicant_process_1_of_2(sm, src_addr, key,extra_len, ver);
} else …// 打印一句警告
}
ret = 1;
out:
os_free(tmp);
return ret;
}
wpa_sm_rx_eapol的流程比较简单,就是先处理EAPOL-Key的基本信息(如计算
MIC、解密Key Data),然后根据情况处理MessageA、Message C或者Group Key
Handshake的Message 1。
由于本例不涉及Group Key Handshake流程,所以下面将介绍Message A及Message
C的处理过程。
(3)wpa_supplicant_process_1_of_4函数
wpa_supplicant_process_1_of_4用于处理Message A,其代码如下所示。
[–>wpa.c::wpa_supplicant_process_1_of_4]
static void wpa_supplicant_process_1_of_4(struct wpa_sm *sm,
const unsigned char *src_addr,
const struct wpa_eapol_key *key, u16 ver)
{
struct wpa_eapol_ie_parse ie; struct wpa_ptk *ptk;
u8 buf[8]; int res;
if (wpa_sm_get_network_ctx(sm) == NULL) {…// 错误处理}
wpa_sm_set_state(sm, WPA_4WAY_HANDSHAKE); // 设置状态为WPA_4WAY_HANDSHAKE
os_memset(&ie, 0, sizeof(ie));
#ifndef CONFIG_NO_WPA2
if (sm->proto == WPA_PROTO_RSN) {
const u8 *_buf = (const u8 ) (key + 1);// buf中是已经解密的Key Data数据
size_t len = WPA_GET_BE16(key->key_data_length);
// 解析Message A中包含的RSN信息。对本例而言,Message A中没有RSN信息
if (wpa_supplicant_parse_ies(_buf, len, &ie) < 0) goto failed;
}
#endif / CONFIG_NO_WPA2 /
// 如果根据RSN IE中的pmkid判断是否有PMKSA缓存项。本例没有RSN IE,所以不存在ie.pmkid
res = wpa_supplicant_get_pmk(sm, src_addr, ie.pmkid);
if (res == -2) return;
if (res)
goto failed;
// 结合前面对背景知识的描述,STA需要创建自己的Nonce
if (sm->renew_snonce) {
if (random_get_bytes(sm->snonce, WPA_NONCE_LEN)) goto failed;
sm->renew_snonce = 0;
}
/
tptk变量存储的是临时PTK,因为现在还不确定PTK是否正确。注意,当收到Message C时,
wpa_sm_rx_eapol的wpa_supplicant_verify_eapol_key_mic函数将把sm->tptk的内容复制到
sm->ptk变量中作为正式的ptk存储。
*/
ptk = &sm->tptk;
wpa_derive_ptk(sm, src_addr, key, ptk);// 派生PTK并将结果保存到tptk变量中
os_memcpy(buf, ptk->u.auth.tx_mic_key, 8);
os_memcpy(ptk->u.auth.tx_mic_key, ptk->u.auth.rx_mic_key, 8);
os_memcpy(ptk->u.auth.rx_mic_key, buf, 8);
sm->tptk_set = 1; // tptk_set为1表示临时PTK存在
// 构造并发送Message B。请读者结合参考资料[23]来研究此函数
if (wpa_supplicant_send_2_of_4(sm, sm->bssid, key, ver, sm->snonce,
sm->assoc_wpa_ie, sm->assoc_wpa_ie_len, ptk)) goto failed;
// 包括AP的Nonce信息
os_memcpy(sm->anonce, key->key_nonce, WPA_NONCE_LEN);
return;
failed:
wpa_sm_deauthenticate(sm, WLAN_REASON_UNSPECIFIED);
}
STA将Message B发送出去后,AP将接收到它并根据AP的处理逻辑进行处理。假设一
切顺利,AP将构造并发送Message C给STA。
STA依然在wpa_sm_rx_eapol中处理Message C,其过程如下。
1)由于Message C包含MIC以及Key Data数据,故它们将在wpa_sm_rx_eapol中的
wpa_supplicant_verify_eapol_key_mic及wpa_supplicant_decrypt_key_data被处理。
这两个函数的代码请感兴趣的读者自行阅读。
2)接下来调用wpa_supplicant_process_3_of_4。这是我们分析的重点。
(4)wpa_supplicant_process_3_of_4函数
wpa_supplicant_process_3_of_4的代码如下所示。
[–>wpa.c::wpa_supplicant_process_3_of_4]
static void wpa_supplicant_process_3_of_4(struct wpa_sm *sm,
const struct wpa_eapol_key *key, u16 ver)
{
u16 key_info, keylen, len;
const u8 *pos; struct wpa_eapol_ie_parse ie;
wpa_sm_set_state(sm, WPA_4WAY_HANDSHAKE);// 还是处于WPA_4WAY_HANDSHAKE状态
key_info = WPA_GET_BE16(key->key_info);
pos = (const u8 ) (key + 1);
len = WPA_GET_BE16(key->key_data_length);
/
解析Message C中包含的IE信息。注意,key data中的数据已经由wpa_supplicant_decrypt_key_data
解密过了。
/
if (wpa_supplicant_parse_ies(pos, len, &ie) < 0) goto failed;
if (ie.gtk && !(key_info & WPA_KEY_INFO_ENCR_KEY_DATA)) goto failed;
…// CONFIG_IEEE80211W
// 校验RSN信息。该校验似乎也是为了防止安全设置项中途发生改变
if (wpa_supplicant_validate_ie(sm, sm->bssid, &ie) < 0) goto failed;
// 比较Message A和Message C中的Nonce值
if (os_memcmp(sm->anonce, key->key_nonce, WPA_NONCE_LEN) != 0)
goto failed;
keylen = WPA_GET_BE16(key->key_length);
…// 参数检查
// 构造并发送Message D,请读者自行阅读该函数
if (wpa_supplicant_send_4_of_4(sm, sm->bssid, key, ver, key_info,
NULL, 0, &sm->ptk)) goto failed;
sm->renew_snonce = 1;
/
调用driver_nl80211的wpa_driver_nl80211_set_key函数将key发给驱动。以后,所有
无线网络数据都将被加密。另外,如果wpa_supplicant.conf文件配置wpa_ptk_rekey
(用来控制PTK的生命周期,时间为秒)时,该函数内部还将注册一个超时函数wpa_sm_rekey_ptk。一旦
wpa_ptk_rekey到期,STA将重新和AP开展4-Way Handshake以重新派生新的PTK。
请读者自行阅读wpa_supplicant_install_ptk函数。
/
if (key_info & WPA_KEY_INFO_INSTALL) // 安装Key
if (wpa_supplicant_install_ptk(sm, key)) goto failed;
// Message C必须设置Secure位
if (key_info & WPA_KEY_INFO_SECURE) {
// 下面这个函数和管理帧加密有关系
wpa_sm_mlme_setprotection(sm, sm->bssid, MLME_SETPROTECTION_PROTECT_TYPE_RX,
MLME_SETPROTECTION_KEY_TYPE_PAIRWISE);
eapol_sm_notify_portValid(sm->eapol, TRUE);// 设置EAPOL SM portValid为TRUE
}
wpa_sm_set_state(sm, WPA_GROUP_HANDSHAKE); // 设置状态为WPA_GROUP_HANDSHAKE
// 对本例而言,Message C中包含了GTK信息
if (ie.gtk && wpa_supplicant_pairwise_gtk(sm, key,
ie.gtk, ie.gtk_len, key_info) < 0) goto failed;
// 和IEEE80211W有关。本书不讨论它
if (ieee80211w_set_keys(sm, &ie) < 0) goto failed;
/
下面这个函数将调用driver wrapper的set_rekey_info函数。该函数和GTK的更新有关。
它对应以下应用场景。
当某STA因为省电或别的什么原因进入休眠状态时,如果AP在STA休眠过程中更新了GTK。当该STA
醒来时,其组播消息密钥肯定失效了。这种情况该如何处理呢?通过set_rekey_info函数,我们可
将该工作交给wlan芯片来完成。即由wlan芯片来负责处理EAPOL-Key帧交换以更新GTK。注意,
如果wlan driver不支持该功能,它将唤醒STA,并将该工作交给WPAS来完成。
这部分功能属于WoWLAN(无线局域网唤醒)机制的一部分,目前只有很少的系统支持它。读者可阅读
参考资料[24][25]以加深对WoWLAN的认识。
*/
wpa_sm_set_rekey_offload(sm);
return;
failed:
wpa_sm_deauthenticate(sm, WLAN_REASON_UNSPECIFIED);
}
wpa_supplicant_process_3_of_4代码逻辑和背景知识介绍中关于Message C的处理
逻辑大体一致。对于包含GTK信息的Message C来说,wpa_supplicant_pairwise_gtk将
被调用以处理GTK对应的KDE信息。马上来看此函数。
(5)wpa_supplicant_pairwise_gtk函数
介绍wpa_supplicant_pairwise_gtk之前,先来看看KDE和GTK的格式,如图4-44所
示。
图4-44 KDE和GTK格式
图4-44中,上图为KDE的格式,其中Type取值为0xdd。Data包含具体的信息。当
Data中的信息为GTK时,OUT取值为00-0F-AC,Data Type取值为1。下图为GTK的格
式。KeyID字段可取值有0~3共4个,它和动态Key有关。Tx字段为1,表示发送和接收组
播数据都需要使用该GTK。值为0表示仅接收组播数据需要使用该GTK。
[–>wpa.c::wpa_supplicant_pairwise_gtk]
static int wpa_supplicant_pairwise_gtk(struct wpa_sm *sm,
const struct wpa_eapol_key *key,
const u8 *gtk, size_t gtk_len, int key_info)
{
#ifndef CONFIG_NO_WPA2
struct wpa_gtk_data gd;
os_memset(&gd, 0, sizeof(gd));
if (gtk_len < 2 || gtk_len - 2 > sizeof(gd.gtk)) return -1; // 参数检查
gd.keyidx = gtk[0] & 0x3;
// 下面这个函数对图4-44中的Tx位进行一些处理
// 它对某些AP错误设置Tx位的情况采取了“绕过去”的策略
gd.tx = wpa_supplicant_gtk_tx_bit_workaround(sm,!!(gtk[0] & BIT(2)));
gtk += 2; gtk_len -= 2;
os_memcpy(gd.gtk, gtk, gtk_len);
gd.gtk_len = gtk_len;
// 检查组播数据加密设置是否合适,然后调用wpa_supplicant_install_gtk函数安装GTK
if (wpa_supplicant_check_group_cipher(sm, sm->group_cipher, gtk_len, gtk_len,
&gd.key_rsc_len, &gd.alg) ||
wpa_supplicant_install_gtk(sm, &gd, key->key_rsc)) return -1;
// 最后一个关键函数
wpa_supplicant_key_neg_complete(sm, sm->bssid,key_info & WPA_KEY_INFO_SECURE);
return 0;
#else
return -1;
#endif
}
上述代码中,请读者自行阅读除wpa_supplicant_key_neg_complete外的其他几个函
数。下面来看最后一个关键函数。
[–>wpa.c::wpa_supplicant_key_neg_complete]
static void wpa_supplicant_key_neg_complete(struct wpa_sm *sm,const u8 *addr, int secure)
{
wpa_sm_cancel_auth_timeout(sm);// 取消超时任务
// 该函数内部将调用wpa_supplicant.c中的wpa_supplicant_set_state。请读者自行阅读
// 不光是一个简单的设置状态,它还需要调用driver的set_supp_port函数
wpa_sm_set_state(sm, WPA_COMPLETED);// 设置WPAS的状态为WPA_COMPLETED
if (secure) {// secure为1
wpa_sm_mlme_setprotection( sm, addr, MLME_SETPROTECTION_PROTECT_TYPE_RX_TX,
MLME_SETPROTECTION_KEY_TYPE_PAIRWISE);
eapol_sm_notify_portValid(sm->eapol, TRUE);
if (wpa_key_mgmt_wpa_psk(sm->key_mgmt))// 本例采用了PSK认证,故可以通知eapSuccess
eapol_sm_notify_eap_success(sm->eapol, TRUE);
eloop_register_timeout(1, 0, wpa_sm_start_preauth, sm, NULL);
}
if (sm->cur_pmksa && sm->cur_pmksa->opportunistic)
sm->cur_pmksa->opportunistic = 0;
…// CONFIG_IEEE80211R
}
至此,4-W ay Handshake的处理就全部完成。而802.1X中Port的状态以及W PAS的
状态则由此过程中相关的函数触发以发生转换。下一节将简单介绍这部分的内容。
(6)W PAS状态机变化
上述4-W ay Handshake处理流程节中,EAPOL模块、EAP模块以及W PAS的状态都
会发生对应的转换。在上述wpa_supplicant_key_neg_complete中,wpa_sm_set_state
将设置W PAS的状态为W PA_COMPLETED。马上来看wpa_sm_set_state的代码,如下
所示。
[–>wpa_i.h::wpa_sm_set_state]
static inline void wpa_sm_set_state(struct wpa_sm sm, enum wpa_states state)
{
/
调用回调函数。由4.3.4节的wpa_supplicant_init_wpa函数中设置,其真实函数为
_wpa_supplicant_set_state,而该函数又会调用wpa_supplicant_set_state。
*/
sm->ctx->set_state(sm->ctx->ctx, state);
}
wpa_sm_set_state将调用前面设置的回调函数进行处理。
最终的处理函数wpa_supplicant_set_state如下所示。
[–>wpa_supplicant.c::wpa_supplicant_set_state]
void wpa_supplicant_set_state(struct wpa_supplicant *wpa_s,
enum wpa_states state)
{
enum wpa_states old_state = wpa_s->wpa_state;
…
if (state == WPA_COMPLETED && wpa_s->new_connection) {
#if defined(CONFIG_CTRL_IFACE) || !defined(CONFIG_NO_STDOUT_DEBUG)
struct wpa_ssid ssid = wpa_s->current_ssid;
#endif
wpa_s->new_connection = 0;
wpa_s->reassociated_connection = 1;
/
设置driver的IfOperStatus为IF_OPER_UP,driver nl80211中对应的函数是
wpa_driver_nl80211_set_operstate。
/
wpa_drv_set_operstate(wpa_s, 1);
#ifndef IEEE8021X_EAPOL
/
以driver nl80211来说,下面这个函数将调用wpa_driver_nl80211_set_supp_port
以设置驱动中STA的标志为NL80211_STA_FLAG_AUTHENTICATED。
/
wpa_drv_set_supp_port(wpa_s, 1);
#endif / IEEE8021X_EAPOL /
wpa_s->after_wps = 0;
} else if (state == WPA_DISCONNECTED || state == WPA_ASSOCIATING ||
state == WPA_ASSOCIATED) {
wpa_s->new_connection = 1;// ASSOCIATING或ASSOCIATED状态下,new_connection被置为1
wpa_drv_set_operstate(wpa_s, 0);// 设置driver的IfOperStatus为IF_OPER_DORMANT
#ifndef IEEE8021X_EAPOL
wpa_drv_set_supp_port(wpa_s, 0);// 取消STA的NL80211_STA_FLAG_AUTHENTICATED标志
#endif / IEEE8021X_EAPOL */
}
wpa_s->wpa_state = state;
…
}
当W PAS的状态变成W PA_COMPLETED后,还需要设置driver的IfOperStatus以及
NL80211_STA_FLAG_AUTHENTICATED标志位。如此这般,W PAS才算和Driver上下
同心。
(7)EAPOL/ EAP状态机变化
在4-W ay Handshake过程中,EAPOL/ EAP状态机也会发生变化。在4.5.3节
eapol_sm_notify_portEnabled函数分析的最后,已知EAPOL/ EAP状态机的状态分别如
下。
·SUPP_PAE进入CONNECTING状态;
·KEY_RX不变(NO_KEY_RECEIVE);
·SUPP_BE进入IDLE状态;
·EAP_SM不变(DISABLED状态)。
在4-W ay Handshake过程中,一共有两个和EAPOL相关的函数被调用:
eapol_sm_notify_portValid函数portValid被设为TRUE。
eapol_sm_notify_eap_success函数代码如下所示。
[–>eapol_supp_sm.c::eapol_sm_notify_eap_success]
void eapol_sm_notify_eap_success(struct eapol_sm *sm, Boolean success)
{
if (sm == NULL) return;
sm->eapSuccess = success;
sm->altAccept = success;
if (success) eap_notify_success(sm->eap);
eapol_sm_step(sm);// 状态机联动
}
// 直接来看eap_notify_success函数
void eap_notify_success(struct eap_sm sm)
{
if (sm) {
sm->decision = DECISION_COND_SUCC;
sm->EAP_state = EAP_SUCCESS;// EAP_SM状态直接被设置为EAP_SUCCESS
}
}
根据4.4.2节状态机联动中对eapol_sm_step的分析,四个状态机更新的顺序分别是
SUPP_PAE、KER_RX、SUPP_BE和EAP_SM。分别结合四个状态机的切换图,我们可知
第一轮循环中:
·eapSuccess将先触发SUPP_PAE进入AUTHENTICATING状态,该状态对应的EA
将设置suppStart为TRUE。
·eapSuccess和suppStart将触发SUPP_BE进入SUCCESS状态,该状态对应的EA
将设置keyRun和suppSuccess为TRUE。
·EAP_SM状态在eap_notify_success中直接被置为EAP_SUCCESS。但在状态切换
中,由于force_disabled变量为TRUE,导致EAP SUPP SM将直接转回DISABLED状态
(和4.4.2节介绍的状态机联动的情况类似)。
·KER_RX状态不变。
由于上述状态机均有状态变化,下面进入第二轮循环:
·suppSuccess和portValid为TRUE将触发SUPP_PAE进入AUTHENTICATED状态
(由于条件变量不会再改变,故SUPP_PAE将不再发生状态变化)。该状态的EA见下文代
码。
·由于UCT的存在,SUPP_BE将跳转到IDLE状态。
·KEY_RX和EAP_SM状态保持不变。
以下是SUPP_PAE的EA代码。
[–>eapol_supp_sm.c::SM_STATE(SUPP_PAE,AUTHENTICATED)]
SM_STATE(SUPP_PAE, AUTHENTICATED)
{
SM_ENTRY(SUPP_PAE, AUTHENTICATED);
sm->suppPortStatus = Authorized;
// 也会调用wpa_drv_set_supp_port函数
eapol_sm_set_port_authorized(sm);
/
设置cb_status为EAPOL_CB_SUCCESS,将触发eapol_sm_step在退出前调用
wpa_supplicant_eapol_cb,此函数对于WPA/RSN企业认证法有重要作用。请读者自行阅读。
*/
sm->cb_status = EAPOL_CB_SUCCESS;
}
请读者结合SUPP_PAE和SUPP_BE的状态切换图来理解上述的状态变化过程。
至此,STA就通过了AP的身份验证。下一步的工作就是dhcpcd从AP那获取一个IP,
然后手机就可以上网了。
(8)EAPOL-Key交换流程总结
下面总结4-W ay Handshake的流程,如图4-45和图4-46所示。
图4-45 Message A处理流程
图4-46 Message C处理流程
图中分别为4-W ay Handshake中Message A与Message C的处理流程。其
中,Message C还携带了GTK信息,这样就无须Group Key Handshake了。
4.6 本章总结和参考资料说明
4.6.1 本章总结
本章对wpa_supplicant进行了一番剖析,涉及如下几个重点内容。
·W PAS的启动:在这条分析路线中,读者能感受到W PAS中多种多样的数据结构以及
之间较为复杂的关系。同时,对极具背景含义的成员变量也进行了深入介绍。
·EAPOL/ EAP模块:先向读者介绍了理论知识,然后结合代码分析了W PAS如何将理
论知识转化成代码。从笔者角度来看,状态机的代码比看状态切换图要复杂得多。读者不妨
自己亲身体验一下。
·扫描、关联及4-W ay Handshake分析:通过一个实例讲解了这一流程涉及的重要函
数和相关知识点。该流程实际上包含的代码远比书中给多,原因是W PAS支持的许多功能
(如W PS、P2P、802.11R、802.11W 等)都在这一流程中有涉及。根据笔者的学习经验,
只有先清楚一个最简单的流程,后面才能循序渐进地把其他功能的分析加进来。
提示 本章编撰时,市面上搭载Android 4.2的手机较少,所以此处的分析目标是
Android 4.1中的wpa_supplicant。不过笔者比较了它和Android 4.2版本中的
wpa_supplicant。虽然4.2版本中的W PAS变化较大,主要体现在一些新增功能和bug修改
上。读者如果搞清楚了本章内容,再研究4.2版本中的W PAS会很轻松。
4.6.2 参考资料说明
1.概述
[1] http:/ / en.wikipedia.org/ wiki/ Extensible_Authentication_Protocol
说明:维基百科关于EAP各种方法的一个简单介绍。
[2] http:/ / hostap.epitest.fi/ wpa_supplicant/ devel/
说明:wpa_supplicant官方开发文档。读者可以简单浏览一下。
2.wpa_ssid结构体
[3] 《802.11-2012》附录M.4"Suggested pass-phrase-to-PSK mapping"
说明:该节介绍了passphrase转换成PSK的方法,甚至还有伪代码实现。感兴趣的读
者不妨结合W PAS中的代码来研究。
[4] 《802.11-2012》第8.4.2.27.2节"Cipher suites"
[5] 《802.11-2012》第8.4.2.27.3节"AKM suites"
说明:上述两小节分别介绍了Cipher和AKM suites的情况。注意,其中定义的取值定
义是指在RSN IE中的取值,和代码中定义的宏不是一回事。
[6] 《802.11-2012》第12章"Fast BSS Transition"
说明:官方文档。难度较大,建议读者阅读《Secure Roaming in 802.11
Networks》一书后再去看它。此书是笔者目前阅读到的关于W i-Fi Roaming相关知识介绍
最完整的一本。
[7] 《Real 802.11 Security:W i-Fi Protected Access and 802.11i》第6章"How
IEEE 802.11 W EP W orks and W hy It Doesn’t"
说明:关于W EP的介绍。对安全感兴趣的读者请仔细阅读此书。
[8] http:/ / www.codealias.info/ technotes/ opportunistic_pmk_pre-caching
说明:关于Opportunistic PMK Caching的简单介绍。
[9] 《Secure Roaming in 802.11 Networks》第8章"Opportunistic Key
Caching"一节
说明:相比参考资料[8]而言,这一节对OKC有更为详尽的介绍。
3.wpa_supplicant结构体
[10] 《802.11无线网络权威指南(第2版)》第7章"802.11:RSN、TKIP与
CCMP",P171-P172
[11] 《802.11-2012》第11.4节"TKIP countermeasures procedures"
说明:上述两个参考资料介绍了TKIP countermeasures的处理方式。请先阅读参考资
料[10]。
[12]
http:/ / www.cisco.com/ en/ US/ docs/ solutions/ Enterprise/ Mobility/ vowlan/ 41dg/ vowlan_ch5.html
[13] 《Secure Roaming in 802.11 Networks》第5.2.5节"Background Scanning"
说明:关于Background Scan技术的介绍。
[14] http:/ / network.chinabyte.com/ 359/ 12453859.shtml
[15] http:/ / www.docin.com/ p-365323002.html
说明:和GAS以及802.11u相关的一些介绍。
4.wpa_supplicant_init_iface分析之三
[16] http:/ / www.mjmwired.net/ kernel/ Documentation/ rfkill.txt
[17] http:/ / lwn.net/ Articles/ 335382/
说明:这两篇资料介绍了rfkill相关的信息。感兴趣的读者不妨仔细阅读。
[18] http:/ / wenku.baidu.com/ view/ c74758d280eb6294dd886c53.html
说明:RFC2863 3.1.13节"IfAdminStatus and IfOperStatus"描述了IfOperStatus
的取值情况及相关说明。
[19]
http:/ / wireless.kernel.org/ en/ developers/ Documentation/ nl80211/ kerneldoc
说明:Linux W ireless Kernel官方网站中关于nl80211内核部分的一些解释。
5.EAP模块分析
[20] http:/ / tools.ietf.org/ pdf/ rfc4137.pdf
说明:RFC4137文档的PDF版。相比TXT版而言,它用图来描述状态机的状态切换。
6.EAPOL模块分析
[21] 802.1X 2004版
说明:W PAS中的802.1X实现是基于802.1X 2004版。相比2010版而言,笔者觉得
2004版的内容更具条理性,尤其是其关于EAPOL各状态机的描述非常清晰。
7.EAPOL-Key交换流程分析
[22] 《Real 802.11 Security:W i-Fi Protected Access and 802.11i》第10
章"W PA and RSN Key Hierarchy"
[23] 《802.11-2012》第11.6节"Keys and key distribution"
说明:这两篇参考资料对Pairwise Key、Group Key以及4-W ay Handshake、
Group Key Handshake都有详细的介绍。
[24] wireless.kernel.org/ en/ users/ Documentation/ W oW LAN
[25] msdn.microsoft.com/ en-
us/ library/ windows/ hardware/ ff571052(v=vs.85).aspx
说明:这两篇文章对W oW LAN有详细介绍。读者可简单阅读。