wpa_supplicant與用戶態程序的交互分析

1 wpa_supplicant與用戶態程序wpa_cli的交互過程

1.1 交互接口類型

wpa_supplicant與用戶態程序交互的主要接口包括以下幾種:

  • 1)命令行界面:通過命令行工具 wpa_cli 可以與 wpa_supplicant 進行交互。wpa_cli 允許用戶執行各種 wpa_supplicant 操作,如配置網絡、掃描網絡、斷開連接等。用戶可以通過命令行輸入命令,然后 wpa_cli 會將命令傳遞給 wpa_supplicant,并返回執行結果。
  • 2)控制接口文件:wpa_supplicant 會創建一個控制接口文件,通常位于 /var/run/wpa_supplicant/ 目錄下,以與外部程序進行通信。通過控制接口文件,外部程序可以向 wpa_supplicant 發送命令,以配置和管理無線網絡連接。這通常涉及到讀寫控制接口文件中的數據,以執行各種操作。
  • 3)D-Bus 接口:wpa_supplicant 也提供了一個 D-Bus 接口,允許外部程序使用 D-Bus 協議與其通信。通過 D-Bus 接口,外部程序可以查詢和配置 wpa_supplicant 的狀態、網絡配置等信息。D-Bus 是一種通用的進程間通信機制,在許多Linux系統上都受支持。
  • 4)自定義接口:有些外部程序可能會使用 wpa_supplicant 提供的自定義接口,通過編程方式與其交互。

用戶態程序和wpa_supplicant兩個進程之間通信的方式一般為unix socket

wpa_cli就是一個用戶態程序,本文以wpa_cli為代表分析wpa_supplicant與用戶態之間的交互

1.2 交互命令和日志

首先,執行wpa_supplicant命令

sudo ./wpa_supplicant -i wlan0 -D nl80211 -c /etc/wpa_supplicant/wpa_supplicant.conf

該命令會調用wpa_supplicant/main.c文件中的main()主函數

然后,執行wpa_cli命令

sudo ./wpa_cli -i wlan0 scan

該命令會調用wpa_supplicant/wpa_cli.c文件中的main()主函數

在wpa_cli端發出掃描命令后,wpa_supplicant端接收到來自wpa_cli的消息,其處理日志如下

