<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

                ??碼云GVP開源項目 12k star Uniapp+ElementUI 功能強大 支持多語言、二開方便! 廣告
                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所示。 :-: ![](https://box.kancloud.cn/a9e9e197b5aa9367f25db60cc5060b2b_961x351.jpg) 圖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) &lt; 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 &lt; 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)的示意圖。 :-: ![](https://box.kancloud.cn/0ab49850ea9f9819ae29a2abf05cb8dc_1088x615.jpg) 圖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所示。 :-: ![](https://box.kancloud.cn/b7093c925e1b25bd6ccbd0ae87fb11b1_1342x465.jpg) 圖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中一些重要函數的調用流程。 :-: ![](https://box.kancloud.cn/f20f788d045370e392827e0ad3751d69_937x664.jpg) 圖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所示。 :-: ![](https://box.kancloud.cn/6af12989b18df44ec1727e667748bf43_1300x681.jpg) 圖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所示。 :-: ![](https://box.kancloud.cn/d5025e61400e3fe66b47b24574f506b0_1275x565.jpg) 圖4-17 hostapd_hw_modes數據結構 wpa_drv_get_hw_feature_data返回的是一個hostapd_hw_modes數組,其內容已經在圖4-17中標記出來。這里展示一個實例,圖4-18所示為修改WPAS后打印的Note 2 hw特性的一部分。 :-: ![](https://box.kancloud.cn/fb8c24b6245b2e7f182b490dd107558f_1093x633.jpg) 圖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所示。 :-: ![](https://box.kancloud.cn/45e4d934c4ba194d996e9c91b94383c1_1068x525.jpg) 圖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中所涉及的幾個重要函數的調用流程。 :-: ![](https://box.kancloud.cn/a8dcdf9952cb2309bb7fe24ed5764af1_1191x1677.jpg) 圖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"。請了解該功能的讀者和大家分享相關知識。
                  <ruby id="bdb3f"></ruby>

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看