STA 连接(二)Supplicant 层 L2 连接全链路
在上一篇中,我们站在前台视角,看了 Framework 的
ClientModeImpl状态机如何收到CMD_START_CONNECT,然后调用WifiNative.connectToNetwork()发起连接。现在命令已经到了后场——本文从 Supplicant 进程的 AIDL 入口开始,追踪添加网络配置、推送参数、选择网络、SME 认证 / 关联决策、驱动命令下发,一直到NL80211_CMD_AUTHENTICATE和NL80211_CMD_ASSOCIATE下发,并贯穿 EAPOL 四次握手到WPA_COMPLETED全过程。这是 Supplicant 层 L2 连接的完整全链路。
前台(Framework)把客人的入住申请表递给了后场的制卡机(Supplicant)。制卡机需要做几件事:把客人的信息录入系统(
addNetwork+ 逐个 setter 推送配置),在系统里选中这条记录触发制卡流程(select),通过安保系统验证客人身份(Auth),签入住单(Assoc),最后用对讲机(nl80211)把指令编码成门禁系统能理解的格式下发到客房控制器(驱动)。本篇就跟着这张房卡,走完从录入到下发对讲机指令的完整旅程。
本文聚焦 Supplicant 内部逻辑和 nl80211 命令下发——驱动收到命令后的固件交互和帧交换在后续驱动层连接执行篇展开,EAPOL 四次握手中 PTK 派生的密码学细节在后续安全协议篇中深入。
全文约 25 分钟读完。如果只关心整体流程,可以跳过代码块,只看每节末尾的「主要功能」小结和 §7 的调用链全景图。
¶ 本章导读
整个 Supplicant 就像酒店后场的制卡机——前台递来入住申请表,制卡机录入客人信息、验证身份、签入住单、发房卡,全程自动化。
本文沿链路逐段展开:
ISupplicantStaIface.addNetwork()如何创建网络配置,以及wpa_supplicant_add_network()内部做了什么WifiConfiguration的 SSID、BSSID、key_mgmt、PSK、EAP 等字段如何通过 AIDL setter 逐个推送到 supplicant 的wpa_ssid结构体networkHandle.select()如何触发wpa_supplicant_select_network(),进而启动扫描或快速关联- SME(Station Management Entity)的完整概念:supplicant 构造 auth/assoc 帧 vs 驱动固件自己处理
sme_send_authentication()的 auth_alg 决策枢纽——OPEN / SAE / FT / FILS 四条分叉的完整逻辑- SME 认证流程:从
sme_auth_start_cb()构造认证帧,到driver_nl80211下发NL80211_CMD_AUTHENTICATE - Auth 响应处理:驱动回调 →
wpa_supplicant_event()→sme_event_auth()→sme_associate()→ 下发NL80211_CMD_ASSOCIATE - Assoc 完成后 EAPOL 四次握手的 supplicant 侧处理:
eapol_sm_step()驱动 EAPOL 状态机,wpa_sm_rx_eapol()分发 msg 1/4 → msg 2/4 → msg 3/4 → msg 4/4 - SME 模式(两步走
CMD_AUTHENTICATE+CMD_ASSOCIATE)vs 非 SME 模式(一步走CMD_CONNECT)的本质区别
本文所有代码块取自 wpa_supplicant 真实源码,为可读性做了精简(去掉 log 语句、license 头、条件编译分支),关键路径保留完整;精简处标注 // ...省略...,文件路径标注在代码块首行。
¶1 制卡机收到指令——AIDL addNetwork 和参数翻译
Framework 通过 AIDL 跨进程调用 ISupplicantStaIface.addNetwork(),wpa_supplicant 创建一个空的 wpa_ssid 网络配置块,然后 Framework 逐个调用 AIDL setter(setSsid、setPskPassphrase、setKeyMgmt 等)把 WifiConfiguration 的字段推入这个配置块,最后 select() 激活连接。
在前一篇中我们看到 Framework 的 SupplicantStaIfaceHal.connectToNetwork() 分三步走:removeAllNetworks(清旧)→ addNetworkAndSaveConfig(建新)→ networkHandle.select()(激活)。其中 addNetworkAndSaveConfig 是整个命令传递中最复杂的环节——需要把 Java 的 WifiConfiguration 对象翻译成 wpa_supplicant 的 C 结构体。
¶1.1 addNetwork 的 AIDL 路径——从 Java 到 C 结构体
Framework 侧的 addNetwork(ifaceName) 通过 AIDL 调用 ISupplicantStaIface.addNetwork()。这条调用跨越进程边界,到达 wpa_supplicant 进程中的 C++ AIDL 服务端:
1 | // wpa_supplicant/aidl/vendor/sta_iface.cpp(源码有部分精简) |
主要功能:
addNetwork()是 AIDL binder 方法的入口,调用validateAndCall()转到addNetworkInternal()执行实际逻辑——这是 AIDL 层的标准模式wpa_supplicant_add_network(wpa_s)是核心调用——它创建一个新的struct wpa_ssid并挂到配置链表中getStaNetworkAidlObjectByIfnameAndNetworkId()把 C 结构体包装成 AIDL binder 对象,后续 Framework 通过这个对象调用 setter 配置网络参数aidl_manager是 AIDL 服务端的全局管理器单例,负责维护ifname + network_id → ISupplicantStaNetworkbinder 对象的映射,避免重复创建
在 wpa_supplicant 核心层,wpa_supplicant_add_network() 的实现非常简洁:
1 | // wpa_supplicant/wpa_supplicant.c(源码有部分精简) |
主要功能:
wpa_config_add_network()在wpa_s->conf->ssid链表末尾分配并追加一个新的wpa_ssid节点- 关键设计:新建网络
disabled = 1——制卡机先录入信息但不激活,等所有字段填完再select()启用 wpa_config_set_network_defaults()填充默认参数(proto=RSN、pairwise=CCMP、group=CCMP 等)
addNetwork 就像制卡机拿出一张空白房卡——卡上什么都没有,disabled=1 意味着这张卡还不能用。接下来要通过 AIDL setter 把客人的信息一项一项写进去。
¶1.2 参数逐个推送——WifiConfiguration 到 wpa_ssid 的翻译引擎
Framework 拿到 addNetwork 返回的 SupplicantStaNetworkHalAidlImpl 对象后,调用 network.saveWifiConfiguration(config)——这个函数把 WifiConfiguration 的每一个字段通过 AIDL setter 逐个推送到 wpa_supplicant。
在 wpa_supplicant 侧,每个 setter 由 sta_network.cpp 中的 handler 接收。以下是关键字段的翻译逻辑:
SSID 推送:
1 | // wpa_supplicant/aidl/vendor/sta_network.cpp(源码有部分精简) |
主要功能:
- SSID 以原始字节数组传递——不做引号包裹或十六进制转换
- 长度上限:802.11 规定 SSID 最长 32 字节(
SSID_MAX_LEN) - 关键联动:如果 passphrase 已经先设置了,SSID 变更后必须重新调用
wpa_config_update_psk()计算 PSK——因为 PSK = PBKDF2 (passphrase, SSID, 4096, 256)
PSK / 密码推送:
1 | // wpa_supplicant/aidl/vendor/sta_network.cpp(源码有部分精简) |
主要功能:
setPskPassphrase()接收 ASCII 密码短语(8-63 字符),存入wpa_ssid->passphrase;如果 SSID 已经设置,立即调用wpa_config_update_psk()计算 32 字节 PSKsetPsk()接收原始 256 位 PSK(64 字符 hex → 32 字节二进制),直接存入wpa_ssid->psk[],并清除 passphrase(二者互斥)psk_set = 1标记表示 PSK 已直接设置,supplicant 后续不会尝试从 passphrase 重新派生
key_mgmt 映射——安全协议选择:
1 | // wpa_supplicant/aidl/vendor/sta_network.cpp(源码有部分精简) |
主要功能:
- AIDL 的
KeyMgmtMask是位掩码——对应 Framework 的WifiConfiguration.KeyMgmt - 自动启用 FT 变体:如果勾选了 WPA_PSK,自动添加 FT_PSK;如果勾选了 SAE,自动添加 FT_SAE——这是 supplicant 对 802.11r 快速漫游的默认行为
key_mgmt会被后续sme_send_authentication()中的 auth_alg 决策枢纽使用——它决定了走 OPEN 认证还是 SAE 认证
BSSID / EAP 等其他字段:
| AIDL Setter | 实际写入的 wpa_ssid 字段 | 说明 |
|---|---|---|
setBssid() | wpa_ssid->bssid[], bssid_set | 零 BSSID 表示 "匹配任意 AP" |
setProto() | wpa_ssid->proto | WPA/RSN/WAPI/OSEN 协议版本 |
setGroupCipher() | wpa_ssid->group_cipher | 组播加密套件(CCMP/TKIP/GCMP) |
setPairwiseCipher() | wpa_ssid->pairwise_cipher | 单播加密套件 |
setRequirePmf() | wpa_ssid->ieee80211w | 管理帧保护(PMF)开关 |
setEapMethod() | wpa_ssid->eap.eap_methods[] | EAP 方法(TLS/TTLS/PEAP/SIM/AKA) |
setEapIdentity() | wpa_ssid->eap.identity | EAP 身份标识 |
setEapPhase2Method() | wpa_ssid->eap.phase2 | 第二阶段 EAP 方法(如 auth=MSCHAPV2) |
每个 setter 最后都调用 resetInternalStateAfterParamsUpdate()。这个函数做了什么?
1 | // wpa_supplicant/aidl/vendor/sta_network.cpp(源码有部分精简) |
主要功能:
- 参数变更触发状态重置:如果修改的是当前连接的网络,之前建立的 PMKSA 缓存和 EAP 会话立即失效——因为证书 / 密码变了,旧的安全上下文不再有效
- 这就是为什么 Framework 修改 WifiConfiguration 后需要 save+reconnect——任何关键参数的修改都会触发安全上下文的清理
saveWifiConfiguration() 就像前台把申请表上的每栏信息(姓名、身份证号、房型)逐格念给制卡机录入。SSID 是姓名,PSK 是身份证号,key_mgmt 是验证方式。每个 AIDL setter 就是制卡机收到一条 "姓名栏 = 张三" 的指令并写入对应字段。
¶1.3 select ()——按下制卡按钮
所有字段写完后,Framework 调用 networkHandle.select() 激活这张卡:
1 | // SupplicantStaNetworkHalAidlImpl.java(源码有部分精简) |
在 wpa_supplicant 侧:
1 | // wpa_supplicant/aidl/vendor/sta_network.cpp(源码有部分精简) |
主要功能:
scan_min_time清零——用户主动点连接,不应受扫描间隔限制。这解释了为什么手动连接比自动重连响应更快wpa_supplicant_select_network()是连接的总调度入口
进入 wpa_supplicant_select_network():
1 | // wpa_supplicant/wpa_supplicant.c(源码有部分精简) |
主要功能:
- 这是
select()之后 supplicant 侧的总调度函数——它决定 "先扫描还是直接连" wpa_supplicant_fast_associate()尝试在 BSS 缓存中找到匹配的目标 AP——如果命中(比如刚扫过,BSS 还没过期),直接走wpa_supplicant_associate(),不命中则先发一次扫描- 断开重连时会加 100ms 延迟(
disconnected ? 100000 : 0),防止 deauth 帧还没发完就连回去 select()在 Framework 的WifiManager.enableNetwork()和WifiManager.reconnect()调用路径中都会触发,但区别在于:enableNetwork()仅启用网络(disableNetwork()的反操作),不强制触发连接;reconnect()则在启用后立即调用reconnectCommand()主动发起连接。在 supplicant 内部,两者最终都走到wpa_supplicant_select_network()——select总是激活连接。
¶1.4 扫描结果如何选出目标网络——pick_network 与评分
wpa_supplicant_select_network() 触发扫描后,扫描完成事件(EVENT_SCAN_RESULTS)在 events.c 中被处理,最终调用 wpa_supplicant_pick_network() 从扫描结果中挑出要关联的 BSS。这个函数是 "连哪个 AP" 的决策中枢:
1 | // wpa_supplicant/events.c(源码有部分精简) |
主要功能:
- 优先级模型:
wpa_s->conf->pssid[]是 "按 priority 分组" 的网络列表(config.h的pssid/num_prio字段),priority越高的网络越先被尝试。用户在WifiConfiguration里设置的priority字段最终体现在这个排序里 - 二次机会:遍历完所有优先级都没找到可用 BSS 时,supplicant 清空 BSSID 黑名单(
wpa_bssid_ignore_clear())再试一次——这是为了从 "之前连接失败的 AP" 里恢复,而不是永久放弃 - 对每个优先级组,
wpa_supplicant_select_bss()(events.c:1741)遍历该组内所有网络配置,逐个调用wpa_scan_res_match()(events.c:1634)做 BSS 级匹配
wpa_scan_res_match() 负责 "单个 BSS 是否匹配当前网络配置"——检查 SSID 是否一致、BSSID 是否被指定或被拉黑、频段是否被禁用、安全能力是否匹配。通过它的过滤后,多个候选 BSS 之间 "谁更好" 由 wpa_scan_result_compar()(scan.c:2362)决定。这是一个多级比较器,按优先级从高到低依次比较:
1 | // wpa_supplicant/scan.c(源码有部分精简,比较器核心判据) |
主要功能:
- 多级判据:安全能力(WPA/RSN IE)→ Privacy → SNR(含信道宽度校正)→ 预估吞吐 → 频段 → 兜底 SNR/qual。逻辑是 "先保证能连上安全网络,再从信号好的里挑最优"
- SAE 优先:WPA3 过渡模式下同一 AP 同时广播 PSK 与 SAE AKM,若 SAE BSS 信号不差于 PSK BSS,supplicant 优先选 SAE——因为 WPA3 更安全
- 信道宽度校正:
wpas_adjust_snr_by_chanwidth()(scan.c:2343)把 SNR 按信道宽度归一化——扫描探测帧通常只在 20MHz 上发,但数据帧用更宽信道,SNR 会不同 - 6GHz 特殊处理:6GHz 频段受 LPI/VLP 功率限制,SNR 可能偏低但实际吞吐更高,所以 SNR 接近时直接比预估吞吐,而非继续比 SNR
选出 BSS 后,调用链回到 wpa_supplicant_associate()(§2.3 详述),进入认证 / 关联阶段。至此 select → 扫描 → pick_network → associate 的完整闭环补齐了。
¶2 制卡机运作机制——走进 SME
SME(Station Management Entity)是 802.11 协议中负责管理 STA 认证和关联状态机的逻辑实体。在 wpa_supplicant 中,SME 模式意味着 supplicant 自己构造 Auth/Assoc 帧并通过两步命令(CMD_AUTHENTICATE → CMD_ASSOCIATE)下发给驱动;非 SME 模式则将 Auth+Assoc 合并为一个 CMD_CONNECT 命令,让驱动 / 固件自动完成帧交换。
¶2.1 什么是 SME
SME 是 802.11 协议栈中的一个概念(IEEE 802.11-2024 第 3 章定义的术语),全称 Station Management Entity。它是 MAC 层的管理大脑,负责:
- 认证状态机:管理 802.11 认证流程(Open System、Shared Key、SAE、FT、FILS)
- 关联状态机:管理 Association/Reassociation 流程
- 安全策略决策:根据配置选择认证算法和加密套件
- 漫游决策:判断是否需要切换 AP
在 Android WiFi 架构中,SME 可以在两个地方实现:
| 实现位置 | 技术术语 | 特点 |
|---|---|---|
| wpa_supplicant 进程 | supplicant SME | supplicant 构造认证帧、控制每步交互,通过 CMD_AUTHENTICATE + CMD_ASSOCIATE 两步下发 |
| 驱动 / 固件 | driver-based SME | 驱动内部处理认证和关联,supplicant 只发一个 CMD_CONNECT,驱动自动完成 Auth+Assoc+4-way handshake |
wpa_supplicant 通过一个标志位判断使用哪种模式。这个标志位在 wpa_supplicant_associate() 的分发点被检查:
1 | // wpa_supplicant/wpa_supplicant.c(源码有部分精简) |
主要功能:
WPA_DRIVER_FLAGS_SME(值0x00000020)定义在src/drivers/driver.h,注释为 "Driver provides separate commands for authentication and association (SME in wpa_supplicant)"- 这个标志由 nl80211 能力探测阶段(
driver_nl80211_capa.c)根据驱动是否在NL80211_ATTR_SUPPORTED_COMMANDS中注册了NL80211_CMD_AUTHENTICATE自动设置 - 注意标志位的语义是反直觉的:
WPA_DRIVER_FLAGS_SME设了意味着 "驱动提供分开的认证 / 关联命令",等于告诉 supplicant "你可以用你自己的 SME 了"
¶2.2 两种模式的完整对比
| 维度 | Supplicant SME 模式 | 非 SME 模式(Driver SME) |
|---|---|---|
| 下发命令 | NL80211_CMD_AUTHENTICATE → NL80211_CMD_ASSOCIATE(两步) | NL80211_CMD_CONNECT(一步) |
| Auth 帧构造 | supplicant(sme_send_authentication) | 驱动 / 固件 |
| Assoc 帧构造 | supplicant(sme_associate) | 驱动 / 固件 |
| 认证帧交互 | supplicant 控制每帧(SAE 4 帧、FT 2 帧) | 驱动自动(Open 2 帧) |
| 适用认证 | SAE、FT、FILS、OWE | Open、WPA2-PSK、WPA2-EAP |
| 失败重试 | supplicant 决定重试策略 | 驱动决定 |
| PMKSA 缓存控制 | supplicant 完全控制(降级、刷新) | 驱动控制 |
| Supplicant 状态机 | 完整 SME 状态机(sme.c) | 仅 WPA/EAPOL 状态机,无 SME 认证状态机 |
SME 模式就像 VIP 客人的分步验证——先去贵宾室确认身份(CMD_AUTHENTICATE),再回来签入住单(CMD_ASSOCIATE),每步都由制卡机亲自把关。非 SME 模式就像普通客人的一步入住——前台一次性核验身份和房型,制卡机只需要给一个 "连接" 指令,剩下的由门禁系统(驱动)自动完成。
¶2.3 wpa_supplicant_associate ()——连接的总入口
在追踪 SME 和非 SME 两条路径之前,我们先看看它们共同的前置准备。wpa_supplicant_associate() 是所有连接的入口函数,它执行三轮准备工作后才进行模式分发:
1 | // wpa_supplicant/wpa_supplicant.c(源码有部分精简) |
这三轮准备的价值:
- 状态清理:
own_disconnect_req = 0很重要——supplicant 用这个字段区分 "自己主动断开" 和 "被 AP 踢掉"。如果不清零,后续收到 AP 的 deauth 时可能被误判为 "是我们自己要断的" - 重关联检测:同一 ESS 走 Reassociation(更快),同一 BSS 保留 SAE 拒绝列表(避免重复尝试已拉黑的 AP)
- MAC 随机化:必须在 Auth 帧发出前完成——一旦发出,MAC 就固定了
这三轮准备完成后,supplicant 进入认证 / 关联流程。整个连接过程在 supplicant 顶层由一套 enum wpa_states 状态机(src/common/defs.h:248)驱动,wpa_supplicant_set_state() 负责迁移状态。本文追踪的链路恰好完整穿越它的主干:
| 状态 | 进入时机 | 触发函数 |
|---|---|---|
WPA_DISCONNECTED | 初始 / 断开 | wpa_supplicant_disassociate() 等 |
WPA_SCANNING | 触发扫描 | wpa_supplicant_req_scan() |
WPA_AUTHENTICATING | 下发认证命令 | wpa_drv_authenticate()(§3.4) |
WPA_ASSOCIATING | 下发关联命令 | wpa_drv_associate()(§4.2) |
WPA_ASSOCIATED | 关联成功 | wpas_notify_state_changed() |
WPA_4WAY_HANDSHAKE | 收到 msg 1/4 | wpa_sm_set_state()(§5.3) |
WPA_GROUP_HANDSHAKE | 收到 msg 3/4 后 | wpa_sm_set_state() |
WPA_COMPLETED | 四次握手完成 | wpa_supplicant_key_neg_complete() |
理解这个状态机是读后续章节的钥匙:SME 认证 / 关联阶段(§3、§4)对应 WPA_AUTHENTICATING 与 WPA_ASSOCIATING 两态,四次握手(§5)对应 WPA_4WAY_HANDSHAKE → WPA_GROUP_HANDSHAKE → WPA_COMPLETED 三态。SME 侧还维护一份私有上下文 struct wpa_supplicant 的 sme 字段(wpa_supplicant_i.h:1015),记录 auth_alg、预构建的 assoc_req_ie、prev_bssid、assoc_auth_type 等——这些字段在认证阶段写入、关联阶段复读,是两步走(auth→assoc)能衔接起来的关键。
如果把 enum wpa_states 看作制卡机内部的工序流转单,sme 结构体就是贴在流转单背面的便签:身份验证这道工序写下的 auth_alg 和 assoc_req_ie,签入住单这道工序直接照着用,省得重新核算一遍客人的认证方式。
¶3 身份验证——SME 认证流程
SME 认证从 sme_authenticate() 开始,经过 radio work 调度进入 sme_send_authentication(),这里根据 key_mgmt 选择 auth_alg(OPEN/SAE/FT/FILS),构造认证帧后通过 wpa_drv_authenticate() 下发 NL80211_CMD_AUTHENTICATE。收到 Auth 响应后,sme_event_auth() 处理结果并触发 sme_associate()。
¶3.1 sme_authenticate ()——SME 模式的启动器
1 | // wpa_supplicant/sme.c(源码有部分精简) |
主要功能:
cwork->sme = 1标记让后续的wpas_connect_work_done()等知道这是 SME 路径的工作- radio work type 使用
"sme-connect"(区别于非 SME 的"connect")——断开连接时radio_remove_works(wpa_s, "sme-connect", 0)精准清理 - SAE 状态重置为
SAE_NOTHING——每次连接都是全新的认证尝试
¶3.2 sme_auth_start_cb ()——收到射频使用权后开始认证
当 radio work 被调度执行时(射频空闲了),回调 sme_auth_start_cb() 被调用:
1 | // wpa_supplicant/sme.c(源码有部分精简) |
主要功能:
- BSS 有效性二次验证很重要——排队期间(等待射频空闲),目标 BSS 可能已被移除或过期
start=1表示这是首次认证尝试(区别于 SAE 内部的重发、FT over-the-air 重试等)
¶3.3 sme_send_authentication ()——auth_alg 决策枢纽
这是 SME 认证的核心函数(约 680 行),其中的 auth_alg 决策链是理解 WiFi 安全协议路由的关键:
1 | // wpa_supplicant/sme.c(源码有部分精简,auth_alg 决策核心路径) |
auth_alg 决策流程图解:
1 | key_mgmt 字段 |
主要功能:
- 决策优先级:LEAP → 用户指定(初始值,后续 SAE/FT 检测可覆盖)→ SAE → FT → SAE PMKSA 降级 → FILS → Open
- 注意:用户手动指定的
auth_alg并不是最终决定——代码在此处只是一个普通 if 块,之后会 fall through 到 SAE 和 FT 检测,它们仍可能覆盖params.auth_alg - SAE PMKSA caching 是 WPA3 的重要优化:如果之前已经完整做过 SAE,PMK 还在缓存中,可以直接降级为 Open auth 跳过 Dragonfly 握手,节省约 200ms
- 每条分叉都受编译宏控制(
CONFIG_SAE、CONFIG_IEEE80211R、CONFIG_FILS),确保没有无用代码
¶3.4 执行认证——下发 NL80211_CMD_AUTHENTICATE
wpa_drv_authenticate() 是一个薄包装:
1 | // wpa_supplicant/driver_i.h(源码有部分精简) |
通过函数指针调用到 nl80211 驱动的 wpa_driver_nl80211_authenticate():
1 | // src/drivers/driver_nl80211.c(源码有部分精简) |
主要功能:
NL80211_CMD_AUTHENTICATE的 netlink 消息携带 BSSID、频率、SSID、IE(认证帧体)、auth_type 五个核心属性- SAE 模式下,
NL80211_ATTR_SAE_DATA承载 Commit/Confirm 的认证数据(不是 IE) - 两个重要的恢复机制:
-ENOENT:内核 cfg80211 的 BSS 缓存过期了。supplicant 先调用wpa_driver_nl80211_scan()发起单信道扫描(含 SSID 过滤);扫描成功后调用nl80211_copy_auth_params()把认证参数缓存起来,并置drv->scan_for_auth = 1标志(scan_for_auth是 driver struct 的 bitfield 标志位,不是函数)。等扫描完成事件回调到达后,再自动重新发起认证-EALREADY/-EEXIST:mac80211 不允许已认证状态下再次认证 → 调用wpa_driver_nl80211_deauthenticate(bss, params->bssid, WLAN_REASON_PREV_AUTH_NOT_VALID)强制断开,然后goto retry重新发送认证命令
wpa_driver_nl80211_authenticate() 就像制卡机用对讲机(nl80211)对门禁系统说:"客人编号 xxx(BSSID),在 5 号楼(freq),姓名是 xxx(SSID),用身份证(auth_type=OPEN)验证一下"。如果门禁回 "我不认识这个客人"(ENOENT),制卡机立一个 "需要扫描确认" 的标记(scan_for_auth = 1),等扫描完成后通过事件回调自动重试。如果门禁回 "这个人已经验证过了"(EALREADY / EEXIST),制卡机先发一条 "请作废" 命令(deauthenticate),再重新验证。
¶3.5 认证响应处理——sme_event_auth ()
驱动完成认证帧交换后,通过 EVENT_AUTH 上报结果。在 wpa_supplicant_event() 事件分发器中(events.c):
1 | case EVENT_AUTH: |
sme_event_auth() 是认证响应的总处理入口:
1 | // wpa_supplicant/sme.c(源码有部分精简) |
主要功能:
- 三层防御检查:第一层检查当前网络有效性 + 认证状态;第二层检查 peer MAC 与
pending_bssid一致性(防止响应来自非预期的 AP);第三层通过 SAE/FT/FILS 分叉进行认证类型匹配 - SAE 的
sme_sae_auth()返回 3 种值:-1(失败)、0(仍在交互中,如 Commit 发完等 Confirm)、1(完成)——这是 SAE 多帧交互的特殊处理 - SAE 完成后(res==1),调用
sme_sae_set_pmk(wpa_s, addr)把 SAE 协商出的 PMK 交给 WPA 状态机。 - 这是 SAE 协议到四次握手的衔接点:没有这一步,后续
process_1_of_4()中wpa_supplicant_get_pmk()找不到 PMK,四次握手会失败。 - peer MAC 校验同时兼容 MLO:MLO(Multi-Link Operation)场景下使用
ap_mld_addr替代pending_bssid进行比对 - auth_alg 降级回退:如果 AP 回复 "不支持的认证算法"(status code 13),supplicant 自动尝试下一个算法(Open → Shared Key → LEAP)
- 认证成功后调用
sme_associate()进入关联阶段
¶4 签入住单——关联流程
认证成功后,sme_associate() 使用认证阶段预构建的 assoc_req_ie,填充 wpa_driver_associate_params,通过 wpa_drv_associate() 下发 NL80211_CMD_ASSOCIATE。
¶4.1 sme_associate ()——构造关联请求
1 | // wpa_supplicant/sme.c(源码有部分精简) |
主要功能:
- 参数复用:
auth_alg已在前序sme_send_authentication()阶段确定并保存到wpa_s->sme.auth_alg,此处不重复设置;assoc_req_ie同样在认证阶段预构建(RSNE、HT/VHT/HE Capabilities 等),sme_associate()直接复用——因为在同一认证周期内 AP 的能力不会改变 - 不同的 auth_type 有不同的 IE 构建逻辑:FILS 需要内嵌 FILS nonce、OWE 需要 DH 参数
wpa_drv_associate()下发关联命令
¶4.2 下发 NL80211_CMD_ASSOCIATE
wpa_drv_associate() 调用 wpa_driver_nl80211_associate()。在 SME 模式下,这个函数不是走 CMD_CONNECT,而是直接构建 CMD_ASSOCIATE:
1 | // src/drivers/driver_nl80211.c(源码有部分精简) |
nl80211_connect_common() 是 CMD_CONNECT 和 CMD_ASSOCIATE 共享的参数填充函数,它负责把以下参数编码为 Netlink 属性:
| NL80211 属性 | 来源 | 说明 |
|---|---|---|
NL80211_ATTR_MAC | params->bssid | 目标 AP 的 MAC 地址 |
NL80211_ATTR_WIPHY_FREQ | params->freq.freq | 目标信道频率(MHz) |
NL80211_ATTR_SSID | params->ssid | 目标网络 SSID |
NL80211_ATTR_IE | params->wpa_ie | WPA/RSN IE(整个 Assoc Req 的核心) |
NL80211_ATTR_WPA_VERSIONS | params->wpa_proto | WPA 协议版本 |
NL80211_ATTR_CIPHER_SUITES_PAIRWISE | params->pairwise_suite | 单播加密套件 |
NL80211_ATTR_CIPHER_SUITE_GROUP | params->group_suite | 组播加密套件 |
NL80211_ATTR_AKM_SUITES | params->key_mgmt_suite | AKM 套件列表 |
NL80211_ATTR_PREV_BSSID | params->prev_bssid | 前一个 AP 的 BSSID(Reassociation 场景) |
表格里的每一行,在 nl80211_connect_common() 中都对应一段 nla_put 编码逻辑。核心三条(WPA 版本、加密套件、AKM)是这样落地的:
1 | // src/drivers/driver_nl80211.c(nl80211_connect_common 核心 nla_put 序列,有精简) |
主要功能:
- 每个属性都用
nla_put_u32()/nla_put()写入,返回值非 0 就return -1终止——netlink 消息一旦塞坏一个属性,整个命令直接作废,宁可失败也不下发半个残缺命令 wpa_cipher_to_cipher_suite()把 supplicant 内部的WPA_CIPHER_*枚举翻译成 IEEE 802.11 定义的 OUI 套件值(如 CCMP →0x000FAC04),内核按 OUI 匹配,不认 supplicant 的私有枚举- AKM 套件不是单个值而是一张表:
key_mgmt_suite映射为主套件,allowed_key_mgmts里其余的允许 AKM 依次追加——这对应 WPA3 过渡模式下同一个 AP 同时支持 SAE 和 PSK 的场景
nl80211_connect_common() 就像制卡机把签好的入住单翻译成对讲机通话:WPA 版本是「房型标准」、加密套件是「门锁型号」、AKM 套件是「验证方式」,每一条都按门禁系统约定的固定频道(Netlink 属性)报出去,缺一条门禁就拒收。
¶4.3 关联响应处理
关联结果同样通过事件机制上报:
成功——EVENT_ASSOC → wpa_supplicant_event() → wpas_notify_state_changed() → 设置 wpa_state = WPA_ASSOCIATED → 调用 wpa_sm_notify_assoc() 进入四次握手阶段。
拒绝——EVENT_ASSOC_REJECT → sme_event_assoc_reject()。这个函数处理三种特殊场景:
| Status Code | 处理方式 | 场景 |
|---|---|---|
WLAN_STATUS_ASSOC_REJECTED_TEMPORARILY | 解析 Timeout Interval IE,设置 comeback 定时器到期后重试 | AP 忙,稍后再来(如 6GHz AP) |
| SAE PMKSA 缓存被拒 | 丢弃 PMKSA 缓存条目,deauth 后用完整 SAE 重新认证 | PMK 过期 / 不匹配 |
| DPP PMKID 无效 | 丢弃 PMKSA 缓存,用 network introduction 协议重新协商 | DPP 连接中继 |
超时——EVENT_ASSOC_TIMED_OUT → sme_event_assoc_timed_out() → 调用 wpas_connection_failed(),标记断开。
sme_event_assoc_reject 的核心处理逻辑(AOSP 源码):
1 | // wpa_supplicant/sme.c(源码有部分精简) |
主要功能:
- comeback 机制是 6GHz AP 的关键特性:AP 忙时不是直接拒绝,而是给 STA 一个 comeback 时间窗口——定时器到期后自动重试,避免了无效的重连循环
- SAE PMKSA 缓存被拒的恢复路径:丢弃旧 PMK → deauth → 调用
wpas_connect_work_done()清理 radio work → 调用wpa_supplicant_mark_disassoc()清理关联状态 → 用完整 SAE 重新连接——这与 §3.3 中 SAE PMKSA caching 的降级逻辑形成呼应(先尝试快速路径,失败则回退到完整 SAE)。代码块中省略了wpas_connect_work_done()和wpa_supplicant_mark_disassoc()两个清理调用(源码有但精简),它们确保重连时 supplicant 处于干净的初始状态 - 兜底的
sme_deauth()确保 mac80211 不会残留未完成的认证状态,这是长期工程实践的产物(源码注释:"In theory, this should not be needed, but mac80211 gets quite confused if the authentication is left pending")
超时的定时器机制(与上面的 reject 路径互补):认证和关联各自挂一个 5 秒定时器,到点仍未收到响应就主动清理。定时器在命令下发时注册(§3.3 的 sme_send_authentication() 和 §4.1 的 sme_associate() 里各有一句 eloop_register_timeout(...)),超时处理函数如下:
1 | // wpa_supplicant/sme.c(源码有部分精简) |
主要功能:
- 定时器是 "状态守护" 而非 "全局兜底":
sme_auth_timer/sme_assoc_timer只在wpa_state仍停在WPA_AUTHENTICATING/WPA_ASSOCIATING时才动作——如果认证已经完成进入下一状态,定时器即使触发也什么都不做 - 驱动侧超时事件走另一条路:
EVENT_AUTH_TIMED_OUT→sme_event_auth_timed_out()(sme.c:2971)、EVENT_ASSOC_TIMED_OUT→sme_event_assoc_timed_out()(sme.c:2980),两者都调用wpas_connection_failed()标记失败并wpa_supplicant_mark_disassoc()清理关联状态 sme_state_changed()(sme.c)在每次状态迁移时被调用,负责在离开WPA_AUTHENTICATING/WPA_ASSOCIATING时取消对应定时器——保证定时器不会误杀已完成的连接
至此认证/关联阶段的失败处理闭环完整了:AP 拒绝(reject,含 comeback/PMKSA 回退)→ 驱动报错(§3.4 的 -ENOENT/-EALREADY)→ 超时(5s 定时器 + 驱动超时事件),三条路最终都收敛到 wpas_connection_failed() 或 sme_deauth(),把 supplicant 拉回干净状态等待重试。
¶5 发房卡——EAPOL 四次握手
关联成功后,supplicant 通过 wpa_sm_notify_assoc() 清理旧 PTK 并准备新握手,然后等待 AP 发送的 EAPOL-Key msg 1/4。收到后 wpa_sm_rx_eapol() 分发到对应处理函数:msg 1/4 → process_1_of_4() 计算 PTK 并回复 msg 2/4 → msg 3/4 → process_3_of_4() 验证 MIC、安装 PTK/GTK 并回复 msg 4/4 → 握手完成。
¶5.1 握手触发——wpa_sm_notify_assoc ()
关联成功后,events.c 调用 wpa_sm_notify_assoc():
1 | // src/rsn_supp/wpa.c(源码有部分精简) |
主要功能:
renew_snonce = 1强制下次握手使用全新的 SNonce(Supplicant Nonce)——保证每次连接的 PTK 都是全新的- 清除旧 PTK 是 802.11 规范要求(§8.4.10):每次 (re) association 后必须删除 PTK SA,除非是 FT 场景
- FT 模式特殊处理:
eapol_sm_notify_portValid(false)踢 EAPOL 重新进入 AUTHENTICATED 状态;wpa_ft_prepare_auth_request(sm, NULL)准备下一次漫游的 FT Auth Request(预构建 FT IE 以加速后续漫游);clear_keys = 0防止按 §8.4.10 要求删除 PTK——FT 场景允许保留 PTK SA
¶5.2 EAPOL 状态机是怎么驱动的?——eapol_sm_step ()
EAPOL 状态机是 supplicant 侧管理认证和密钥的中央调度器。它由 eapol_sm_step() 驱动,每次被调用时以循环模式运行三个子状态机:
1 | // src/eapol_supp/eapol_supp_sm.c(源码有部分精简) |
三大子状态机的职责:
| 子状态机 | 核心状态 | 职责 |
|---|---|---|
| SUPP_PAE | LOGOFF → DISCONNECTED → CONNECTING → AUTHENTICATING → AUTHENTICATED | 管理 802.1X 端口授权状态 |
| KEY_RX | NO_KEY_RECEIVE → KEY_RECEIVE | 接收 EAPOL-Key 帧 |
| SUPP_BE | IDLE → REQUEST → RESPONSE → SUCCESS / FAIL | 处理 EAP 消息交换 |
为什么 EAPOL 和 WPA 是两个独立的状态机?
这源自 802.1X 架构的分层设计:EAPOL 状态机负责 "端口授权"(能不能上网的开关),WPA 状态机负责 "密钥协商"(用什么密钥加密)。二者分工不同:
- EAPOL 状态机:管理 802.1X 端口授权状态(PAE = Port Access Entity)。它回答的是 "这个端口现在能让数据通过吗?"——不管是 EAP 认证通过(企业网)还是 PSK 模式跳过(家庭网),最终都要它把
portValid设为 true,数据面才真正打通。 - WPA 状态机:管理四次握手和组密钥更新(
wpa.c)。它回答的是 "当前用的加密密钥是什么?"——负责 PMK 获取、PTK 推导、GTK 安装。
对于 PSK 模式,EAPOL 的 SUPP_PAE 直接从 CONNECTING → AUTHENTICATED(跳过 EAP 认证),但 WPA 状态机仍然正常执行四次握手——因为即使不需要 802.1X 认证,密钥协商仍然必不可少。这就是为什么 wpa_supplicant_rx_eapol() 中 PSK 模式的 EAPOL-Key 帧直接交给 wpa_sm_rx_eapol() 处理,而不是走 EAPOL 状态机的 SUPP_BE。
与 EAPOL 的轮询式状态机(eapol_sm_step() 循环驱动)不同,WPA 侧的密钥协商状态机是事件驱动的:它没有自己的 step() 循环,而是由 wpa_sm_rx_eapol() 收到 EAPOL-Key 帧后直接分发到对应处理函数,处理函数内部用 wpa_sm_set_state() 推进状态。四次握手期间的状态链如下:
1 | WPA_ASSOCIATED ──(收到 msg 1/4)──▶ WPA_4WAY_HANDSHAKE ──(收到 msg 3/4)──▶ WPA_GROUP_HANDSHAKE |
WPA_ASSOCIATED → WPA_4WAY_HANDSHAKE:wpa_supplicant_process_1_of_4()里的wpa_sm_set_state(sm, WPA_4WAY_HANDSHAKE)(wpa.c:1019)触发,标志 PTK 推导开始WPA_4WAY_HANDSHAKE → WPA_GROUP_HANDSHAKE:wpa_supplicant_process_3_of_4()里wpa_sm_set_state(sm, WPA_GROUP_HANDSHAKE)(wpa.c:2928)触发,PTK 已安装、GTK 待安装WPA_GROUP_HANDSHAKE → WPA_COMPLETED:wpa_supplicant_key_neg_complete()里wpa_sm_set_state(sm, WPA_COMPLETED)触发,密钥协商收尾
这套状态命名(WPA_4WAY_HANDSHAKE/WPA_GROUP_HANDSHAKE/WPA_COMPLETED)来自 enum wpa_states(defs.h:248),与顶层 supplicant 状态机共用同一枚举——这也是 §2.3 那张状态表能一路连到 §5 的原因:SME 认证 / 关联(WPA_AUTHENTICATING/WPA_ASSOCIATING)和 WPA 密钥协商(WPA_4WAY_HANDSHAKE 起)是同一状态机的两段。
¶5.3 四次握手——从 msg 1/4 到 msg 4/4
AP 在关联完成后会立即发送 EAPOL-Key msg 1/4(携带 ANonce)。supplicant 的接收路径是:
1 | Driver report EAPOL frame |
wpa_supplicant_rx_eapol() 是 EAPOL 帧的总入口:
1 | // wpa_supplicant/wpa_supplicant.c(源码有部分精简) |
主要功能:
- 竞态处理:EAPOL 帧和关联事件可能以任意顺序到达(它们走不同的驱动回调路径)。如果 EAPOL 先到但关联事件还没到,帧会被缓存在
pending_eapol_rx中,等EVENT_ASSOC到达后再处理 WPA_DRIVER_FLAGS_4WAY_HANDSHAKE_PSK标志表示驱动自己做完四次握手——这种情况下 supplicant 直接标记 portValid=true,跳过整个握手流程
四次握手消息分发(在 wpa_sm_rx_eapol() 内部):
| 收到的帧 | 判定条件 | 处理函数 | 发生什么 |
|---|---|---|---|
| msg 1/4 | Pairwise key, 无 MIC | wpa_supplicant_process_1_of_4() | 收到 ANonce → 生成 SNonce → 计算 PTK → 构造并发送 msg 2/4(携带 SNonce + MIC) |
| msg 3/4 | Pairwise key, 有 MIC + ENCR | wpa_supplicant_process_3_of_4() | 验证 MIC → 安装 PTK → 安装 GTK → 构造并发送 msg 4/4(确认 ACK) |
| Group Key msg 1/2 | Group key, 有 MIC, 无 ACK | wpa_supplicant_process_1_of_2() | 验证 MIC → 安装新 GTK → 回复 Group Key msg 2/2(ACK)。注意:这是独立的两帧组密钥握手(Group Key Handshake),不是四次握手的第 5/6 帧。触发场景:四次握手完成后 AP 发起 GTK 更新(rekey) |
msg 1/4 处理细节(wpa_supplicant_process_1_of_4()):
1 | // src/rsn_supp/wpa.c(源码有部分精简) |
主要功能:
- 解析 AP 发来的 key_data,提取 PMKID(用于找到对应的 PMK)
wpa_derive_ptk()是 PTK 计算的实现——使用 PRF-512 伪随机函数,输入 PMK + ANonce + SNonce + MAC 地址,输出 KCK/KEK/TK- PTK 先存为临时 key(
tptk),等 msg 3/4 MIC 验证通过后才正式安装——这是抵抗篡改攻击的关键设计 - ** 为什么 PTK 先存
tptk不直接安装?** 为了抵抗篡改攻击:如果攻击者在 msg 1/4 或 msg 2/4 中篡改了 ANonce/SNonce,PTK 就会算错。 - 所以先算出来暂存为
tptk,等 msg 3/4 的 MIC 验证通过(证明双方算出的 PTK 一致)后才正式安装,确保只有正确的密钥才会被用于加密数据。
四次握手速查表:
| 消息编号 | 发送方 | 关键内容 | 这一步做了什么 | 为什么这样设计 |
|---|---|---|---|---|
| msg 1/4 | AP → STA | ANonce | AP 发送随机数 ANonce,供 STA 计算 PTK | ANonce 是 PTK 推导的必要输入——没有它,STA 无法生成加密密钥 |
| msg 2/4 | STA → AP | SNonce + MIC | STA 生成 SNonce,计算 PTK(存为临时 tptk),回复 MIC | SNonce 保证每次握手的 PTK 不同;MIC 用 KCK 签名,防止伪造 |
| msg 3/4 | AP → STA | GTK + MIC + INSTALL + SECURE | AP 验证 msg 2/4 的 MIC,下发 GTK,指示安装 PTK 和打开端口 | INSTALL 和 SECURE 分步处理:先确保密钥正确,再授权数据面——安全分层 |
| msg 4/4 | STA → AP | ACK(MIC) | STA 回复确认 ACK,正式安装 PTK | 先发 ACK 再安装 PTK:防止 AP 因未收到 ACK 而认为握手失败、触发重传 |
msg 3/4 处理细节(wpa_supplicant_process_3_of_4()):
1 | // src/rsn_supp/wpa.c(源码有部分精简) |
主要功能:
- ANonce 一致性校验是抵抗中间人攻击的关键——如果 msg 3/4 的 ANonce 与 msg 1/4 不同,说明有人在中间篡改
WPA_KEY_INFO_INSTALL标志是 PTK 安装的信号——在此之前 PTK 只是临时计算,不真正用于加密WPA_KEY_INFO_SECURE置位后调用eapol_sm_notify_portValid(true)——这是数据面打通的最后一道闸门- ** 为什么 msg 4/4 先发 ACK 再安装 PTK?** 代码中
send_4_of_4(wpa.c 第 2685 行)在INSTALL处理(第 2694 行)之前——顺序是精心设计的。 - 如果先安装 PTK 再发 ACK,万一 msg 4/4 丢包,AP 因未收到 ACK 而认为握手失败、重传 msg 3/4,但此时 STA 已经用新 PTK 了,两边密钥状态不一致。先发 ACK 确保 AP 知道握手成功,再安装 PTK 保证一致性。
PTK/GTK 安装到哪里?
wpa_supplicant_install_ptk()和wpa_supplicant_install_gtk()最终调用wpa_drv_set_key(),通过 nl80211 下发NL80211_CMD_NEW_KEY到内核- 内核 cfg80211/mac80211 将密钥写入 WiFi 芯片的硬件密钥表(hardware key table)——不是存在内存里,是写到芯片的寄存器 / 专用 SRAM 中
- PTK 写入 Pairwise Key 表项(key_idx 固定为 0),Key Type 为 Pairwise——硬件用它对单播数据帧做硬件级 AES-CCMP/GCMP 加解密,CPU 完全不参与加解密运算
- GTK 写入 Group Key 表项(key_idx 由 AP 指定,0-3),Key Type 为 Group——硬件用它对广播 / 组播帧做硬件级解密
- 密钥只存在于 WiFi 芯片内部,wpa_supplicant 进程甚至内核都无法再读出明文密钥——这是硬件安全隔离的底线
下次收到数据帧时,WiFi 芯片的硬件引擎根据帧的 MAC 地址自动选择 Pairwise Key 或 Group Key 表项,在 DMA 传输过程中完成加解密,CPU 看到的是已经解密后的明文数据。
Group Key Handshake 发生在四次握手完成之后,因此移至 §5.5 独立展开。
¶5.4 握手完成——WPA_COMPLETED
四次握手完成后,wpa_supplicant_key_neg_complete() 被调用:
1 | // src/rsn_supp/wpa.c(关键步骤摘要) |
此时连接正式建立。从这之后,数据帧可以使用协商好的 PTK 进行加密通信。
四次握手就像制卡机给客人发房卡——msg 1/4 是门禁系统发来门锁的随机数(ANonce),msg 2/4 是制卡机用自己的随机数(SNonce)混入门锁数算出密钥(PTK)并回复,msg 3/4 是门禁系统确认密钥正确并告知公共区密钥(GTK),msg 4/4 是制卡机回复 "房卡已激活"。至此,客人可以用房卡进房间了。
¶5.5 补充:Group Key Handshake(组密钥更新)
四次握手完成后的通信使用 PTK 保护单播帧,但广播/组播帧需要 GTK(Group Transient Key)保护。GTK 的初始值在 msg 3/4 中由 AP 下发,之后 AP 会定期发起 Group Key Handshake(两帧交换,独立于四次握手)来更新 GTK,防止长期使用同一组密钥。
802.11 规范将这组两帧交换称为 Group Key Handshake(IEEE 802.11-2024 §12.7.7),而不是 "四次握手的第 5/6 帧"。supplicant 的
wpa_sm_rx_eapol()里通过key_info的 Key Type 位(bit 4)区分:置 1 为 Pairwise key(走四次握手),置 0 为 Group key(走组密钥握手)。
触发时机:AP 在四次握手完成后,通过 GTK KDE 的超时或事件触发 GTK rekey。驱动收到 EAPOL-Key 帧后上报 supplicant,在 wpa_sm_rx_eapol() 中检测到 Key Type=Group → 调用 wpa_supplicant_process_1_of_2()。
Group Key Handshake 流程:
1 | AP STA |
核心处理函数(AOSP wpa_supplicant 源码):
1 | // src/rsn_supp/wpa.c(源码有部分精简) |
主要功能:
- 前置条件:
msg_3_of_4_ok必须为 true——四次握手未完成时收到的 Group Key 帧直接丢弃 - GTK 解析:GTK 存储在 EAPOL-Key 帧的 key_data 字段中,以 KDE(Key Data Encapsulation)格式编码,用 KEK 加密
- 安全保证:
ENCR_KEY_DATA标志位必须置位——GTK 绝不能明文传输,否则同一 BSS 内其他 STA 可窃听 - Key ID 轮转:
keyidx取 GTK KDE 首字节的低 2 位,AP 通过在不同的 keyidx(0/1/2/3)之间切换来实现无缝 GTK 更新 - rekey offload:如果驱动支持,
wpa_sm_set_rekey_offload()把新 GTK 下发到驱动硬件,后续 GTK 更新由驱动自己处理,不再经过 supplicant
Group Key Handshake 就像酒店更换公共区域的通用门禁码——不影响每个房间的独立密码(PTK),但所有人进健身房/泳池的门禁码要统一更换。AP 是酒店安保部,定期换码防止被破解;msg 1/2 是安保部用每个房间的专属加密通道(KEK)把新码传给制卡机,msg 2/2 是制卡机确认 "新码已生效"。
¶6 两条路——SME vs 非 SME 模式全景对比
SME 模式两步走(CMD_AUTHENTICATE → CMD_ASSOCIATE),非 SME 模式一步走(CMD_CONNECT)。区别的本质是谁来控制认证帧的交换——supplicant 还是驱动。
¶6.1 完整时序对比
此图仅展示 SME 模式路径(
CMD_AUTHENTICATE+CMD_ASSOCIATE两步走);非 SME 模式(CMD_CONNECT一步走)的时序见下方 ASCII 时序图。
SME 模式(SAE / FT / FILS / OWE):
1 | supplicant nl80211 / 驱动 |
非 SME 模式(WPA2-PSK / Open):
1 | supplicant nl80211 / 驱动 |
¶6.2 为什么 WPA2-PSK 走非 SME
WPA2-PSK 的 802.11 认证阶段和 Open 网络完全相同——只交换两个 Authentication 帧(Algorithm=Open System 的 Req + Resp),不需要额外的帧交换。真正的安全发生在 Association 之后的四次握手。
因此 Auth+Assoc 可以合并成一个 CMD_CONNECT 命令交给驱动处理——驱动发出 Auth Req → 等待 Auth Resp → 发出 Assoc Req → 等待 Assoc Resp,四步自动完成。supplicant 不需要介入中间步骤,只需要等待最终结果。
普通客人的入住不需要在贵宾室验证——前台直接核验身份证(Auth=Open,只是形式),然后签入住单(Assoc),发房卡(4-way handshake)。整个过程一步到位。
非 SME 路径的 IE 构建——wpas_populate_assoc_ies ()
非 SME 模式下,supplicant 虽然不控制 Auth/Assoc 帧交互,但仍然负责构建 Association Request 帧体中的 IE(Information Elements)。这个工作由 wpas_populate_assoc_ies() 完成——它是一个约 580 行的巨型函数(wpa_supplicant.c:3469),返回动态分配的 IE 缓冲区,供 wpa_drv_associate() 通过 NL80211_ATTR_IE 传给 NL80211_CMD_CONNECT。
1 | // wpa_supplicant/wpa_supplicant.c:3469(源码有部分精简) |
主要功能:
- 返回
static u8 *(分配的 IE 缓冲区),通过params->wpa_ie传出,最终作为NL80211_ATTR_IE随NL80211_CMD_CONNECT下发 - RSNE 构建由
wpa_supplicant_set_suites()完成:根据ssid->key_mgmt、pairwise_cipher、PMF 设置等生成 AP 期望的 RSNE 字节流 - HT/VHT/HE Capabilities 不在此函数中构建——这些能力由驱动通过 nl80211 能力探测阶段(
NL80211_ATTR_HT_CAPABILITY等)自动携带,不是 supplicant 的职责 - auth_alg 也在此函数中自动选择(SAE/LEAP/FILS 等),与 SME 模式的
sme_send_authentication()形成平行决策
非 SME 路径的失败收敛:SME 路径靠 §4.3 的 sme_auth_timer/sme_assoc_timer 两个 5 秒定时器兜底,非 SME 路径则是另一套。wpas_start_assoc_cb()(wpa_supplicant.c:4232)末尾调用 wpa_drv_associate() 下发 CMD_CONNECT——若驱动立即返回错误(ret < 0)且声明了 WPA_DRIVER_FLAGS_VALID_ERROR_CODES,supplicant 直接调 wpas_connection_failed() 并迁回 WPA_DISCONNECTED;否则置 assoc_failed = 1,继续等驱动后续事件。下发成功后,wpa_supplicant_req_auth_timeout() 挂一个认证超时定时器(默认 60 秒,ap_scan==1 时 10 秒),超时后驱动上报的 EVENT_AUTH_TIMED_OUT/EVENT_ASSOC_TIMED_OUT 同样收敛到 wpas_connection_failed()。
wpas_connection_failed()(wpa_supplicant.c:8519)是两条路径共同的失败终点:把失败 BSSID 加入忽略列表(wpa_bssid_ignore_add()),consecutive_conn_failures 加一,并按失败次数做指数退避(100/500/1000/5000/10000 毫秒)后发起下一次扫描,避免在同一个坏 AP 上无限重试。
¶6.3 为什么 SAE 默认走 SME(以及 SAE offload 例外)
SAE(WPA3)的认证阶段使用 Dragonfly 协议,需要四帧 Commit+Confirm 交换:
- STA → AP:SAE Commit(椭圆曲线元素 + 标量)
- AP → STA:SAE Commit(对方元素 + 标量)
- STA → AP:SAE Confirm(验证标签)
- AP → STA:SAE Confirm(对方验证标签)
这个过程中需要椭圆曲线运算(PWE 生成)、验证对方 Commit 的合法性、计算 Confirm 标签——默认情况下,supplicant 必须逐帧控制这些密码学操作,因此 SAE 走 SME 模式,通过 CMD_AUTHENTICATE 下发每帧的认证数据(NL80211_ATTR_SAE_DATA)。
但 "必须" 不是绝对的——SAE offload 是例外。
一些驱动(如 QCOM 的 wlan、MTK 的 connac)在固件中内置了完整的 SAE 状态机,可以独立完成 Dragonfly 握手。这种情况下,supplicant 不再逐帧控制 SAE 交互,而是把 SAE 密码通过 NL80211_ATTR_SAE_PASSWORD 属性一次性下发给驱动,驱动内部完成 Commit/Confirm 交换后直接上报认证结果。
代码中的判断链:
1 | sme_send_authentication() 中 auth_alg 决策 |
SAE 默认走 SME 模式的
sme_send_authentication()进行逐帧控制;当驱动通过能力探测声明 SAE offload 时,supplicant 走非 SME 路径,通过CMD_CONNECT一次性下发 SAE 密码,由驱动固件内置的 SAE 状态机完成 Dragonfly 握手。具体平台的 offload 支持情况见下方 §6.4。
¶6.4 QCOM 与 MTK 的实际差异
MTK 驱动层代码(AIS FSM 状态定义、connector 回调等)将在后续驱动层连接执行篇展开,本节仅在 wpa_supplicant 层面对比两个平台的能力上报差异。
先看 supplicant 侧怎么判断走不走 SME。能力探测阶段,wiphy_info_supp_cmds()(driver_nl80211_capa.c:220)遍历驱动上报的 NL80211_ATTR_SUPPORTED_COMMANDS,遇到 NL80211_CMD_AUTHENTICATE 就置 auth_supported = 1;随后 wpa_driver_nl80211_get_info()(driver_nl80211_capa.c:1220)据此设置 WPA_DRIVER_FLAGS_SME。
而这份属性列表由内核 cfg80211 按驱动注册的 ops 生成——nl80211_add_commands_unsplit()(net/wireless/nl80211.c)里 CMD(auth, AUTHENTICATE) 这条宏只在驱动注册了 .auth 回调时,才会把 NL80211_CMD_AUTHENTICATE 加进列表;NL80211_CMD_CONNECT 则只要注册了 .connect 就会上报。
两个平台在这一环节的注册情况是:
| 注册项 | QCOM (qcacld-3.0) | MTK (gen4m) |
|---|---|---|
.connect | wlan_hdd_cfg80211_connect(wlan_hdd_cfg80211.c:27302) | mtk_cfg_connect(gl_init.c:1240) |
.assoc | 未注册 | mtk_cfg_assoc(gl_init.c:1257) |
.auth | 未注册 | 未注册 |
.external_auth | wlan_hdd_cfg80211_external_auth(wlan_hdd_cfg80211.c:27370) | mtk_cfg80211_external_auth(gl_init.c:1300) |
关键结论:两边都注册了 .connect,但都没有注册 .auth。因此两者上报的 NL80211_ATTR_SUPPORTED_COMMANDS 里都没有 NL80211_CMD_AUTHENTICATE,supplicant 也就不会给这两个平台设置 WPA_DRIVER_FLAGS_SME——它们都是非 SME(driver-based SME)驱动,Auth/Assoc 帧交换一律走 CMD_CONNECT:
| 场景 | QCOM (wlan) | MTK (wlan) | 机制 |
|---|---|---|---|
| WPA2-PSK | CMD_CONNECT | CMD_CONNECT | 非 SME,驱动完成 Auth+Assoc |
| SAE offload | CMD_CONNECT + SAE password | CMD_CONNECT + SAE password | 驱动固件内置 SAE 状态机 |
| SAE non-offload | CMD_CONNECT + external_auth | CMD_CONNECT + external_auth | 两侧都注册 .external_auth,supplicant 做 SAE 密码学、驱动只交换帧 |
| FT 漫游 | CMD_CONNECT(驱动内置 FT) | CMD_CONNECT(驱动内置 FT) | 都非 SME,FT 由驱动处理 |
| WPA2-EAP | CMD_CONNECT | CMD_CONNECT | EAP 在 supplicant 侧处理 |
唯一可见的差异是 MTK 额外注册了 .assoc(以及 WiFi Direct 配置下的 .deauth/.disassoc),因此它的 NL80211_ATTR_SUPPORTED_COMMANDS 会比 QCOM 多一个 NL80211_CMD_ASSOCIATE。
但因为缺少配套的 .auth,这个命令对 supplicant 的 SME 决策没有影响——wiphy_info_supp_cmds() 只检查 NL80211_CMD_AUTHENTICATE 和 NL80211_CMD_CONNECT,并不关心 NL80211_CMD_ASSOCIATE。
换句话说,SME 与非 SME 的分水岭从来不是 "哪家更先进",而是 "驱动愿不愿意把认证帧的每一步交回给 supplicant"。
QCOM 和 MTK 都选择了由驱动兜底,把 supplicant 挡在认证帧交换之外。
¶7 完整调用链总结
从 Framework 的 connectToNetwork() 到 nl80211 命令下发,连接命令跨越了 Java → AIDL → C → Netlink 四个边界:
1 | ══════════════════════ Framework (Java) ══════════════════════ |
¶ 关键函数速查
| 函数 | 文件 | 作用 |
|---|---|---|
StaIface::addNetworkInternal() | aidl/vendor/sta_iface.cpp | AIDL 服务端:创建 wpa_ssid |
wpa_supplicant_add_network() | wpa_supplicant/wpa_supplicant.c | 分配并初始化 wpa_ssid |
StaNetwork::setSsidInternal() | aidl/vendor/sta_network.cpp | SSID AIDL setter |
StaNetwork::setPskPassphraseInternal() | 同上 | PSK passphrase setter |
StaNetwork::setKeyMgmtInternal() | 同上 | key_mgmt setter(含 FT 自动启用) |
StaNetwork::selectInternal() | 同上 | AIDL select:调用 wpa_supplicant_select_network |
wpa_supplicant_select_network() | wpa_supplicant/wpa_supplicant.c | 连接总调度:启用网络 + 触发扫描 |
wpa_supplicant_pick_network() | wpa_supplicant/events.c | 从扫描结果选网络:按 priority 组遍历 |
wpa_scan_result_compar() | wpa_supplicant/scan.c | 候选 BSS 多级评分比较器 |
wpa_supplicant_associate() | wpa_supplicant/wpa_supplicant.c | 连接总入口:四阶段准备 + 模式分发 |
sme_authenticate() | wpa_supplicant/sme.c | SME 模式启动:创建 sme-connect radio work |
sme_send_authentication() | 同上 | auth_alg 决策枢纽 + 构造 Auth 帧 |
sme_event_auth() | 同上 | Auth 响应处理 + 触发 sme_associate |
sme_associate() | 同上 | 构造 Assoc Req + 下发 CMD_ASSOCIATE |
sme_event_assoc_reject() | 同上 | Assoc 拒绝处理(comeback/PMKSA 回退) |
sme_state_changed() | 同上 | 状态迁移时清理 auth/assoc 定时器 |
wpa_drv_authenticate() | wpa_supplicant/driver_i.h | 驱动认证包装(函数指针) |
wpa_drv_associate() | 同上 | 驱动关联包装(函数指针) |
wpa_driver_nl80211_authenticate() | src/drivers/driver_nl80211.c | 构建 NL80211_CMD_AUTHENTICATE |
wpa_driver_nl80211_associate() | 同上 | 入口分流:CMD_CONNECT vs CMD_ASSOCIATE |
nl80211_connect_common() | 同上 | 共享参数填充(SSID/BSSID/Freq/IE/Cipher/AKM) |
wpa_sm_notify_assoc() | src/rsn_supp/wpa.c | 关联完成触发 WPA 状态机 |
eapol_sm_step() | src/eapol_supp/eapol_supp_sm.c | EAPOL 状态机驱动 |
wpa_supplicant_rx_eapol() | wpa_supplicant/wpa_supplicant.c | EAPOL 帧接收入口 |
wpa_sm_rx_eapol() | src/rsn_supp/wpa.c | 四次握手消息分发 |
Auth 和 Assoc 命令下发到内核之后,驱动具体怎么执行帧交换?QCOM 的 CM 状态机(5+9 状态)如何管理连接?MTK 的 AIS + SAA 两层状态机(17+8 状态)如何协作?这是下一篇「驱动层连接执行」要追踪的内容。
本篇涉及的 IEEE 802.11-2024 章节:
- 第 3 章:Station Management Entity (SME) 定义
- §11.3.4:Authentication and deauthentication
- §11.3.5:Association, reassociation, and disassociation
- §12.4.1 / §12.4.5:Simultaneous Authentication of Equals (SAE) 概述与协议
- §12.7.7:Group key handshake
- §13.5:Fast BSS Transition (FT) protocol
- §8.4.10:PTK SA 生命周期管理(wpa_supplicant 源码注释引用的旧版章节号)
本文代码出自 external/wpa_supplicant_8 与 packages/modules/Wifi 仓库。