wlan0: Control interface command 'SCAN'
wlan0: Setting scan request: 0.000000 sec
wlan0: Starting AP scan for wildcard SSID
WPS: Building WPS IE for Probe Request
WPS:  * Version (hardcoded 0x10)
WPS:  * Request Type
WPS:  * Config Methods (3108)
WPS:  * UUID-E
WPS:  * Primary Device Type
WPS:  * RF Bands (1)
WPS:  * Association State
WPS:  * Configuration Error (0)
WPS:  * Device Password ID (0)
WPS:  * Manufacturer
WPS:  * Model Name
WPS:  * Model Number
WPS:  * Device Name
WPS:  * Version2 (0x20)
P2P: * P2P IE header
P2P: * Capability dev=25 group=00
P2P: * Listen Channel: Regulatory Class 81 Channel 1
wlan0: Add radio work 'scan'@0x5558d881f330
wlan0: First radio work item in the queue - schedule start immediately
wlan0: Starting radio work 'scan'@0x5558d881f330 after 0.000006 second wait
wlan0: nl80211: scan request
Scan requested (ret=0) - scan timeout 30 seconds
nl80211: Drv Event 33 (NL80211_CMD_TRIGGER_SCAN) received for wlan0
wlan0: nl80211: Scan trigger
wlan0: Event SCAN_STARTED (47) received
wlan0: Own scan request started a scan in 0.000000 seconds
RTM_NEWLINK: ifi_index=4 ifname=wlan0 wext ifi_family=0 ifi_flags=0x11043 ([UP][RUNNING][LOWER_UP])
nl80211: Drv Event 34 (NL80211_CMD_NEW_SCAN_RESULTS) received for wlan0
wlan0: nl80211: New scan results available
nl80211: Scan probed for SSID ''
nl80211: Scan included frequencies: 2412 2417 2422 2427 2432 2437 2442 2447 2452 2457 2462 2467 2472
wlan0: Event SCAN_RESULTS (3) received
wlan0: Scan completed in 11.500205 seconds
nl80211: Received scan results (35 BSSes)
nl80211: Scan results indicate BSS status with 48:2f:6b:2a:07:80 as associated
wlan0: BSS: Start scan result update 3
wlan0: BSS: Add new id 56 BSSID 7c:10:c9:b4:d0:48 SSID 'ASUS_2G' freq 2412
wlan0: BSS: Add new id 57 BSSID 9c:8c:d8:00:a8:e0 SSID 'i-amlogic' freq 2412
wlan0: BSS: Add new id 58 BSSID 9a:00:74:f7:03:b6 SSID 'ChinaNet-UuxC' freq 2412
wlan0: BSS: Add new id 59 BSSID 9c:8c:d8:00:a8:e1 SSID 'sunshine' freq 2412
wlan0: BSS: Add new id 60 BSSID 9c:8c:d8:00:a8:e2 SSID 'galaxy' freq 2412
wlan0: BSS: Add new id 61 BSSID 48:5b:ea:eb:9d:30 SSID 'ChinaNet-DFrr' freq 2432
wlan0: BSS: Add new id 62 BSSID 9c:8c:d8:fe:de:60 SSID 'i-amlogic' freq 2462
BSS: last_scan_res_used=35/64
wlan0: New scan results available (own=1 ext=0)
WPS: AP 48:5b:ea:eb:9d:30 type 0 added
WPS: AP[0] b8:3a:08:17:7f:71 type=0 tries=0 last_attempt=-1 sec ago bssid_ignore=0
WPS: AP[1] 92:a5:af:5e:27:dc type=0 tries=0 last_attempt=-1 sec ago bssid_ignore=0
WPS: AP[2] 58:48:49:0b:b8:63 type=0 tries=0 last_attempt=-1 sec ago bssid_ignore=0
WPS: AP[3] 50:2b:73:c9:11:29 type=0 tries=0 last_attempt=-1 sec ago bssid_ignore=0
WPS: AP[4] 48:5b:ea:eb:a1:2c type=0 tries=0 last_attempt=-1 sec ago bssid_ignore=0
WPS: AP[5] 9c:74:6f:40:a0:40 type=0 tries=0 last_attempt=-1 sec ago bssid_ignore=0
WPS: AP[6] a2:cd:b6:00:c9:b9 type=0 tries=0 last_attempt=-1 sec ago bssid_ignore=0
WPS: AP[7] 14:f5:09:dd:64:f6 type=0 tries=0 last_attempt=-1 sec ago bssid_ignore=0
WPS: AP[8] 48:5b:ea:eb:9d:30 type=0 tries=0 last_attempt=-1 sec ago bssid_ignore=0
wlan0: Radio work 'scan'@0x5558d881f330 done in 11.507001 seconds
wlan0: radio_work_free('scan'@0x5558d881f330): num_active_works --> 0
wlan0: Scan results matching the currently selected network
wlan0: 6: 48:2f:6b:2a:d9:40 freq=2462 level=-56 snr=33 est_throughput=65000
wlan0: 9: 48:2f:6b:2a:07:80 freq=2462 level=-58 snr=31 est_throughput=65000
wlan0: 13: 9c:8c:d8:00:a8:e0 freq=2412 level=-62 snr=27 est_throughput=65000
wlan0: 29: 9c:8c:d8:fe:3f:80 freq=2462 level=-80 snr=9 est_throughput=19500
wlan0: 31: 9c:8c:d8:fe:de:60 freq=2462 level=-88 snr=1 est_throughput=3250
wlan0: Selecting BSS from priority group 0
wlan0: 0: 22:f2:2c:43:84:a1 ssid='' wpa_ie_len=22 rsn_ie_len=20 caps=0x431 level=-54 freq=2462
wlan0:    skip - SSID not known
wlan0: 1: 18:f2:2c:43:84:a1 ssid='TV-SE' wpa_ie_len=22 rsn_ie_len=20 caps=0x1431 level=-56 freq=2462
wlan0:    skip - SSID mismatch
wlan0: 2: f6:84:8d:21:4c:3b ssid='' wpa_ie_len=22 rsn_ie_len=20 caps=0x411 level=-67 freq=2462
wlan0:    skip - SSID not known
wlan0: 3: f4:84:8d:21:4c:3b ssid='QA_2.4G' wpa_ie_len=22 rsn_ie_len=20 caps=0x1411 level=-68 freq=2462
wlan0:    skip - SSID mismatch
wlan0: 4: 7c:10:c9:b4:d0:48 ssid='ASUS_2G' wpa_ie_len=0 rsn_ie_len=20 caps=0x1411 level=-69 freq=2412
wlan0:    skip - SSID mismatch
wlan0: 5: 48:2f:6b:2a:d9:41 ssid='sunshine' wpa_ie_len=0 rsn_ie_len=20 caps=0x431 level=-56 freq=2462
wlan0:    skip - SSID mismatch
wlan0: 6: 48:2f:6b:2a:d9:40 ssid='i-amlogic' wpa_ie_len=0 rsn_ie_len=20 caps=0x431 level=-56 freq=2462
wlan0:    selected based on RSN IE
wlan0:    selected BSS 48:2f:6b:2a:d9:40 ssid='i-amlogic'
wlan0: Considering within-ESS reassociation
wlan0: Current BSS: 48:2f:6b:2a:07:80 freq=2462 level=-58 snr=31 est_throughput=65000
wlan0: Selected BSS: 48:2f:6b:2a:d9:40 freq=2462 level=-56 snr=33 est_throughput=65000
wlan0: Using signal poll values for the current BSS: level=-59 snr=30 est_throughput=65000
wlan0: Skip roam - Current BSS has good SNR (30 > 25)
wlan0: BSS: Remove id 26 BSSID 6c:b1:58:e4:97:0d SSID 'TP-LINK_970D' due to wpa_bss_flush_by_age
wlan0: BSS: Remove id 31 BSSID b8:3a:08:17:7f:71 SSID 'Moonflower' due to wpa_bss_flush_by_age
wlan0: BSS: Remove id 41 BSSID 48:2f:6b:2a:37:80 SSID 'i-amlogic' due to wpa_bss_flush_by_age
wlan0: BSS: Remove id 48 BSSID 9c:54:c2:fb:66:30 SSID 'cyem' due to wpa_bss_flush_by_age
wlan0: BSS: Remove id 52 BSSID 14:f5:09:dd:64:f6 SSID '' due to wpa_bss_flush_by_age

1.3?wpa_cli的main函數

wpa_cli的main函數依次調用了以下子函數

  • 1)調用wpa_cli_open_global_ctrl()函數,用于打開global接口,因為本文執行命令時沒有指定-g參數,所以該函數實際沒有起到作用

  • 2)wpa_cli_open_connection()函數,用于打開socket連接,修改全局變量ctrl_conn的值,傳入該函數的參數即為命令中-i指定的waln0

  • 3)wpa_request()函數,用于向wpa_supplicant發起請求命令,傳入該函數的參數即為全局接口ctrl_conn和命令中的scan字符

