天天看點

wpa_supplicant用法及分析

 Android系統中對于WIFI的設定內建到了“設定”中,其實跟手動設定差不多。這裡介紹下如何手動連接配接WIFI,以友善以後調試WIFI。

        第一步要做的就是要加載WIFI子產品驅動了。當然如果你的WIFI是編譯到核心裡面的,就不需要的。我們的WIFI晶片用的是BCM4330,編譯為子產品。

insmod /system/lib/modules/kernel/drivers/net/wireless/bcm4330/bcm4330.ko firmware_path=/system/vendor/firmware/bcm4330.bin \ nvram_path=/system/vendor/firmware/nvram.txt

       這樣驅動子產品加載後,需要啟動wpa_supplicant

[email protected]:/ # wpa_supplicant -Dwext -iwlan0 -C/data/system/wpa_supplicant -c/data/misc/wifi/wpa_supplicant.conf &  

然後ps|grep wpa看看有沒有起來,在wifi工作過程中,這個程序要始終都在的。

        接着啟動用戶端wpa_cli進行配置并連接配接wifi熱點

130|[email protected]:/ # wpa_cli -p/data/system/wpa_supplicant -iwlan0

wpa_cli v0.8.x

Copyright (c) 2004-2011, Jouni Malinen <[email protected]> and contributors

This program is free software. You can distribute it and/or modify it

under the terms of the GNU General Public License version 2.

Alternatively, this software may be distributed under the terms of the

BSD license. See README and COPYING for more details.

Interactive mode

其中/data/system/wpa_supplicant 是剛剛啟動wpa_supplicant的時候建立的一個套接字

接着進行搜尋wifi

> scan

OK

<3>CTRL-EVENT-SCAN-RESULTS 

檢視搜到的有哪些熱點

> scan_result

bssid / frequency / signal level / flags / ssid

40:16:9f:67:0f:00       2462    -42     [WPA-PSK-TKIP+CCMP][WPA2-PSK-TKIP+CCMP][WPS][ESS]       HHCN-NET

b0:48:7a:49:44:68       2437    -58     [WPA2-PSK-CCMP][WPS][ESS]       HHTech.Arch

e0:05:c5:97:d8:5a       2412    -71     [WPA-PSK-CCMP][WPA2-PSK-CCMP-preauth][ESS]      1103-5

40:16:9f:67:0c:a6       2412    -74     [WPA-PSK-TKIP+CCMP][WPA2-PSK-TKIP+CCMP][WPS][ESS]       hardware

b0:48:7a:49:65:54       2437    -58     [WPA-PSK-CCMP][WPA2-PSK-CCMP][WPS][ESS] Dept_driver

接着設定wifi,就連接配接Dept_driver這個熱點吧

> add_net

3

> set_net 3 ssid "Dept_driver"

OK

Dept_driver的加密方式是WPA2-PSK

> set_net 3 psk "password"

OK

> select_net 3

OK

<3>CTRL-EVENT-STATE-CHANGE id=0 state=0 BSSID=00:00:00:00:0[ 2752.332061] dhd_aoe_hostip_clr failed code -23

0:00

<3>CTR[ 2752.337768] dhd_aoe_arp_clr failed code 1

L-EVENT-STATE-CHANGE id=-1 state=3 BSSID=00:00:00:00:00:00

<3>CTRL-EVENT-DISCONNECTED bssid=00:00:00:00:00:00 reason=0

<3>CTRL-EVENT-STATE-CHANGE id=-1 state=0 BSSID=00:00:00:00:00:00

<3>CTRL-EVENT-STATE-CHANGE id=-1 state=3 BSSID=00:00:00:00:00:00

<3>CTRL-EVENT-SCAN-RESULTS 

<3>WPS-AP-AVAILABLE 

<3>Trying to associate with b0:48:7a:49:65:54 (SSID='Dept_driver' freq=2437 MHz)

<3>CTRL-EVENT-STATE-CHANGE id=-1 state=5 BSSID=b0:48:7a:49:65:54

