锐单电子商城 , 一站式电子元器件采购平台!
  • 电话:400-990-0325

深入理解wpa_supplicant

时间:2023-02-01 19:00:00 47frc连接器4x1电力变送器

第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.一种非常重要的理论知识方式。
本章分为两条路线进行分析wpa_supplicant相关功能模块。
·路线1:首先介绍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函数内

锐单商城拥有海量元器件数据手册IC替代型号,打造电子元器件IC百科大全!

相关文章