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(¶ms, 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(¶ms);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事件
相关文章:
wpa_supplicant与用户态程序的交互分析
1 wpa_supplicant与用户态程序wpa_cli的交互过程 1.1 交互接口类型 wpa_supplicant与用户态程序交互的主要接口包括以下几种: 1)命令行界面:通过命令行工具 wpa_cli 可以与 wpa_supplicant 进行交互。wpa_cli 允许用户执行各种 wpa_suppli…...

JavaScript继承 寄生组合式继承 extends
JavaScript继承 1、JS 的继承到底有多少种实现方式呢? 2、ES6 的 extends 关键字是用哪种继承方式实现的呢? 继承种类 原型链继承 function Parent1() {this.name parentlthis.play [1, 2, 3] }function Child1() {this.type child2 }Child1.prototype new Parent1(…...
Nginx 和Tomcat比较
Nginx和Tomcat是两种不同的技术,它们在应用场景、性能、动态处理能力等方面有所区别: 应用场景 Nginx通常用作静态内容服务器或代理服务器,可以将外部请求转发给其他应用服务器,如Tomcat、Django等。而Tomcat则主要用作应用服…...

p18 线性代数,行阶梯型矩阵
行阶梯型矩阵 行最简型矩阵...
leetcode—— 动态规划—— 零钱兑换
给你一个整数数组 coins ,表示不同面额的硬币;以及一个整数 amount ,表示总金额。 计算并返回可以凑成总金额所需的 最少的硬币个数 。如果没有任何一种硬币组合能组成总金额,返回 -1 。 你可以认为每种硬币的数量是无限的。 示…...

java面试题(spring框架篇)(黑马 )
树形图: 一、Spring框架种的单例bean是线程安全吗? Service Scope("singleton") public class UserServiceImpl implements UserService{ } singleton:bean在每个Spring IOC容器中只有一个实例 protype:一个bean的定义可以有多个…...
LeetCode27 移除元素
题目 给你一个数组 nums 和一个值 val,你需要 原地 移除所有数值等于 val 的元素,并返回移除后数组的新长度。不要使用额外的数组空间,你必须仅使用 O(1) 额外空间并 原地 修改输入数组。元素的顺序可以改变。你不需要考虑数组中超出新长度后…...

自测-5 Shuffling Machine(python版本)
文章预览: 题目翻译算法python代码oj反馈结果 题目 翻译 shuffle是用于随机化一副扑克牌的过程。由于标准的洗牌技术被认为是薄弱的,并且为了避免员工通过不适当的洗牌与赌徒合作的“内部工作”,许多赌场使用了自动洗牌机。你的任务是模拟一…...

你真的会设计测试用例吗?
前言 最近干的最多的事情就是设计测试用例、评审测试用例了,于是我不禁又想到了一个经典的问题:如何设计出优秀的测试用例? 可能有些童鞋看到这个问题会有些不以为然,这有什么好想的?干个测试谁还不会设计测试用例&a…...

外贸网站模板建站
测绘检测wordpress外贸主题 简洁实用的wordpress外贸主题,适合做测绘检测仪器设备的外贸公司使用。 https://www.jianzhanpress.com/?p5337 白马非马衣服WordPress外贸建站模板 白马非马服装行业wordpress外贸建站模板,适用于时间服装企业的官方网站…...

多点通信与域套接字:2024/3/4
作业1:广播 发送端: #include <myhead.h> int main(int argc, const char *argv[]) {//1.创建套接字int sfdsocket(AF_INET,SOCK_DGRAM,0);if(sfd-1){perror("socket error");return -1;}printf("sfd%d\n",sfd);//2.设置当前…...

52.2k star! 自己部署gpt4free, 免费使用各种GPT
GPT4Free是一个由开发者Xtekky在GitHub上发布的开源项目,它可以免费地使用GPT-3.5、GPT-4、llama、gemini-pro、bard、claude等多种大模型。截止到当前(2024.1.30)已经有52.2k star,可见其受欢迎程度。 github地址:https://github.com/xtekky…...

【HbuilderX】 uniapp实现 android申请权限 和 退出app返回桌面
目录 android申请权限: 监听用户是否开启权限或关闭权限: 退出app返回桌面: android申请权限: 首先在 manifest.json 内添加你所需要用到权限 添加权限插件 permission.js 一次就好1/权限插件 - Gitee.comhttps://gitee.co…...

计算机网络之传输层 + 应用层
.1 CIDR地址块中还有三个特殊的地址块 a. 前缀 n 32 , 即32位IP地址都是前缀, 没有主机号, 这其实就是一个IP地址, 用于主机路由 b. 前缀 n 31 , 这个地址块中有两个IP地址, 主机号分别为0/1 , 这个地址块用于点对点链路 c. 前缀 n 0 , 用于默认路由使用二叉线索树查找转发…...

五、软考-系统架构设计师笔记-信息安全技术基础知识
信息安全技术基础知识 1、信息安全基础知识概述 信息安全的概念 信息安全包括 5 个基本要素: 机密性:确保信息不暴露给未授权的实体或进程。完整性:只有得到允许的人才能修改数据,并且能够判别出数据是否已被篡改。可用性:得到授权的实体在需要时可以…...

vue3+uniapp在微信小程序实现一个2048小游戏
一、效果展示 二、代码 <template><view class"page"><view class"top"><view class"score">得分:{{total}}</view><view class"time">用时:{{allTime}}s</view></view><view cl…...
常见的浏览器跨域解决方法
1. 前端方法:JSONP(仅适用于GET请求) JSONP(JSON with Padding)是一种利用<script>标签的src属性不受同源策略限制的特性来实现跨域数据请求的方法。JSONP通过在前端动态创建<script>标签,并将…...
飞桨模型转ONNX模型教程
文章目录 飞桨模型转ONNX模型教程1. ONNX简介2. Paddle2ONNX安装3. 获取Paddle2ONNX模型库4. 飞桨转ONNX教程4.1 飞桨训练模型导出为ONNX模型4.2 飞桨部署模型转为ONNX模型4.3 验证ONNX模型4.4 使用ONNX模型进行推理 5. 注意事项 飞桨模型转ONNX模型教程 1. ONNX简介 ONNX是一…...
vue使用swiper(轮播图)-真实项目使用
一、安装 我直接安装的vue-awesome-swiper": "^3.1.3"指定版本 npm install vue-awesome-swiper3.1.3 swiper --save二、vue页面使用,写了一个小demo <template><div class"vue-swiper"><h1>{{ msg }}</h1><…...
C++ 创建并初始化对象
创建并初始化C对象 当我们创建一个C对象时,它需要占用一些内存,即使我们写一个完全为空的类,类中没有成员,什么也没有,它至少也要占用一个字节的内存。但是我们类中有很多成员,它们需要存储在某地方&#…...

springboot 百货中心供应链管理系统小程序
一、前言 随着我国经济迅速发展,人们对手机的需求越来越大,各种手机软件也都在被广泛应用,但是对于手机进行数据信息管理,对于手机的各种软件也是备受用户的喜爱,百货中心供应链管理系统被用户普遍使用,为方…...

【力扣数据库知识手册笔记】索引
索引 索引的优缺点 优点1. 通过创建唯一性索引,可以保证数据库表中每一行数据的唯一性。2. 可以加快数据的检索速度(创建索引的主要原因)。3. 可以加速表和表之间的连接,实现数据的参考完整性。4. 可以在查询过程中,…...

YSYX学习记录(八)
C语言,练习0: 先创建一个文件夹,我用的是物理机: 安装build-essential 练习1: 我注释掉了 #include <stdio.h> 出现下面错误 在你的文本编辑器中打开ex1文件,随机修改或删除一部分,之后…...
Linux云原生安全:零信任架构与机密计算
Linux云原生安全:零信任架构与机密计算 构建坚不可摧的云原生防御体系 引言:云原生安全的范式革命 随着云原生技术的普及,安全边界正在从传统的网络边界向工作负载内部转移。Gartner预测,到2025年,零信任架构将成为超…...

GruntJS-前端自动化任务运行器从入门到实战
Grunt 完全指南:从入门到实战 一、Grunt 是什么? Grunt是一个基于 Node.js 的前端自动化任务运行器,主要用于自动化执行项目开发中重复性高的任务,例如文件压缩、代码编译、语法检查、单元测试、文件合并等。通过配置简洁的任务…...
Go语言多线程问题
打印零与奇偶数(leetcode 1116) 方法1:使用互斥锁和条件变量 package mainimport ("fmt""sync" )type ZeroEvenOdd struct {n intzeroMutex sync.MutexevenMutex sync.MutexoddMutex sync.Mutexcurrent int…...
Web中间件--tomcat学习
Web中间件–tomcat Java虚拟机详解 什么是JAVA虚拟机 Java虚拟机是一个抽象的计算机,它可以执行Java字节码。Java虚拟机是Java平台的一部分,Java平台由Java语言、Java API和Java虚拟机组成。Java虚拟机的主要作用是将Java字节码转换为机器代码&#x…...

MyBatis中关于缓存的理解
MyBatis缓存 MyBatis系统当中默认定义两级缓存:一级缓存、二级缓存 默认情况下,只有一级缓存开启(sqlSession级别的缓存)二级缓存需要手动开启配置,需要局域namespace级别的缓存 一级缓存(本地缓存&#…...

消防一体化安全管控平台:构建消防“一张图”和APP统一管理
在城市的某个角落,一场突如其来的火灾打破了平静。熊熊烈火迅速蔓延,滚滚浓烟弥漫开来,周围群众的生命财产安全受到严重威胁。就在这千钧一发之际,消防救援队伍迅速行动,而豪越科技消防一体化安全管控平台构建的消防“…...

C++_哈希表
本篇文章是对C学习的哈希表部分的学习分享 相信一定会对你有所帮助~ 那咱们废话不多说,直接开始吧! 一、基础概念 1. 哈希核心思想: 哈希函数的作用:通过此函数建立一个Key与存储位置之间的映射关系。理想目标:实现…...