资讯详情

深入理解wpa_supplicant

第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(&params, 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(&params); … 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函数内

标签: 47frc连接器4x1电力变送器

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

锐单商城 - 一站式电子元器件采购平台