[ 2753.153717] wl_iw_set_essid: join SSID=Dept_driver ch=6

<3>CTRL-EVENT-STATE-CHANGE id=3 state=6 BSSID=b0:48:7a:49:65:54

<3>Associated with b0:48:7a:49:65:54

<3>CTRL-EVENT-STATE-CHANGE id=3 state=7 BSSID=00:00:00:00:00:00

<3>CTRL-EVENT-STATE-CHANGE id=3 state=8 BSSID=00:00:00:00:00:00

<3>WPA: Key negotiation completed with b0:48:7a:49:65:54 [PTK=CCMP GTK=CCMP]

<3>CTRL-EVENT-CONNECTED - Connection to b0:48:7a:49:65:54 completed (reauth) [id=3 id_str=]

<3>CTRL-EVENT-STATE-CHANGE id=3 state=9 BSSID=00:00:00:00:00:00

> enable_net 3

OK

> q

這樣,就都設定好了,接着用dhcpd配置設定一個IP位址

[email protected]:/ # dhcpcd wlan0                                                  

dhcpcd[3940]: version 5.2.10 starting

dhcpcd[3940]: host does not support a monotonic clock - timing can skew

dhcpcd[3940]: wlan0: rebinding lease of 192.168.1.216

dhcpcd[3940]: wlan0: acknowledged 192.168.1.216 from 192.168.1.1 `�'

dhcpcd[3940]: wlan0: leased 192.168.1.216 for 7200 seconds

dhcpcd[3940]: forked to background, child pid 3971

[email protected]:/ # busybox ifconfig wlan0                                        

wlan0     Link encap:Ethernet  HWaddr AC:E8:7B:89:D3:C0  

          inet addr:192.168.1.216  Bcast:255.255.255.255  Mask:255.255.255.0

          inet6 addr: fe80::aee8:7bff:fe89:d3c0/64 Scope:Link

          UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1

          RX packets:6637 errors:0 dropped:25 overruns:0 frame:0

          TX packets:4357 errors:0 dropped:0 overruns:0 carrier:0

          collisions:0 txqueuelen:1000 

          RX bytes:3825961 (3.6 MiB)  TX bytes:704832 (688.3 KiB)

這樣就完成了。可以上網了。

wpa supplicant  架構分析

1. 啟動指令 

wpa supplicant 在啟動時,啟動指令可以帶有很多參數,目前我們的啟動指令如下: 

wpa_supplicant /system/bin/wpa_supplicant -Dwext -ieth0 -c/data/wifi/wpa_supplicant.conf -f/data/wifi/wpa_log.txt 

wpa_supplicant對于啟動指令帶的參數,用了兩個資料結構來儲存, 

一個是 wpa_params, 另一個是wpa_interface. 

這主要是考慮到wpa_supplicant是可以同時支援多個網絡接口的。 

wpa_params資料結構主要記錄與網絡接口無關的一些參數設定。 

而每一個網絡接口就用一個wpa_interface資料結構來記錄。 

在啟動指令行中,可以用-N來指定将要描述一個新的網絡接口,對于一個新的網絡接口,可以用下面六個參數描述: 

-i<ifname> : 網絡接口名稱 

-c<conf>: 配置檔案名稱 

-C<ctrl_intf>: 控制接口名稱 

-D<driver>: 驅動類型 

-p<driver_param>: 驅動參數 

-b<br_ifname>: 橋接口名稱 

2. wpa_supplicant 初始化流程 

2.1. main()函數: 

在這個函數中,主要做了四件事。 

a. 解析指令行傳進的參數。 

b. 調用wpa_supplicant_init()函數,做wpa_supplicant的初始化工作。 

c. 調用wpa_supplicant_add_iface()函數,增加網絡接口。 

d. 調用wpa_supplicant_run()函數,讓wpa_supplicant真正的run起來。 

2.2. wpa_supplicant_init()函數: 

a. 打開debug 檔案。 

b. 注冊EAP peer方法。 

c. 申請wpa_global記憶體,該資料結構作為統領其他資料結構的一個核心, 主要包括四個部分: 

wpa_supplicant *ifaces    

wpa_params params    

ctrl_iface_global_priv *ctrl_iface   

ctrl_iface_dbus_priv *dbus_ctrl_iface   

d. 設定wpa_global中的wpa_params中的參數。 

e. 調用eloop_init函數将全局變量eloop中的user_data指針指向wpa_global。 

f. 調用wpa_supplicant_global_ctrl_iface_init函數初始化global 控制接口。 

g. 調用wpa_supplicant_dbus_ctrl_iface_init函數初始化dbus 控制接口。 

h. 将該daemon的pid寫入pid_file中。 

2.3. wpa_supplicant_add_iface()函數: 

該函數根據啟動指令行中帶有的參數增加網絡接口, 有幾個就增加幾個。 

a. 因為wpa_supplicant是與網絡接口對應的重要的資料結構,是以,首先配置設定一個wpa_supplicant資料結構的記憶體。 

b. 調用wpa_supplicant_init_iface() 函數來做網絡接口的初始工作,主要包括: 

設定驅動類型,預設是wext; 

讀取配置檔案,并将其中的資訊設定到wpa_supplicant資料結構中的conf 指針指向的資料結構,它是一個wpa_config類型; 

指令行設定的控制接口ctrl_interface和驅動參數driver_param覆寫配置檔案裡設定,指令行中的優先; 

拷貝網絡接口名稱和橋接口名稱到wpa_config資料結構; 

對于網絡配置塊有兩個連結清單描述它,一個是 config->ssid,它按照配置檔案中的順序依次挂載在這個連結清單上,還有一個是pssid,它是一個二級指針,指向一個指針數組,該指針數組按照優先級從高到底的順序依次儲存wpa_ssid指針,相同優先級的在同一連結清單中挂載。 

c. 調用wpa_supplicant_init_iface2() 函數,主要包括: 

調用wpa_supplicant_init_eapol()函數來初始化eapol; 

調用相應類型的driver的init()函數; 

設定driver的param參數; 

調用wpa_drv_get_ifname()函數獲得網絡接口的名稱,對于wext類型的driver,沒有這個接口函數;

調用wpa_supplicant_init_wpa()函數來初始化wpa,并做相應的初始化工作; 

調用wpa_supplicant_driver_init()函數,來初始化driver接口參數;在該函數的最後,會 

wpa_s->prev_scan_ssid = BROADCAST_SSID_SCAN; 

wpa_supplicant_req_scan(wpa_s, interface_count, 100000); 

來主動發起scan, 

調用wpa_supplicant_ctrl_iface_init()函數,來初始化控制接口;對于UNIX SOCKET這種方式,其本地socket檔案是由配置檔案裡的ctrl_interface參數指定的路徑加上網絡接口名稱; 

2.4. wpa_supplicant_run()函數: 

初始化完成之後,讓wpa_supplicant的main event loop run起來。 

在wpa_supplicant中,有許多與外界通信的socket,它們都是需要注冊到eloop event子產品中的,具體地說,就是在eloop_sock_table中增加一項記錄,其中包括了sock_fd, handle, eloop_data, user_data。 

eloop event子產品就是将這些socket組織起來,統一管理,然後在eloop_run中利用select機制來管理socket的通信。 

3. Wpa_supplicant提供的接口 

從通信層次上劃分,wpa_supplicant提供向上的控制接口 control interface,用于與其他子產品(如UI)進行通信,其他子產品可以通過control interface 來擷取資訊或下發指令。Wpa_supplicant通過socket通信機制實作下行接口,與核心進行通信,擷取資訊或下發指令。 

3.1 上行接口 

Wpa_supplicant提供兩種方式的上行接口。一種基于傳統dbus機制實作與其他程序間的IPC通信;另一種通過Unix domain socket機制實作程序間的IPC通信。 

3.1.1 Dbus接口 

該接口主要在檔案“ctrl_iface_dbus.h”,“ctrl_iface_dbus.c”,“ctrl_iface_dbus_handler.h”和“ctrl_iface_dbus_handler.c”中實作,提供一些基本的控制方法。 

DBusMessage * wpas_dbus_new_invalid_iface_error(DBusMessage *message); 

DBusMessage * wpas_dbus_global_add_interface(DBusMessage *message, 

                                        struct wpa_global *global); 

DBusMessage * wpas_dbus_global_remove_interface(DBusMessage *message, 

                                          struct wpa_global *global); 

DBusMessage * wpas_dbus_global_get_interface(DBusMessage *message, 

                                        struct wpa_global *global); 

DBusMessage * wpas_dbus_global_set_debugparams(DBusMessage *message, 

                                          struct wpa_global *global); 

DBusMessage * wpas_dbus_iface_scan(DBusMessage *message, 

                               struct wpa_supplicant *wpa_s); 

DBusMessage * wpas_dbus_iface_scan_results(DBusMessage *message, 

                                      struct wpa_supplicant *wpa_s); 

DBusMessage * wpas_dbus_bssid_properties(DBusMessage *message, 

                                    struct wpa_supplicant *wpa_s, 

                                    struct wpa_scan_res *res); 

DBusMessage * wpas_dbus_iface_capabilities(DBusMessage *message, 

                                      struct wpa_supplicant *wpa_s); 

DBusMessage * wpas_dbus_iface_add_network(DBusMessage *message, 

                                     struct wpa_supplicant *wpa_s); 

DBusMessage * wpas_dbus_iface_remove_network(DBusMessage *message, 

                                        struct wpa_supplicant *wpa_s); 

DBusMessage * wpas_dbus_iface_set_network(DBusMessage *message, 

                                     struct wpa_supplicant *wpa_s, 

                                     struct wpa_ssid *ssid); 

DBusMessage * wpas_dbus_iface_enable_network(DBusMessage *message, 

                                        struct wpa_supplicant *wpa_s, 

                                        struct wpa_ssid *ssid); 

DBusMessage * wpas_dbus_iface_disable_network(DBusMessage *message, 

                                         struct wpa_supplicant *wpa_s, 

                                         struct wpa_ssid *ssid); 

DBusMessage * wpas_dbus_iface_select_network(DBusMessage *message, 

                                             struct wpa_supplicant *wpa_s); 

DBusMessage * wpas_dbus_iface_disconnect(DBusMessage *message, 

                                    struct wpa_supplicant *wpa_s); 

DBusMessage * wpas_dbus_iface_set_ap_scan(DBusMessage *message, 

                                          struct wpa_supplicant *wpa_s); 

DBusMessage * wpas_dbus_iface_set_smartcard_modules( 

       DBusMessage *message, struct wpa_supplicant *wpa_s); 

DBusMessage * wpas_dbus_iface_get_state(DBusMessage *message, 

                                   struct wpa_supplicant *wpa_s); 

DBusMessage * wpas_dbus_iface_get_scanning(DBusMessage *message, 

                                      struct wpa_supplicant *wpa_s); 

DBusMessage * wpas_dbus_iface_set_blobs(DBusMessage *message, 

                                    struct wpa_supplicant *wpa_s); 

DBusMessage * wpas_dbus_iface_remove_blobs(DBusMessage *message, 

                                      struct wpa_supplicant *wpa_s); 

3.1.2 Unix domain socket 接口 

該接口主要在檔案“wpa_ctrl.h”,“wpa_ctrl.c”,“ctrl_iface_unix.c”,“ctrl_iface.h”和“ctrl_iface.c”實作。 

(1)“wpa_ctrl.h”,“wpa_ctrl.c”完成對control interface的封裝,對外提供統一的接口。其主要的工作是通過Unix domain socket建立一個control interface 的client結點,與作為server的wpa_supplicant結點通信。 

主要功能函數: 

struct wpa_ctrl * wpa_ctrl_open(const char *ctrl_path); 

void wpa_ctrl_close(struct wpa_ctrl *ctrl); 

int wpa_ctrl_request(struct wpa_ctrl *ctrl, const char *cmd, size_t cmd_len, 

                   char *reply, size_t *reply_len, 

                   void (*msg_cb)(char *msg, size_t len)); 

Note: 

       Wpa_supplicant 提供兩種由外部子產品擷取資訊的方式:一種是外部子產品通過發送request 指令然後擷取response的問答模式,另一種是wpa_supplicant主動向外部發送event事件,由外部子產品監聽接收。 

       一般的常用做法是外部子產品通過調用wpa_ctrl_open()兩次,建立兩個control interface接口,一個為ctrl interface,用于發送指令,擷取資訊,另一個為monitor interface,用于監聽接收來自于wpa_supplicant的event時間。此舉可以降低通信的耦合性,避免response和event的互相幹擾。 

int wpa_ctrl_attach(struct wpa_ctrl *ctrl); 

int wpa_ctrl_detach(struct wpa_ctrl *ctrl); 

int wpa_ctrl_pending(struct wpa_ctrl *ctrl); 

int wpa_ctrl_recv(struct wpa_ctrl *ctrl, char *reply, size_t *reply_len); 

(2)“ctrl_iface_unix.c”實作wpa_supplicant的Unix domain socket通信機制中server結點,完成對client結點的響應。 

       其中最主要的兩個函數為: 

static void wpa_supplicant_ctrl_iface_receive(int sock, void *eloop_ctx, 

                                         void *sock_ctx) 

static void wpa_supplicant_ctrl_iface_send(struct ctrl_iface_priv *priv, 

                                      int level, const char *buf, 

                                      size_t len) 

(3)“ctrl_iface.h”和“ctrl_iface.c”主要實作了各種request指令的底層處理函數。 

3.2 下行接口 

Wpa_supplicant提供的下行接口主要用于和kernel(driver)進行通信,下發指令和擷取資訊。 

Wpa_supplicant下行接口主要包括三種重要的接口: 

1.    PF_INET socket接口,主要用于向kernel 發送ioctl指令,控制并擷取相應資訊。 

2.    PF_NETLINK socket接口,主要用于接收kernel發送上來的event 事件。 

3.    PF_PACKET socket接口,主要用于向driver傳遞802.1X封包。 

主要涉及到的檔案包括:“driver.h”,“drivers.c”,“driver_wext.h”,“driver_wext.c”,“l2_packet.h”和“l2_packet_linux.c”。其中“driver.h”,“drivers.c”,“driver_wext.h”和“driver_wext.c”實作PF_INET socket接口和PF_NETLINK socket接口;“l2_packet.h”和“l2_packet_linux.c”實作PF_PACKET socket接口。 

(1)“driver.h”,“drivers.c”主要用于封裝底層差異對外顯示一個相同的wpa_driver_ops接口。Wpa_supplicant可支援atmel, Broadcom, ipw, madwifi, ndis, nl80211, wext等多種驅動。 

其中一個最主要的資料結構為wpa_driver_ops, 其定義了driver相關的各種操作接口。 

(2)“driver_wext.h”,“driver_wext.c”實作了wext形式的wpa_driver_ops,并建立了PF_INET socket接口和PF_NETLINK socket接口,然後通過這兩個接口完成與kernel的資訊互動。 

Wext提供的一個主要資料結構為: 

struct wpa_driver_wext_data { 

       void *ctx; 

       int event_sock; 

       int ioctl_sock; 

       int mlme_sock; 

       char ifname[IFNAMSIZ + 1]; 

       int ifindex; 

       int ifindex2; 

       int if_removed; 

       u8 *assoc_req_ies; 

       size_t assoc_req_ies_len; 

       u8 *assoc_resp_ies; 

       size_t assoc_resp_ies_len; 

       struct wpa_driver_capa capa; 

       int has_capability; 

       int we_version_compiled; 

       int use_crypt; 

       int auth_alg_fallback; 

       int operstate; 

       char mlmedev[IFNAMSIZ + 1]; 

       int scan_complete_events; 

}; 

其中event_sock 為PF_NETLINK socket接口,ioctl_sock為PF_INET socket借口。 

Driver_wext.c實作了大量底層處理函數用于實作wpa_driver_ops操作參數,其中比較重要的有: 

void * wpa_driver_wext_init(void *ctx, const char *ifname); 

void wpa_driver_wext_deinit(void *priv); 

static void wpa_driver_wext_event_receive(int sock, void *eloop_ctx, 

                                     void *sock_ctx); 

最後,将實作的操作函數映射到一個全局的wpa_driver_ops類型資料結構 wpa_driver_wext_ops中。 

const struct wpa_driver_ops wpa_driver_wext_ops = { 

       .name = "wext", 

       .desc = "Linux wireless extensions (generic)", 

       .get_bssid = wpa_driver_wext_get_bssid, 

       .get_ssid = wpa_driver_wext_get_ssid, 

       .set_wpa = wpa_driver_wext_set_wpa, 

       .set_key = wpa_driver_wext_set_key, 

       .set_countermeasures = wpa_driver_wext_set_countermeasures, 

       .set_drop_unencrypted = wpa_driver_wext_set_drop_unencrypted, 

       .scan = wpa_driver_wext_scan, 

       .get_scan_results2 = wpa_driver_wext_get_scan_results, 

       .deauthenticate = wpa_driver_wext_deauthenticate, 

       .disassociate = wpa_driver_wext_disassociate, 

       .set_mode = wpa_driver_wext_set_mode, 

       .associate = wpa_driver_wext_associate, 

       .set_auth_alg = wpa_driver_wext_set_auth_alg, 

       .init = wpa_driver_wext_init, 

       .deinit = wpa_driver_wext_deinit, 

       .add_pmkid = wpa_driver_wext_add_pmkid, 

       .remove_pmkid = wpa_driver_wext_remove_pmkid, 

       .flush_pmkid = wpa_driver_wext_flush_pmkid, 

       .get_capa = wpa_driver_wext_get_capa, 

       .set_operstate = wpa_driver_wext_set_operstate, 

}; 

(3)“l2_packet.h”和“l2_packet_linux.c”主要用于實作PF_PACKET socket接口,通過該接口,wpa_supplicant可以直接将802.1X packet發送到L2層,而不經過TCP/IP協定棧。 

其中主要的功能函數為: 

struct l2_packet_data * l2_packet_init( 

       const char *ifname, const u8 *own_addr, unsigned short protocol, 

       void (*rx_callback)(void *ctx, const u8 *src_addr, 

                         const u8 *buf, size_t len), 

       void *rx_callback_ctx, int l2_hdr); 

void l2_packet_deinit(struct l2_packet_data *l2); 

int l2_packet_send(struct l2_packet_data *l2, const u8 *dst_addr, u16 proto, 

                 const u8 *buf, size_t len); 

static void l2_packet_receive(int sock, void *eloop_ctx, void *sock_ctx); 

4. Control interface commands 

       PING 

       MIB 

       STATUS 

       STATUS-VERBOSE 

       PMKSA 

       SET <variable> <valus> 

       LOGON 

       LOGOFF 

       REASSOCIATE 

       RECONNECT 

       PREAUTH <BSSID> 

       ATTACH 

       DETACH 

       LEVEL <debug level> 

       RECONFIGURE 

       TERMINATE 

       BSSID <network id> <BSSID> 

       LIST_NETWORKS 

       DISCONNECT 

       SCAN 

       SCAN_RESULTS 

       BSS 

       SELECT_NETWORK <network id> 

       ENABLE_NETWORK <network id> 

       DISABLE_NETWORK <network id> 

       ADD_NETWORK 

       REMOVE_NETWORK <network id> 

       SET_NETWORK <network id> <variable> <value> 

       GET_NETWORK <network id> <variable> 

       SAVE_CONFIG