//wpa_supplicant\wpa_cli.c
int main(int argc, char *argv[])
{int c;int daemonize = 0;int ret = 0;
?if (os_program_init())return -1;
?for (;;) {c = getopt(argc, argv, "a:Bg:G:hi:p:P:rs:v");if (c < 0)break;switch (c) {case 'i':os_free(ctrl_ifname);ctrl_ifname = os_strdup(optarg);break;}
?if (eloop_init())return -1;
?if (wpa_cli_open_global_ctrl() < 0)return -1;
?eloop_register_signal_terminate(wpa_cli_terminate, NULL);
?if (wpa_cli_open_connection(ctrl_ifname, 0) < 0) {fprintf(stderr, "Failed to connect to non-global ""ctrl_ifname: %s  error: %s\n",ctrl_ifname ? ctrl_ifname : "(nil)",strerror(errno));return -1;}ret = wpa_request(ctrl_conn, argc - optind,&argv[optind]);
?os_free(ctrl_ifname);eloop_destroy();wpa_cli_cleanup();
?return ret;
}

1.3.1?wpa_cli_open_global_ctrl函數

wpa_cli_open_global_ctrl()函數沒有輸入參數,返回參數為整型變量,返回0表示成功,返回-1表示失敗,wpa_cli_open_global_ctrl()函數中繼續調用了wpa_ctrl_open2()函數,該函數用于打開wpa_supplicant的控制接口

wpa_ctrl_open2()函數返回一個指向控制接口數據的指針wpa_ctrl,該函數有2個輸入參數,如下

  • 第1個輸入參數為wpa_supplicant控制接口的unix套接字路徑,實際傳入的是NULL

  • 第2個輸入參數為客戶端(wpa_cli)的unix套接字路徑,實際傳入的是NULL

函數結構如下:

//wpa_supplicant\wpa_cli.c
static struct wpa_ctrl *ctrl_conn;
static const char *global = NULL;//wpa_supplicant\wpa_cli.c
static int wpa_cli_open_global_ctrl(void)
{ctrl_conn = wpa_ctrl_open(global);return 0;
}//src\common\wpa_ctrl.c
struct wpa_ctrl * wpa_ctrl_open(const char *ctrl_path)
{return wpa_ctrl_open2(ctrl_path, NULL);
}//src\common\wpa_ctrl.c
struct wpa_ctrl * wpa_ctrl_open2(const char *ctrl_path, const char *cli_path)
{struct wpa_ctrl *ctrl;if (ctrl_path == NULL)return NULL;
}

因為在命令中沒有指定-g參數,所以全局變量global參數默認為NULL

因此傳遞給函數wpa_ctrl_open2()的參數ctrl_path為NULL,所以實際上該函數返回為NULL,全局變量ctrl_conn也就被設置為NULL

1.3.2 wpa_cli_open_connection函數

wpa_cli_open_connection()函數用于打開和wpa_supplicant的連接,并在函數中改變全局變量ctrl_conn的值,wpa_cli_open_connection()函數返回1個整型變量,成功返回0,失敗返回-1,該函數有2個輸入參數,如下

  • 傳入第1個參數接口名稱,實際傳入的為命令中-i指定的wlan

  • 傳入第2個參數為指定連接的方式或附加方式,實際傳入0,表示只建立連接,不附加到接口

函數結構如下:

//wpa_supplicant\wpa_cli.c
#ifndef CONFIG_CTRL_IFACE_DIR
#define CONFIG_CTRL_IFACE_DIR "/var/run/wpa_supplicant"
#endif /* CONFIG_CTRL_IFACE_DIR */
static const char *ctrl_iface_dir = CONFIG_CTRL_IFACE_DIR;
static const char *client_socket_dir = NULL;
static struct wpa_ctrl *ctrl_conn;//wpa_supplicant\wpa_cli.c
static int wpa_cli_open_connection(const char *ifname, int attach)
{char *cfile = NULL;int flen, res;if (ifname == NULL)return -1;if (cfile == NULL) {flen = os_strlen(ctrl_iface_dir) + os_strlen(ifname) + 2;cfile = os_malloc(flen);if (cfile == NULL)return -1;res = os_snprintf(cfile, flen, "%s/%s", ctrl_iface_dir,ifname);if (os_snprintf_error(flen, res)) {os_free(cfile);return -1;}}ctrl_conn = wpa_ctrl_open2(cfile, client_socket_dir);if (ctrl_conn == NULL) {os_free(cfile);return -1;}os_free(cfile);return 0;
}

該函數仍然調用了wpa_ctrl_open2()函數,并將返回的控制接口指針wpa_ctrl賦值給全局變量ctrl_conn

ctrl_conn為全局變量,類型為結構體指針wpa_ctrl

//src\common\wpa_ctrl.c
struct wpa_ctrl {int s;struct sockaddr_un local;struct sockaddr_un dest;
};

wpa_ctrl的接口類型實際有3種,分別為udp、unix、pipe,本文只分析unix

wpa_cli_open_connection()函數調用wpa_ctrl_open2()函數時,與之前的wpa_cli_open_global_ctrl()函數不一樣

wpa_ctrl_open2()函數返回1個結構體指針ctrl,該函數有2個輸入參數,如下

  • 傳入的第1個參數已經是接口的名稱/var/run/wpa_supplicant/wlan0,而不是NULL了

  • 傳入的第2個參數是client_socket_dir,其初始值依然是NULL

此時的wpa_ctrl_open2函數結構如下:

//src\common\wpa_ctrl.c
#ifndef CONFIG_CTRL_IFACE_CLIENT_DIR
#define CONFIG_CTRL_IFACE_CLIENT_DIR "/tmp"
#endif /* CONFIG_CTRL_IFACE_CLIENT_DIR */
#ifndef CONFIG_CTRL_IFACE_CLIENT_PREFIX
#define CONFIG_CTRL_IFACE_CLIENT_PREFIX "wpa_ctrl_"
#endif /* CONFIG_CTRL_IFACE_CLIENT_PREFIX *///src\common\wpa_ctrl.c
struct wpa_ctrl * wpa_ctrl_open2(const char *ctrl_path, const char *cli_path)
{struct wpa_ctrl *ctrl;static int counter = 0;int ret;size_t res;int tries = 0;int flags;if (ctrl_path == NULL)return NULL;ctrl = os_zalloc(sizeof(*ctrl));if (ctrl == NULL)return NULL;ctrl->s = socket(PF_UNIX, SOCK_DGRAM, 0);if (ctrl->s < 0) {os_free(ctrl);return NULL;}ctrl->local.sun_family = AF_UNIX;counter++;
try_again:ret = os_snprintf(ctrl->local.sun_path,sizeof(ctrl->local.sun_path),CONFIG_CTRL_IFACE_CLIENT_DIR "/"CONFIG_CTRL_IFACE_CLIENT_PREFIX "%d-%d",(int) getpid(), counter);if (os_snprintf_error(sizeof(ctrl->local.sun_path), ret)) {close(ctrl->s);os_free(ctrl);return NULL;}tries++;if (bind(ctrl->s, (struct sockaddr *) &ctrl->local,sizeof(ctrl->local)) < 0) {if (errno == EADDRINUSE && tries < 2) {unlink(ctrl->local.sun_path);goto try_again;}close(ctrl->s);os_free(ctrl);return NULL;}ctrl->dest.sun_family = AF_UNIX;res = os_strlcpy(ctrl->dest.sun_path, ctrl_path, sizeof(ctrl->dest.sun_path));if (res >= sizeof(ctrl->dest.sun_path)) {close(ctrl->s);os_free(ctrl);return NULL;}if (connect(ctrl->s, (struct sockaddr *) &ctrl->dest,sizeof(ctrl->dest)) < 0) {close(ctrl->s);unlink(ctrl->local.sun_path);os_free(ctrl);return NULL;}flags = fcntl(ctrl->s, F_GETFL);if (flags >= 0) {flags |= O_NONBLOCK;if (fcntl(ctrl->s, F_SETFL, flags) < 0) {perror("fcntl(ctrl->s, O_NONBLOCK)");}}return ctrl;
}

對該函數說明如下:

  • 調用socket()為控制接口創建套接字ctrl->s,地址族為PF_UNIX,表示本地unix域套接字,類型為SOCK_DGRAM,表示無連接通信,協議為0,表示自動選擇

  • 創建本地套接字的地址族ctrl->local.sun_family為AF_UNIX

  • 創建本地套接字的地址,例如創建路徑為/tmp/wpa_ctrl_13152-1,其命名方式為/tmp/wpa_ctrl_進程pid-嘗試次數

  • 調用bind()將創建的套接字與本地地址綁定

  • 創建目標套接字的地址族ctrl->dest.sun_family為AF_UNIX

  • 創建目標套接字的地址,例如創建路徑為/var/run/wpa_supplicant/wlan0,其命名方式為/var/run/wpa_supplicant/接口名稱

  • 調用connect()將本地socket與目標地址連接

  • 通過一系列socket函數實現wpa_cli與wpa_supplicant進行通信,如果出錯則會調用close()關閉socket連接

  • 最后通過fcntl()獲取套接字ctrl->s的標志位,并將套接字設置為非阻塞模式,以避免在目標程序意外終止時導致程序永遠阻塞

整個過程中設置了控制接口的ctrl->s、ctrl->local、ctrl→dest這3個成員的值

//src\common\wpa_ctrl.c
struct wpa_ctrl {int s;  //文件描述符struct sockaddr_un local;  //本地UNIX域套接字的地址信息struct sockaddr_un dest;   //目標UNIX域套接字的地址信息
};

相關定義如下

//winsock.h
#define AF_UNIX 1
#define PF_UNIX AF_UNIX//sys/un.h
struct sockaddr_un {sa_family_t sun_family;    // 地址族,通常設置為 AF_UNIXchar sun_path[UNIX_PATH_MAX];  // 套接字文件的路徑
};

代碼運行后,返回的結構體指針wpa_ctrl的相關值如下:

ctrl->s: 3
ctrl->local.sun_family: 1
ctrl->local.sun_path: /tmp/wpa_ctrl_13152-1
ctrl->dest.sun_family: 1
ctrl->dest.sun_path: /var/run/wpa_supplicant/wlan0

socket()函數返回的文件描述符從0開始分配,其中 :

  • 0表示標準輸入(stdin)

  • 1 表示標準輸出(stdout)

  • 2 表示標準錯誤輸出(stderr)

  • 3 表示一個新的文件描述符,不與標準輸入、輸出或錯誤輸出重疊

  • 如果返回值為-1,表明創建socket套接字失敗

最終,全局變量ctrl_conn的值也就被修改為ctrl

1.3.3?wpa_request函數

在main()函數的最后,調用了wpa_request()函數發送命令

ret = wpa_request(ctrl_conn, argc - optind, &argv[optind]);

argc表示參數的個數,sudo ./wpa_cli -i wlan0 scan?這條命令的參數共有4個(除sudo),所以argc為4

optind表示解析命令行參數的狀態,初始值為1,每處理一個參數(-i、wlan0、scan均為參數),optind的值加1,當解析完所有參數時,optind的值為3

&argv[optind]表示最后一個參數的地址,即字符串scan

wpa_request()函數返回1個整型變量,成功返回0,失敗返回-1,該函數有3個輸入參數,如下

  • 第1個輸入參數為結構體指針ctrl,實際傳遞為全局變量ctrl_conn的值

  • 第2個輸入參數為待處理的參數個數argc,實際傳遞為argc - optind,即為4 - 3 = 1

  • 第3個輸入參數為具體的參數數組,實際為字符數組,內容為"scan"

該函數結構如下:

//wpa_supplicant\wpa_cli.c
struct wpa_cli_cmd {const char *cmd;int (*handler)(struct wpa_ctrl *ctrl, int argc, char *argv[]);char ** (*completion)(const char *str, int pos);enum wpa_cli_cmd_flags flags;const char *usage;
};//wpa_supplicant\wpa_cli.c
static const struct wpa_cli_cmd wpa_cli_commands[] = {{ "scan", wpa_cli_cmd_scan, NULL,cli_cmd_flag_none,"= request new BSS scan" },
}//wpa_supplicant\wpa_cli.c
static int wpa_request(struct wpa_ctrl *ctrl, int argc, char *argv[])
{const struct wpa_cli_cmd *cmd, *match = NULL;int count;int ret = 0;ifname_prefix = NULL;if (argc == 0)return -1;count = 0;cmd = wpa_cli_commands;while (cmd->cmd) {if (os_strncasecmp(cmd->cmd, argv[0], os_strlen(argv[0])) == 0){match = cmd;if (os_strcasecmp(cmd->cmd, argv[0]) == 0) {/* we have an exact match */count = 1;break;}count++;}cmd++;}if (count > 1) {printf("Ambiguous command '%s'; possible commands:", argv[0]);cmd = wpa_cli_commands;while (cmd->cmd) {if (os_strncasecmp(cmd->cmd, argv[0],os_strlen(argv[0])) == 0) {printf(" %s", cmd->cmd);}cmd++;}printf("\n");ret = 1;} else if (count == 0) {printf("Unknown command '%s'\n", argv[0]);ret = 1;} else {ret = match->handler(ctrl, argc - 1, &argv[1]);}return ret;
}

該函數將參數"scan"與已經定義的數組wpa_cli_commands中的命令元素進行完全匹配

while (cmd->cmd) {if (os_strncasecmp(cmd->cmd, argv[0], os_strlen(argv[0])) == 0){match = cmd;if (os_strcasecmp(cmd->cmd, argv[0]) == 0) {count = 1;break;}}cmd++;
}

匹配到相同的命令后調用該命令對應的句柄函數,并將未處理參數個數減1,scan后已經沒有參數,所以此時傳遞給句柄的值為0

match->handler(ctrl, argc - 1, &argv[1]);

scan命令對應的句柄函數為wpa_cli_cmd_scan(),之后的調用關系如下:

  • 繼續調用到wpa_cli_cmd()函數,傳遞cmd參數為"SCAN"

  • 繼續調用到wpa_ctrl_command()函數,傳遞cmd參數為"SCAN"

  • 繼續調用到_wpa_ctrl_command()函數,傳遞cmd參數為"SCAN"

  • 最終調用到wpa_ctrl_request()函數,傳遞cmd參數為"SCAN",回調函數為wpa_cli_msg_cb()

主要函數調用如下:

//wpa_supplicant\wpa_cli.c
static int wpa_cli_cmd_scan(struct wpa_ctrl *ctrl, int argc, char *argv[])
{return wpa_cli_cmd(ctrl, "SCAN", 0, argc, argv);
}//wpa_supplicant\wpa_cli.c
static int wpa_cli_cmd(struct wpa_ctrl *ctrl, const char *cmd, int min_args,int argc, char *argv[])
{char buf[4096];if (write_cmd(buf, sizeof(buf), cmd, argc, argv) < 0)return -1;return wpa_ctrl_command(ctrl, buf);
}//wpa_supplicant\wpa_cli.c
static int wpa_ctrl_command(struct wpa_ctrl *ctrl, const char *cmd)
{return _wpa_ctrl_command(ctrl, cmd, 1);
}//wpa_supplicant\wpa_cli.c
static int _wpa_ctrl_command(struct wpa_ctrl *ctrl, const char *cmd, int print)
{char buf[4096];size_t len;int ret;len = sizeof(buf) - 1;ret = wpa_ctrl_request(ctrl, cmd, os_strlen(cmd), buf, &len,wpa_cli_msg_cb);return 0;
}

wpa_ctrl_request()函數返回1個整型變量,成功返回0,失敗返回-1,該函數有6個輸入參數,如下

  • 參數ctrl是socket控制接口

  • 參數cmd是發送給wpa_supplicant的命令

  • 參數cmd_len是命令長度

  • 參數reply是wpa_supplicant對命令的回復

  • 參數reply_len是回復的長度

  • 參數msg_cb是綁定的消息回調函數

該函數結構如下:

//src\common\wpa_ctrl.c
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))
{struct timeval tv;struct os_reltime started_at;int res;fd_set rfds;const char *_cmd;char *cmd_buf = NULL;size_t _cmd_len;{_cmd = cmd;_cmd_len = cmd_len;}errno = 0;started_at.sec = 0;started_at.usec = 0;
retry_send:if (send(ctrl->s, _cmd, _cmd_len, 0) < 0) {if (errno == EAGAIN || errno == EBUSY || errno == EWOULDBLOCK){if (started_at.sec == 0)os_get_reltime(&started_at);else {struct os_reltime n;os_get_reltime(&n);if (os_reltime_expired(&n, &started_at, 5))goto send_err;}os_sleep(1, 0);goto retry_send;}send_err:os_free(cmd_buf);return -1;}os_free(cmd_buf);for (;;) {tv.tv_sec = 10;tv.tv_usec = 0;FD_ZERO(&rfds);FD_SET(ctrl->s, &rfds);res = select(ctrl->s + 1, &rfds, NULL, NULL, &tv);if (res < 0 && errno == EINTR)continue;if (res < 0)return res;if (FD_ISSET(ctrl->s, &rfds)) {res = recv(ctrl->s, reply, *reply_len, 0);if (res < 0)return res;if ((res > 0 && reply[0] == '<') ||(res > 6 && strncmp(reply, "IFNAME=", 7) == 0)) {if (msg_cb) {if ((size_t) res == *reply_len)res = (*reply_len) - 1;reply[res] = '\0';msg_cb(reply, res);}continue;}*reply_len = res;break;} else {return -2;}}return 0;
}

