wpa_supplicant_init_iface內容非常多,我們將通過逐步展示代碼段的方法,分五部分介
紹。
**wpa_supplicant.c::wpa_supplicant_init_iface代碼段一**
~~~
static int wpa_supplicant_init_iface(struct wpa_supplicant *wpa_s,
struct wpa_interface *iface)
{
const char *ifname, *driver;
struct wpa_driver_capa capa;
if (iface->confname) {
......// CONFIG_BACKEND_FILE處理,此宏指明WPAS使用的配置項信息來源于文件
// Android定義了它
wpa_s->conf = wpa_config_read(wpa_s->confname);
}
......
~~~
由上述代碼可知,init_iface初始化的第一個工作是解析運行時配置文件。其中,wpa_s->confname的值為"/data/misc/wifi/wpa_supplicant.conf",解析函數是wpa_config_read。
1. wpa_supplicant_init_iface分析之一
這個函數本身沒有特別之處,僅是把配置文件中的信息轉換成對應的數據結構。
**config_file.c::wpa_config_read**
~~~
struct wpa_config * wpa_config_read(const char *name)
{
FILE *f;
char buf[256], *pos;
int errors = 0, line = 0;
struct wpa_ssid *ssid, *tail = NULL, *head = NULL;
struct wpa_config *config; // 配置文件在代碼中對應的數據結構
int id = 0;
config = wpa_config_alloc_empty(NULL, NULL);
......
f = fopen(name, "r");
......
while (wpa_config_get_line(buf, sizeof(buf), f, &line, &pos)) {
if (os_strcmp(pos, "network={") == 0) {
// 讀取配置文件中的network項,并將其轉化成一個wpa_ssid類型的對象
ssid = wpa_config_read_network(f, &line, id++);
......
// 根據圖4-10所示,wpa_ssid通過next成員變量構成了一個單向鏈表
if (head == NULL) { head = tail = ssid;}
else { tail->next = ssid; tail = ssid;}
// network項屬于配置文件的一部分,故wpa_ssid對象也包含在wpa_config對象中
if (wpa_config_add_prio_network(config, ssid)) {......}
......// CONFIG_NO_CONFIG_BLOBS,blob是配置文件中的一個字段,用于存儲有些身
// 份認證算法需要用的證書之類的信息。本例沒有使用blob配置項
// 解析其他項
} else if (wpa_config_process_global(config, pos, line) < 0) {......}
}
fclose(f);
config->ssid = head;
......
return config;
}
~~~
wpa_config和wpa_ssid這兩個數據結構都是配置文件中的信息在代碼中的反映。讀者可查看wpa_supplicant.conf配置模板文件來了解各個配置項的含義。
上述代碼中,wpa_config_process_global的實現有一些特別,它通過宏的方式來定義解析項及對應的解析函數。由于解析函數最終結果就是設置wpa_config中對應項的值,故本章不討論其細節,感興趣的讀者不妨自行閱讀它們。
2. wpa_supplicant_init_iface分析之二
wpa_supplicant_init_iface函數代碼段二如下所示。
**wpa_supplicant.c::wpa_supplicant_init_iface代碼段二**
~~~
......// 接wpa_supplicant_init_iface代碼段一
if (os_strlen(iface->ifname) >= sizeof(wpa_s->ifname)) {......}
// 將wpa_interface中的ifname復制到wpa_supplicant的ifname變量中
os_strlcpy(wpa_s->ifname, iface->ifname, sizeof(wpa_s->ifname));
......
// 下面這兩個函數和EAPOL狀態機相關,我們將在4.4節介紹
eapol_sm_notify_portEnabled(wpa_s->eapol, FALSE);
eapol_sm_notify_portValid(wpa_s->eapol, FALSE);
driver = iface->driver;
next_driver:
if (wpa_supplicant_set_driver(wpa_s, driver) < 0) return -1;
~~~
wpa_supplicant_set_driver將根據driver wrapper名(本例是"nl80211")找到wpa_driver數組中nl80211指定的driver wrapper對象wpa_driver_nl80211_ops,然后調用其global_init函數。直接來看global_init函數的實現。
**提示** global_init函數將返回全局driver wrapper上下文信息,它保存在wpa_global的drv_priv數組中。
**(1)global_init函數分析**
global_init是wpa_driver_ops結構體中的一個類型為函數指針的成員變量。nl80211對應的
driver wrapper將其設置為nl80211_global_init,代碼如下所示。
**driver_nl80211.c::nl80211_global_init**
~~~
static void * nl80211_global_init(void)
{
struct nl80211_global *global;
struct netlink_config *cfg;
global = os_zalloc(sizeof(*global));
global->ioctl_sock = -1;
dl_list_init(&global->interfaces);
global->if_add_ifindex = -1;
cfg = os_zalloc(sizeof(*cfg));
......
cfg->ctx = global;
/*
下面這三條語句用于創建netlink socket來接收來自內核的網卡狀態變化事件(如UP、DORMANT、
REMOVED),然后通過eloop_register_read_sock注冊一個netlink_recv函數用于處理接收
到的socket消息。
netlink_recv函數內部將根據消息的類別來調用newlink_cb和dellink_cb以處理網卡狀態變
化事件。這兩個回調函數處理比較簡單,讀者可在閱讀完本章后再自行研究它們。
*/
cfg->newlink_cb = wpa_driver_nl80211_event_rtm_newlink;
cfg->dellink_cb = wpa_driver_nl80211_event_rtm_dellink;
global->netlink = netlink_init(cfg);
// 將加入netlink中AF_NETLINK協議中的RTMGRP_LINK組播組
......
// nl80211利用netlink機制和wlan driver交互
if (wpa_driver_nl80211_init_nl_global(global) < 0) ......// 錯誤處理
global->ioctl_sock = socket(PF_INET, SOCK_DGRAM, 0);
......
return global;
}
~~~
上面代碼涉及一個比較重要的數據結構,即代表nl80211 driver wrapper全局上下文信息的nl80211_global,其結構如圖4-12所示。
:-: 
圖4-12 nl80211_global結構體
需要注意的是nl80211_global包含兩個nl_handle對象。nl_handle的真實類型就是libnl定義的nl_socket。其中,nl用于發送netlink消息,nl_event用于接收netlink消息。
這兩個nl_handle對象的初始化由wpa_driver_nl80211_init_nl_global函數完成,馬上來看它。
**(2)wpa_driver_nl80211_init_nl_global函數分析**
wpa_driver_nl80211_init_nl_global是global_init的核心函數,其代碼如下所示。
**driver_nl80211.c::wpa_driver_nl80211_init_nl_global**
~~~
static int wpa_driver_nl80211_init_nl_global(struct nl80211_global *global)
{
// 此函數利用了第3章中介紹的API
int ret;
// 創建一個netlink回調對象
global->nl_cb = nl_cb_alloc(NL_CB_DEFAULT);
/*
nl_create_handle返回值的類型為nl_handle*,而nl_handle在driver_nl802.11c中
就是nl_socket(代碼中的定義:#define nl_handle nl_sock)。
nl_create_handle內部調用genl_connect連接到內核對應的模塊。注意,該函數最后的字符串參數
(如此處的"nl")僅用于輸出調試信息。
*/
global->nl = nl_create_handle(global->nl_cb, "nl");
/*
向netenlink中的"nl"模塊查詢"nl80211"模塊的編號。注意,genl_ctrl_resolve函數本
來由libnl2定義,但driver_nl80211.c通過
#define genl_ctrl_resolve android_genl_ctrl_resolve
宏將其指向android_genl_ctrl_resolve。該函數內部通過發送查詢消息來獲取"nl80211"
模塊的family值。請讀者自行閱讀android_genl_ctrl_resolve函數。
*/
global->nl80211_id = genl_ctrl_resolve(global->nl, "nl80211");
......
// 創建另外一個nl_sock對象,其用途是接收netlink消息
global->nl_event = nl_create_handle (global->nl_cb, "event");
......
/*
下面這幾個函數的作用如下。
nl_get_multicast_id:先從nl80211模塊中獲得對應的組播組編號,如"scan"、"mlme"以及
"regulatory"組播組的編號。
nl_socket_add_membership:加入某個組播組。這樣,當某個組播有消息發送時,nl_event就能收到了。
*/
ret = nl_get_multicast_id(global, "nl80211", "scan");
ret = nl_socket_add_membership(global->nl_event, ret);
ret = nl_get_multicast_id(global, "nl80211", "mlme");
ret = nl_socket_add_membership(global->nl_event, ret);
ret = nl_get_multicast_id(global, "nl80211", "regulatory");
ret = nl_socket_add_membership(global->nl_event, ret);
nl_cb_set(global->nl_cb, NL_CB_SEQ_CHECK, NL_CB_CUSTOM,
no_seq_check, NULL);// 設置序列號檢查函數為no_seq_check
nl_cb_set(global->nl_cb, NL_CB_VALID, NL_CB_CUSTOM,
process_global_event, global);// 設置netlink消息回調處理函數
/*
將nl_event對應的socket注冊到eloop中,回調函數為wpa_driver_nl80211_event_receive,
該函數內部將調用nl_recv_msg,而nl_recv_msg又會調用process_global_event。所以,我們只
要關注process_global_event就可以了。
*/
eloop_register_read_sock(nl_socket_get_fd(global->nl_event),
wpa_driver_nl80211_event_receive, global->nl_cb, global->nl_event);
return 0;
......
}
~~~
wpa_driver_nl80211_init_nl_global內容比較多,此處總結一下其工作內容。
* 創建了兩個nl_handle對象,分別是global->nl和gobal->event。nl_handle內部定義一個socket句柄。所以,兩個nl_handle等同于兩個socket句柄。global->event用于接收netlink消息。nl80211定義了幾個組播組,此處選擇加入其中的"scan"、"mlme"和"regulatory"三個組播組,它們分別對應于掃描信息、mlme信息及管制信息。wlan driver內部會往這三個組播發送相關的消息。這樣,global->event就能收到它們。
* 接著將global->event對應的socket注冊到eloop讀事件隊列中。如此,內核發送的netlink消息就能被wpa_driver_nl80211_event_receive處理。wpa_driver_nl80211_event_receive內部將調用libnl API中的nl_recv_msg來接收消息,而它又會觸發最重要的process_global_event函數被調用。
* global->nl用來向wlan driver發送netlink消息。根據第3章對genlmsg的介紹,其內部有一個變量用于指明family,而nl80211對應的family編號則保存在global->nl80211_id中。
提示 根據筆者的心得,讀者大可不必對libnl等進行深入細致的源碼分析。對WPAS的來說,僅了解libnl2 API的用法即可。
3. wpa_supplicant_init_iface分析之三
介紹完wpa_supplicant_set_driver后,現在回到wpa_supplicant_init_iface,繼續看第三段代碼。
**wpa_supplicant.c::wpa_supplicant_init_iface代碼段三**
~~~
......// 接wpa_supplicant_set_driver代碼段
// 又是一個關鍵函數
wpa_s->drv_priv = wpa_drv_init(wpa_s, wpa_s->ifname);
......
// 設置driver參數,本例沒有使用這一項功能
if (wpa_drv_set_param(wpa_s, wpa_s->conf->driver_param) < 0) {...... }
// 從driver中獲取網卡名
ifname = wpa_drv_get_ifname(wpa_s);
if (ifname && os_strcmp(ifname, wpa_s->ifname) != 0) {
// 如果不一致則替換配置文件中設置的網卡設備名
os_strlcpy(wpa_s->ifname, ifname, sizeof(wpa_s->ifname));
}
~~~
上一節初始化driver wrapper的全局上下文信息后(通過調用global_init來完成),接著要處理的就是單個driver wrapper了。該工作由wpa_drv_init函數完成。其內部將調用driver wrapper的init2函數(注意,如果driver wrapper定義了init2函數,init2將唯一被調用,否則將調用其定義的init函數)。
直接來看driver_nl80211實現的init2函數,其代碼如下所示。
**driver_nl80211.c::wpa_driver_nl80211_init**
~~~
static void * wpa_driver_nl80211_init(void *ctx, const char *ifname, void *global_priv)
{
struct wpa_driver_nl80211_data *drv;
struct rfkill_config *rcfg; struct i802_bss *bss;
......
drv = os_zalloc(sizeof(*drv));
......
drv->global = global_priv;
drv->ctx = ctx; // ctx的真正類型是wpa_supplicant
bss = &drv->first_bss; bss->drv = drv;
os_strlcpy(bss->ifname, ifname, sizeof(bss->ifname));
drv->monitor_ifidx = -1; drv->monitor_sock = -1; drv->eapol_tx_sock = -1;
// ap_scan_as_station變量和hostapd有關
drv->ap_scan_as_station = NL80211_IFTYPE_UNSPECIFIED;
// ①下面兩個關鍵函數見后文解釋
if (wpa_driver_nl80211_init_nl(drv)) {......}
if (nl80211_init_bss(bss)) goto failed;
/*
下面這個函數將讀取/sys/class/net/wlan0/phy80211/name文件的內容,并將其保存到
wpa_driver_nl80211_data->phyname變量中。該文件存儲了Wi-Fi物理設備的名稱,如phy0等。
它由wifi wlan注冊時動態生成,所以其值有可能變化。
注意,/sys/class/net/wlan0中的wlan0為無線網絡設備名,它由wpa_supplicant -i參數指明。
*/
nl80211_get_phy_name(drv);
rcfg = os_zalloc(sizeof(*rcfg));
rcfg->ctx = drv;
os_strlcpy(rcfg->ifname, ifname, sizeof(rcfg->ifname));
// 和rfkill相關,見下文解釋
rcfg->blocked_cb = wpa_driver_nl80211_rfkill_blocked;
rcfg->unblocked_cb = wpa_driver_nl80211_rfkill_unblocked;
drv->rfkill = rfkill_init(rcfg);
......
// 關鍵函數②
if (wpa_driver_nl80211_finish_drv_init(drv)) goto failed;
// 見下文關于PF_PACKET的解釋
drv->eapol_tx_sock = socket(PF_PACKET, SOCK_DGRAM, 0);
if (drv->data_tx_status) { ......}
if (drv->global) {
// 把自己加到nl80211_global中的interfaces鏈表中去
dl_list_add(&drv->global->interfaces, &drv->list);
drv->in_interface_list = 1;
}
return bss; // wpa_driver_nl80211_init返回的是一個i802_bss結構體對象
......
}
~~~
上述代碼包含的知識點較多,涉及rfkill以及PF_PACKET背景知識,以及三個關鍵函數,
wpa_driver_nl80211_init_nl、nl80211_init_bss和wpa_driver_nl80211_finish_drv_init。
**(1)rfkill背景知識**
rfkill代表radio frequency(RF)connector kill switch support,它是Kernel中的一個子系統(subsystem)。其功能是控制系統中射頻設備的電源(包括Wi-Fi、GPS、BlueTooth、FM等設備。注意,這些設備驅動只有把自己注冊到rfkill子系統中后,rfkill才能對它們起作用)的工作以避免浪費電力。rfkill有軟硬兩種方式來禁止(block)RF設備。
* hard block:不能通過軟件來重新啟用RF設備。據觀察,Android手機還沒有hard block功能。不過筆者猜測某些筆記本有這個功能。例如,筆者的Dell筆記本上有一個特殊的開關,一旦把它關上,Wi-Fi模塊就不能工作①。
* soft block:可以用軟件來重新啟用RF設備。
rfkill對用戶空間提供了相應的控制接口,主要是通過/dev/rfkill設備文件來完成相關操作。我們通過wpa_driver_nl80211_init中調用的一個名為rfkill_init的函數來認識如何使用rfkill。該函數代碼如下所示。
**rfkill.c::rfkill_init**
~~~
struct rfkill_data * rfkill_init(struct rfkill_config *cfg)
{
/*
rfkill_data 是WPAS自定義的一個數據結構,主要用于設置兩個回調函數用于處理block
和unblock的情況。
由上面一段代碼可知,這兩個回調函數分別是wpa_driver_nl80211_rfkill_blocked和
wpa_driver_nl80211_rfkill_unblocked。
*/
struct rfkill_data *rfkill;
struct rfkill_event event; // rfkill_event代表rfkill事件
ssize_t len;
rfkill = os_zalloc(sizeof(*rfkill));
rfkill->cfg = cfg;
// O_RDONLY標志表示driver_nl80211只讀取rfkill事件,而不會去操作rfkill模塊
rfkill->fd = open("/dev/rfkill", O_RDONLY);
......
// 設置I/O操作為非阻塞式
if (fcntl(rfkill->fd, F_SETFL, O_NONBLOCK) < 0) {......}
for (;;) {// 讀者知道為什么這里是一個for無限循環嗎?
// 讀取/dev/rfkill中已有的事件信息。rfkill事件信息保存在rfkill_event結構體中
len = read(rfkill->fd, &event, sizeof(event));
if (len < 0) {
if (errno == EAGAIN) break; // 無數據可讀,則跳出循環
break; // 其他錯誤也跳出循環
}
......
/*
rfkill_event的op變量代表rfkill事件的類型,目前可取值有RFKILL_OP_ADD(代表一
個設備添加到了rfkill子系統)、RFKILL_OP_DEL等。
rfkill_event的type變量代表該rfkill事件所對應設備的類型。目前可取值有RFKILL_
TYPE_WLAN(無線網卡設備)、RFKILL_TYPE_BLUETOOTH(藍牙設備)等。
*/
if (event.op != RFKILL_OP_ADD || event.type != RFKILL_TYPE_WLAN)
continue;
if (event.hard) { // 表示是否為hard block
rfkill->blocked = 1;
} else if (event.soft) { // 表示是否為soft block
rfkill->blocked = 1;
} // 如果hard和soft均未被設置,則表示該設備屬于unblock狀態,即設備允許被使用
}
// 為eloop注冊一個讀事件,一旦rfkill有新的事件到來,則eloop會觸發rfkill_receive函數被調用
eloop_register_read_sock(rfkill->fd, rfkill_receive, rfkill, NULL);
return rfkill;
......// 錯誤處理
}
~~~
從上述代碼可知,WPAS只是監控rfkill設備以獲取發生在其上的rfkill_event,而它并不操作rfkill以關閉或啟用無線設備。
**提示** 關于rfkill更多的信息請閱讀參考資料[16][17]。
**(2)PF_PACKET背景知識**
PF_PACKET有時也被稱為AF_PACKET,是socket域(domain)中的一種,用于直接在OSI/RM的數據鏈路層(Data Link Layer)上收發數據。所以,通過AF_PACKET,用戶空間可直接實現在物理層之上的協議,如EAP和EAPOL等。
**提醒** 由3.3.1節可知,DLL層還可細分為LLC子層和MAC子層。
下面將通過一些具體代碼段來展示PF_PAKCET的使用。
**AF_PACKET用法示例**
~~~
/*
socket函數的第二個參數叫socket_type。AP_PACKET中可以使用SOCK_DGRAM和SOCK_RAW,二者
略有區別,主要體現在如何處理物理層地址信息上。使用AP_PACKET時,需要為數據包設置物理層地址,
它由結構體struct sockaddr_ll來表達。當socket_type設置為:
SOCK_RAM:用戶接收到的數據包也將包含物理層地址,并且發送數據時,驅動將使用用戶指定的物理層
地址來填充數據包。
SOCK_DGRAM:它比SOCK_RAW要高級一點。用戶接收的數據包將不包括物理層地址信息,而用戶發送時
指定的物理層地址也僅是一個參考,kernel會根據實際情況來填充一個更為合適的物理層地址。
另外,程序可以通過bind函數指定接收某個網卡設備上的數據包。
*/
int fd = socket(PF_PACKET, SOCK_DGRAM,htons(ETH_P_EAPOL));
// 最后一個參數代表EAPOL協議類型
struct sockaddr_ll ll; // sockaddr_ll結構體代表地址信息
memset(&ll, 0, sizeof(ll));
ll.sll_family = PF_PACKET; // 該變量必須被設置成AF_PACKET
ll.sll_ifindex = ifindex; // 網絡設備的索引號
ll.sll_protocol = htons(ETH_P_EAPOL);
bind(fd, (struct sockaddr *) &ll, sizeof(ll));// 綁定到指定的網絡設備
......// 其他處理
// 發送數據
struct sockaddr_ll ll2;// 目標地址
memset(&ll2, 0, sizeof(ll2));
ll.sll_family = AF_PACKET;
ll.sll_ifindex = ifindex
ll.sll_protocol = htons(ETH_P_EAPOL); // 幀類型,此處代表EAPOL幀
ll.sll_halen = ETH_ALEN; // 目標MAC地址長度
memcpy(ll.sll_addr, dst_addr, ETH_ALEN);// sll_addr用于表示目標物理層地址(即MAC地址)
// 發送EAPOL幀
ret = sendto(fd, buf, len, 0, (struct sockaddr *) &ll2,sizeof(ll2));
......
// 接收數據
struct sockaddr_ll ll3;
socklen_t fromlen;
memset(&ll3, 0, sizeof(ll3));
fromlen = sizeof(ll3);
int res = recvfrom(fd, buf, sizeof(buf), 0, (struct sockaddr *) &ll3, &fromlen);
~~~
關于PF_PACKET更為詳細的信息,請讀者通過man 7 packet查詢Linux手冊。接著來看wpa_driver_nl80211_init中的三個重要函數,首先是wpa_driver_nl80211_init_nl和nl80211_init_bss。
**(3)wpa_driver_nl80211_init_nl與nl80211_init_bss函數分析**
這兩個函數都使用了libnl創建了回調對象,代碼如下所示。
**driver_nl80211.c::wpa_driver_nl80211_init_nl**
~~~
/*
socket函數的第二個參數叫socket_type。AP_PACKET中可以使用SOCK_DGRAM和SOCK_RAW,二者
略有區別,主要體現在如何處理物理層地址信息上。使用AP_PACKET時,需要為數據包設置物理層地址,
它由結構體struct sockaddr_ll來表達。當socket_type設置為:
SOCK_RAM:用戶接收到的數據包也將包含物理層地址,并且發送數據時,驅動將使用用戶指定的物理層
地址來填充數據包。
SOCK_DGRAM:它比SOCK_RAW要高級一點。用戶接收的數據包將不包括物理層地址信息,而用戶發送時
指定的物理層地址也僅是一個參考,kernel會根據實際情況來填充一個更為合適的物理層地址。
另外,程序可以通過bind函數指定接收某個網卡設備上的數據包。
*/
int fd = socket(PF_PACKET, SOCK_DGRAM,htons(ETH_P_EAPOL));
// 最后一個參數代表EAPOL協議類型
struct sockaddr_ll ll; // sockaddr_ll結構體代表地址信息
memset(&ll, 0, sizeof(ll));
ll.sll_family = PF_PACKET; // 該變量必須被設置成AF_PACKET
ll.sll_ifindex = ifindex; // 網絡設備的索引號
ll.sll_protocol = htons(ETH_P_EAPOL);
bind(fd, (struct sockaddr *) &ll, sizeof(ll));// 綁定到指定的網絡設備
......// 其他處理
// 發送數據
struct sockaddr_ll ll2;// 目標地址
memset(&ll2, 0, sizeof(ll2));
ll.sll_family = AF_PACKET;
ll.sll_ifindex = ifindex
ll.sll_protocol = htons(ETH_P_EAPOL); // 幀類型,此處代表EAPOL幀
ll.sll_halen = ETH_ALEN; // 目標MAC地址長度
memcpy(ll.sll_addr, dst_addr, ETH_ALEN);// sll_addr用于表示目標物理層地址(即MAC地址)
// 發送EAPOL幀
ret = sendto(fd, buf, len, 0, (struct sockaddr *) &ll2,sizeof(ll2));
......
// 接收數據
struct sockaddr_ll ll3;
socklen_t fromlen;
memset(&ll3, 0, sizeof(ll3));
fromlen = sizeof(ll3);
int res = recvfrom(fd, buf, sizeof(buf), 0, (struct sockaddr *) &ll3, &fromlen);static int wpa_driver_nl80211_init_nl(struct wpa_driver_nl80211_data *drv)
{
drv->nl_cb = nl_cb_alloc(NL_CB_DEFAULT);
......
nl_cb_set(drv->nl_cb, NL_CB_SEQ_CHECK, NL_CB_CUSTOM,no_seq_check, NULL);
nl_cb_set(drv->nl_cb, NL_CB_VALID, NL_CB_CUSTOM, process_drv_event, drv);
return 0;
}
static int nl80211_init_bss(struct i802_bss *bss)
{
bss->nl_cb = nl_cb_alloc(NL_CB_DEFAULT);
......
nl_cb_set(bss->nl_cb, NL_CB_SEQ_CHECK, NL_CB_CUSTOM,no_seq_check, NULL);
nl_cb_set(bss->nl_cb, NL_CB_VALID, NL_CB_CUSTOM, process_bss_event, bss);
return 0;
}
~~~
不過,它們僅創建了nl_cb對象,卻并未創建nl_handle(即沒有創建nl socket)。沒有和socket綁定,這些回調對象就不可能真正被用上。它們什么時候用呢?此處先提前介紹一下使用它們的代碼。
**driver_nl80211.c::nl80211_alloc_mgmt_handle**
~~~
static int nl80211_alloc_mgmt_handle(struct i802_bss *bss)
{
struct wpa_driver_nl80211_data *drv = bss->drv;
if (bss->nl_mgmt) {....../*重復注冊*/return -1; }
bss->nl_mgmt = nl_create_handle(drv->nl_cb, "mgmt"); // 注意該函數的第一個參數
eloop_register_read_sock(nl_socket_get_fd(bss->nl_mgmt),
wpa_driver_nl80211_event_receive, bss->nl_cb, bss->nl_mgmt);
return 0;
}
static void wpa_driver_nl80211_event_receive(int sock, void *eloop_ctx, void *handle)
{
struct nl_cb *cb = eloop_ctx;
nl_recvmsgs(handle, cb); // cb是bss->nl_cb
}
~~~
注意,上述代碼有一個非常奇怪的地方。bss->nl_mgmt創建時使用了drv->nl_cb對象,該回調對象由wpa_driver_nl80211_init_nl創建,其對應的回調函數是process_drv_event。nl_create_handle返回的實際上是一個nl_socket對象,其內部有一個s_cb變量指向nl_create_handle的第一個參數(本例中即是drv->nl_cb)。注冊到eloop模塊中的
wpa_driver_nl80211_event_receive函數,在處理回調的時候卻使用了bss->nl_cb,該回調對象對應的是process_bss_event函數。
也就是說,上述函數一共使用了兩個回調對象,一個是drv->nl_cb,另外一個是bss->nl_cb。什么時候調用drv->nl_cb,什么時候調用bss->nl_cb呢?
根據筆者對比Android中libnl2和libnl2官方代碼的結果,nl_recvmsgs將使用指定的nl_cb對象進行回調(即它的第二個參數,本例中的bss->nl_cb),而nl_recvmsgs_default將使用nl_socket中s_cb指定的回調對象(即本例中的drv->nl_cb)。不過,Android的libnl2并沒有nl_recvmsgs_default函數。所以,drv->nl_cb實際上永遠不會被用到。
**注意** 綜合4.3.4節對wpa_driver_nl80211_init_nl_global的分析,WPAS中實際上真正使用到的回調對象就是兩個:一個是bss->nl_cb,對應的回調函數是process_bss_event,另一個是global->nl_cb,對應的回調函數是process_global_event。
另外,作為練習,請通過以下命令查看上一節提到的與drv->nl_cb和bss->nl_cb使用有關的信息。
**git blame src/drivers/driver_nl80211.c|grep process_drv_event**
**git show d6c9aab8**
**(4)wpa_driver_nl80211_finish_drv_init函數分析**
wpa_driver_nl80211_init中的最后一個關鍵函數代碼如下所示。
**driver_nl80211.c::wpa_driver_nl80211_finish_drv_init**
~~~
static int wpa_driver_nl80211_finish_drv_init(struct wpa_driver_nl80211_data *drv)
{
struct i802_bss *bss = &drv->first_bss;
int send_rfkill_event = 0;
drv->ifindex = if_nametoindex(bss->ifname);// 獲取網卡設備的索引,屬于netdevice編程范疇
drv->first_bss.ifindex = drv->ifindex;
#ifndef HOSTAPD // hostapd是另外一個程序,本書不討論
if (drv->ifindex != drv->global->if_add_ifindex &&
/*
①設置接口類型為NL80211_IFTYPE_STATION,見下文解釋。注意,這個函數內容非常豐富,
其中包含很多和P2P相關的信息。本章暫時不考慮它。另外,此函數內部會調用到上一節提到的
nl80211_alloc_mgmt_handle。
*/
wpa_driver_nl80211_set_mode(bss, NL80211_IFTYPE_STATION) < 0) {......}
/*
linux_set_iface_flags通過ioctl方式啟動ifname對應的網卡設備。
該函數使用了netdevice API,請讀者回顧表2-2。
其使用的參數為SIOCSIFFLAGS和IFF_UP。
*/
if (linux_set_iface_flags(drv->global->ioctl_sock, bss->ifname, 1)) {
// 注意,如果linux_set_iface_flags返回非0值(即啟動設備失敗)
// 要判斷是不是rfkill禁止了該設備
if (rfkill_is_blocked(drv->rfkill)) {
......// 如果是因為rfkill原因導致設備被禁止,則需要通知wpa_supplicant
drv->if_disabled = 1;// 設置if_disabled為1,表示該設備被rfkill禁止了
send_rfkill_event = 1; // 該值表示需要設置WPAS的狀態
} else {......}
}
// ②設置Wi-Fi設備工作狀態為,IF_OPER_DORMANT,見下文解釋
netlink_send_oper_ifla(drv->global->netlink, drv->ifindex,1, IF_OPER_DORMANT);
#endif /* HOSTAPD */
// ③獲取Wi-Fi設備的capability,見下文解釋
if (wpa_driver_nl80211_capa(drv)) return -1;
// 通過ioctl方式獲取指定網卡的MAC地址,也屬于netdeivce編程范疇,回顧表2-2
if (linux_get_ifhwaddr(drv->global->ioctl_sock, bss->ifname,bss->addr)) return -1;
if (send_rfkill_event) {
/*
添加一個超時任務,超時時間為0秒。超時處理函數為wpa_driver_nl80211_send_rfkill,該
函數內部將設置wpa_states為WPA_INTERFACE_DISABLED。
可參考4.3.3節了解WPA_INTER FACE_DISABLED狀態。
*/
eloop_register_timeout(0, 0, wpa_driver_nl80211_send_rfkill,drv, drv->ctx);
}
return 0;
}
~~~
wpa_driver_nl80211_finish_drv_init代碼不長,但內容卻比較豐富,先簡單總結一下其工作流程。
* 1)調用wpa_driver_nl80211_set_mode函數設置Wi-Fi設備類型為NL80211_IFTYPE_STATION。下文將詳細介紹Wi-Fi設備類型的知識。
* 2)調用linux_set_iface_flags通過netdevice API啟用該Wi-Fi設備。如果失敗,則需要判斷該設備是否被rfkill block。
* 3)調用netlink_send_oper_ifla函數設置網卡的工作狀態(Interface Operational Status,IfOperStatus)為IF_OPER_DORMANT。關于IfOperStatus詳情見下文解釋。
* 4)調用wpa_driver_nl80211_capa獲取Wi-Fi設備的處理能力(capability)。詳情見下文解釋。
* 5)最后,調用linux_get_ifhwaddr獲取Wi-Fi設備的MAC地址,并判斷是否需要設置超時函數wpa_driver_nl80211_send_rfkill。
上述內容中有兩個背景知識(設備類型以及工作狀態)和一個重要函數wpa_driver_nl80211_capa。此處先來認識設備類型。
一般而言,一塊網絡接口設備只有一個MAC地址,但現在許多設備都支持多個所謂的虛擬設備(Virtual Interface),每一個虛擬設備都對應有一個虛擬MAC地址。例如,圖4-13所示為Wi-Fi P2P中一種名為并發設備(Concurrent Device)的示意圖。
:-: 
圖4-13 P2P Concurrent設備
圖4-13中,位于中間的Wi-Fi設備一方面以P2P Device的身份和左下角的另一個P2P Device相連,另一方面又以STA的身份和右邊的AP相連。P2P Device和STA的工作方式不盡相同,怎么實現這種并發設備呢?解決方法就是通過這種虛擬設備的方式,使P2P Device和STA分別使用不同的Virtual Interface和Virtual MAC。
**提示** 目前,市面上許多Android手機打開Wi-Fi P2P功能后就必須關閉STA功能,而筆者的Note 2就能做到P2P和STA同時工作。
NL80211定義了多種Virtual Interface類型,如下所示。
~~~
enum nl80211_iftype {
NL80211_IFTYPE_UNSPECIFIED,
NL80211_IFTYPE_ADHOC, // IBSS類型
NL80211_IFTYPE_STATION, // 基礎結構型網絡中的STA
NL80211_IFTYPE_AP, // 基礎結構型網絡中的AP
NL80211_IFTYPE_AP_VLAN, // 和VLAN有關,本書不討論
NL80211_IFTYPE_WDS, // 無線橋接。本書不討論
NL80211_IFTYPE_MONITOR, // 可接收無線網絡所有的數據包,它提供類似AirPcap這樣的功能
NL80211_IFTYPE_MESH_POINT, // Mesh網絡節點,本書不討論
NL80211_IFTYPE_P2P_CLIENT, // P2P相關,見后續章節
NL80211_IFTYPE_P2P_GO, // P2P相關,見后續章節
NUM_NL80211_IFTYPES,
NL80211_IFTYPE_MAX = NUM_NL80211_IFTYPES - 1
};
~~~
下面來看接口設備的工作狀態IfOperStatus,其定義來自RFC2863。
**if.h**
~~~
enum {
IF_OPER_UNKNOWN, // 未知狀態
// 下面這個狀態表示因為系統缺乏該接口設備所依賴的模塊(一般是硬件模塊)而導致接口設備不能工作
IF_OPER_NOTPRESENT, //
IF_OPER_DOWN, // 接口不能工作
/*
相比DOWN狀態,LOWERLAEYDOWN指出了接口設備不能工作的原因是其所依賴的更低一層的設備不
能正常工作。
*/
IF_OPER_LOWERLAYERDOWN,
IF_OPER_TESTING, // 接口處于測試狀態中
/*
接口處于休眠或暫停(pending)狀態中。這種狀態表示接口設備在等待某個事情的發生(例如上層
有數據要發送,則會將DORMANT狀態設置為UP狀態)。
*/
IF_OPER_DORMANT,
IF_OPER_UP, // 接口可工作
};
~~~
關于IfOperStatus,請讀者閱讀參考資料[18]。
最后,看關鍵函數wpa_driver_nl80211_capa,其功能是用于獲取無線網絡設備的capability。代碼如下所示。
**driver_nl80211.c::wpa_driver_nl80211_capa**
~~~
static int wpa_driver_nl80211_capa(struct wpa_driver_nl80211_data *drv)
{
struct wiphy_info_data info;
// 發送netlink命令NL80211_CMD_GET_WIPHY來獲取Wi-Fi設備的信息。下文將單獨用一節來介紹此函數
if (wpa_driver_nl80211_get_info(drv, &info)) return -1;
drv->has_capability = 1;
/*
drv->capa變量的類型是struct wpa_driver_capa,用于表示設備的capability,這些capa如下。
key_mgmt:該設備支持的密鑰管理類型。默認支持WPA、WPA-PSK、WPA2和WPA2-PSK。
enc:支持的加密算法類型。默認支持WEP40、WEP104、TKIP和CCMP。
auth:支持的身份驗證類型:默認支持Open System、Shared和LEAP。
*/
drv->capa.key_mgmt = WPA_DRIVER_CAPA_KEY_MGMT_WPA | WPA_DRIVER_CAPA_KEY_MGMT_WPA_PSK |
WPA_DRIVER_CAPA_KEY_MGMT_WPA2 | WPA_DRIVER_CAPA_KEY_MGMT_WPA2_PSK;
drv->capa.enc = WPA_DRIVER_CAPA_ENC_WEP40 | WPA_DRIVER_CAPA_ENC_WEP104 |
WPA_DRIVER_CAPA_ENC_TKIP | WPA_DRIVER_CAPA_ENC_CCMP;
drv->capa.auth = WPA_DRIVER_AUTH_OPEN | WPA_DRIVER_AUTH_SHARED | WPA_DRIVER_AUTH_LEAP;
/*
WPA_DRIVER_FLAGS_SANE_ERROR_CODES選項主要針對associate操作。當關聯操作失敗后,
如果driver支持該選項,則表明driver能處理失敗之后的各種收尾工作(key、timeout等工作)。
否則,WPAS需要自己處理這些事情。
*/
drv->capa.flags |= WPA_DRIVER_FLAGS_SANE_ERROR_CODES;
/*
WPA_DRIVER_FLAGS_SET_KEYS_AFTER_ASSOC_DONE標志標明association成功后,Kernel
driver需要設置WEP key。這個標志出現的原因是由于Kernel API發生了變動,使得只能在關聯
成功后才能設置key。
*/
drv->capa.flags |= WPA_DRIVER_FLAGS_SET_KEYS_AFTER_ASSOC_DONE;
/*
下面這兩個標志表示Kernel中的driver是否能反饋EAPOL數據幀發送情況以及Deauthentication/
Disassociation幀發送情況(TX Report)。
*/
drv->capa.flags |= WPA_DRIVER_FLAGS_EAPOL_TX_STATUS;
drv->capa.flags |= WPA_DRIVER_FLAGS_DEAUTH_TX_STATUS;
/*
以下幾個選項都和設備做AP使用有關,也就是和hostapd相關。此處簡單介紹一下它們。
device_ap_sme表示AP集成了SME。讀者還記得SME嗎?3.3.6節曾介紹過。
*/
drv->device_ap_sme = info.device_ap_sme;
/*
poll_command_supported:hostapd需要判斷STA是否還活躍,即心跳檢測。檢測方法是發送
null數據幀(即不帶任何數據的無線MAC數據幀),如果STA還活躍的話,一定會回復ACK給AP(讀
者還記得CSMA/CA機制嗎?)。發送null數據幀的工作可以由Kernel driver完成,也可以由
hostapd來完成。如果Kernel driver支持poll_command_supported,hostapd只要發送
netlink命令NL80211_CMD_PROBE_CLIENT給Kernel驅動,所有工作就由Kernel驅動完成。
否則,hostapd需要自己構造一個null數據幀,然后再發送出去。
*/
drv->poll_command_supported = info.poll_command_supported;
/*
和WPA_DRIVER_FLAGS_EAPOL_TX_STATUS有關。如果wlan驅動支持的話,EAPOL幀TX
Report將通知給用戶空間的driver wrapper,即此處的driver_nl80211。
*/
drv->data_tx_status = info.data_tx_status;
/*
use_monitor也和AP心跳檢測STA有關。如果Kernel driver不支持poll_command_
supported的話,hostapd可通過創建一個NL80211_IFTYPE_MONITOR類型的接口設備用于監控
STA的活躍情況。
*/
drv->use_monitor = !info.poll_command_supported;
if (drv->device_ap_sme && drv->use_monitor) {
// monitor_supported表示kernel driver是否支持創建NL80211_IFTYPE_MONITOR類
// 型的接口設備
if (!info.monitor_supported)
drv->use_monitor = 0;
}
/*
經過測試,Galaxy Note 2機器中上述變量取值情況如下。
device_ap_sme為1,poll_command_supported為0,data_tx_status為0,use_monitor為1,
capa.flags取值情況見下文。
*/
if (!drv->use_monitor && !info.data_tx_status)
drv->capa.flags &= ~WPA_DRIVER_FLAGS_EAPOL_TX_STATUS;
return 0;
}
~~~
Galaxy Note 2手機中得到的flags為0x2c0c0,它是如下幾個選項的組合。
~~~
#define WPA_DRIVER_FLAGS_AP 0x00000040
#define WPA_DRIVER_FLAGS_SET_KEYS_AFTER_ASSOC_DONE 0x00000080
#define WPA_DRIVER_FLAGS_SANE_ERROR_CODES 0x00004000
#define WPA_DRIVER_FLAGS_OFFCHANNEL_TX 0x00008000
#define WPA_DRIVER_FLAGS_DEAUTH_TX_STATUS 0x00020000
~~~
上述wpa_driver_nl80211_capa函數中,先調用wpa_driver_nl80211_get_info函數,從wlandriver獲取設備信息,然后wpa_driver_nl80211_capa再做一些處理。
此處向讀者展示一下wpa_driver_nl80211_get_info的內容,請讀者關注其中和nl80211用法有關的部分。
**提醒** WPAS中類似wpa_driver_nl80211_get_info的函數還有很多,僅以wpa_driver_nl80211_get_info為代表進行介紹。
(5)wpa_driver_nl80211_get_info函數分析
wpa_driver_nl80211_get_info代碼如下所示。
**driver_nl80211.c::wpa_driver_nl80211_get_info**
~~~
static int wpa_driver_nl80211_get_info(struct wpa_driver_nl80211_data *drv,struct wiphy_info_data *info)
{
struct nl_msg *msg;
os_memset(info, 0, sizeof(*info));
info->capa = &drv->capa;
msg = nlmsg_alloc();
......
// 構造一個NL80211_CMD_GET_WIPHY命令以獲取設備信息
nl80211_cmd(drv, msg, 0, NL80211_CMD_GET_WIPHY);
// NL80211_CMD_GET_WIPHY命令需要攜帶ifindex參數以指明要查詢哪個設備
NLA_PUT_U32(msg, NL80211_ATTR_IFINDEX, drv->first_bss.ifindex);
// 發送命令并等待回復,回復消息將由wiphy_info_handler函數處理
if (send_and_recv_msgs(drv, msg, wiphy_info_handler, info) == 0)
return 0;
......
}
~~~
在driver_nl80211.c中,wpa_driver_nl80211_get_info函數非常具有典型性。當driverwrapper和wlan driver通信時,需要構造一個nl_msg消息,然后往其中填寫對應的參數。發送該消息時,如果需要等待driver的回復,還可以設置一個回復消息處理函數用于解析接收到的回復消息。
上述代碼中,wiphy_info_handler就是這個回調函數。其內容非常長。不過,絕大部分代碼都是在解析netlink消息。因此,我們僅看其中與接口類型解析相關的代碼片段即可窺斑見豹。
**driver_nl80211.c::wiphy_info_handler**
~~~
static int wiphy_info_handler(struct nl_msg *msg, void *arg)
{
struct nlattr *tb[NL80211_ATTR_MAX + 1];
struct genlmsghdr *gnlh = nlmsg_data(nlmsg_hdr(msg));
struct wiphy_info_data *info = arg;
......
struct wpa_driver_capa *capa = info->capa;
static struct nla_policy
......// 其他信息解析
if (tb[NL80211_ATTR_SUPPORTED_IFTYPES]) {
struct nlattr *nl_mode;
int i;
nla_for_each_nested(nl_mode, // 遍歷netlink attribute信息
tb[NL80211_ATTR_SUPPORTED_IFTYPES], i) {
switch (nla_type(nl_mode)) {
case NL80211_IFTYPE_AP:// wlan driver支持設置接口類型為AP
capa->flags |= WPA_DRIVER_FLAGS_AP; // Galaxy Note 2支持此項
break;
......
case NL80211_IFTYPE_MONITOR:
info->monitor_supported = 1;
break;
}
}
}
......// 其他信息解析
return NL_SKIP;
}
~~~
關于driver_nl80211.c中nl80211使用最典型的wpa_driver_nl80211_get_info即介紹到此,讀者可閱讀nl80211頭文件來了解NL80211_CMD_GET_WIPHY和NL80211_ATTR_SUPPORTED_IFTYPES相關的信息,其注釋非常詳盡。另外,讀者可閱讀參考
資料[19]以了解更多和nl80211命令相關的知識。
通過上面內容可知,wpa_supplicant_init_iface代碼段三中最主要的函數是wpa_drv_init,下面總結它的相關知識。
**(6)wpa_drv_init相關知識總結**
本節對wpa_drv_init函數進行了詳細分析,其中涉及的兩個重要數據結構如圖4-14所示。
:-: 
圖4-14 wpa_driver_nl80211_data和i802_bss結構體
圖4-14中:
* wpa_driver_nl80211_data通過first_bss成員包含一個i802_bss結構體對象,而i802_bss內部通過next指針構成一個單向鏈表。
* wpa_driver_nl80211_init最后返回的是一個i802_bss對象,它就是driver wrapper上下文信息。i802_bss通過drv變量指向一個wpa_driver_nl80211_data對象。
**提醒** 根據前文的分析,global_init函數返回的是全局driver wrapper上下文信息。對于nl80211 driver wrapper來說,這個全局上下文信息就是一個nl80211_global對象。
圖4-15所示為wpa_drv_init中一些重要函數的調用流程。
:-: 
圖4-15 wpa_drv_init流程
圖4-15所示函數較多,內容也比較豐富,請讀者注意其中涉及的背景知識。
4. wpa_supplicant_init_iface分析之四
繼續來看wpa_supplicant_init_iface函數,這次要分析的代碼片段如下所示。
**wpa_supplicant.c::wpa_supplicant_init_iface代碼段四**
~~~
......// 接wpa_drv_init
// ①初始化wpa上下文信息。見下文解釋
if (wpa_supplicant_init_wpa(wpa_s) < 0)
return -1;
// 設置wpa_s->wpa指向一個wpa_sm對象,下面這兩個函數用于設置wpa_sm中的一些成員變量
wpa_sm_set_ifname(wpa_s->wpa, wpa_s->ifname,
wpa_s->bridge_ifname[0] ? wpa_s->bridge_ifname : NULL);
wpa_sm_set_fast_reauth(wpa_s->wpa, wpa_s->conf->fast_reauth);
/*
如果運行時配置文件(即wpa_supplicant.conf)設置了dot11RSNAConfigPMKLifetime、
dot11RSNAConfigPMKReauthThreshold和dot11RSNAConfigSATimeout,則使用配置文件中的值
來替換wpa_sm中的默認值。下文將詳細介紹這個三個變量的含義。
*/
if (wpa_s->conf->dot11RSNAConfigPMKLifetime &&
wpa_sm_set_param(wpa_s->wpa, RSNA_PMK_LIFETIME,
wpa_s->conf->dot11RSNAConfigPMKLifetime)) {......}
......// 處理dot11RSNAConfigPMKReauthThreshold和dot11RSNAConfigSATimeout
// ②獲取Wi-Fi設備的hardware特性
wpa_s->hw.modes = wpa_drv_get_hw_feature_data(wpa_s,&wpa_s->hw.num_modes,
&wpa_s->hw.flags);
// wpa_drv_get_capa函數已經見識過了,但這里出現了上一節沒有介紹的新成員
if (wpa_drv_get_capa(wpa_s, &capa) == 0) {
// ③capability信息,見下文解釋
wpa_s->drv_capa_known = 1;
// 筆者的Note 2中,capa.flags的值為0x2c0c0
wpa_s->drv_flags = capa.flags;
wpa_s->probe_resp_offloads = capa.probe_resp_offloads;
wpa_s->max_scan_ssids = capa.max_scan_ssids;
wpa_s->max_sched_scan_ssids = capa.max_sched_scan_ssids;
wpa_s->sched_scan_supported = capa.sched_scan_supported;
wpa_s->max_match_sets = capa.max_match_sets;
wpa_s->max_remain_on_chan = capa.max_remain_on_chan;
wpa_s->max_stations = capa.max_stations;
}
if (wpa_s->max_remain_on_chan == 0)
wpa_s->max_remain_on_chan = 1000;
~~~
上述代碼片段共有三個關鍵點,分別如下。
* wpa_supplicant_init_wpa函數用于初始化wpa_sm相關的資源。
* wpa_drv_get_hw_feature_data函數用于獲取hw特性。其中一些變量涉及較深的背景知識。
* wpa_drv_get_capa是獲取driver的capability。這個函數在上一節已經介紹過了,但本節出現了一些新的capability信息。
**(1)wpa_supplicant_init_wpa函數分析**
wpa_supplicant_init_wpa函數代碼并不復雜,主要完成以下兩件事情。
* 創建一個wpa_sm_ctx對象并填充其中的函數指針成員。
* 初始化wpa_sm狀態機。
代碼如下所示。
**wpas_glue.c::wpa_supplicant_init_wpa**
~~~
int wpa_supplicant_init_wpa(struct wpa_supplicant *wpa_s)
{
#ifndef CONFIG_NO_WPA
struct wpa_sm_ctx *ctx;
ctx = os_zalloc(sizeof(*ctx));
......
ctx->ctx = wpa_s;
ctx->msg_ctx = wpa_s;
ctx->set_state = _wpa_supplicant_set_state;
......// 其他成員變量設置
wpa_s->wpa = wpa_sm_init(ctx);
#endif /* CONFIG_NO_WPA */
return 0;
}
~~~
wpa_sm_init的代碼如下所示。
**wpa.c::wpa_sm_init**
~~~
struct wpa_sm * wpa_sm_init(struct wpa_sm_ctx *ctx)
{
struct wpa_sm *sm;
sm = os_zalloc(sizeof(*sm));
dl_list_init(&sm->pmksa_candidates);
sm->renew_snonce = 1;
sm->ctx = ctx;
// 下面這三個MIB相關成員變量的解釋見下文
sm->dot11RSNAConfigPMKLifetime = 43200;
sm->dot11RSNAConfigPMKReauthThreshold = 70;
sm->dot11RSNAConfigSATimeout = 60;
// 創建PMKSA緩存,用于存儲PMKSA
sm->pmksa = pmksa_cache_init(wpa_sm_pmksa_free_cb, sm, sm);
......
return sm;
}
~~~
上述兩段代碼中涉及的函數指針暫且先略過,先介紹其中的幾個重要數據結構,它們如圖4-16所示。
:-: 
圖4-16 wpa_sm_ctx和wpa_sm等結構體
圖4-16顯示了四個重要數據結構的內容。
* struct wpa_sm_ctx定義一些函數指針。這些函數的作用留待后續用到時再介紹。
* struct wpa_sm結構體名為狀態機(SM代表State Machine),但和WPAS中其他狀態機比起來,它更像是一個存儲狀態的上下文信息。該結構體內部通過eapol變量指向一個struct eapol_sm對象。4.4節將詳細分析eapol_sm。
* struct rsn_pmksa_cache、struct rsn_pmksa_cache_entry與PMKSA緩存有關。每一個rsn_pmksa_cache_entry代表一個PMKSA條目。注意,rsn_pmksa_cache_entry中有一個名為aa的數組,其存儲的是Authenticator的Address。一般情況下它和AP的bssid相同。
PMKSA還和幾個MIB選項有關,它們被定義成wpa_sm中的同名成員變量(數據類型都是unsigned in),分別如下。
* **dot11RSNAConfigPMKLifetime**:表示每一個PMKSA條目的有效時間(單位為秒),默認是43200秒。過了有效時間后,需要重新計算PMKSA。
* **dot11RSNAConfigPMKReauthThreshold**:用于指明PMKSA條目有效時間過去百分之多少后,需要重新進行身份認證。默認是70%。
* **dot11RSNAConfigSATimeout**:指明supplicant和Authenticator雙方進行身份驗證的最長時間。默認是60秒。在此時間內沒有完成身份驗證,則認為驗證失敗。
* **dot11RSNA4WayHandshakeFailures**:用于保存4-Way Handshake失敗的次數。
下面來看代碼中的第二個關鍵函數wpa_drv_get_hw_feature_data及相關的背景知識。
**(2)wpa_drv_get_hw_feature_data函數分析**
該函數內部將通過wpa_driver_ops結構體中的get_hw_feature_data指針調用driver_nl80211實現的wpa_driver_nl80211_get_hw_feature_data函數以獲取wifi hw特性。此處不討論其函數實現,而是看看hw特性都有哪些內容。hw特性由數據結構hostapd_hw_modes來表達,如圖4-17所示。
:-: 
圖4-17 hostapd_hw_modes數據結構
wpa_drv_get_hw_feature_data返回的是一個hostapd_hw_modes數組,其內容已經在圖4-17中標記出來。這里展示一個實例,圖4-18所示為修改WPAS后打印的Note 2 hw特性的一部分。
:-: 
圖4-18 Note 2 dump信息
圖4-18所示為hostapd_hw_modes數組中第三個元素的信息,它展示了硬件中和802.11b相關的特性。共13個信道,以及每個信道的中心頻率以及最大功率,支持四種傳輸速率。
現在來看最后一個關鍵點。
(3)capability信息及含義
wpa_supplicant_init_wpa代碼片段最后還顯示了一些capability信息,它們的含義如下。
* **probe_resp_offloads**:當設備做AP使用時(即運行hostapd),它需要發送Probe Response幀以回復其他STA的Probe Request幀。Probe Response幀(或者AP發送的Beacon幀)的內容需要hostapd來填充。這個變量用于指明哪些vendor specific的內容將由Wi-Fi驅動或者硬件去填充。目前NL80211.h通過枚舉類型nl80211_probe_resp_offload_support_attr來定義所能支持的協議,包括WPSv1、WPSv2、P2P和802.11u。
* **max_scan_ssids**:一個Probe Request要么指定wildcard ssid以掃描周圍所有的無線網絡,要么指定某個ssid以掃描特定無線網絡。為了方便wpa_supplicant的使用,driver新增了一個功能,使得上層可通過一次scan請求來掃描多個不同ssid的無線網絡。注意,此功能只是方便了WPAS內部的使用。由于規范定義的Probe Request幀只能攜帶一個ssid參數。所以,上層即使想一次scan多個ssid,硬件實際上還是要為每一個ssid發送一個Probe Request幀。
* **max_sched_scan_ssids和sched_scan_supported**:與計劃掃描有關。max_sched_scan_ssids和max_scan_ssids作用類似,是方便wpa_supplicant同時掃描多個ssid而設置的。
* **max_match_sets**:使用計劃掃描時,可以給驅動指定一個ssid過濾列表。只有掃描結果符合ssid過濾列表的那些無線網絡才會通知wpa_supplicant以開展后續處理。由于該過濾功能可由Wi-Fi硬件來完成,所以它可以節省一部分電力(即無須軟件去執行過濾功能)。
* **max_remain_on_chan**:該變量和off-channel transmition功能有關。該功能使得Wi-Fi硬件能在某個特定信道(channel)上保持awake狀態一定時間用于傳輸某些MAC幀(例如管理幀中的一種名為Action的幀)。該功能叫off-channel的原因是,STA實際上在另一個信道(此channel叫on-channel)上和AP保持連接。舉一個簡單的例子,假設STA和所關聯的AP工作在2.4GHz第6頻段。在某些時候,STA會轉移到2.4GHz其他頻段以接收或處理其他STA(P2P的情況)或AP發送的MAC幀。上述例子中,6頻段就是on-channel,而其他頻段則是off-channel。max_remain_on_chan變量用于指明STA在off-channel中工作的最長時間,以毫秒為單位。為什么要限制off-channel時間呢?還是以上述例子為例,STA和AP工作在第6頻段,二者數據傳輸也是在第6頻段。當STA轉移到其他頻段時,它將無法接收第6頻段所發送的數據。如max_remain_on_chan時間過長,用戶將發現數據傳輸率大幅降低②。
* **max_stations**:當手機做AP使用時(即無線網絡接口設備的類型為NL80211_IFTYPE_AP),該變量表示最多支持多少個STA與之關聯。
下面接著分析wpa_supplicant_init_iface如下所示最后一部分代碼。
5. wpa_supplicant_init_iface分析之五
wpa_supplicant_init_iface最后一段代碼如下所示。
**wpa_supplicant.c::wpa_supplicant_init_iface代碼段五**
~~~
// ①初始化driver wrapper模塊最后一部分內容
if (wpa_supplicant_driver_init(wpa_s) < 0)
return -1;
......// TDLS相關,本書不討論
......// 設置country
// 初始化WPS相關模塊,本章不討論
if (wpas_wps_init(wpa_s)) return -1;
// ②初始化EAPOL模塊。這部分內容4.4節介紹
if (wpa_supplicant_init_eapol(wpa_s) < 0)
return -1;
wpa_sm_set_eapol(wpa_s->wpa, wpa_s->eapol);
// ③初始化ctrl i/f模塊
wpa_s->ctrl_iface = wpa_supplicant_ctrl_iface_init(wpa_s);
......
wpa_s->gas = gas_query_init(wpa_s); // GAS相關,本書不討論
#ifdef CONFIG_P2P
if (wpas_p2p_init(wpa_s->global, wpa_s) < 0) {......// P2P模塊初始化,見第7章分析}
#endif /* CONFIG_P2P */
// ④bss相關,詳情見下文
if (wpa_bss_init(wpa_s) < 0)
return -1;
return 0;// wpa_supplicant_init_iface終于成功返回
~~~
上述代碼包括四個關鍵函數,其中第二個關鍵點和EAPOL模塊相關,其內容4.4節再介紹。
**(1)wpa_supplicant_driver_init函數分析**
先來看第一個關鍵函數wpa_supplicant_driver_init,代碼如下所示。
**wpa_supplicant.c::wpa_supplicant_driver_init**
~~~
int wpa_supplicant_driver_init(struct wpa_supplicant *wpa_s)
{
static int interface_count = 0;
// 關鍵函數,見下文代碼分析
if (wpa_supplicant_update_mac_addr(wpa_s) < 0) return -1;
if (wpa_s->bridge_ifname[0]) {......
// 橋接相關內容,本書不討論
}
// 清除driver中保存的key相關的信息
wpa_clear_keys(wpa_s, NULL);
// 設置TKIP countermeasure值為0
wpa_drv_set_countermeasures(wpa_s, 0);
// 清空drive wrapper及driver中保存的pmkid信息。
wpa_drv_flush_pmkid(wpa_s);
// 設置wpa_supplicant結構體中的一些變量的初值
wpa_s->prev_scan_ssid = WILDCARD_SSID_SCAN;
wpa_s->prev_scan_wildcard = 0;
// 判斷wpa_conf中是否有使能的網絡
if (wpa_supplicant_enabled_networks(wpa_s->conf)) {
......// 當前配置文件中沒有使能任何一個網絡,故此段代碼略去
} else // 設置狀態為WPA_INACTIVE。該函數比較簡單,請讀者自行閱讀
wpa_supplicant_set_state(wpa_s, WPA_INACTIVE);
return 0;
}
~~~
上述代碼中唯一需要介紹的就是wpa_supplicant_update_mac_addr,因為它和圖4-1中的l2_packet模塊初始化有關,其代碼如下所示。
**wpa_supplicant.c::wpa_supplicant_update_mac_addr**
~~~
int wpa_supplicant_update_mac_addr(struct wpa_supplicant *wpa_s)
{
if (wpa_s->driver->send_eapol) { ......// nl80211 driver wrapper沒有定義該函數
} else if (!(wpa_s->drv_flags &
WPA_DRIVER_FLAGS_P2P_DEDICATED_INTERFACE)) {
// WPA_DRIVER_FLAGS_P2P_DEDICATED_INTERFACE和P2P有關,本例不支持該參數
l2_packet_deinit(wpa_s->l2); // 先釋放之前創建的l2_packet模塊
// 初始化l2_packet
wpa_s->l2 = l2_packet_init(wpa_s->ifname,
wpa_drv_get_mac_addr(wpa_s), // 獲取接口的MAC地址
ETH_P_EAPOL,
// 收到的EAPOL及EAP幀將由此函數負責處理
wpa_supplicant_rx_eapol, wpa_s, 0);
......
} else { ......}
// 將l2_packet_data中的own_addr內容復制到wpa_supplicant的own_addr成員變量
if (wpa_s->l2 && l2_packet_get_own_addr(wpa_s->l2, wpa_s->own_addr)) {......}
// 再把wpa_supplicant的own_addr復制到wpa_sm中的own_addr中
wpa_sm_set_own_addr(wpa_s->wpa, wpa_s->own_addr);
......
return 0;
}
~~~
l2_packet_init內部就是創建一個PF_PACKET域的socket。注意,l2_packet_init最后一個參數為0,這樣,socket的類型將是SOCK_DGRAM。l2_packet_init返回值類型為l2_packet_data,其成員如圖4-19所示。
:-: 
圖4-19 l2_packet_data成員
l2_packet_init通過eloop_register_read_sock函數為圖4-19中的socket句柄fd注冊一個讀事件回調函數l2_packet_receive,而該函數將接收socket數據,然后回調rx_callback。該函數對于4-Way Handshake非常重要,后文將詳細介紹此處設置的回調函數(wpa_supplicant_rx_eapol)。
下面來看第三個關鍵函數wpa_supplicant_ctrl_iface_init。
**(2)wpa_supplicant_ctrl_iface_init函數分析**
該函數內部將創建一個unix域socket,然后向eloop注冊一個讀事件處理函數。Android平臺對此函數進行了定制,主要是利用圖4-3中init配置文件中wpa_supplicant的socket選項。init在fork出一個wpa_supplicant子進程時將創建一個socket,并通過環境變量傳給wpa_supplicant子進程。
**提示** 對socket選項感興趣的讀者可閱讀《深入理解Android:卷Ⅰ》3.2.3節“啟動Zygote”。
**ctrl_iface_unix.c::wpa_supplicant_ctrl_iface_init**
~~~
wpa_supplicant_ctrl_iface_init(struct wpa_supplicant *wpa_s)
{
struct ctrl_iface_priv *priv;
struct sockaddr_un addr;
......
priv = os_zalloc(sizeof(*priv));
dl_list_init(&priv->ctrl_dst);
priv->wpa_s = wpa_s;
priv->sock = -1;
buf = os_strdup(wpa_s->conf->ctrl_interface);
......
#ifdef ANDROID // Android平臺定義了此編譯宏
// addr.sun_patch的值為wpa_wlan0。該值和圖4-5中socket選項指定的值一樣
os_snprintf(addr.sun_path, sizeof(addr.sun_path), "wpa_%s",
wpa_s->conf->ctrl_interface);
priv->sock = android_get_control_socket(addr.sun_path);// 獲取socket句柄
if (priv->sock >= 0)
goto havesock; // 直接跳轉
#endif /* ANDROID */
......
havesock:
#endif /* ANDROID */
// 客戶端發送命令都由wpa_supplicant_ctrl_iface_receive處理
eloop_register_read_sock(priv->sock, wpa_supplicant_ctrl_iface_receive,
wpa_s, priv);
// 讀者還記得4.3.2節wpa_supplicant_init分析中提到的消息全局回調函數嗎
wpa_msg_register_cb(wpa_supplicant_ctrl_iface_msg_cb);
os_free(buf);
return priv;
}
~~~
上述代碼中,客戶端發送的命令將由wpa_supplicant_ctrl_iface_receive函數處理。
提示 后文分析線路二中用戶發送的WPAS命令時,就將直接分析此函數。
**(3)wpa_bss_init函數分析**
最后來看wpa_bss_init函數。
**bss.c::wpa_bss_init**
~~~
int wpa_bss_init(struct wpa_supplicant *wpa_s)
{
// bss和bss_id是wpa_supplicant結構體中的成員變量,它們通過鏈表的方式來保存wpa_bss信息
dl_list_init(&wpa_s->bss);
dl_list_init(&wpa_s->bss_id);
// 注冊一個超時任務,超時時間為WPA_BSS_EXPIRATION_PERIOD,值為10秒
eloop_register_timeout(WPA_BSS_EXPIRATION_PERIOD, 0,wpa_bss_timeout, wpa_s, NULL);
return 0;
}
~~~
wpa_supplicant注冊了一個定時任務用于定時更新其保存的wpa_bss信息,一旦某個無線網絡在一定時間內沒有更新或使用,則需要從鏈表中把它去掉。
超時任務的函數代碼如下。
**bss.c::wpa_bss_timeout**
~~~
static void wpa_bss_timeout(void *eloop_ctx, void *timeout_ctx)
{
struct wpa_supplicant *wpa_s = eloop_ctx;
// bss_expiration_age默認是1800秒
// 下面這個函數將更新wpa_bss鏈表以刪除一些無用的wpa_bss對象
wpa_bss_flush_by_age(wpa_s, wpa_s->conf->bss_expiration_age);
eloop_register_timeout(WPA_BSS_EXPIRATION_PERIOD, 0,wpa_bss_timeout, wpa_s, NULL);
}
~~~
6. wpa_supplicant_add_iface流程總結
看到這里,讀者一定會感慨,線路一走下來比較艱難。其中所調用函數之多、每個變量背后含義之豐富都是初學者要面臨的挑戰。在此,通過圖4-20展示wpa_supplicant_add_iface中所涉及的幾個重要函數的調用流程。
:-: 
圖4-20 wpa_supplicant_add_iface重要流程
圖4-20中的第8個函數調用wpa_driver_nl80211_init的內容在圖4-15中。請讀者結合這兩個圖來學習調用流程。
即使用了如此之多的筆墨,wpa_supplicant_init初始化所涉及的內容依然不能全部覆蓋到。下一節介紹非常重要的兩個模塊:EAP和EAPOL。
① 注意,此結論為筆者根據筆記本的表現形式進行的猜測。如有讀者知道其工作原理不妨與大家分享。
② max_remain_on_chan的官方解釋可參考nl80211關于 NL80211_CMD_REMAIN_ON_CHANNEL的定義,其原文是"Request to remain awake on the specified channel for the specified amount of time.This can be used to do off-channel operations like transmit aPublic Action frame and wait for aresponse while being associated to an AP on another channel"。請了解該功能的讀者和大家分享相關知識。
- 前言
- 第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 參考資料說明
- 附錄