ENABLE_NETWORK命令由wpa_supplicant_ctrl_iface_enable_network進行處理,其代碼如
下所示。
**ctrl_iface.c::wpa_supplicant_ctrl_iface_enable_network**
~~~
static int wpa_supplicant_ctrl_iface_enable_network(struct wpa_supplicant *wpa_s,char *cmd)
{
int id;
struct wpa_ssid *ssid;
if (os_strcmp(cmd, "all") == 0) { // 使能所有無線網絡
ssid = NULL;
} else {
id = atoi(cmd); // 本例中的id為0
ssid = wpa_config_get_network(wpa_s->conf, id); // 找到id為0的無線網絡配置對象
......
// 在前面ADD_NETWORK中,disabled為1,表示還沒有使能它。disable為2的情況和P2P有關
if (ssid->disabled == 2) {......}
}
wpa_supplicant_enable_network(wpa_s, ssid);
return 0;
}
~~~
來看wpa_supplicant_enable_network,其代碼如下所示。
**wpa_supplicant.c::wpa_supplicant_enable_network**
~~~
void wpa_supplicant_enable_network(struct wpa_supplicant *wpa_s, struct wpa_ssid *ssid)
{
struct wpa_ssid *other_ssid;
int was_disabled;
if (ssid == NULL) {
......// 處理ENABLE_NETWORK all的情況
} else if (ssid->disabled && ssid->disabled != 2) {
if (wpa_s->current_ssid == NULL) {// WPAS當前沒有活躍的無線網絡,所以current_ssid為空
wpa_s->reassociate = 1; // 注意這個變量的值
// ADD_NETWORK只是添加了一個無線網絡配置項
// 接下來要發起掃描工作以和對應的無線網絡進行交互
// 下面這個函數將發起scan操作。后面兩個0代表時間。詳情見下節分析
wpa_supplicant_req_scan(wpa_s, 0, 0);
}
was_disabled = ssid->disabled;
ssid->disabled = 0; // 設置disabled為0
if (was_disabled != ssid->disabled)
wpas_notify_network_enabled_changed(wpa_s, ssid);
}
}
~~~
正如代碼中注釋所說,ADD_NETWORK不過是為WPAS添加了一個無線網絡配置項罷了。該無線網絡是否存在?通過SET_NETWORK配置的信息是否正確?這些問題的解答首先從無線網絡掃描開始。
#### **一、無線網絡掃描流程分析**
ENABLE_NETWORK將發起無線網絡掃描請求,這是由wpa_supplicant_req_scan完成的,其代碼如下所示。
**scan.c::wpa_supplicant_req_scan**
~~~
void wpa_supplicant_req_scan(struct wpa_supplicant *wpa_s, int sec, int usec)
{
#ifndef ANDROID // Android平臺上,該宏被定義
......// 不討論非Android平臺上的代碼
#endif
eloop_cancel_timeout(wpa_supplicant_scan, wpa_s, NULL);
// 在本例中,sec和usec都是0,所以wpa_supplicant_scan將很快得到執行。該函數是掃描的核心代碼
eloop_register_timeout(sec, usec, wpa_supplicant_scan, wpa_s, NULL);
}
~~~
wpa_supplicant_scan是無線網絡掃描的核心函數,其代碼比較復雜,我們分段來看。
**1、wpa_supplicant_scan分析之一**
這一段代碼主要和scan請求的參數準備有關。
**scan.c::wpa_supplicant_scan代碼段一**
~~~
static void wpa_supplicant_scan(void *eloop_ctx, void *timeout_ctx)
{
struct wpa_supplicant *wpa_s = eloop_ctx;
struct wpa_ssid *ssid; int scan_req = 0, ret;
struct wpabuf *extra_ie; // 用于存儲Information Element信息
struct wpa_driver_scan_params params; // 發給驅動的scan請求命令
// 用于記錄一個scan請求能包含多少個ssid。請參考4.3.4節關于capability的介紹
size_t max_ssids;
enum wpa_states prev_state;
// wpa_state取值為WPA_INACTIVE,由4.3.4節wpa_supplicant_driver_init函數中的代碼設置
if (wpa_s->wpa_state == WPA_INTERFACE_DISABLED) { return;}
// disconnected為0,scan_req為1,都是wpa_supplicant構造時的默認值
if (wpa_s->disconnected && !wpa_s->scan_req) { ......}
/*
搜索wpa_config中所有的無線網絡配置項,看其中是否有使能的無線網絡。本例中,在掃描之前,
已經將目標wpa_ssid的disabled變量置為0,這樣,下面這個函數調用將返回非0值,使得整個if判斷為假。
*/
if (!wpa_supplicant_enabled_networks(wpa_s->conf) && !wpa_s->scan_req) {......}
/*
ap_scan是一個很有意思的參數,它和AP掃描和選擇有關,默認值為1。值為1:表示WPAS來完成
AP掃描和選擇的絕大部分工作(包括關聯、EAPOL認證等工作)。值為0:表示驅動完成AP掃描和選
擇的工作。這種驅動比較少見,筆者未能找到關于WPA_DRIVER_FLAGS_WIRED標志的合理解釋,有
知曉的讀者不妨和大家分享一下相關知識。值為2:和0類似,不過在NDIS(Windows上的網絡設備
驅動)中用得較多。
*/
if (wpa_s->conf->ap_scan != 0 && (wpa_s->drv_flags & WPA_DRIVER_FLAGS_WIRED)){......}
if (wpa_s->conf->ap_scan == 0) {// 如果驅動能完成大部分工作的話,WPAS的工作量將大大減少
wpa_supplicant_gen_assoc_event(wpa_s);
return; // 無須后面的流程
}
......// CONFIG_P2P:P2P相關,本章不討論
if (wpa_s->conf->ap_scan == 2)
max_ssids = 1;
else {
max_ssids = wpa_s->max_scan_ssids; // 一個scan請求能包含多少個ssid
if (max_ssids > WPAS_MAX_SCAN_SSIDS)
max_ssids = WPAS_MAX_SCAN_SSIDS;
}
scan_req = wpa_s->scan_req; // scan_req為1
wpa_s->scan_req = 0; // scan_req被置為0
os_memset(¶ms, 0, sizeof(params));
// 初始化scan請求的參數,其類型為wpa_driver_scan_params
}
~~~
根據第3章關于無線網絡掃描的介紹,一個Probe Request要么指定wildcard ssid以掃描周圍所有的無線網絡,要么指定某個ssid以掃描特定無線網絡。為了方便WPAS的使用,wlandriver新增了一個功能,使得上層可通過一次scan請求來掃描多個不同ssid的無線網絡。一個scan請求在代碼中對應的數據結構就是wpa_driver_scan_params。而wpa_supplicant_scan最重要的工作就是準備好這個請求。
**2、wpa_supplicant_scan分析之二**
接著來看代碼段二。
**scan.c::wpa_supplicant_scan代碼段二**
~~~
......// 接上段代碼
prev_state = wpa_s->wpa_state; // 此時的wpa_state是WPA_INACTIVE
if (wpa_s->wpa_state == WPA_DISCONNECTED || wpa_s->wpa_state == WPA_INACTIVE)
wpa_supplicant_set_state(wpa_s, WPA_SCANNING);// 設置WPAS狀態為WPA_SCANNING
/*
connect_without_scan指向一個wpa_ssid對象。它對應的應用場景是:WPAS事先通過某種方
式(例如后續章節將要介紹的WPS)已經知道要連接的無線網絡了,所以此處就無須掃描,僅關聯它即可。
*/
if (scan_req != 2 && wpa_s->connect_without_scan) {
for (ssid = wpa_s->conf->ssid; ssid; ssid = ssid->next) {
if (ssid == wpa_s->connect_without_scan)
break;
}
wpa_s->connect_without_scan = NULL;
if (ssid) {
wpa_supplicant_associate(wpa_s, NULL, ssid); // 關聯到目標網絡
return;
}
}
// 搜索wpa_config中的所有無線網絡配置項,看看哪些需要包含到這次scan請求中
ssid = wpa_s->conf->ssid;
/*
prev_scan_ssid用于記錄上一次scan請求的最后一個ssid。它對應了如下的應用場景。
假設scan請求一次只能攜帶2個ssid,如果要掃描wpa_config中配置的全部網絡項(假設是4個),
則需要發起兩次scan請求。所以,當prev_scan_ssid上一次掃描的并非全部無線網絡的話(由
wildcardssid來判斷),則此處要接著掃描之前沒有掃描的那些無線網絡。
以本例而言,prev_scan_ssid初始值是WILDCARD_SSID_SCAN(其值為1)。
*/
if (wpa_s->prev_scan_ssid != WILDCARD_SSID_SCAN) {
while (ssid) {
if (ssid == wpa_s->prev_scan_ssid) {
ssid = ssid->next;
break;
}
ssid = ssid->next;
}
}
if (scan_req != 2 && wpa_s->conf->ap_scan == 2) {
......// 不考慮這種情況
#ifndef ANDROID
......
#endif
} else {
struct wpa_ssid *start = ssid, *tssid;
int freqs_set = 0;
if (ssid == NULL && max_ssids > 1)
ssid = wpa_s->conf->ssid;
while (ssid) {
/*
有一些AP被設置為hidden ssid。即它不響應wildcard ssid掃描的Probe Request,
同時,自己發送的Beacon幀也不攜帶ssid信息。這樣,只有知道ssid的STA才能和這
些AP連接上,其安全性略有提高。scan_ssid就是用來判斷此無線網絡是否需要指明ssid。
本例中的"Test"無線網絡沒有隱藏 ssid,所以scan_ssid值為0。否則需要通過SET_
NETWORK 0 scan_ssid 1來設置它。
*/
if (!ssid->disabled && ssid->scan_ssid) {
// 把ssid信息加到params的ssids數組中
params.ssids[params.num_ssids].ssid = ssid->ssid;
params.ssids[params.num_ssids].ssid_len = ssid->ssid_len;
params.num_ssids++;
// 如果本次scan請求的ssid個數已經達到driver能支持的最大數,則跳出循環
if (params.num_ssids + 1 >= max_ssids) break;
}
ssid = ssid->next;
if (ssid == start)
break;
if (ssid == NULL && max_ssids > 1 && start != wpa_s->conf->ssid)
ssid = wpa_s->conf->ssid;
}
/*
處理掃描時的頻率選擇。如果已經知道目標無線網絡的工作信道,可以直接設定頻率參數以
優化掃描過程。否則,無線網卡將嘗試在各個信道上搜索目標無線網絡。本例沒有使用頻率參數。
*/
for (tssid = wpa_s->conf->ssid; tssid; tssid = tssid->next) {
if (tssid->disabled) continue;
if ((params.freqs || !freqs_set) && tssid->scan_freq) {
int_array_concat(¶ms.freqs,tssid->scan_freq);
} else {
os_free(params.freqs);
params.freqs = NULL;
}
freqs_set = 1;
}
int_array_sort_unique(params.freqs); // 對所有頻率參數進行升序排序
}
if (ssid && max_ssids == 1) { // 如果scan請求最多只能包含一個ssid
if (!wpa_s->prev_scan_wildcard) {
params.ssids[0].ssid = NULL; // 掃描wildcast ssid
params.ssids[0].ssid_len = 0;
wpa_s->prev_scan_wildcard = 1;
} else {
wpa_s->prev_scan_ssid = ssid;
wpa_s->prev_scan_wildcard = 0;
}
} else if (ssid) {
wpa_s->prev_scan_ssid = ssid;
params.num_ssids++;
} else {
wpa_s->prev_scan_ssid = WILDCARD_SSID_SCAN;
params.num_ssids++;
}
// 對頻率參數進行修改,和P2P以及WPS有關,本章略過它們
wpa_supplicant_optimize_freqs(wpa_s, ¶ms);
// 是否需要攜帶附件的IE信息。主要用在WPS等情況,本章略過它們
extra_ie = wpa_supplicant_extra_ies(wpa_s, ¶ms);
if (params.freqs == NULL && wpa_s->next_scan_freqs) {
params.freqs = wpa_s->next_scan_freqs;
} else os_free(wpa_s->next_scan_freqs);
wpa_s->next_scan_freqs = NULL;
/*
scan請求可以設置一個過濾條件,掃描完畢后,driver wrapper會過濾掉那些不符合條件的無線
網絡。注意,filter_ssids用來保存那些不能被過濾的無線網絡ssid。即,掃描到的無線網絡不在
filter_ssids中時,它將被過濾掉。過濾的代碼在driver_nl80211.c nl80211_scan_filtered
函數中,其調用之處在同一文件里的bss_info_handler函數中。
*/
params.filter_ssids = wpa_supplicant_build_filter_ssids(wpa_s->conf,
¶ms.num_filter_ssids);
if (extra_ie) {
params.extra_ies = wpabuf_head(extra_ie);
params.extra_ies_len = wpabuf_len(extra_ie);
}
#ifdef CONFIG_P2P
......
#endif /* CONFIG_P2P */
~~~
上述wpa_supplicant_scan代碼段主要展示了如何填寫掃描請求參數,復雜之處在于其對細節的處理。下面來看最后一個代碼段。
**3、wpa_supplicant_scan分析之三**
當scan請求的參數準備好后,wpa_supplicant_scan將直接向driver wrapper發起scan請求。
**scan.c::wpa_supplicant_scan代碼段三**
~~~
static int wpa_driver_nl80211_scan(void *priv, struct wpa_driver_scan_params *params)
{
struct i802_bss *bss = priv; // i802_bss是driver wrapper的上下文信息
struct wpa_driver_nl80211_data *drv = bss->drv;
// 獲取wpa_driver_nl80211_data對象
int ret = 0, timeout;
struct nl_msg *msg, *ssids, *freqs, *rates;
size_t i;
drv->scan_for_auth = 0;
msg = nlmsg_alloc();ssids = nlmsg_alloc();freqs = nlmsg_alloc();
rates = nlmsg_alloc();
// 這個函數的主要功能是將wpa_driver_scan_params參數轉換成對應的netlink command
os_free(drv->filter_ssids);
drv->filter_ssids = params->filter_ssids;
params->filter_ssids = NULL;
drv->num_filter_ssids = params->num_filter_ssids;
nl80211_cmd(drv, msg, 0, NL80211_CMD_TRIGGER_SCAN);
NLA_PUT_U32(msg, NL80211_ATTR_IFINDEX, drv->ifindex); // 指定網卡設備編號
for (i = 0; i < params->num_ssids; i++) // 填充ssid信息
NLA_PUT(ssids, i + 1, params->ssids[i].ssid_len,params->ssids[i].ssid);
if (params->num_ssids)
nla_put_nested(msg, NL80211_ATTR_SCAN_SSIDS, ssids);
if (params->extra_ies) // 填充附加IE信息
NLA_PUT(msg, NL80211_ATTR_IE, params->extra_ies_len,params->extra_ies);
if (params->freqs) { // 填充頻率信息
for (i = 0; params->freqs[i]; i++) NLA_PUT_U32(freqs, i + 1, params->freqs[i]);
nla_put_nested(msg, NL80211_ATTR_SCAN_FREQUENCIES, freqs);
}
if (params->p2p_probe) {......// P2P相關}
/*
發送請求給wlan驅動。返回值只是表示該命令是否正確發送給了驅動。掃描結束事件將通過
driver event返回給WPAS。下文將分析如何處理掃描結束事件。
*/
ret = send_and_recv_msgs(drv, msg, NULL, NULL);
msg = NULL;
if (ret) { ......// 錯誤處理}
timeout = 10;
/*
一般情況下,driver完成掃描后需要通知WPAS一個scan complete事件。如果驅動不通知的話,
WPAS就會自己去查詢driver以獲取掃描到的無線網絡信息。如何知曉driver是否會通知該事件呢?
WPAS中是通過scan_complete_events變量來判斷的。值得指出的是,該變量的取值是測試出來的。
即scan_complete_events初始值為0。如果掃描后收到了scan complete事件,該值將被
修改為1。由于本例中,該變量是第一次碰到,所以其值為0。
*/
if (drv->scan_complete_events) timeout = 30;
eloop_cancel_timeout(wpa_driver_nl80211_scan_timeout, drv, drv->ctx);
// 本例中,timeout為10秒
eloop_register_timeout(timeout, 0, wpa_driver_nl80211_scan_timeout, drv, drv->ctx);
......
return ret;
}
~~~
就本例而言,ENABLE_WORK命令處理的第一步就是要掃描周圍的無線網絡。至于目標無線網絡是否存在,則屬于掃描結果處理流程的工作了。
**4、無線網絡掃描流程總結**
圖4-31所示為觸發掃描功能的流程圖。
:-: 
圖4-31 觸發掃描的流程圖
圖4-31所示函數中,wpa_supplicant_scan的內容較為豐富,其中有很多細節內容。讀者初次學習時,可以先不考慮這些細節,只要把握圖4-31中的函數調用流程即可。
馬上來看掃描結果的處理流程。
#### **二、掃描結果處理流程分析**
在上一節的掃描請求中,driver_nl80211發送了NL80211_CMD_TRIGGER_SCAN命令給wlan driver以通知它開始掃描周圍的無線網絡。當wlan driver完成此任務后,它將向4.3.4節中wpa_driver_nl80211_init_nl_global函數中注冊的三個netlink組播之一的"scan"組播地址發送結果。而driver_nl80211對應處理來自wlan driver netlink消息處理回調函數為
process_global_event,而它最終又會調用do_process_drv_event進行處理。所有,直接來看do_process_drv_event中和掃描結果相關的代碼即可。
**driver_nl80211.c::do_process_drv_event**
~~~
static void do_process_drv_event(struct wpa_driver_nl80211_data *drv,
int cmd, struct nlattr **tb)
{
/*
ap_scan_as_station和hostapd有關,該值默認等于NL80211_IFTYPE_UNSPECIFIED。讀
者可參考4.3.4節"wpa_supplicant_init_iface分析之三"中對wpa_driver_nl80211_init
的分析。
*/
if (drv->ap_scan_as_station != NL80211_IFTYPE_UNSPECIFIED &&
(cmd == NL80211_CMD_NEW_SCAN_RESULTS ||cmd == NL80211_CMD_SCAN_ABORTED)) {......}
switch (cmd) {
// driver wrapper發送NL80211_CMD_TRIGGER_SCAN命令后,wlan driver也會回復同名的一個消息
case NL80211_CMD_TRIGGER_SCAN:
wpa_printf(MSG_DEBUG, "nl80211: Scan trigger");
break;
......// 其他消息處理
case NL80211_CMD_NEW_SCAN_RESULTS:
// 收到來自driver的scan回復,所以設置scan_complete_events變量為1
drv->scan_complete_events = 1;
// 取消超時任務
eloop_cancel_timeout(wpa_driver_nl80211_scan_timeout, drv, drv->ctx);
/*
圖4-1所示的WPAS軟件結構中曾提到driver wrapper將通過發送driver event的方式
觸發WPAS其他模塊進行對應處理。下面這個函數即是用來處理和發送與scan相關的driver
event的。其詳情見下文。
*/
send_scan_event(drv, 0, tb);
break;
......
}
return
}
~~~
上述代碼中的send_scan_event將解析來自wlan driver的掃描完畢事件,然后再向WPAS發送driver event,其代碼如下所示。
**driver_nl80211.c::send_scan_event**
~~~
static void send_scan_event(struct wpa_driver_nl80211_data *drv, int aborted,
struct nlattr *tb[])
{
// 代表driver event的數據結構
// 它是一個聯合體,包含了assoc_info、auth_info、scan_info等較多內容
union wpa_event_data event;
struct nlattr *nl;
int rem;
// 掃描信息,包含頻率數組(int類型的數組)以及wpa_driver_scan_ssid數組
// 請讀者特別注意scan_info不是掃描結果
struct scan_info *info;
#define MAX_REPORT_FREQS 50
int freqs[MAX_REPORT_FREQS];
int num_freqs = 0;
/*
scan_for_auth變量和wlan driver有關,它描述了如下的應用場景。
當WPAS發起認證(authentication)操作時,它將向driver發送NL80211_CMD_
AUTHENTICATE命令。driver可能因為某種原因(超時等),其內部存儲的目標BSS(代表目標無
線網絡)信息失效。這時,它將返回ENOENT給WPAS。正常情況下,WPAS應該重新掃描。但為了加
快這個流程,WPAS可以單獨掃描目標無線網絡(因為WPAS還是保存了目標無線網絡的信息,例如
頻道等),然后再發起認證操作。從筆者角度來看,該場景對應的問題是wlan driver沒有目標
BSS信息,而WPAS有目標BSS信息。WPAS和wlan driver是兩個不同模塊,二者的信息并不能
總是保持一致。既然WPAS有目標BSS信息,那么可以通過更加快捷的方法讓wlan driver也得
到這個信息(通過在指定頻道到掃描目標BSS),從而加快整個流程(從WPAS角度來看,用戶加入無
線網絡的流程已經進行到Authentication階段,此時再退回到重新掃描的階段實在有些麻煩)。
關于scan_for_auth的官方解釋,請通過前面介紹的git blame命令獲取commit message,
方法是先通過"git blame ./src/drivers/driver_nl80211.c | grep scan_for_auth"
獲得和scan_for_auth相關的commit號,然后再用git log commit號查看。
git blame的用法請參考4.2.4節。
*/
if (drv->scan_for_auth) {......}
os_memset(&event, 0, sizeof(event));
info = &event.scan_info;
info->aborted = aborted;
if (tb[NL80211_ATTR_SCAN_SSIDS]) {// 遍歷NL80211_ATTR_SCAN_SSIDS屬性
nla_for_each_nested(nl, tb[NL80211_ATTR_SCAN_SSIDS], rem) {
struct wpa_driver_scan_ssid *s = &info->ssids[info->num_ssids];
s->ssid = nla_data(nl); s->ssid_len = nla_len(nl);
info->num_ssids++;
if (info->num_ssids == WPAS_MAX_SCAN_SSIDS) break;
}
}
if (tb[NL80211_ATTR_SCAN_FREQUENCIES]) {// 獲取netlink消息中的頻率信息
nla_for_each_nested(nl, tb[NL80211_ATTR_SCAN_FREQUENCIES], rem) {
freqs[num_freqs] = nla_get_u32(nl);
num_freqs++;
if (num_freqs == MAX_REPORT_FREQS - 1) break;
}
info->freqs = freqs;
info->num_freqs = num_freqs;
}
// wpa_supplicant_event被driver event模塊用來發送driver事件給WPAS
wpa_supplicant_event(drv->ctx, EVENT_SCAN_RESULTS, &event);
}
~~~
上述代碼中請注意struct scan_info的作用,它定義于聯合體wpa_event_data中,包含了本次掃描請求掃描了哪些SSID、對應的頻率等。
>[info] 提示 scan_info不是scan后得到的結果信息,而是代表驅動處理scan請求時的一些處理信息。對于那些不支持通知scan complete事件的driver而言,scan_info就沒法獲得。例如,超時掃描處理wpa_driver_nl80211_scan_timeout中調用wpa_supplicant_event時就沒有scan_info。
scan_info到底有什么作用呢?后文將詳細介紹它。
wpa_supplicant_event是driver wrapper向WPAS通知driver event的接口函數,其代碼如下所示。
**events.c::wpa_supplicant_event**
~~~
void wpa_supplicant_event(void *ctx, enum wpa_event_type event,
union wpa_event_data *data)
{
struct wpa_supplicant *wpa_s = ctx;
u16 reason_code = 0;
int locally_generated = 0;
......
switch (event) {
......// driver event總類非常多,現在只分析EVENT_SCAN_RESULT即可
case EVENT_SCAN_RESULTS:
wpa_supplicant_event_scan_results(wpa_s, data);
break;
......
}
}
~~~
代碼很簡單,下面直接來分析此處的目標函數wpa_supplicant_event_scan_results。
**1、wpa_supplicant_event_scan_results函數分析**
函數代碼如下。
**events.c::wpa_supplicant_event_scan_results**
~~~
static void wpa_supplicant_event_scan_results(struct wpa_supplicant *wpa_s,
union wpa_event_data *data)
{
const char *rn, *rn2;
struct wpa_supplicant *ifs;
// 筆者編譯的WPAS實際上打開了ANDROID_P2P宏,但本章不討論P2P相關內容
#ifdef ANDROID_P2P
if (_wpa_supplicant_event_scan_results(wpa_s, data, 0) < 0)
#else
if (_wpa_supplicant_event_scan_results(wpa_s, data) < 0)
#endif
return;
/*
get_radio_name函數和4.3.4節"wpa_supplicant_init_iface分析之三"中提到的
/sys/class/net/wlan0/phy80211/name文件內容有關。driver_nl80211實現了這個函數,
它將返回上面這個文件的內容。
*/
if (!wpa_s->driver->get_radio_name) return;
/*
下面這段代碼的作用主要是看虛擬接口設備(參考4.3.4節中對Virtual Interface的介紹)
是不是使用了同一個物理設備。如果使用了同一個物理設備,則這個虛擬設備獲得的掃描結果可以
和其他虛擬設備共享(避免重復掃描)。本章不對其中細節進行介紹。
*/
rn = wpa_s->driver->get_radio_name(wpa_s->drv_priv);
/*
global指向wpa_global對象,ifaces是wpa_global的成員變量,指向一個wpa_supplicant
對象的隊列,可參考圖4-7和圖4-11。在WPAS中,每一個wpa_supplicant對象會和一個虛擬接口
設備關聯。
*/
for (if s = wpa_s->global->ifaces; ifs; ifs = ifs->next) {
if (ifs == wpa_s || !ifs->driver->get_radio_name)
continue;
rn2 = ifs->driver->get_radio_name(ifs->drv_priv);
if (rn2 && os_strcmp(rn, rn2) == 0) {// 比較兩個虛擬設備對應的物理設備是否為同一個
#ifdef ANDROID_P2P
......
#else // 和其他虛擬設備分享掃描結果
_wpa_supplicant_event_scan_results(ifs, data);
#endif
}
}
}
~~~
上述代碼中,_wpa_supplicant_event_scan_results是核心處理函數并且內容較多,所以下面將分段研究它。
**2、_wpa_supplicant_event_scan_results分析之一**
先來看第一個代碼段,代碼如下所示。
**events.c::_wpa_supplicant_event_scan_results代碼段一**
~~~
#ifdef ANDROID_P2P
static int _wpa_supplicant_event_scan_results(struct wpa_supplicant *wpa_s,
union wpa_event_data *data, int suppress_event)
#else
static int _wpa_supplicant_event_scan_results(struct wpa_supplicant *wpa_s,
union wpa_event_data *data)
#endif
{
struct wpa_bss *selected; struct wpa_ssid *ssid = NULL;
struct wpa_scan_results *scan_res;// 這才是真正的掃描結果
int ap = 0;
......
wpa_supplicant_notify_scanning(wpa_s, 0);
......// 和P2P相關,本章不討論
// 獲得無線網絡掃描結果。就本例而言,前面得到的scan_info信息將傳遞到下面這個函數
scan_res = wpa_supplicant_get_scan_results(wpa_s,data ? &data->scan_info : NULL, 1);
if (scan_res == NULL) {......// 掃描結果為空,重新發起掃描}
#ifndef CONFIG_NO_RANDOM_POOL
/*
把掃描結果中得到的無線網絡頻率、信號強度等信息取出來,然后寫到圖4-1中的crpto模塊。
這樣,能增加隨機數生成的隨機性。WPAS中nonce的生成都會利用隨機數生成器。感興趣的讀者
不妨自行研究相關內容。
*/
......
#endif /* CONFIG_NO_RANDOM_POOL */
~~~
wpa_supplicant_get_scan_results比較重要,我們通過代碼來介紹它。
**scan.c::wpa_supplicant_get_scan_results**
~~~
struct wpa_scan_results * wpa_supplicant_get_scan_results(struct wpa_supplicant *wpa_s,
struct scan_info *info, int new_scan)
{
struct wpa_scan_results *scan_res;
size_t i;
int (*compar)(const void *, const void *) = wpa_scan_result_compar;
/*
調用driver interface的get_scan_results2函數,對nl80211來說,其真實函數是wpa_driver_
nl80211_get_scan_results,它將向wlan driver發送NL80211_CMD_GET_SCAN命令以獲取
掃描結果。另外,wpa_driver_nl80211_get_scan_results中還涉及一個比較有意思的函數
wpa_driver_nl80211_check_bss_status。其作用請讀者利用前面介紹的git blame方法來查詢。
*/
scan_res = wpa_drv_get_scan_results2(wpa_s);
if (scan_res == NULL) {return NULL;} //
#ifdef CONFIG_WPS
......// WPS相關
#endif /* CONFIG_WPS */
/*
對掃描結果進行排序,排序函數是wpa_scan_result_compar,它將根據無線網絡的信噪比(SNR)
進行排序信噪比越高,無線網絡信號越強。注意,除了SNR外,wpa_scan_result_compar還有別
的比較條件,感興趣的讀者可行閱讀該函數。
*/
qsort(scan_res->res, scan_res->num, sizeof(struct wpa_scan_res *),compar);
......
/*
更新WPAS中保存的那些bss信息(即無線網絡信息,每一個無線網絡由wpa_bss表示)。我們在4.3.4節中
曾經介紹過和wpa_bss相關的知識。WPAS維護了一個wpa_bss鏈表,每次掃描時都可能更新這個鏈表
(添加新掃描得到的wpa_bss、刪除某些老舊的wpa_bss)。
*/
wpa_bss_update_start(wpa_s);
for (i = 0; i < scan_res->num; i++)
wpa_bss_update_scan_res(wpa_s, scan_res->res[i]);
/*
這里用上了scan_info。那些沒有包含在本次scan_info中的wpa_bss不用更新。結合4.5.3節
“wpa_supplicant_scan分析之二”中提到的prev_scan_ssid的作用,能想到為什么不更新那些
沒有包含在scan_info中的wpa_bss嗎?
*/
wpa_bss_update_end(wpa_s, info, new_scan);
return scan_res;
}
~~~
:-: 
圖4-32 wpa_scan_res相關數據結構
**3、_wpa_supplicant_event_scan_results分析之二**
接著來看_wpa_supplicant_event_scan_results下一個代碼片段。
**events.c::_wpa_supplicant_event_scan_results代碼段二**
~~~
......// 接_wpa_supplicant_event_scan_results代碼片段一
/*
如果wpa_supplicant對掃描結果有特殊處理,則調用scan_res_handler對應的函數處理。
目前只有P2P情況下scan_res_handler才起作用。
*/
if (wpa_s->scan_res_handler) { ......return 0;}
......// hostapd的情況,本書不討論
#ifdef ANDROID_P2P
if(!suppress_event)
#endif
{ ......// 通知客戶端,掃描結束 }
wpas_notify_scan_done(wpa_s, 1);
if ((wpa_s->conf->ap_scan == 2 && !wpas_wps_searching(wpa_s))) {}
if (wpa_s->disconnected) {......}
/*
還記得4.5.3節“wpa_supplicant_scan分析之一”中的ap_scan變量嗎?下面這個
wpas_driver_bss_selection函數判斷是否由driver來控制無線網絡的選擇。顯然,
本例中它將返回0值。不過,由于本例不支持bgscan(由CONFIG_BGSCAN宏控制),
bgscan_notify_scan返回0。
這樣,下面這個if判斷失敗。
*/
if (!wpas_driver_bss_selection(wpa_s)&&bgscan_notify_scan(wpa_s, scan_res) == 1) {
wpa_scan_results_free(scan_res);
return 0;
}
// 從掃描結果中選擇一個合適的無線網絡
// 注意,在本例中,下面這個函數將返回目標無線網絡對應的wpa_bss對象
selected = wpa_supplicant_pick_network(wpa_s, scan_res, &ssid);
}
~~~
上面這段代碼相對簡單。注意最后一句代碼中調用的wpa_supplicant_pick_network函數。它將根據scan_res(掃描結果)、wpa_bss(代表一個真實BSS的信息)和wpa_ssid(代表用戶設置的某個無線網絡配置項)的匹配情況來選擇合適的無線網絡。匹配檢查包含很多內容,例如ssid是否匹配、安全設置是否匹配、速率是否匹配等。最終,該函數返回一個被選中的目標無
線網絡的wpa_bss對象。
>[info] 提示 wpa_supplicant_pick_network函數實際上檢查的項非常多。由于篇幅問題,本章不能一 一道來,感興趣的讀者請仔細研究。
**4、_wpa_supplicant_event_scan_results分析之三**
接著來看_wpa_supplicant_event_scan_results的第三段代碼。
**events.c::_wpa_supplicant_event_scan_results代碼段三**
~~~
......// 接代碼段二
if (selected) { // 本例中返回的selected無線網絡wpa_bss對象代表目標"Test"無線網絡
int skip;
/*
判斷是否需要漫游。簡單來說,漫游表示需要切換無線網絡。對本例而言,由于之前沒有和AP關聯,
所以此處是需要漫游的(即需要切換無線網絡)。
wpa_supplicant_need_to_roam中還包括對同一個ESS中如何選擇更合適的BSS有一些
簡單的判斷,主要是根據信號強度來選擇。
*/
skip = !wpa_supplicant_need_to_roam(wpa_s, selected, ssid,scan_res);
wpa_scan_results_free(scan_res);
if (skip) {// 無須切換網絡,所以沒有太多要做的工作
wpa_supplicant_rsn_preauth_scan_results(wpa_s);
return 0;
}
// 關聯至目標無線網絡。我們下一節再分析它
if (wpa_supplicant_connect(wpa_s, selected, ssid) < 0) {......}
// 預認證(Pre-Authentication)處理,和802.11中的Fast Transition有關,本書不討論
wpa_supplicant_rsn_preauth_scan_results(wpa_s);
} else { ......// 沒有匹配的無線網絡情況的處理 }
return 0;
}
~~~
到此為止,掃描結果的處理基本完畢,剩下的工作就是通過wpa_supplicant_connect向目標AP發起關聯等請求以加入"Test"無線網絡。
在介紹wpa_supplicant_connect之前,先總結一下掃描結果處理流程。
**5、掃描結果處理流程總結**
掃描結果的處理流程相對比較復雜,如圖4-33所示。其中有幾個函數包含一些非常重要的細節,讀者在研究過程中要特別注意。
* wpa_supplicant_pick_nework:在掃描結果中找到一個最佳的無線網絡。
* wpa_supplicant_need_to_roam:判斷是否需要切換網絡。
* wpa_supplicant_rsn_preauth_scan_results:更新PMKSA緩存信息。其工作和Pre-Authentication有關。不過Pre-Authenticaton真正的實現也需要AP的支持。
:-: 
圖4-33 掃描處理流程
圖4-34所示為筆者通過AirPcap截獲的目標AP發送的Probe Response幀。
:-: 
圖4-34 Probe Response幀內容
請讀者注意圖4-34中所示的RSN信息,它描述了AP所支持的RSN功能。下節介紹WPAS如何關聯到目標無線網絡時將用到它。
#### **三、關聯無線網絡處理流程分析**
關聯無線網絡處理的流程從wpa_supplicant_connect開始,其代碼如下所示。
**events.c::wpa_supplicant_connect**
~~~
int wpa_supplicant_connect(struct wpa_supplicant *wpa_s,struct wpa_bss *selected,
struct wpa_ssid *ssid)
{
// WPS相關,本章不討論
if (wpas_wps_scan_pbc_overlap(wpa_s, selected, ssid)) {......}
/*
4.5.3節介紹的wpa_supplicant_enable_network函數中,reassociate值被設置為1。
當WPAS處于ASSOCIATING狀態時,wpa_s->pending_bssid用于存儲目標網絡的BSSID。
*/
if (wpa_s->reassociate || (os_memcmp(selected->bssid, wpa_s->bssid, ETH_ALEN) != 0 &&
((wpa_s->wpa_state != WPA_ASSOCIATING && wpa_s->wpa_state != WPA_AUTHENTICATING) ||
os_memcmp(selected->bssid, wpa_s->pending_bssid, ETH_ALEN) != 0))) {
// 和EAP-SIM/AKA認證方法有關。在此處初始化相關資源。本章不討論
if (wpa_supplicant_scard_init(wpa_s, ssid)) {
wpa_supplicant_req_new_scan(wpa_s, 10, 0);
return 0;
}
wpa_supplicant_associate(wpa_s, selected, ssid);// 發起關聯操作
} else {......}
return 0;
}
~~~
就本例而言,wpa_supplicant_connect最重要的工作就是觸發STA發起關聯操作。關聯操作通過wpa_supplicant_associate函數來完成。這部分代碼比較復雜,我們分段來看。
**1、wpa_supplicant_associate分析之一**
和發起無線掃描請求一樣,wpa_supplicant_associate函數主要目的就是填充一個用于向wlan driver發起關聯請求的struct wpa_driver_associate_params類型的對象,然后調用driverinterface對應的接口函數。由于關聯時考慮的因素非常多,所以對應的處理也比較煩瑣。本節先介紹第一段內容。
**wpa_supplicant.c::wpa_supplicant_associate代碼段一**
~~~
void wpa_supplicant_associate(struct wpa_supplicant *wpa_s,
struct wpa_bss *bss, struct wpa_ssid *ssid)
{
u8 wpa_ie[200]; size_t wpa_ie_len;
int use_crypt, ret, i, bssid_changed;
int algs = WPA_AUTH_ALG_OPEN; // 認證方法
enum wpa_cipher cipher_pairwise, cipher_group;// 單播數據和組播數據加密方法
struct wpa_driver_associate_params params; // 此函數主要目的是正確填充params的內容
int wep_keys_set = 0; struct wpa_driver_capa capa;
int assoc_failed = 0; struct wpa_ssid *old_ssid;
#ifdef CONFIG_HT_OVERRIDES
......// 802.11n相關的內容
#endif /* CONFIG_HT_OVERRIDES */
#ifdef CONFIG_IBSS_RSN
......// IBSS相關內容
#endif /* CONFIG_IBSS_RSN */
#ifdef ANDROID_P2P
int freq = 0;
#endif
if (ssid->mode == WPAS_MODE_AP || ssid->mode == WPAS_MODE_P2P_GO ||
ssid->mode == WPAS_MODE_P2P_GROUP_FORMATION) { ...... return; }
#ifdef CONFIG_TDLS
......// TDLS是WFA定義的另外一項規范。本書不討論
#endif /* CONFIG_TDLS */
/*
我們曾在4.3.3節功能相關成員變量及背景知識中曾介紹過CONFIG_SME相關的信息。
Galaxy Note 2不支持WPA_DRIVER_FLAGS_SME參數。
*/
if ((wpa_s->drv_flags & WPA_DRIVER_FLAGS_SME) && ssid->mode == IEEE80211_MODE_INFRA) {
sme_authenticate(wpa_s, bss, ssid);
return;
}
os_memset(¶ms, 0, sizeof(params));
wpa_s->reassociate = 0;
// wpas_driver_bss_selection于判斷是否由wlan driver來完成無線網絡選擇,本例它返回FALSE
if (bss && !wpas_driver_bss_selection(wpa_s)) {
#ifdef CONFIG_IEEE80211R
......// 802.11R相關
#endif /* CONFIG_IEEE80211R */
bssid_changed = !is_zero_ether_addr(wpa_s->bssid);
os_memset(wpa_s->bssid, 0, ETH_ALEN);// 設置wpa_supplicant bssid數組成員值為全0
// 將BSSID復制到wpas->pending_bssid中
os_memcpy(wpa_s->pending_bssid, bss->bssid, ETH_ALEN);
if (bssid_changed)
wpas_notify_bssid_changed(wpa_s);
#ifdef CONFIG_IEEE80211R
......// 和802.11R有關
#endif /* CONFIG_IEEE80211R */
#ifdef CONFIG_WPS
} else if (......WPS相關) {
......
#endif /* CONFIG_WPS */
} else os_memset(wpa_s->pending_bssid, 0, ETH_ALEN);
// 取消計劃掃描和普通掃描任務
wpa_supplicant_cancel_sched_scan(wpa_s);
wpa_supplicant_cancel_scan(wpa_s);
/*
清空上一次association時使用的WPA/RSN IE信息,這些信息保存在wpa_supplicant
對象的assoc_wpa_ie(類型為u8*)對應的buffer中。
*/
wpa_sm_set_assoc_wpa_ie(wpa_s->wpa, NULL, 0);
}
~~~
如果不考慮各種編譯宏選項(對WPS、802.11R支持),上述代碼段還算比較簡單。
**2、wpa_supplicant_associate分析之二**
接著來看代碼段二。
**wpa_supplicant.c::wpa_supplicant_associate代碼段二**
~~~
......// 接代碼段一
#ifdef IEEE8021X_EAPOL
// 本例中,為目標無線網絡配置的key_mgmt為SET_NETWORK 0 key_mgmt WPA-PSK
// 通過"SET_NETWORK 0 key_mgmt WPA-PSK"命令來完成
if (ssid->key_mgmt & WPA_KEY_MGMT_IEEE8021X_NO_WPA) {
......// 處理key_mgmt為非WPA的情況}
#endif /* IEEE8021X_EAPOL */
/*
auth_alg為認證方法,可取值有WPA_AUTH_ALG_OPEN、WPA_AUTH_ALG_SHARED等。根據第3章
對WPA的介紹,如果要使用WPA的話,在和AP關聯時必須使用Open System(即WPA_AUTH_ALG_OPEN)。
如果沒有設置該值,其值默認為0。
*/
if (ssid->auth_alg) {......}
/*
下面這個if判斷很重要,請讀者注意。
wpa_bss_get_vendor_ie函數用于獲取wpa_bss中的和vendor相關的IE,此處IE對應的標志是
WPA_IE_VENDOR_TYPE(該值被定義為:#define WPA_IE_VENDOR_TYPE 0x0050f201)。
讀者可參考圖4-34中Probe Response幀中最后一個IE項(Microsoft:WPA IE,
因為MS的OUI是00-50-f2)。注意:該OUI也供WFA定義的幾種規范使用。
wpa_bss_get_ie(bss, WLAN_EID_RSN)) 用于獲取RSN IE。圖4-34中也包含RSN IE。
wpa_key_mgmt_wpa(ssid->key_mgmt)用于判斷無線網絡配置時設置的key_mgmt是否
和WPA相關(本例中,key_mgmt被設為WPA-PSK,它屬于WPA的一種)。
簡而言之,下面這個if條件就是判斷目標無線網絡是否支持WPA/RSN功能,并且無線網絡配置項
是否也設置key_mgmt與WPA相關。很顯然,本例滿足下面這個if條件。
*/
if (bss && (wpa_bss_get_vendor_ie(bss, WPA_IE_VENDOR_TYPE) ||
wpa_bss_get_ie(bss, WLAN_EID_RSN)) && wpa_key_mgmt_wpa(ssid->key_mgmt)) {
int try_opportunistic;
/*
ssid->proto默認值為DEFAULT_PROTO,它由4.5.1節中ADD_NETWORK命令處理的
wpa_config_set_network_defaults函數完成。proactive_key_caching默認值為0。
*/
try_opportunistic = ssid->proactive_key_caching && (ssid->proto & WPA_PROTO_RSN);
/*
下面這個函數用于從pmksa緩存中取出current_ssid對應的pmkid cache項(類型為rsn_
pmksa_cache),然后將其賦值給wpa_sm中的cur_pmksa變量中。此時我們還沒有pmksa
緩存信息,故if條件失敗。
*/
if (pmksa_cache_set_current(wpa_s->wpa, NULL, bss->bssid,wpa_s->current_ssid,
try_opportunistic) == 0){
// 設置eapol_sm的cached_pmk為1(由該函數第二個參數決定),表示要使用pmksa
eapol_sm_notify_pmkid_attempt(wpa_s->eapol, 1);
}
wpa_ie_len = sizeof(wpa_ie);
/*
wpa_supplicant_set_suites函數比較繁瑣,其目的是生成一個用于關聯請求的IE信息。
這些信息包括:group_cipher、pairwise_cipher、key_mgmt。信息的選擇需要考慮AP的情況,
即圖4-34中AP包含的RSN和WPA IE。最終的選擇如下:
proto=WPA_PROTO_RSN、group_cipher=pairwise_cipher=WPA_CIPHER_CCMP
key_mgmt=WPA_KEY_MGMT_PSK
感興趣的讀者不妨自行閱讀wpa_supplicant_set_suites。
*/
if (wpa_supplicant_set_suites(wpa_s, bss, ssid,wpa_ie, &wpa_ie_len)) {......}
} else if (wpa_key_mgmt_wpa_any(ssid->key_mgmt)) {
......
#ifdef CONFIG_WPS
.......// WPS處理
#endif /* CONFIG_WPS */
} else {......}
......// P2P和interworking相關的處理
// 清除wlan driver中的key設置
wpa_clear_keys(wpa_s, bss ? bss->bssid : NULL);
use_crypt = 1;
/*
將WPAS中定義的數據類型轉換成driver wrapper使用的數據類型。例如,WPAS使用WPA_CIPHER_CCMP,
而driver wrapper對應的值為CIPHER_CCMP。
*/
cipher_pairwise = cipher_suite2driver(wpa_s->pairwise_cipher);
cipher_group = cipher_suite2driver(wpa_s->group_cipher);
if (wpa_s->key_mgmt == WPA_KEY_MGMT_NONE ||
wpa_s->key_mgmt == WPA_KEY_MGMT_IEEE8021X_NO_WPA) {......}
if (wpa_s->key_mgmt == WPA_KEY_MGMT_WPS) use_crypt = 0;
#ifdef IEEE8021X_EAPOL
if (wpa_s->key_mgmt == WPA_KEY_MGMT_IEEE8021X_NO_WPA) {
......// 選擇單播和組播數據加密方法}
#endif /* IEEE8021X_EAPOL */
if (wpa_s->key_mgmt == WPA_KEY_MGMT_WPA_NONE) {......}
// 設置wpa_sm的狀態為WPA_ASSOCIATING
wpa_supplicant_set_state(wpa_s, WPA_ASSOCIATING);
}
}
~~~
這段代碼主要是根據AP的情況選擇合適的加密方法及認證方法。雖然目的很簡單,但判斷條件以及相關函數卻比較煩瑣,而且還有相當一部分代碼是針對P2P、WPS等不同情況的處理。建議讀者先了解其目的,以后根據工作需要再來研讀其中的細節。
**3、wpa_supplicant_associate分析之三**
下面來看wpa_supplicant_associate最后一段代碼。
**wpa_supplicant.c::wpa_supplicant_associate代碼段三**
~~~
......// 接代碼段二
if (bss) { // 填充struct wpa_driver_associate_params中的信息
params.ssid = bss->ssid;
params.ssid_len = bss->ssid_len;
if (!wpas_driver_bss_selection(wpa_s)) {
params.bssid = bss->bssid;
params.freq = bss->freq;
}
}......// 其他處理
// 填充參數
params.wpa_ie = wpa_ie; params.wpa_ie_len = wpa_ie_len;
params.pairwise_suite = cipher_pairwise; params.group_suite = cipher_group;
params.key_mgmt_suite = key_mgmt2driver(wpa_s->key_mgmt);
params.wpa_proto = wpa_s->wpa_proto;params.auth_alg = algs;
params.mode = ssid->mode;
for (i = 0; i < NUM_WEP_KEYS; i++) {......// 設置wep key相關信息}
params.wep_tx_keyidx = ssid->wep_tx_keyidx;
......
/*
設置wlan driver是否丟棄未加密數據包。注意,在通過RSN身份驗證前,EAPOL 4-Way Handshake等
EAPOL數據是沒有加密的,所以這個變量只針對非EAPOL/EAP數據包。本例中use_crpyt為1。
*/
params.drop_unencrypted = use_crypt;
#ifdef CONFIG_IEEE80211W
......// 802.11w相關
#endif /* CONFIG_IEEE80211W */
params.p2p = ssid->p2p_group;
/*
uapsd為Unscheduled Automatic Power Save Delivery的縮寫,也稱為WMM power save,
屬于WFA定義的一種標準,和節電有關。
*/
if (wpa_s->parent->set_sta_uapsd)
params.uapsd = wpa_s->parent->sta_uapsd;
else
params.uapsd = -1;
#ifdef ANDROID_P2P
......// P2P相關
#endif
// 調用driver interface的associate函數以發起關聯請求。該函數非常重要,下一節介紹
ret = wpa_drv_associate(wpa_s, ¶ms);
if (ret < 0) {......// 錯誤處理}
if (wpa_s->key_mgmt == WPA_KEY_MGMT_WPA_NONE) {......// 其他處理
#ifdef CONFIG_IBSS_RSN
} else if (......) {......// IBSS相關
#endif /* CONFIG_IBSS_RSN */
} else {
int timeout = 60;// 設置一個超時時間,身份認證必須在60秒內完成
if (assoc_failed) timeout = ssid->mode == WPAS_MODE_IBSS ? 10 : 5;
else if (wpa_s->conf->ap_scan == 1) // 對本例而言,timeout最終取值為10秒
timeout = ssid->mode == WPAS_MODE_IBSS ? 20 : 10;
// 設置一個超時任務:對應函數為wpa_supplicant_timeout,用于處理身份認證超時的情況
wpa_supplicant_req_auth_timeout(wpa_s, timeout, 0);
}
......
if (wpa_s->current_ssid && wpa_s->current_ssid != ssid)
eapol_sm_invalidate_cached_session(wpa_s->eapol);// 設置上一次eapol session無效
old_ssid = wpa_s->current_ssid;
wpa_s->current_ssid = ssid;// 設置wpa_supplicant對象的current_ssid和current_bss變量
wpa_s->current_bss = bss;
// 下面這個函數實際上是將加密/身份驗證信息設置到wpa_sm對應的變量中去
wpa_supplicant_rsn_supp_set_config(wpa_s, wpa_s->current_ssid);
// 配置eapol sm和eap sm。其中:portControl被置為AUTO
// eapSuccess=altAccept=eapFail=altReject=FALSE。這個過程沒有狀態發生變化
wpa_supplicant_initiate_eapol(wpa_s);
if (old_ssid != wpa_s->current_ssid)
wpas_notify_network_changed(wpa_s);
}
~~~
至此,wpa_supplicant_associate函數就分析完畢。讀者如果還記得第3章關于STA加入AP的步驟的話,可能會有一些疑惑。STA加入AP的順序是先發送Authentication請求,然后再發送Association請求。但此處僅調用了wpa_drv_associate函數,似乎忽略了Authentication這一步。該問題的回答將放在下一節,直接來看driver nl80211對應的wpa_driver_nl80211_associate函數。
**4、wpa_driver_nl80211_associate函數分析**
代碼如下所示。
**driver_nl80211.c::wpa_driver_nl80211_associate**
~~~
static int wpa_driver_nl80211_associate(void *priv,
struct wpa_driver_associate_params *params)
{
struct i802_bss *bss = priv;
struct wpa_driver_nl80211_data *drv = bss->drv;
int ret = -1; struct nl_msg *msg;
// 處理AP和IBSS的情況
if (params->mode == IEEE80211_MODE_AP) return wpa_driver_nl80211_ap(drv, params);
if (params->mode == IEEE80211_MODE_IBSS)
return wpa_driver_nl80211_ibss(drv, params);
// 目前大部分手機不支持WPA_DRIVER_FLAGS_SME
if (!(drv->capa.flags & WPA_DRIVER_FLAGS_SME)) {
enum nl80211_iftype nlmode = params->p2p ? // 本例對應的是非p2p情況
NL80211_IFTYPE_P2P_CLIENT : NL80211_IFTYPE_STATION;
// 設置設備類型,可參考4.3.4節wpa_driver_nl80211_finish_drv_init函數分析
if (wpa_driver_nl80211_set_mode(priv, nlmode) < 0) return -1;
return wpa_driver_nl80211_connect(drv, params);
}
/*
注意:根據第3章對STA加入AP操作的描述,STA需要首先發送authentication請求,然后再發送
association請求。在“wpa_supplicant_associate分析之一”中,有一個sme_authenticate
函數,它將發起authentication請求。對于不支持SME的wlan driver來說,WPAS只要向wlan driver
發送connect命令即可完成association和authentication這兩步。這種實現方式和wlan driver
以及cfg80211有關。本書不擬對此展開詳細討論。讀者可在wpa_supplicant官方源碼中利用
"git log a8c5b43a"命令查看相關信息。
*/
......// 對于支持SME的wlan driver,則需要發送NL80211_CMD_ASSOCIATE命令
}
~~~
來看wpa_driver_nl80211_connect函數,其代碼如下所示。
**driver_nl80211.c::wpa_driver_nl80211_connect**
~~~
static int wpa_driver_nl80211_connect(struct wpa_driver_nl80211_data *drv,
struct wpa_driver_associate_params *params)
{
struct nl_msg *msg; enum nl80211_auth_type type;
int ret = 0; int algs;
msg = nlmsg_alloc();
nl80211_cmd(drv, msg, 0, NL80211_CMD_CONNECT);// 構造一個NL80211_CMD_CONNECT命令
NLA_PUT_U32(msg, NL80211_ATTR_IFINDEX, drv->ifindex);// 設置本次命令對應的網絡接口
// 設備索引號填充bssid信息
if (params->bssid) NLA_PUT(msg, NL80211_ATTR_MAC, ETH_ALEN, params->bssid);
if (params->freq) {......// 填充頻率信息}
if (params->ssid) {// 填充ssid信息
NLA_PUT(msg, NL80211_ATTR_SSID, params->ssid_len,params->ssid);
os_memcpy(drv->ssid, params->ssid, params->ssid_len);// 將ssid信息保存起來
drv->ssid_len = params->ssid_len;
}
......
if (params->pairwise_suite != CIPHER_NONE) {
......// 處理pairwise_cipher取值情況
NLA_PUT_U32(msg, NL80211_ATTR_CIPHER_SUITES_PAIRWISE, cipher);
}
......// 其他各項參數處理
// 向wlan driver發送NL80211_CMD_CONNECT命令
ret = send_and_recv_msgs(drv, msg, NULL, NULL);
msg = NULL;
if (ret) {......// 錯誤處理}
ret = 0;
......
return ret;
}
}
~~~
至此,WPAS就把CONNECT請求發給了驅動,驅動將完成Authentication幀和Association Request幀的處理。和scan請求類似,關聯無線網絡處理流程主要是為了填充一個wpa_driver_associate_params類型的對象。不過該對象中各個參數的設置頗有來頭,需要對802.11規范有相當了解才可以做到正確無誤。
**5、關聯無線網絡處理流程總結**
來看一下關聯無線網絡處理流程所涉及的幾個重要函數調用,如圖4-35所示。
:-: 
圖4-35 wpa_supplicant_connect流程
圖4-35中最復雜的就是wpa_supplicant_associate函數,希望讀者能結合代碼進行閱讀。
WPAS成功調用wpa_supplicant_associate后,將等待NL80211_CMD_CONNECT命令的處理結果。該結果由wlan driver通過NL80211_CMD_CONNECT類型的消息返回給driver wrapper。
下面一節將分析WPAS如何處理該消息。
#### **四、關聯事件處理流程分析**
結合上節所述內容,WPAS中的driver wrapper將收到來自wlan driver的NL80211_CMD_CONNECT命令,其對應的處理代碼如下所示。
**driver_nl80211.c::do_process_drv_event**
~~~
static void do_process_drv_event(struct wpa_driver_nl80211_data *drv, int cmd,
struct nlattr **tb)
{
// 曾在4.5.3節“掃描結果處理流程分析”中見過該函數處理NL80211_CMD_NEW_SCAN_RESULTS
......
switch (cmd) {
......
case NL80211_CMD_CONNECT:// wlan driver返回該命令的處理結果
case NL80211_CMD_ROAM:
/*
調用mlme_event_connect進行處理,其中NL80211_ATTR_STATUS_CODE屬性保存了AP的處理結果
(即Association請求的處理結果)、NL80211_ATTR_MAC屬性保存了AP的MAC地址(就是bssid)、
NL80211_ATTR_REQ_IE屬性保存了Association Request請求時包含的IE(STA發送的IE)、
NL80211_ATTR_RESP_IE屬性保存了Association Response幀包含的IE信息(AP發送的IE)。
*/
mlme_event_connect(drv, cmd, tb[NL80211_ATTR_STATUS_CODE],
tb[NL80211_ATTR_MAC], tb[NL80211_ATTR_REQ_IE], tb[NL80211_ATTR_RESP_IE]);
......
}
}
~~~
當wlan driver返回NL80211_CMD_CONNECT命令時,其真正的處理函數實際上是mlme_event_connect,此函數代碼如下所示。
**driver_nl80211.c::mlme_event_connect**
~~~
static void mlme_event_connect(struct wpa_driver_nl80211_data *drv,
enum nl80211_commands cmd, struct nlattr *status, struct nlattr *addr,
struct nlattr *req_ie,struct nlattr *resp_ie)
{
/*
driver wrapper通知WPAS其他模塊時候使用的事件類型。我們曾在4.5.3節“掃描結果處理流程分析”
中對send_scan_event函數分析時見過。
*/
union wpa_event_data event;
if (drv->capa.flags & WPA_DRIVER_FLAGS_SME) {......// wlan driver支持SME時的處理}
os_memset(&event, 0, sizeof(event));
if (cmd == NL80211_CMD_CONNECT && nla_get_u16(status) != WLAN_STATUS_SUCCESS) {
......// 關聯AP失敗的處理
wpa_supplicant_event(drv->ctx, EVENT_ASSOC_REJECT, &event);
return;
}
drv->associated = 1;
if (addr)// 保存bssid
os_memcpy(drv->bssid, nla_data(addr), ETH_ALEN);// 保存bssid信息
if (req_ie) {// 保存Association Request ie信息
event.assoc_info.req_ies = nla_data(req_ie);
event.assoc_info.req_ies_len = nla_len(req_ie);
}
if (resp_ie) {// 保存Association Response ie信息
event.assoc_info.resp_ies = nla_data(resp_ie);
event.assoc_info.resp_ies_len = nla_len(resp_ie);
}
// 通過發送NL80211_GET_SCAN命令獲取STA的工作頻率
event.assoc_info.freq = nl80211_get_assoc_freq(drv);
// 重要函數,見下文分析
wpa_supplicant_event(drv->ctx, EVENT_ASSOC, &event);
}
~~~
wpa_supplicant_event中處理EVENT_ASSOC消息的是wpa_supplicant_event_assoc,下面將分段介紹它。
**1、wpa_supplicant_event_assoc分析之一**
代碼如下所示。
**events.c::wpa_supplicant_event_assoc代碼段一**
~~~
static void wpa_supplicant_event_assoc(struct wpa_supplicant *wpa_s,
union wpa_event_data *data)
{
u8 bssid[ETH_ALEN]; int ft_completed;
int bssid_changed; struct wpa_driver_capa capa;
......// CONFIG_AP處理
// 判斷Fast Transition是否完成,由于本例沒有設置CONFIG_80211R宏,所以下面這個函數返回0
ft_completed = wpa_ft_is_completed(wpa_s->wpa);
/*
在4.5.3節“wpa_supplicant_associate分析之一”中,wpa_supplicant對象的assoc_wpa_ie
被清空。此處需要保存這些IE信息。wpa_supplicant_event_associnfo比較煩瑣,主要是更新RSN/WPA
IE信息,請感興趣的讀者自行閱讀。
*/
if (data && wpa_supplicant_event_associnfo(wpa_s, data) < 0) return;
wpa_supplicant_set_state(wpa_s, WPA_ASSOCIATED);// 設置wpa_sm裝為WPA_ASSOCIATED
// 從driver wrapper中獲得bssid信息
if (wpa_drv_get_bssid(wpa_s,bssid)>=0&&os_memcmp(bssid,wpa_s->bssid,ETH_ALEN)!=0){
random_add_randomness(bssid, ETH_ALEN); // 和隨機數相關
bssid_changed = os_memcmp(wpa_s->bssid, bssid, ETH_ALEN);
os_memcpy(wpa_s->bssid, bssid, ETH_ALEN); // 設置bssid
os_memset(wpa_s->pending_bssid, 0, ETH_ALEN); // 清空pending_bssid
if (bssid_changed)
wpas_notify_bssid_changed(wpa_s);
// 和動態WEP Key有關,本書不討論
if (wpa_supplicant_dynamic_keys(wpa_s)&&!ft_completed)wpa_clear_keys(wpa_s, bssid);
/*
就本例而言(ap_scan為1,并且current_ssid不為空),下面這個函數作用不大。對其他情況而言,
該函數負責的工作我們在前面都見識過了。
*/
if (wpa_supplicant_select_config(wpa_s) < 0) {......// 錯誤處理}
/*
這個if條件對應的代碼段讓筆者非常困惑,因為在4.5.3節“wpa_supplicant_associate
分析之三”的最后已經為wpa_s->current_bss賦值了。此處為何又再賦值?通過筆者實測,bss
和current_bss的值是相同的。添加這段代碼的人也沒有說明為什么(讀者不妨參考“git show
8f770587”命令的結果)。有知曉其來龍去脈的讀者不妨和大家分享相關知識。
*/
if (wpa_s->current_ssid) {
struct wpa_bss *bss = NULL;
bss = wpa_supplicant_get_new_bss(wpa_s, bssid);
if (!bss) {
wpa_supplicant_update_scan_results(wpa_s);
bss = wpa_supplicant_get_new_bss(wpa_s, bssid);
}
if (bss)
wpa_s->current_bss = bss;
}
if (wpa_s->conf->ap_scan==1&&wpa_s->drv_flags&WPA_DRIVER_FLAGS_BSS_SELECTION){
......// wlan driver支持BSS SELECTION功能時對應的處理
}
}
~~~
wpa_supplicant_event_assoc函數的歷史比較悠久(根據git commit信息,它從0.6.3版本開始就存在。而實際歷史可能還要更久,因為WPAS從0.6.3版本后才開始使用git來管理代碼)。在筆者研究的WPAS相關代碼中,wpa_supplicant_event_assoc函數算是相當復雜的了。其中一個主要原因是該函數內部有很多細節需要考慮,有些細節甚至是為了解決某個bug。對于初學者而言,筆者建議可只關注該函數涉及的主要流程。
**2、wpa_supplicant_event_assoc分析之二**
接下來分析該函數最后一段代碼。
**events.c::wpa_supplicant_event_assoc代碼段二**
~~~
......// 接代碼段一
#ifdef CONFIG_SME
......// 支持SME的情況
#endif /* CONFIG_SME */
if (wpa_s->current_ssid) {
/*
初始化SIM/USIM卡。根據代碼中的注釋:在ap_scan為1的情況下,該函數在此之前就被調用過。
而其他情況下,需要在此處調用它。在4.5.3節關聯無線網絡處理流程分析中見過此函數。
*/
wpa_supplicant_scard_init(wpa_s, wpa_s->current_ssid);
}
wpa_sm_notify_assoc(wpa_s->wpa, bssid);
// 初始化wpa_sm中和后續EAPOL-Key交換相關聯的變量
/*
wpa_s->l2在4.3.4節wpa_supplicant_driver_init函數分析中通過l2_packet_init函數調用被賦值。
所以下面這個if條件為真。對Linux平臺來說,l2_packet_notify_auth_start
實際上是一個空函數。
*/
if (wpa_s->l2) l2_packet_notify_auth_start(wpa_s->l2);
if (!ft_completed) {// 設置EAPOL相關外部變量
eapol_sm_notify_portEnabled(wpa_s->eapol, FALSE);
eapol_sm_notify_portValid(wpa_s->eapol, FALSE);
}
// 本例采用的就是WPA-PSK認證算法
if (wpa_key_mgmt_wpa_psk(wpa_s->key_mgmt) || ft_completed)
eapol_sm_notify_eap_success(wpa_s->eapol, FALSE);
// 設置EAPOL模塊中的portEnabled為TRUE,將狀態機一系列動作。詳情見下文
eapol_sm_notify_portEnabled(wpa_s->eapol, TRUE);
wpa_s->eapol_received = 0;
if (......){......}
} else if (!ft_completed) {
wpa_supplicant_req_auth_timeout(wpa_s, 10, 0);// 重新注冊一個認證超時任務
}
wpa_supplicant_cancel_scan(wpa_s);// 取消scan任務
......// 處理wlan driver支持WPA_DRIVER_FLAGS_4WAY_HANDSHAKE功能以及ft_complete為1時的情況
if (wpa_s->pending_eapol_rx) {
/*
pending_eapol_rx變量對應如下的應用場景。
有時候在WPAS收到來自wlan driver的EVENT_ASSOC事件之前(即wpa_supplicant_event_
assoc被調用之前),AP就發送EAPOL消息過來(后文會分析相關代碼)以觸發認證流程。而由于
WPAS還未處理EVENT_ASSOC事件。所以,EAPOL消息會先保存到pending_eapol_rx中,直到
wpa_supplicant_event_assoc函數中再來處理(即收到了EVENT_ASSOC事件)。筆者測試過
程中發現有些AP會出現這種情況。
*/
}
......// WEP的情況
......// IBSS的情況
}
~~~
wpa_supplicant_event_assoc函數終于分析完畢。正如上一節最后所提到的那樣,該函數實際上非常復雜,細節內容很難在本章篇幅內一一覆蓋。同時,對初學者來說,流程比細節更重要,所以可先略過這些細節。
上述代碼中,eapol_sm_notify_portEnabled被調用以設置portEnable為TRUE。EAPOL模塊包含眾多SM,它們是否會因為這個變量的變化而隨之發生狀態改變呢?來看下節。
**3、eapol_sm_notify_portEnabled函數**
eapol_sm_notify_portEnabled的代碼如下所示。
**eapol_supp_sm.c::eapol_sm_notify_portEnabled**
~~~
void eapol_sm_notify_portEnabled struct eapol_sm *sm, Boolean enabled)
{
......
sm->portEnabled= enabled;// 設置portEnabled變量為TRUE
eapol_sm_step(sm); // 狀態機聯動。這部分代碼在4.4.2節介紹“狀態機聯動”時出現過
}
~~~
eapol_sm_step將依次更新SUPP_PAE、KEY_RX、SUPP_BE和EAP_SM狀態信息。在該函數
執行之前,這四個狀態機的狀態依次如下。
* SUPP_PAE為DISCONNECTED狀態;
* KEY_RX為NO_KEY_RECEIVE狀態;
* SUPP_BE為IDLE狀態;
* EAP_SM為DISABLED狀態。
根據代碼(eapol_supp_sm.c中SM_STEP(SUPP_PAE))以及圖4-28可知,SUPP_PAE下一個要進入的狀態是CONNECTING,其EA(Entry Aciton)代碼如下。
**eapol_supp_sm.c::SM_STATE(SUPP_PAE,CONNECTING)**
~~~
SM_STATE(SUPP_PAE, CONNECTING)
{
// SUPP_PAE_state此時的值為SUPP_PAE_DISCONNECTED,故send_start為0
int send_start = sm->SUPP_PAE_state == SUPP_PAE_CONNECTING;
SM_ENTRY(SUPP_PAE, CONNECTING);
if (send_start) {
sm->startWhen = sm->startPeriod;
sm->startCount++;
} else {
#ifdef CONFIG_WPS
sm->startWhen = 1;
#else /* CONFIG_WPS */
sm->startWhen = 3; // 設置startWhen值為3
#endif /* CONFIG_WPS */
}
eapol_enable_timer_tick(sm); // 使能Port Timers SM
sm->eapolEap = FALSE;
if (send_start) eapol_sm_txStart(sm); // 發送EAPOL Start包。此時該函數不會被調用
}
~~~
讀者可參考代碼以及狀態機示意圖以了解其他狀態機的切換。該函數執行完畢后,各個狀態機的變化如下。
* SUPP_PAE進入CONNECTING狀態;
* KEY_RX不變(NO_KEY_RECEIVE);
* SUPP_BE進入IDLE狀態;
* EAP_SM不變(DISABLED狀態)。
>[info] 注意 根據圖4-21關于EAP SUPP_SM的描述,當portEnabled值為TRUE時,應該從DISABLED狀態切換至INITIALIZE狀態。不過,在4.5.3節“wpa_supplicant_associate分析之三”中曾調用過wpa_supplicant_initiate_eapol函數。在該函數中,由于本例使用的認證算法是WPA-PSK,所以force_disabled變量為TRUE,導致EAP SUPP SM不能轉換至INITIALIZE狀態。(參考eap.c::SM_STEP(EAP)函數中第一個else if判斷。)
**4、關聯事件處理流程總結**
關聯事件處理流程中涉及的重要函數調用如圖4-36所示。注意,wpa_supplicant_event_assoc中略去了部分細節代碼。請讀者清楚圖中的幾個重要函數后,再根據本節所述內容研究這些細節。
至此,WPAS已經和目標AP完成了Association操作,接來下將進入802.1X身份認證流程。根據圖3-44所示,接下來的工作將是4-Way Handshake和Group Key Handshake的處理。
:-: 
圖4-36 NL80211_CMD_CONNECT處理流程
#### **五、EAPOL-Key交換流程分析**
對本例而言,本節所說的EAPOL-Key交換包括4-Way Handshake和Group Key Handshake過程(注意,由于采用的是PSK認證方式,故本節分析的交互流程將不涉及STA和AuthenticatorServer開展的身份認證的流程)。
首先發起的是4-Way Handshake Key交換。對于WPA-PSK認證方法來說,STA不會發送EAPOL-Start消息給AP。根據筆者的測試,完成關聯操作后,Galaxy Note 2會發送一個Null function(沒有實際數據)的數據包給AP(如圖4-37所示),而AP接收到該消息后發現STA還未通過認證,所以它就會被觸發以開始4-Way Handshake流程(參考圖3-46的左圖)。
:-: 
圖4-37 Null function數據包
由圖4-37可知,Galaxy Note 2向AP發送一個Null function數據包后,AP就開始了4-Way Handshake流程。它首先發送一個EAPOL幀給STA(圖4-37中"Message 1 of 4"這一項)。我們在4.3.4節的最后曾提到l2_packet模塊(參考圖4-1)用來接收PACKET類型socket數據的函數是wpa_supplicant_rx_eapol。實際上,該函數就是WPAS中用來接收EAP/EAPOL數據包的。所以,wpa_supplicant_rx_eapol將處理AP發送過來的EAPOL幀。馬上來看此函數,代碼如下所示。
**wpa_supplicant.c::wpa_supplicant_rx_eapol**
~~~
void wpa_supplicant_rx_eapol(void *ctx, const u8 *src_addr,
const u8 *buf, size_t len)
{
struct wpa_supplicant *wpa_s = ctx;
/*
讀者還記得4.5.3節“wpa_supplicant_event_assoc分析之二”最后關于pending_eapol_rx
的解釋嗎?當wpa_state狀態不為WPA_ASSOCIATED的時候,如果收到AP發來的數據包,則先保存
起來,然后留待wpa_supplicant_event_assoc中去處理。
*/
if (wpa_s->wpa_state < WPA_ASSOCIATED) {
wpabuf_free(wpa_s->pending_eapol_rx);
wpa_s->pending_eapol_rx = wpabuf_alloc_copy(buf, len);// 復制數據
if (wpa_s->pending_eapol_rx) {
os_get_time(&wpa_s->pending_eapol_rx_time);
os_memcpy(wpa_s->pending_eapol_rx_src, src_addr,ETH_ALEN);
}
return;
}
......// CONFIG_AP處理
if (wpa_s->key_mgmt == WPA_KEY_MGMT_NONE) { return; }
/*
eapol_received用于記錄收到的EAPOL/EAP包個數。初次進來,需要設置認證超時任務(這個
任務已經設置過多次了,不過wpa_supplicant_req_auth_timeout會先取消上一次設置的認證超時
任務,然后再設置新的)。
*/
if (wpa_s->eapol_received == 0 &&
(!(wpa_s->drv_flags & WPA_DRIVER_FLAGS_4WAY_HANDSHAKE) ||
!wpa_key_mgmt_wpa_psk(wpa_s->key_mgmt) || wpa_s->wpa_state != WPA_COMPLETED) &&
(wpa_s->current_ssid == NULL || wpa_s->current_ssid->mode != IEEE80211_MODE_IBSS)
) {
wpa_supplicant_req_auth_timeout(wpa_s,
(wpa_key_mgmt_wpa_ieee8021x(wpa_s->key_mgmt) ||
wpa_s->key_mgmt == WPA_KEY_MGMT_IEEE8021X_NO_WPA ||
wpa_s->key_mgmt == WPA_KEY_MGMT_WPS) ? 70 : 10, 0);
}
wpa_s->eapol_received++;
if (wpa_s->countermeasures) { return; }
......// CONFIG_IBSS_RSN處理
os_memcpy(wpa_s->last_eapol_src, src_addr, ETH_ALEN);
/*
對于非PSK認證方法(如802.1X認證),EAPOL/EAP幀由eapol_sm_rx_eapol進行處理。
另外,與四次握手和組播密鑰交換相關的EAPOL-Key幀也不在eapol_sm_rx_eapol中處理。
*/
if (!wpa_key_mgmt_wpa_psk(wpa_s->key_mgmt) &&
eapol_sm_rx_eapol(wpa_s->eapol, src_addr, buf, len) > 0) return;
/*
driver_nl80211沒有實現poll函數。該函數的目的是為了從驅動中獲取Association過程中涉及
的IE信息對于nl80211來說,關聯完成后,我們就已經收到了這些信息,所以不需要再次獲取它們了。
*/
wpa_drv_poll(wpa_s);
if (!(wpa_s->drv_flags & WPA_DRIVER_FLAGS_4WAY_HANDSHAKE))
wpa_sm_rx_eapol(wpa_s->wpa, src_addr, buf, len);// 關鍵函數。見下文分析
else if (wpa_key_mgmt_wpa_ieee8021x(wpa_s->key_mgmt)) {
eapol_sm_notify_portValid(wpa_s->eapol, TRUE);
}
}
~~~
wpa_sm_rx_eapol是本節的重點。在介紹之前,先來介紹EAPOL-Key交換的背景知識。一方面對3.3.7節RSNA介紹的補充,另一方面該背景知識對我們理解wpa_sm_rx_eapol函數也有重要幫助。
**1、背景知識介紹[22][23]**
由3.3.7節可知,RSNA過程中有兩組Key需要交換和派生。
* PTK(Pairwise Transient Key,成對臨時密鑰)用于加解密單播數據。它從PMK(Pairwise Master Key)派生(derive)而來。對于SOHO網絡環境來說,PMK就是PSK。它由用戶設置的passphrase擴展而來(請讀者參考4.5.2節中wpa_config_update_psk)。AP和STA需要通過4-Way Handshake協議交換和派生PTK。
* GTK(Group Transient Key,組臨時密鑰)用于加解密組播數據。它由GMK(Group MasterKey,其來源由AP或Authenticator Server決定)派生而來。AP和STA通過Group Key Handshake協議交換和派生GTK。Group Key Handshake必須在完成4-Way Handshake之后才能開展。
提示 Group Key Handshake對應的場景比較特殊。對組播數據而言,AP和所有和它關聯的STA使用同一個GTK。不過,如果中途有STA離開(取消和AP的關聯)的話,從安全角度考慮,AP和剩下的STA之間最好更換一個新的GTK。AP將根據情況來觸發Group Key Handshake流程。另外,AP可通過4-Way Handshake告知STA其當前使用的GTK。這種情況下,AP和STA之間
無須開展Group Key Handshake流程。
下面,通過實例來介紹4-Way Handshake涉及的四次幀交換以及其中包含的數據信息。
* 第一個EAPOL-Key幀(以后簡稱Message A)由AP發送給STA,其內容如圖4-38所示。Key Descriptor Type及以下的內容屬于Key Descriptor。Key Descriptor用來描述EAPOL-Key幀的Key信息,由多個字段組成。
* 第一個字段是Key Descriptor Type,目前取值有三個,分別是RC4、WPA和RSN(即WPA2),802.11中可使用后兩個。
* Key Information字段(2字節)包含多個標志位。下文將一一介紹它們。
* Key Length字段(2字節)表示PTK密鑰的長度。對CCMP來說,它是16字節(128位),而對TKIP來說,其長度是32字節(256位)。讀者可參考圖3-47中的CCMP-TK、TKIP-TK和TKIP-MIC Key。CCMP和TKIP長度不一致的原因是CCMP可用一個Key完成數據加解密和MIC校驗,而TKIP使用不同的Key來完成加解密以及MIC校驗。
* Replay Counter字段(8字節)和防止重放攻擊有關。一個簡單的應用場景為STA之前收到過Replay Counter為1的包。假如它又收到一個Replay Counter為0的包,則認為發生了重放攻擊,STA將丟棄Replay Counter為0的包。對Message A來說,該值必須為0。
* Key Nonce字段(32字節)用于存儲Nonce值。對Message A來說,此Nonce值由AP生成,所以也叫ANonce。
* Key IV字段(16字節)表示初始向量,用于Key生成。Message A中該字段為全0。
* Key RSC字段(8字節)是Key Receive Sequence Counter的縮寫,也和重放攻擊有關。該字段用于四次握手的第三幀以及組播Key握手的第一幀中。
* Key ID字段(8字節)對WPA/RSN來說,該字段未使用。
* Key MIC字段(可變字節長度)表示存儲MIC數據,其長度和具體的算法有關。筆者查詢了802.11文檔[22],規范中列出的幾種算法對應的MIC長度都是16字節。
* Key Data Length字段(2字節)表示Key Data長度。圖4-38沒有攜帶Key Data,所以該項為0。如果該項不為0,Key Descriptor還將在Key Data Length后添加一個"Key Data"項。
:-: 
圖4-38 Message A的內容
圖4-38中,Key Information字段的內容如圖4-39所示。
:-: 
圖4-39 Key Information字段
如圖4-39所示:
* Key Descriptor Version:用于指示Key Descriptor的版本號。當值為2時,表示Key Descriptor的Key Data用AES加密,而Key MIC由HMAC-SHA1算法計算而來。
* Key Type:值為1,表示該幀用于PTK派生,值為0表示該幀用于GTK派生。
* Reserved:該字段必須為0(圖4-38中,該字段也被稱為Key Index)。
* Install:當Key Type為1時,Install值為1表示STA需要安裝PTK(將PTK傳給驅動,下文源碼分析時將看到相關函數)。Key Type為0時,Install必須為0。
* Key ACK:AP發送EAPOL-Key幀給STA時,如果需要STA發送回復數據,則設置其為1。
* Key MIC:如果EAPOL-Key幀包含MIC信息,其值被設為1,否則為0。
* Secure:當STA或AP派生出了PTK或GTK后,STA或AP發出的EAPOL-Key幀將該值設為1。
* Error:使用TKIP時,如果STA檢查到MIC錯誤,則設置該值為1。對于其他情況下的MIC錯誤,該值和Request都必須被設為1。
* Request:STA請求AP發起4-Way Handshake(Key Type同時被設為1)或者Group KeyHandshake(Key Type同時被設為0)流程時,其值被設為1。或者和Error都被設為1以報告MIC錯誤。
* Encrypted Key Data:表示Key Data是否加密。
* SMK:Station-to-station link Master Key的縮寫,是另外一種Key交換協議。本書不討論。同上述介紹,相信讀者對Key Descriptor有了一定的了解。現在馬上來介紹四次握手協議中每個EAPOL-Key幀所包含的內容以及接收方的處理邏輯。
Message A由AP發送給STA,其內容如下。
* Key Info:Error位為0,因為這是第一幀數據。Secure位為0表示該幀沒有加密的數據。KeyMIC為0表示該幀不包含MIC數據。Key ACK位為1表示AP要求STA回復此幀。Key Install為0表示現在還無法安裝PTK。Key Type設為1表示當前是Pairwise密鑰派生。本例中,Key DescriptionVersion值為2。
* Replay Counter:本例中它被設為1。STA需要保存這個值以檢測重放攻擊。
* Key Nonce:AP生成的隨機數,也叫ANonce。
* 由于本幀不包含Key信息,所以其他字段都設為0。
提醒 802.11規范中指出,Message A的Key Data可以攜帶PMKID信息(包含在RSN IE或其他廠商自定義的IE中)。不過本例中,Message A沒有攜帶它。
STA收到Message A的處理邏輯如下。
1. 生成一個Nonce,也叫SNonce。
2. 派生PTK。
3. 構造第二個EAPOL-Key幀,稱為Message B。
圖4-40為Message B的截圖。
:-: 
圖4-40 Message B的內容
如圖4-40所示:
* 由Key Info的設置可知該幀包括MIC數據(Key MIC位為1)。
* Replay Counter的值必須等于Message A中的Replay Counter。
* Key MIC是對整個EAPOL-Key幀進行計算而來,計算方法由Key Descriptor Version指定(圖4-40中的HMAC-SHA1 MIC方法)。
* Key Data包含一個RSN Information Element。該RSN IE來自STA之前和AP在關聯操作時獲得的RSN IE。規范沒有說明為何此處要包含RSN IE。不過[23]倒是有一句簡單的說法。即為了防止STA中途更改安全參數。
* AP收到Message B的處理如下。
* 派生PTK。
* 檢查Message B的MIC值是否正確。如果發現MIC錯誤,則丟棄(而且是silently)MessageB。
這種情況下,4-Way Handshake流程已經被中斷。如果MIC正確,AP將構造第三個EAPOLKey 幀,此處稱為Message C,其內容如圖4-41所示。
:-: 
圖4-41 Message C的內容
由圖4-41可知:
* Install為1,表示STA收到該幀后就可以安裝PTK了。Secure為1表示AP已經派生了PTK。Encrpted Key Data為1,表示Key Data被加密了。
* Replay Counter為2,比前面的值增加1。
* Key Nonce值和Message A的一樣。
* MIC對EAPOL-Key整個進行計算得來。
* Key IV為0。注意,如果Key Descriptor Version為2時,該字段必須為0。否則可以是其他隨機數。
* Key Data由PTK加密后而來。其解密后的內容包括AP在Probe Response或Beacon幀包含RSN IE信息,另外還有可能包括GTK信息。如果有GTK的話,4-Way Handshake完畢后就無須開展Group Key Handshake流程。
STA收到Message C的處理如下。
1. 檢查Replay Counter,計算MIC以及利用自己的PTK解密Key Data以獲取RSN IE。
2. 如果Message C包含GTK信息,則取出GTK。注意,GTK以及和Key相關的信息存儲在KDE(Key Data Element)的IE中。關于KDE和GTK的格式,請讀者繼續閱讀下文。
3. 構造并發送最后一個EAPOL-Key幀,稱為Message D。
4. 為Driver安裝PTK。
Message D的內容如圖4-42所示。由圖可知,Replay Counter和Message C一樣。MIC通過對EAPOL-Key整個進行計算得來。
AP收到Message D的處理如下。
1. 再次計算MIC,如果正確則為driver安裝PTK。
2. 更新Replay Counter,如果以后需要更新Key,則使用一個不同的Replay Counter。
至此,4-Way Handshake成功完成,而AP和STA以后的單播數據將全部通過PTK進行加密。這就使得我們無法通過AirPcap解析Group Key Handshake數據包。關于Group Key Handshake的流程請看參考資料[22][23]。
:-: 
圖4-42 Message D的內容
根據前文所述,AP在Message C中可以攜帶GTK信息。這樣4-Way Handshake完畢后,STA無須開展Group Key Handshake。
> 注意 經過測試,有些AP每次在4-Way Handshake完畢后就發起Group Key Handshake,而有些AP在Message C中直接包含了GTK,這樣就避免了Group Key Handshake。接下來的代碼分析以后一種情況為目標。
另外,請注意圖3-47,該圖展示了PTK的組成。以CCMP為例,PTK包含三個部分,分別是KCK、KEK和TK。其中KCK用來處理MIC字段,KEK用來處理Key Data字段,而TK則用于握手協議完畢后的數據加密。
介紹完背景知識后,馬上來研究wpa_sm_rx_eapol函數,它是不是遵守了規范所描述的流程呢?
**2、wpa_sm_rx_eapol函數**
在分析代碼之前,先介紹兩個重要的數據結構,如圖4-43所示。
* struct ieee802_1x_hdr為EAPOL幀頭信息。
* struct wpa_eapol_key為EAPOL-Key幀的數據信息。
:-: 
圖4-43 EAPOL-Key幀對應的數據結構
wpa_sm_rx_eapol代碼如下所示。
**wpa.c::wpa_sm_rx_eapol**
~~~
int wpa_sm_rx_eapol(struct wpa_sm *sm, const u8 *src_addr,
const u8 *buf, size_t len)
{
size_t plen, data_len, extra_len;
struct ieee802_1x_hdr *hdr; struct wpa_eapol_key *key;
u16 key_info, ver; u8 *tmp; int ret = -1;
struct wpa_peerkey *peerkey = NULL;
......// 參數檢查
tmp = os_malloc(len); os_memcpy(tmp, buf, len);
hdr = (struct ieee802_1x_hdr *) tmp;
key = (struct wpa_eapol_key *) (hdr + 1);
plen = be_to_host16(hdr->length);
data_len = plen + sizeof(*hdr);
// 檢查EAPOL-Key幀的類型
if (hdr->version < EAPOL_VERSION) { }
if (hdr->type != IEEE802_1X_TYPE_EAPOL_KEY)// 本函數只處理EAPOL-Key幀
{ ret = 0; goto out;}
if (key->type != EAPOL_KEY_TYPE_WPA && key->type != EAPOL_KEY_TYPE_RSN) {
ret = 0; goto out; // 只處理key類型為WPA和RSN的情況
}
/*
通知lowerLayerSuccess。該函數最后一個參數表示此次調用是否來自EAPOL模塊內部,
由于wpa_sm_rx_eapol并非EAPOL模塊內部,所以該參數為0。
*/
eapol_sm_notify_lower_layer_success(sm->eapol, 0);
// 取出Key Information字段,并保存到key_info變量中
key_info = WPA_GET_BE16(key->key_info);
ver = key_info & WPA_KEY_INFO_TYPE_MASK; // 獲取key descriptor version
if (ver != WPA_KEY_INFO_TYPE_HMAC_MD5_RC4 && ver !=
WPA_KEY_INFO_TYPE_HMAC_SHA1_AES)
goto out; // 根據規范的要求做相應處理
......// CONFIG_IEEE80211R和CONFIG_IEEE80211W的處理
// 加密算法兼容性檢查
if (sm->pairwise_cipher==WPA_CIPHER_CCMP&&ver!=
WPA_KEY_INFO_TYPE_HMAC_SHA1_AES){
// 下面這個if判斷用于檢查組播數據加密設置是否正確
if(sm->group_cipher!=WPA_CIPHER_CCMP&&!(key_info&WPA_KEY_INFO_KEY_TYPE)){
......// 打印一句兼容性警告
} else goto out;
}
......// CONFIG_PEERKEY處理。PeerKey對應peerkey handshake流程,它和IEEE802.11e DLS有關
// 檢查Replay Counter,如果收到的Replay Counter比之前接收的值小,則丟棄該幀
if (!peerkey && sm->rx_replay_counter_set && os_memcmp(key->replay_counter,
sm->rx_replay_counter, WPA_REPLAY_COUNTER_LEN) <= 0) goto out;
// STA收到的EAPOL-Key幀必須設置ACK位或SMK位為1
if (!(key_info & (WPA_KEY_INFO_ACK | WPA_KEY_INFO_SMK_MESSAGE)) ) goto out;
// 只有STA向AP發送的EAPOL-Key幀才能設置Request位
if (key_info & WPA_KEY_INFO_REQUEST) goto out;
/*
MIC位不為0,則需要解析MIC數據。對STA來說,只有Message C會攜帶MIC信息。
wpa_supplicant_verify_eapol_key_mic比較簡單,請讀者自行閱讀它。其功能是:STA
根據KCK計算MIC,然后將其和接收到的MIC進行比較。如果MIC值檢查正常,則PTK正確。
*/
if ((key_info & WPA_KEY_INFO_MIC) && !peerkey &&
wpa_supplicant_verify_eapol_key_mic(sm, key, ver, tmp, data_len)) goto out;
extra_len = data_len - sizeof(*hdr) - sizeof(*key);
if (WPA_GET_BE16(key->key_data_length) > extra_len) goto out;// 參數檢查
extra_len = WPA_GET_BE16(key->key_data_length);
// Encrpted Key Data位為1,表示該幀包含加密數據,需要利用KEK進行解密
// 該函數也比較簡單,請讀者自行閱讀
if (sm->proto == WPA_PROTO_RSN && (key_info & WPA_KEY_INFO_ENCR_KEY_DATA)) {
if (wpa_supplicant_decrypt_key_data(sm, key, ver)) goto out;
extra_len = WPA_GET_BE16(key->key_data_length);
}
// Key Info Type值為1,表示為Pairwise Key。值為0表示Group Key
if (key_info & WPA_KEY_INFO_KEY_TYPE) {
if (key_info & WPA_KEY_INFO_KEY_INDEX_MASK) goto out;
if (peerkey) { peerkey_rx_eapol_4way(sm, peerkey, key, key_info, ver);
} else if (key_info & WPA_KEY_INFO_MIC) {// 對STA而言,只有Message C包含MIC信息
wpa_supplicant_process_3_of_4(sm, key, ver);// 處理Message C
} else {// 處理Message A
wpa_supplicant_process_1_of_4(sm, src_addr, key, ver);
}
} else if (key_info & WPA_KEY_INFO_SMK_MESSAGE) {// SMK的情況
peerkey_rx_eapol_smk(sm, src_addr, key, extra_len, key_info, ver);
} else {// Group Key Handshake處理
if (key_info & WPA_KEY_INFO_MIC) {// 組播密鑰交換。本章不討論此函數
wpa_supplicant_process_1_of_2(sm, src_addr, key,extra_len, ver);
} else ......// 打印一句警告
}
ret = 1;
out:
os_free(tmp);
return ret;
}
~~~
wpa_sm_rx_eapol的流程比較簡單,就是先處理EAPOL-Key的基本信息(如計算MIC、解密Key Data),然后根據情況處理MessageA、Message C或者Group Key Handshake的Message 1。
由于本例不涉及Group Key Handshake流程,所以下面將介紹Message A及Message C的處理過程。
**3、wpa_supplicant_process_1_of_4函數**
wpa_supplicant_process_1_of_4用于處理Message A,其代碼如下所示。
**wpa.c::wpa_supplicant_process_1_of_4**
~~~
static void wpa_supplicant_process_1_of_4(struct wpa_sm *sm,
const unsigned char *src_addr,
const struct wpa_eapol_key *key, u16 ver)
{
struct wpa_eapol_ie_parse ie; struct wpa_ptk *ptk;
u8 buf[8]; int res;
if (wpa_sm_get_network_ctx(sm) == NULL) {......// 錯誤處理}
wpa_sm_set_state(sm, WPA_4WAY_HANDSHAKE); // 設置狀態為WPA_4WAY_HANDSHAKE
os_memset(&ie, 0, sizeof(ie));
#ifndef CONFIG_NO_WPA2
if (sm->proto == WPA_PROTO_RSN) {
const u8 *_buf = (const u8 *) (key + 1);// buf中是已經解密的Key Data數據
size_t len = WPA_GET_BE16(key->key_data_length);
// 解析Message A中包含的RSN信息。對本例而言,Message A中沒有RSN信息
if (wpa_supplicant_parse_ies(_buf, len, &ie) < 0) goto failed;
}
#endif /* CONFIG_NO_WPA2 */
// 如果根據RSN IE中的pmkid判斷是否有PMKSA緩存項。本例沒有RSN IE,所以不存在ie.pmkid
res = wpa_supplicant_get_pmk(sm, src_addr, ie.pmkid);
if (res == -2) return;
if (res)
goto failed;
// 結合前面對背景知識的描述,STA需要創建自己的Nonce
if (sm->renew_snonce) {
if (random_get_bytes(sm->snonce, WPA_NONCE_LEN)) goto failed;
sm->renew_snonce = 0;
}
/*
tptk變量存儲的是臨時PTK,因為現在還不確定PTK是否正確。注意,當收到Message C時,
wpa_sm_rx_eapol的wpa_supplicant_verify_eapol_key_mic函數將把sm->tptk的內容復制到
sm->ptk變量中作為正式的ptk存儲。
*/
ptk = &sm->tptk;
wpa_derive_ptk(sm, src_addr, key, ptk);// 派生PTK并將結果保存到tptk變量中
os_memcpy(buf, ptk->u.auth.tx_mic_key, 8);
os_memcpy(ptk->u.auth.tx_mic_key, ptk->u.auth.rx_mic_key, 8);
os_memcpy(ptk->u.auth.rx_mic_key, buf, 8);
sm->tptk_set = 1; // tptk_set為1表示臨時PTK存在
// 構造并發送Message B。請讀者結合參考資料[23]來研究此函數
if (wpa_supplicant_send_2_of_4(sm, sm->bssid, key, ver, sm->snonce,
sm->assoc_wpa_ie, sm->assoc_wpa_ie_len, ptk)) goto failed;
// 包括AP的Nonce信息
os_memcpy(sm->anonce, key->key_nonce, WPA_NONCE_LEN);
return;
failed:
wpa_sm_deauthenticate(sm, WLAN_REASON_UNSPECIFIED);
}
~~~
STA將Message B發送出去后,AP將接收到它并根據AP的處理邏輯進行處理。假設一切順利,AP將構造并發送Message C給STA。
STA依然在wpa_sm_rx_eapol中處理Message C,其過程如下。
1. 由于Message C包含MIC以及Key Data數據,故它們將在wpa_sm_rx_eapol中的wpa_supplicant_verify_eapol_key_mic及wpa_supplicant_decrypt_key_data被處理。這兩個函數的代碼請感興趣的讀者自行閱讀。
2. 接下來調用wpa_supplicant_process_3_of_4。這是我們分析的重點。
**4、wpa_supplicant_process_3_of_4函數**
wpa_supplicant_process_3_of_4的代碼如下所示。
**wpa.c::wpa_supplicant_process_3_of_4**
~~~
static void wpa_supplicant_process_3_of_4(struct wpa_sm *sm,
const struct wpa_eapol_key *key, u16 ver)
{
u16 key_info, keylen, len;
const u8 *pos; struct wpa_eapol_ie_parse ie;
wpa_sm_set_state(sm, WPA_4WAY_HANDSHAKE);// 還是處于WPA_4WAY_HANDSHAKE狀態
key_info = WPA_GET_BE16(key->key_info);
pos = (const u8 *) (key + 1);
len = WPA_GET_BE16(key->key_data_length);
/*
解析Message C中包含的IE信息。注意,key data中的數據已經由wpa_supplicant_decrypt_key_data
解密過了。
*/
if (wpa_supplicant_parse_ies(pos, len, &ie) < 0) goto failed;
if (ie.gtk && !(key_info & WPA_KEY_INFO_ENCR_KEY_DATA)) goto failed;
......// CONFIG_IEEE80211W
// 校驗RSN信息。該校驗似乎也是為了防止安全設置項中途發生改變
if (wpa_supplicant_validate_ie(sm, sm->bssid, &ie) < 0) goto failed;
// 比較Message A和Message C中的Nonce值
if (os_memcmp(sm->anonce, key->key_nonce, WPA_NONCE_LEN) != 0)
goto failed;
keylen = WPA_GET_BE16(key->key_length);
......// 參數檢查
// 構造并發送Message D,請讀者自行閱讀該函數
if (wpa_supplicant_send_4_of_4(sm, sm->bssid, key, ver, key_info,
NULL, 0, &sm->ptk)) goto failed;
sm->renew_snonce = 1;
/*
調用driver_nl80211的wpa_driver_nl80211_set_key函數將key發給驅動。以后,所有
無線網絡數據都將被加密。另外,如果wpa_supplicant.conf文件配置wpa_ptk_rekey
(用來控制PTK的生命周期,時間為秒)時,該函數內部還將注冊一個超時函數wpa_sm_rekey_ptk。一旦
wpa_ptk_rekey到期,STA將重新和AP開展4-Way Handshake以重新派生新的PTK。
請讀者自行閱讀wpa_supplicant_install_ptk函數。
*/
if (key_info & WPA_KEY_INFO_INSTALL) // 安裝Key
if (wpa_supplicant_install_ptk(sm, key)) goto failed;
// Message C必須設置Secure位
if (key_info & WPA_KEY_INFO_SECURE) {
// 下面這個函數和管理幀加密有關系
wpa_sm_mlme_setprotection(sm, sm->bssid, MLME_SETPROTECTION_PROTECT_TYPE_RX,
MLME_SETPROTECTION_KEY_TYPE_PAIRWISE);
eapol_sm_notify_portValid(sm->eapol, TRUE);// 設置EAPOL SM portValid為TRUE
}
wpa_sm_set_state(sm, WPA_GROUP_HANDSHAKE); // 設置狀態為WPA_GROUP_HANDSHAKE
// 對本例而言,Message C中包含了GTK信息
if (ie.gtk && wpa_supplicant_pairwise_gtk(sm, key,
ie.gtk, ie.gtk_len, key_info) < 0) goto failed;
// 和IEEE80211W有關。本書不討論它
if (ieee80211w_set_keys(sm, &ie) < 0) goto failed;
/*
下面這個函數將調用driver wrapper的set_rekey_info函數。該函數和GTK的更新有關。
它對應以下應用場景。
當某STA因為省電或別的什么原因進入休眠狀態時,如果AP在STA休眠過程中更新了GTK。當該STA
醒來時,其組播消息密鑰肯定失效了。這種情況該如何處理呢?通過set_rekey_info函數,我們可
將該工作交給wlan芯片來完成。即由wlan芯片來負責處理EAPOL-Key幀交換以更新GTK。注意,
如果wlan driver不支持該功能,它將喚醒STA,并將該工作交給WPAS來完成。
這部分功能屬于WoWLAN(無線局域網喚醒)機制的一部分,目前只有很少的系統支持它。讀者可閱讀
參考資料[24][25]以加深對WoWLAN的認識。
*/
wpa_sm_set_rekey_offload(sm);
return;
failed:
wpa_sm_deauthenticate(sm, WLAN_REASON_UNSPECIFIED);
}
~~~
wpa_supplicant_process_3_of_4代碼邏輯和背景知識介紹中關于Message C的處理邏輯大體一致。對于包含GTK信息的Message C來說,wpa_supplicant_pairwise_gtk將被調用以處理GTK對應的KDE信息。馬上來看此函數。
**5、wpa_supplicant_pairwise_gtk函數**
介紹wpa_supplicant_pairwise_gtk之前,先來看看KDE和GTK的格式,如圖4-44所示。
:-: 
圖4-44 KDE和GTK格式
圖4-44中,上圖為KDE的格式,其中Type取值為0xdd。Data包含具體的信息。當Data中的信息為GTK時,OUT取值為00-0F-AC,Data Type取值為1。下圖為GTK的格式。KeyID字段可取值有0~3共4個,它和動態Key有關。Tx字段為1,表示發送和接收組播數據都需要使用該GTK。值為0表示僅接收組播數據需要使用該GTK。
**wpa.c::wpa_supplicant_pairwise_gtk**
~~~
static int wpa_supplicant_pairwise_gtk(struct wpa_sm *sm,
const struct wpa_eapol_key *key,
const u8 *gtk, size_t gtk_len, int key_info)
{
#ifndef CONFIG_NO_WPA2
struct wpa_gtk_data gd;
os_memset(&gd, 0, sizeof(gd));
if (gtk_len < 2 || gtk_len - 2 > sizeof(gd.gtk))
return -1; // 參數檢查
gd.keyidx = gtk[0] & 0x3;
// 下面這個函數對圖4-44中的Tx位進行一些處理
// 它對某些AP錯誤設置Tx位的情況采取了“繞過去”的策略
gd.tx = wpa_supplicant_gtk_tx_bit_workaround(sm,!!(gtk[0] & BIT(2)));
gtk += 2; gtk_len -= 2;
os_memcpy(gd.gtk, gtk, gtk_len);
gd.gtk_len = gtk_len;
// 檢查組播數據加密設置是否合適,然后調用wpa_supplicant_install_gtk函數安裝GTK
if (wpa_supplicant_check_group_cipher(sm, sm->group_cipher, gtk_len, gtk_len,
&gd.key_rsc_len, &gd.alg) ||
wpa_supplicant_install_gtk(sm, &gd, key->key_rsc))
return -1;
// 最后一個關鍵函數
wpa_supplicant_key_neg_complete(sm, sm->bssid,key_info & WPA_KEY_INFO_SECURE);
return 0;
#else
return -1;
#endif
}
~~~
上述代碼中,請讀者自行閱讀除wpa_supplicant_key_neg_complete外的其他幾個函數。下面來看最后一個關鍵函數。
**wpa.c::wpa_supplicant_key_neg_complete**
~~~
static void wpa_supplicant_key_neg_complete(struct wpa_sm *sm,const u8 *addr, int secure)
{
wpa_sm_cancel_auth_timeout(sm);// 取消超時任務
// 該函數內部將調用wpa_supplicant.c中的wpa_supplicant_set_state。請讀者自行閱讀
// 不光是一個簡單的設置狀態,它還需要調用driver的set_supp_port函數
wpa_sm_set_state(sm, WPA_COMPLETED);// 設置WPAS的狀態為WPA_COMPLETED
if (secure) {// secure為1
wpa_sm_mlme_setprotection( sm, addr, MLME_SETPROTECTION_PROTECT_TYPE_RX_TX,
MLME_SETPROTECTION_KEY_TYPE_PAIRWISE);
eapol_sm_notify_portValid(sm->eapol, TRUE);
if (wpa_key_mgmt_wpa_psk(sm->key_mgmt))// 本例采用了PSK認證,故可以通知eapSuccess
eapol_sm_notify_eap_success(sm->eapol, TRUE);
eloop_register_timeout(1, 0, wpa_sm_start_preauth, sm, NULL);
}
if (sm->cur_pmksa && sm->cur_pmksa->opportunistic)
sm->cur_pmksa->opportunistic = 0;
.....// CONFIG_IEEE80211R
}
~~~
至此,4-Way Handshake的處理就全部完成。而802.1X中Port的狀態以及WPAS的狀態則由此過程中相關的函數觸發以發生轉換。下一節將簡單介紹這部分的內容。
**6、WPAS狀態機變化**
上述4-Way Handshake處理流程節中,EAPOL模塊、EAP模塊以及WPAS的狀態都會發生對應的轉換。在上述wpa_supplicant_key_neg_complete中,wpa_sm_set_state將設置WPAS的狀態為WPA_COMPLETED。馬上來看wpa_sm_set_state的代碼,如下所示。
**wpa_i.h::wpa_sm_set_state**
~~~
static inline void wpa_sm_set_state(struct wpa_sm *sm, enum wpa_states state)
{
/*
調用回調函數。由4.3.4節的wpa_supplicant_init_wpa函數中設置,其真實函數為
_wpa_supplicant_set_state,而該函數又會調用wpa_supplicant_set_state。
*/
sm->ctx->set_state(sm->ctx->ctx, state);
}
~~~
wpa_sm_set_state將調用前面設置的回調函數進行處理。
最終的處理函數wpa_supplicant_set_state如下所示
**wpa_supplicant.c::wpa_supplicant_set_state**
~~~
void wpa_supplicant_set_state(struct wpa_supplicant *wpa_s,
enum wpa_states state)
{
enum wpa_states old_state = wpa_s->wpa_state;
......
if (state == WPA_COMPLETED && wpa_s->new_connection) {
#if defined(CONFIG_CTRL_IFACE) || !defined(CONFIG_NO_STDOUT_DEBUG)
struct wpa_ssid *ssid = wpa_s->current_ssid;
#endif
wpa_s->new_connection = 0;
wpa_s->reassociated_connection = 1;
/*
設置driver的IfOperStatus為IF_OPER_UP,driver nl80211中對應的函數是
wpa_driver_nl80211_set_operstate。
*/
wpa_drv_set_operstate(wpa_s, 1);
#ifndef IEEE8021X_EAPOL
/*
以driver nl80211來說,下面這個函數將調用wpa_driver_nl80211_set_supp_port
以設置驅動中STA的標志為NL80211_STA_FLAG_AUTHENTICATED。
*/
wpa_drv_set_supp_port(wpa_s, 1);
#endif /* IEEE8021X_EAPOL */
wpa_s->after_wps = 0;
} else if (state == WPA_DISCONNECTED || state == WPA_ASSOCIATING ||
state == WPA_ASSOCIATED) {
wpa_s->new_connection = 1;// ASSOCIATING或ASSOCIATED狀態下,new_connection被置為1
wpa_drv_set_operstate(wpa_s, 0);// 設置driver的IfOperStatus為IF_OPER_DORMANT
#ifndef IEEE8021X_EAPOL
wpa_drv_set_supp_port(wpa_s, 0);// 取消STA的NL80211_STA_FLAG_AUTHENTICATED標志
#endif /* IEEE8021X_EAPOL */
}
wpa_s->wpa_state = state;
.......
}
~~~
當WPAS的狀態變成WPA_COMPLETED后,還需要設置driver的IfOperStatus以及NL80211_STA_FLAG_AUTHENTICATED標志位。如此這般,WPAS才算和Driver上下同心。
**7、EAPOL/EAP狀態機變化**
在4-Way Handshake過程中,EAPOL/EAP狀態機也會發生變化。在4.5.3節eapol_sm_notify_portEnabled函數分析的最后,已知EAPOL/EAP狀態機的狀態分別如下。
* SUPP_PAE進入CONNECTING狀態;
* KEY_RX不變(NO_KEY_RECEIVE);
* SUPP_BE進入IDLE狀態;
* EAP_SM不變(DISABLED狀態)。
在4-Way Handshake過程中,一共有兩個和EAPOL相關的函數被調用:
eapol_sm_notify_portValid函數portValid被設為TRUE。
eapol_sm_notify_eap_success函數代碼如下所示。
**eapol_supp_sm.c::eapol_sm_notify_eap_success**
~~~
void eapol_sm_notify_eap_success(struct eapol_sm *sm, Boolean success)
{
if (sm == NULL) return;
sm->eapSuccess = success;
sm->altAccept = success;
if (success) eap_notify_success(sm->eap);
eapol_sm_step(sm);// 狀態機聯動
} // 直接來看eap_notify_success函數
void eap_notify_success(struct eap_sm *sm)
{
if (sm) {
sm->decision = DECISION_COND_SUCC;
sm->EAP_state = EAP_SUCCESS;// EAP_SM狀態直接被設置為EAP_SUCCESS
}
}
~~~
根據4.4.2節狀態機聯動中對eapol_sm_step的分析,四個狀態機更新的順序分別是SUPP_PAE、KER_RX、SUPP_BE和EAP_SM。分別結合四個狀態機的切換圖,我們可知第一輪循環中:
* eapSuccess將先觸發SUPP_PAE進入AUTHENTICATING狀態,該狀態對應的EA將設置suppStart為TRUE。
* eapSuccess和suppStart將觸發SUPP_BE進入SUCCESS狀態,該狀態對應的EA將設置keyRun和suppSuccess為TRUE。
* EAP_SM狀態在eap_notify_success中直接被置為EAP_SUCCESS。但在狀態切換中,由于force_disabled變量為TRUE,導致EAP SUPP SM將直接轉回DISABLED狀態(和4.4.2節介紹的狀態機聯動的情況類似)。
* KER_RX狀態不變。由于上述狀態機均有狀態變化,下面進入第二輪循環:
* suppSuccess和portValid為TRUE將觸發SUPP_PAE進入AUTHENTICATED狀態(由于條件變量不會再改變,故SUPP_PAE將不再發生狀態變化)。該狀態的EA見下文代碼。
* 由于UCT的存在,SUPP_BE將跳轉到IDLE狀態。
* KEY_RX和EAP_SM狀態保持不變。
以下是SUPP_PAE的EA代碼。
**eapol_supp_sm.c::SM_STATE(SUPP_PAE,AUTHENTICATED**
~~~
SM_STATE(SUPP_PAE, AUTHENTICATED)
{
SM_ENTRY(SUPP_PAE, AUTHENTICATED);
sm->suppPortStatus = Authorized;
// 也會調用wpa_drv_set_supp_port函數
eapol_sm_set_port_authorized(sm);
/*
設置cb_status為EAPOL_CB_SUCCESS,將觸發eapol_sm_step在退出前調用
wpa_supplicant_eapol_cb,此函數對于WPA/RSN企業認證法有重要作用。請讀者自行閱讀。
*/
sm->cb_status = EAPOL_CB_SUCCESS;
}
~~~
請讀者結合SUPP_PAE和SUPP_BE的狀態切換圖來理解上述的狀態變化過程。
至此,STA就通過了AP的身份驗證。下一步的工作就是dhcpcd從AP那獲取一個IP,然后手機就可以上網了。
**8、EAPOL-Key交換流程總結**
下面總結4-Way Handshake的流程,如圖4-45和圖4-46所示。
:-: 
圖4-45 Message A處理流程
:-: 
圖4-46 Message C處理流程
圖中分別為4-Way Handshake中Message A與Message C的處理流程。其中,Message C還攜帶了GTK信息,這樣就無須Group Key Handshake了。
- 前言
- 第1章 準備工作
- 1.1 Android系統架構
- 1.2 工具使用
- 1.2.1 Source Insight的使用
- 1.2.2 Eclipse的使用
- 1.2.3 BusyBox的使用
- 1.3 本書資源下載說明
- 第2章 深入理解Netd
- 2.1 概述
- 2.2 Netd工作流程
- 2.2.1 main函數分析
- 2.2.2 NetlinkManager分析
- 2.2.3 CommandListener分析
- 2.2.4 DnsProxyListener分析
- 2.2.5 MDnsSdListener分析
- 2.3 CommandListener中的命令
- 2.3.1 iptables、tc和ip命令
- 2.3.2 CommandListener構造函數和測試工具ndc
- 2.3.3 InterfaceCmd命令
- 2.3.4 IpFwd和FirewallCmd命令
- 2.3.5 ListTtysCmd和PppdCmd命令
- 2.3.6 BandwidthControlCmd和IdletimerControlCmd命令
- 2.3.7 NatCmd命令
- 2.3.8 TetherCmd和SoftapCmd命令
- 2.3.9 ResolverCmd命令
- 2.4 NetworkManagementService介紹
- 2.4.1 create函數詳解
- 2.4.2 systemReady函數詳解
- 2.5 本章總結和參考資料說明
- 2.5.1 本章總結
- 2.5.2 參考資料說明
- 第3章 Wi-Fi基礎知識
- 3.1 概述
- 3.2 無線電頻譜和802.11協議的發展歷程
- 3.2.1 無線電頻譜知識
- 3.2.2 IEEE 802.11發展歷程
- 3.3 802.11無線網絡技術
- 3.3.1 OSI基本參考模型及相關基本概念
- 3.3.2 802.11知識點導讀
- 3.3.3 802.11組件
- 3.3.4 802.11 Service介紹
- 3.3.5 802.11 MAC服務和幀
- 3.3.6 802.11 MAC管理實體
- 3.3.7 無線網絡安全技術知識點
- 3.4 Linux Wi-Fi編程API介紹
- 3.4.1 Linux Wireless Extensions介紹
- 3.4.2 nl80211介紹
- 3.5 本章總結和參考資料說明
- 3.5.1 本章總結
- 3.5.2 參考資料說明
- 第4章 深入理解wpa_supplicant
- 4.1 概述
- 4.2 初識wpa_supplicant
- 4.2.1 wpa_supplicant架構
- 4.2.2 wpa_supplicant編譯配置
- 4.2.3 wpa_supplicant命令和控制API
- 4.2.4 git的使用
- 4.3 wpa_supplicant初始化流程
- 4.3.1 main函數分析
- 4.3.2 wpa_supplicant_init函數分析
- 4.3.3 wpa_supplicant_add_iface函數分析
- 4.3.4 wpa_supplicant_init_iface函數分析
- 4.4 EAP和EAPOL模塊
- 4.4.1 EAP模塊分析
- 4.4.2 EAPOL模塊分析
- 4.5 wpa_supplicant連接無線網絡分析
- 4.5.1 ADD_NETWORK命令處理
- 4.5.2 SET_NETWORK命令處理
- 4.5.3 ENABLE_NETWORK命令處理
- 4.6 本章總結和參考資料說明
- 4.6.1 本章總結
- 4.6.2 參考資料說明
- 第5章 深入理解WifiService
- 5.1 概述
- 5.2 WifiService的創建及初始化
- 5.2.1 HSM和AsyncChannel介紹
- 5.2.2 WifiService構造函數分析
- 5.2.3 WifiStateMachine介紹
- 5.3 加入無線網絡分析
- 5.3.1 Settings操作Wi-Fi分析
- 5.3.2 WifiService操作Wi-Fi分析
- 5.4 WifiWatchdogStateMachine介紹
- 5.5 Captive Portal Check介紹
- 5.6 本章總結和參考資料說明
- 5.6.1 本章總結
- 5.6.2 參考資料說明
- 第6章 深入理解Wi-Fi Simple Configuration
- 6.1 概述
- 6.2 WSC基礎知識
- 6.2.1 WSC應用場景
- 6.2.2 WSC核心組件及接口
- 6.3 Registration Protocol詳解
- 6.3.1 WSC IE和Attribute介紹
- 6.3.2 802.11管理幀WSC IE設置
- 6.3.3 EAP-WSC介紹
- 6.4 WSC代碼分析
- 6.4.1 Settings中的WSC處理
- 6.4.2 WifiStateMachine的處理
- 6.4.3 wpa_supplicant中的WSC處理
- 6.4.4 EAP-WSC處理流程分析
- 6.5 本章總結和參考資料說明
- 6.5.1 本章總結
- 6.5.2 參考資料說明
- 第7章 深入理解Wi-Fi P2P
- 7.1 概述
- 7.2 P2P基礎知識
- 7.2.1 P2P架構
- 7.2.2 P2P Discovery技術
- 7.2.3 P2P工作流程
- 7.3 WifiP2pSettings和WifiP2pService介紹
- 7.3.1 WifiP2pSettings工作流程
- 7.3.2 WifiP2pService工作流程
- 7.4 wpa_supplicant中的P2P
- 7.4.1 P2P模塊初始化
- 7.4.2 P2P Device Discovery流程分析
- 7.4.3 Provision Discovery流程分析
- 7.4.4 GO Negotiation流程分析
- 7.5 本章總結和參考資料說明
- 7.5.1 本章總結
- 7.5.2 參考資料說明
- 第8章 深入理解NFC
- 8.1 概述
- 8.2 NFC基礎知識
- 8.2.1 NFC概述
- 8.2.2 NFC R/W運行模式
- 8.2.3 NFC P2P運行模式
- 8.2.4 NFC CE運行模式
- 8.2.5 NCI原理
- 8.2.6 NFC相關規范
- 8.3 Android中的NFC
- 8.3.1 NFC應用示例
- 8.3.2 NFC系統模塊
- 8.4 NFC HAL層討論
- 8.5 本章總結和參考資料說明
- 8.5.1 本章總結
- 8.5.2 參考資料說明
- 第9章 深入理解GPS
- 9.1 概述
- 9.2 GPS基礎知識
- 9.2.1 衛星導航基本原理
- 9.2.2 GPS系統組成及原理
- 9.2.3 OMA-SUPL協議
- 9.3 Android中的位置管理
- 9.3.1 LocationManager架構
- 9.3.2 LocationManager應用示例
- 9.3.3 LocationManager系統模塊
- 9.4 本章總結和參考資料說明
- 9.4.1 本章總結
- 9.4.2 參考資料說明
- 附錄