該函數調用send()發送命令到wpa_supplicant

然后在for循環里調用select()監視使用的socket文件,添加到可讀文件集合,超時時間設置為10s

然后調用recv()接收來自wpa_supplicant的回復

最后,如果回調函數存在,則調用回調函數msg_cb,因回調函數設置為wpa_cli_msg_cb,所以實際調用了wpa_cli_msg_cb()函數

//src\common\wpa_ctrl.c
static void wpa_cli_msg_cb(char *msg, size_t len)
{printf("%s\n", msg);
}

調用wpa_cli_msg_cb()函數時傳遞的mes參數為reply,所以該函數的功能是打印wpa_supplicant回復的消息

在終端顯示的對命令sudo ./wpa_cli -i wlan0 scan的回復為ok

最后整個程序結束

1.4?wpa_supplicant的main函數

wpa_supplicant程序的入口為wpa_supplicant\main.c下的main()函數

//wpa_supplicant\main.c
int main(int argc, char *argv[])
{int c, i;struct wpa_interface *ifaces, *iface;int iface_count, exitcode = -1;struct wpa_params params;struct wpa_global *global;os_memset(&params, 0, sizeof(params));params.wpa_debug_level = MSG_INFO;iface = ifaces = os_zalloc(sizeof(struct wpa_interface));iface_count = 1;for (;;) {c = getopt(argc, argv,"b:Bc:C:D:de:f:g:G:hi:I:KLMm:No:O:p:P:qsTtuvW");if (c < 0)break;switch (c) {case 'c':iface->confname = optarg;break;case 'D':iface->driver = optarg;break;case 'i':iface->ifname = optarg;break;}}exitcode = 0;global = wpa_supplicant_init(&params);wpa_printf(MSG_INFO, "Successfully initialized " "wpa_supplicant");for (i = 0; exitcode == 0 && i < iface_count; i++) {struct wpa_supplicant *wpa_s;wpa_s = wpa_supplicant_add_iface(global, &ifaces[i], NULL);}if (exitcode == 0)exitcode = wpa_supplicant_run(global);return exitcode;
}

