相信自己,只有不想做的,没有做不到的。
分类: Android平台
2014-09-15 23:26:07
第4章 深入理解wpa_supplicant
wpa_supplicant是一个开源软件项目,它实现了Station[1]对无线网络进行管理和控制的功能。根据官方[2]描述,wpa_supplicant所支持的功能非常多,此处列举其中几个重要的功能点。
支持WPA和IEEE 802.11i所定义的大部分功能:这部分功能集中在安全方面,包括:
支持多种EAP Method:主要和802.1X中Supplicant的功能有关,wpa_supplicant支持多达25种EAP Method,包括:
读者可阅读参考资料[1]以了解更多EAP方法的知识。
对各种无线网卡和驱动的支持:
提示:wpa_supplicant虽然支持Windows平台,但笔者相信绝大多数读者使用的都是Windows自带的无线网络管理程序(或者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的发布历史,请读者参考。
本书的分析目标是wpa_supplicant_8,它包含三个主要子目录,分别是:
wpa_supplicant是Android用户空间中无线网络部分的核心模块,所有Framework层中和Wi-Fi相关的操作最终都将借由wpa_supplicant来完成。另外,wpa_supplicant本身对802.11、802.1X和Wi-Fi Alliance定义的一些规范都有极好的支持。所以,分析它将是加深理解802.11及相关理论知识的一个非常重要的途径。
本章拟打算带领读者从两条分析路线来掌握wpa_supplicant和相关的功能模块。
提示:后续章节还将围绕Android中无线网络技术开展更多的讨论:
1)第5章将介绍Android Framework中的WifiService及其相关模块。
2)第6、7章节将继续wpa_supplicant之旅,其内容和WPS、Wi-Fi P2P以及WifiP2pService有关。
为了行文方便,本书将用WPAS来表示wpa_supplicant。另外,后文代码分析中还能见到一种重要的数据结构,它也叫wpa_supplicant。请读者根据上下文信息来理解wpa_supplicant的含义。
正式开始分析之旅前,我们先来简单了解下wpa_supplicant。
本节介绍WPAS一些外围知识,包括软件结构、编译配置、控制命令和对应控制API的用法。其中,控制命令的格式和API的用法将在后续介绍WifiService相关模块时会见到。另外,在研究WPAS时,能熟练掌握用git查询历史版本信息也非常关键。WPAS的故事首先从其软件架构开始。
wpa_supplicant是一个比较庞大的开源软件项目,包含500多个文件,20万行代码,其内部模块构成如图4-1所示[2]。
图4-1 wpa_supplicant软件架构
图4-1所示的WPAS软件架构包括如下重要模块:
WPAS支持众多功能,使用前往往需根据平台或驱动的特性进行编译配置,下面通过一个实例来介绍如何在Android中编译wpa_supplicant。
本实例的背景情况如下:
笔者有一台三星Galaxy Note2手机,其OS为Android 4.1.2。现在,笔者打算编译一个AOSP(Android Open Source Project)的wpa_supplicant程序以替换Note2中原有的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中的wifi设备为博通公司的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外,WPAS也定义了自己的编译配置文件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芯片不同,WPAS可能还依赖其他模块。所以,在具体实施时,BoardConfig.mk(或其他文件,视具体情况而定)也需要做修改。
由图4-1的介绍可知,WPAS对外通过控制接口模块与客户端通信。在Android平台中,WPAS的客户端是位于Framework中的WifiService。用户在Settings界面进行Wi-Fi相关的操作最终都会经由WifiService通过发送命令的方式转交给wpa_supplicant去执行。WPAS定义了许多命令,常见的:
除了接收来自Client的命令外,WPAS也会主动给Client发送命令。例如,WPAS需用户为某个无线网络输入密码。这类命令称之为Interactive Request,其格式如下。
提示:除了“CTRL-EVENT-XXX”之外,WPAS还支持形如“WPA:XXX”和“WPS-XXX”的通知事件。这些事件和WPA和WPS有关。下一章分析WifiService时我们还能见到它们。
图4-2所示为笔者利用wpa_cli测试status命令得到的结果。
图4-2 wpa_cli命令测试示意
图4-2所示为status命令的结果。图中最后几行显示WPAS向wpa_cli返回了两个CTRL-EVENT信息。
Android平台中WifiService是WPAS的客户端,它和WPAS交互时必须使用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控制对象
下面这个函数用于发送命令给WPAS。
//客户端发送命令给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是一个回调函数,该参数的设置和WPAS中C/S通信机制的设计有关:
从Client角度来看,它发送给WPAS的命令所对应的回复属于solicited event(意为有请求的事件),而前面所提到的CTRL-EVENT事件(用于通知事件)对应为unsolicited event(意为未请求的事件)。当Client在等待某个命令的回复时,WPAS同时可能有些通知事件要发送给客户端,这些通知事件不是该命令的回复,所以不能通过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对象来简化自己的逻辑处理:
提示:下一章分析WifiService时将见到这种创建两个wpa_ctrl对象的做法。
WPAS难度较大的一个重要原因是其注释较少,很多变量的含义没有任何解释。笔者也为此大伤脑筋。不得以,只能通过查看WPAS代码的历史版本来寻根溯源。经过实践,笔者总结了利用git来查询WPAS历史版本信息的一些步骤,分别如下。
用git clone命令下载WPAS官方代码。
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所示。
图4-4 git log结果
图4-4展示了a11241fa对应的commit消息。由于提交者一般会在该消息中添加注释性内容。所以可通过研究这些内容来了解代码中某些变量的含义。
下面正式开始WPAS的代码分析之旅。首先是WPAS的初始化流程分析。
[1]注意,wpa_supplicant项目中还包含一个名为hostapd程序的代码,它实现了AP的功能。本书不拟讨论hostpad的代码。
[2] wpa_supplicant项目的官方地址为
[3]根据审稿专家的反馈,wpa_supplicant仅支持Linux Wireless Extension V19以后的版本。
==========略略略略略略略略略略略略略略略===============
本节将介绍本章第二条分析路线,即通过命令行发送命令的方式触发wpa_supplicant进行相关工作,使手机加入一个利用WPA-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.1"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)开启无线网络后,WifiService和wpa_supplicant都开始工作了。为了避免WifiService的干扰,可以把Settings中的那些已知的无线网络信息都清除。
3)由于wpa_supplicant支持多个客户端,所以wpa_cli可以和WifiService共同工作。只要不操作Settings中无线网络相关的选项,WifiService就不会干扰wpa_cli。
4)然后按上述步骤执行wpa_cli。
根据前文所述,所有来自客户端的命令都由wpa_supplicant_ctrl_iface_receive函数处理(参考4.3.4中“wpa_supplicant_ctrl_iface_init介绍”一节)。该函数代码非常简单,就是根据客户端发送的命令进行对应处理。
[-->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_NETWORK、SET_NETWORK以及ENABLE_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_NETWORK的真正处理在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相关的变量,此处简单介绍一下:
“ADD_NETWORK”命令比较简单,它最终将返回给客户端对应的无线网络配置的编号。在本例中,它是0。
下面来看客户端通过“SET_NETWORK”为该无线网络配置项设置参数的处理过程。
============================略略略略略===================
本章对wpa_supplicant进行了一番剖析,涉及如下几个重点内容:
提示:本章编撰时,市面上搭载Android 4.2的手机较少。所以此处的分析目标是Android 4.1中的wpa_supplicant。不过笔者比较了它和Android 4.2中的wpa_supplicant。虽然4.2中的WPAS变化较大,但主要体现在一些新增功能和Bug修改上。读者如果搞清楚了本章所介绍的内容,再转去研究4.2中的WPAS所需要的学习成本不会太大。
[1]
维基百科关于EAP各种方法的一个简单介绍。
[2] wpa_supplicant/devel/
wpa_supplicant官方开发文档。读者可以简单浏览一下。
[3] 802.11-2012 附录M.4 “Suggested pass-phrase-to-PSK mapping”
该节介绍了passphrase转换成PSK的方法,甚至还有伪代码实现。感兴趣的读者不妨结合WPAS中的代码来研究它。
[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”一书后再去看它。提醒,此书是笔者目前阅读到的关于Wi-Fi Roaming相关知识介绍最完整的一本。
[7] Real 802.11 Security:Wi-Fi Protected Access and 802.11i 第6章“How IEEE 802.11 WEP Works and Why It Doesn't”
关于WEP介绍的章节。另外,对安全感兴趣的读者请仔细阅读此书。
[8]
关于Opportunistic PMK Caching的简单介绍。
[9] Secure Roaming in 802.11 Networks第8章“Opportunistic Key Caching”一节
相比[8]而言,这一节对OKC有更为详尽的介绍。
[10] 802.11无线网络权威指南(第二版)第七章“802.11:RSN、TKIP与CCMP”,P171-P172
[11] 802.11-2012 第11.4.2.4节“TKIP countermeasures procedures”
上述两个参考资料介绍了TKIP countermeasures的处理方式。请读者先阅读[10]。
[12]
[13] Secure Roaming in 802.11 Networks第5.2.5节“Background Scanning”
Background Scan技术的介绍。
[14]
[15]
和GAS以及802.11u相关的一些介绍。
[16] http://www.mjmwired.net/kernel/Documentation/rfkill.txt
[17]
这两篇资料介绍了rfkill相关的信息。感兴趣的读者不妨仔细阅读它们。
[18]
RFC2863 3.1.13“IfAdminStatus and IfOperStatus”一节描述了IfOperStatus的取值情况及相关说明。
[19] http://wireless.kernel.org/en/developers/Documentation/nl80211/kerneldoc
linux wireless kernel官方网站中nl80211内核部分的一些解释。
[20]
RFC4137文档的PDF版。相比TXT版而言,它用图来描述状态机的状态切换。
[21] 802.1X 2004版
WPAS中的802.1X实现是基于802.1X 2004版。相比2010版而言,笔者觉得2004版的内容更具条理性。尤其是其关于EAPOL各状态机的描述非常清晰。
[22] Real 802.11 Security:Wi-Fi Protected Access and 802.11i 第10章“WPA and RSN Key Hierarchy”
[23] 802.11-2012 第11.6“Keys and key distribution”
这两篇参考资料对Pairwise Key和Group Key以及4-Way Handshake、Group Key Handshake都有详细的介绍。
[24] wireless.kernel.org/en/users/Documentation/WoWLAN
[25] msdn.microsoft.com/en-us/library/windows/hardware/ff571052(v=vs.85).aspx
这两篇文章对WoWLAN有一番介绍。读者可简单阅读它们。