在main()函數中關于調試級別的設置語句為:

params.wpa_debug_level = MSG_INFO;

將調試級別設置為MSG_DEBUG,可以增加調試信息,修改如下

params.wpa_debug_level = MSG_DEBUG;

在main()函數中主要調用了以下函數

  • 1)wpa_supplicant_init

  • 2)wpa_supplicant_add_iface

  • 3)wpa_supplicant_run

1.4.1?wpa_supplicant_add_iface函數

對關鍵的wpa_supplicant_add_iface()函數分析如下:

//wpa_supplicant\wpa_supplicant.c
struct wpa_supplicant * wpa_supplicant_add_iface(struct wpa_global *global,struct wpa_interface *iface,struct wpa_supplicant *parent)
{struct wpa_supplicant *wpa_s;struct wpa_interface t_iface;struct wpa_ssid *ssid;wpa_s = wpa_supplicant_alloc(parent);wpa_s->global = global;if (wpa_supplicant_init_iface(wpa_s, &t_iface)) {wpa_printf(MSG_DEBUG, "Failed to add interface %s",iface->ifname);wpa_supplicant_deinit_iface(wpa_s, 0, 0);return NULL;}wpa_s->next = global->ifaces;global->ifaces = wpa_s;return wpa_s;
}//wpa_supplicant\wpa_supplicant.c
static int wpa_supplicant_init_iface(struct wpa_supplicant *wpa_s,const struct wpa_interface *iface)
{wpa_printf(MSG_DEBUG, "Initializing interface '%s' conf '%s' driver ""'%s' ctrl_interface '%s' bridge '%s'", iface->ifname,iface->confname ? iface->confname : "N/A",iface->driver ? iface->driver : "default",iface->ctrl_interface ? iface->ctrl_interface : "N/A",iface->bridge_ifname ? iface->bridge_ifname : "N/A");wpa_s->ctrl_iface = wpa_supplicant_ctrl_iface_init(wpa_s);return 0;
}

接著會跳轉到unix的控制接口文件中

//wpa_supplicant\ctrl_iface_unix.c
struct ctrl_iface_priv *wpa_supplicant_ctrl_iface_init(struct wpa_supplicant *wpa_s)
{struct ctrl_iface_priv *priv;priv = os_zalloc(sizeof(*priv));if (priv == NULL)return NULL;dl_list_init(&priv->ctrl_dst);dl_list_init(&priv->msg_queue);priv->wpa_s = wpa_s;priv->sock = -1;if (wpas_ctrl_iface_open_sock(wpa_s, priv) < 0) {os_free(priv);return NULL;}return priv;
}static int wpas_ctrl_iface_open_sock(struct wpa_supplicant *wpa_s,struct ctrl_iface_priv *priv)
{eloop_register_read_sock(priv->sock, wpa_supplicant_ctrl_iface_receive, wpa_s, priv);wpa_msg_register_cb(wpa_supplicant_ctrl_iface_msg_cb);
}

所以當wpa_supplicant程序接收到socket消息時就執行wpa_supplicant_ctrl_iface_receive()函數

//wpa_supplicant\ctrl_iface_unix.c
static void wpa_supplicant_ctrl_iface_receive(int sock,void *eloop_ctx,void *sock_ctx)
{struct wpa_supplicant *wpa_s = eloop_ctx;struct ctrl_iface_priv *priv = sock_ctx;char *buf;int res;struct sockaddr_storage from;socklen_t fromlen = sizeof(from);char *reply = NULL, *reply_buf = NULL;size_t reply_len = 0;int new_attached = 0;buf = os_malloc(CTRL_IFACE_MAX_LEN + 1);res = recvfrom(sock, buf, CTRL_IFACE_MAX_LEN + 1, 0,(struct sockaddr *) &from, &fromlen);buf[res] = '\0';reply_buf = wpa_supplicant_ctrl_iface_process(wpa_s, buf, &reply_len);reply = reply_buf;os_memset(buf, 0, res);if (!reply && reply_len == 1) {reply = "FAIL\n";reply_len = 5;} else if (!reply && reply_len == 2) {reply = "OK\n";reply_len = 3;}if (reply) {wpas_ctrl_sock_debug("ctrl_sock-sendto", sock, reply, reply_len);if (sendto(sock, reply, reply_len, 0, (struct sockaddr *) &from, fromlen) < 0) {int _errno = errno;wpa_dbg(wpa_s, MSG_DEBUG,"ctrl_iface sendto failed: %d - %s",_errno, strerror(_errno));}}os_free(reply_buf);os_free(buf);
}

在該函數主要中進行以下處理

  • 調用recvfrom()函數接收來自wpa_cli的命令,將接收數據保存在字符指針buf里

  • 調用wpa_supplicant_ctrl_iface_process()函數處理命令,返回結果保存在字符指針reply_buf和字符指針reply中

  • 調用sendto()函數向socket發送回復reply

進一步分析wpa_supplicant接收wpa_cli的消息入口為wpa_supplicant_ctrl_iface_process()函數

//wpa_supplicant\ctrl_iface.c
char * wpa_supplicant_ctrl_iface_process(struct wpa_supplicant *wpa_s,char *buf,size_t *resp_len)
{char *reply;const int reply_size = 4096;int reply_len;int level = wpas_ctrl_cmd_debug_level(buf);wpa_dbg(wpa_s, level, "Control interface command '%s'", buf);reply = os_malloc(reply_size);os_memcpy(reply, "OK\n", 3);reply_len = 3;if (os_strcmp(buf, "SCAN") == 0) {wpas_ctrl_scan(wpa_s, NULL, reply, reply_size, &reply_len);} else if (os_strncmp(buf, "SCAN ", 5) == 0) {wpas_ctrl_scan(wpa_s, buf + 5, reply, reply_size, &reply_len);}if (reply_len < 0) {os_memcpy(reply, "FAIL\n", 5);reply_len = 5;}*resp_len = reply_len;return reply;
}//wpa_supplicant\ctrl_iface.c
static void wpas_ctrl_scan(struct wpa_supplicant *wpa_s, char *params,char *reply, int reply_size, int *reply_len)
{if (!wpa_s->sched_scanning && !wpa_s->scanning &&((wpa_s->wpa_state <= WPA_SCANNING) ||(wpa_s->wpa_state == WPA_COMPLETED))) {wpa_supplicant_req_scan(wpa_s, 0, 0);} else if (wpa_s->sched_scanning) {wpa_supplicant_req_scan(wpa_s, 0, 0);}
}

最終執行到scan.c文件中的wpa_supplicant_req_scan()函數,發起掃描請求

//wpa_supplicant\scan.c
void wpa_supplicant_req_scan(struct wpa_supplicant *wpa_s, int sec, int usec)
{int res;res = eloop_deplete_timeout(sec, usec, wpa_supplicant_scan, wpa_s,NULL);wpa_dbg(wpa_s, MSG_DEBUG, "Setting scan request: %d.%06d sec", sec, usec);eloop_register_timeout(sec, usec, wpa_supplicant_scan, wpa_s, NULL);
}

nl80211向底層驅動發送觸發掃描的NL80211_CMD_TRIGGER_SCAN命令

wpa_driver_nl80211_scan()函數最后調用send_and_recv_msgs()函數

在該函數內繼續調用send_and_recv()函數,在該函數內繼續調用libnl庫的nl_send_auto_complete()函數、nl_recvmsgs()函數向內核驅動發送和接收消息

其中,libnl(Linux Netlink庫)是一個用于處理Linux內核通信機制Netlink的C庫

調用到nl80211驅動,接收到底層驅動返回給nl80211驅動接口的NL80211_CMD_TRIGGER_SCAN觸發掃描驅動事件

nl80211驅動上報給wpa_supplicant的event事件

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/713351.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/713351.shtml
英文地址,請注明出處:http://en.pswp.cn/news/713351.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

Spark Shuffle Tracking 原理分析

Shuffle Tracking Shuffle Tracking 是 Spark 在沒有 ESS(External Shuffle Service)情況&#xff0c;并且開啟 Dynamic Allocation 的重要功能。如在 K8S 上運行 spark 沒有 ESS。本文檔所有的前提都是基于以上條件的。 如果開啟了 ESS&#xff0c;那么 Executor 計算完后&a…

MySQL 表的基本操作,結合項目的表自動初始化來講

有了數據庫以后&#xff0c;我們就可以在數據庫中對表進行增刪改查了&#xff0c;這也就意味著&#xff0c;一名真正的 CRUD Boy 即將到來&#xff08;&#x1f601;&#xff09;。 查表 查看當前數據庫中所有的表&#xff0c;使用 show tables; 命令 由于當前數據庫中還沒有…

基于Python3的數據結構與算法 - 09 希爾排序

一、引入 希爾排序是一種分組插入排序的算法。 二、排序思路 首先取一個整數d1 n/2&#xff0c;將元素分為d1個組&#xff0c;每組相鄰量取元素距離為d1&#xff0c;在各組內直接進行插入排序&#xff1b;取第二個整數d2 d1/2&#xff0c; 重復上述分組排序過程&#xff0…

Angular 2 中的樣式綁定和 NgStyle

在 Angular 2 模板中綁定內聯樣式很容易。以下是一個綁定單個樣式值的示例&#xff1a; 你還可以指定單位&#xff0c;例如在這里我們將單位設置為 em&#xff0c;但也可以使用 px、% 或 rem&#xff1a; <p [style.font-size.em]"3">A paragraph at 3em! &l…

CSS 自測題 -- 用 flex 布局繪制骰子(一、二、三【含斜三點】、四、五、六點)

一點 <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8" /><meta name"viewport" content"widthdevice-width, initial-scale1.0" /><title>css flex布局-畫骰子</title><sty…

vue3 滾動條觸底監聽

問題&#xff1a;指定區域內&#xff0c;顯示返回的數據&#xff0c;要求先顯示20條&#xff0c;區域超出部分滾動顯示&#xff0c;對滾動條進行監聽&#xff0c;滾動條觸底后&#xff0c;繼續顯示下20條... 解決過程&#xff1a; 1.在區域的div上&#xff0c;添加scroll事件…

Unity 切換場景

場景切換前必須要將場景拖動到Build中 同步加載場景 using System.Collections; using System.Collections.Generic; //using UnityEditor.SearchService; using UnityEngine; // 場景管理 需要導入該類 using UnityEngine.SceneManagement;public class c3 : MonoBehaviour {…

redis五大基礎類型【重點】

之前寫過一點小知識&#xff1a;https://blog.csdn.net/qq_45927881/article/details/134959181?spm1001.2014.3001.5501 參考鏈接 https://xiaolincoding.com/redis/data_struct/command.html#%E4%BB%8B%E7%BB%8D 目錄 1. string&#xff08;字符串&#xff09;2. Hash&#…

MySql安全加固:配置不同用戶不同賬號禁止使用舊密碼禁止MySql進程管理員權限

MySql安全加固&#xff1a;配置不同用戶不同賬號&禁止使用舊密碼&禁止MySql進程管理員權限 1.1 檢查是否配置不同用戶不同賬號1.2 檢查是否禁止使用舊密碼1.3 禁止MySql進程管理員權限 &#x1f496;The Begin&#x1f496;點點關注&#xff0c;收藏不迷路&#x1f496…

【c++】通訊錄管理系統

1.系統功能介紹及展示 2.創建項目 3.菜單實現 4.退出功能實現 5.添加聯系人—結構體設計 6.添加聯系人—功能實現 7.顯示聯系人 8.刪除練習人—檢測聯系人是否存在 9.刪除聯系人—功能實現 10.查找聯系人 11.修改聯系人 12.清空通訊錄 #include <iostream> #include <…

什么是VR虛擬社區|VR元宇宙平臺|VR主題館加盟

VR虛擬社區是指一種基于虛擬現實技術構建的在線社交平臺或環境&#xff0c;用戶可以在其中創建虛擬化的個人形象&#xff08;也稱為avatars&#xff09;并與其他用戶進行交流、互動和合作。在VR虛擬社區中&#xff0c;用戶可以選擇不同的虛擬場景和環境&#xff0c;如虛擬公園、…

fly-barrage 前端彈幕庫(3):滾動彈幕的設計與實現

項目官網地址&#xff1a;https://fly-barrage.netlify.app/&#xff1b; &#x1f451;&#x1f40b;&#x1f389;如果感覺項目還不錯的話&#xff0c;還請點下 star &#x1f31f;&#x1f31f;&#x1f31f;。 Gitee&#xff1a;https://gitee.com/fei_fei27/fly-barrage&a…

顯示器開機正常,插入HDMI線卻不顯示畫面,換了HDMI線還是不行?

環境&#xff1a; 惠普/P24VG4 DELL筆記本 問題描述&#xff1a; 顯示器開機正常&#xff0c;插入HDMI線卻不顯示畫面&#xff0c;換了HDMI線還是不行&#xff0c;是不是顯示器壞了&#xff1f; 解決方案&#xff1a; 1.前往顯示器設置菜單里面查看input 2.把輸入源默認設…

二百二十五、海豚調度器——用DolphinScheduler調度執行Flume數據采集任務

一、目的 數倉的數據源是Kafka&#xff0c;因此離線數倉需要用Flume采集Kafka中的數據到HDFS中 在實際項目中&#xff0c;不可能一直在Xshell中啟動Flume任務&#xff0c;一是項目的Flume任務很多&#xff0c;二是一旦Xshell頁面關閉Flume任務就會停止&#xff0c;這樣非常不…

案例研究|DataEase助力眾陶聯應對產業鏈數據可視化挑戰

佛山眾陶聯供應鏈服務有限公司&#xff08;以下簡稱為“眾陶聯”&#xff09;成立于2016年&#xff0c;是由34家陶瓷企業共同創辦的建陶行業工業互聯網平臺&#xff0c;股東產值占整個行業的22.5%。眾陶聯以數據賦能為核心&#xff0c;積極探索新的交易和服務模式&#xff0c;構…

ant-design-vue如何限制圖片上傳的尺寸?

handleBeforeUpload(file, fileList) {// fileList 只包含了當次上傳的文件列表&#xff0c;不包含已上傳的文件列表// 所以長度要加上已上傳的文件列表的長度const isLimit this.fileList.length fileList.length > this.limit;const indexOfFile fileList.findIndex(it…

C++ STL 之容器 vector 常見用法

一. 什么是vector vector為“變長數組”&#xff0c;即長度根據需要而自動改變的數組。 頭文件&#xff1a; #include <vector>using namespace std;單獨定義一個vector&#xff1a;vector<typename> name&#xff0c;相當于一維數組 name[SIZE] &#xff0c;其長…

mac-docker-php容器連接mac中的pgsql數據庫失敗以及出現table_msg存錯誤時的解決方法

以php中的thinkphp 5.1為例&#xff0c;php容器連接mac中的pgsql數據庫失敗時&#xff0c;出現如下錯誤 [7] PDOException in Connection.php line 528 SQLSTATE[08006] [7] could not connect to server: Connection refused Is the server running on host "localhost&…

Git 配置處理客戶端無法正常訪問到 github 原網站時,npm 下載依賴包失敗的問題

Git 配置處理客戶端無法正常訪問到 github 原網站時&#xff0c;npm 下載依賴包失敗的問題 使用 github 的鏡像網站地址或類似的替代產品地址&#xff0c;代替到 npm 拉取依賴包的 git 地址本地Git配置 例如&#xff1a;執行一下命令&#xff0c;則是以https://kgithub.com 替…

requests庫/urllib3庫返回WEB響應內容的處理差異

requests庫是一個廣泛使用的HTTP庫&#xff0c;用于發送HTTP請求和處理響應。 以下是requests庫中一些主要類和方法的詳細介紹&#xff1a;requests庫主要類和方法 類:requests.models.Response: status_code: 響應狀態碼。text: 以Unicode形式返回響應內容。content: 以字節形…