• 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的消息,其处理日志如下

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

    1. //wpa_supplicant\wpa_cli.c
    2. int main(int argc, char *argv[])
    3. {
    4. int c;
    5. int daemonize = 0;
    6. int ret = 0;
    7. if (os_program_init())
    8. return -1;
    9. for (;;) {
    10. c = getopt(argc, argv, "a:Bg:G:hi:p:P:rs:v");
    11. if (c < 0)
    12. break;
    13. switch (c) {
    14. case 'i':
    15. os_free(ctrl_ifname);
    16. ctrl_ifname = os_strdup(optarg);
    17. break;
    18. }
    19. if (eloop_init())
    20. return -1;
    21. if (wpa_cli_open_global_ctrl() < 0)
    22. return -1;
    23. eloop_register_signal_terminate(wpa_cli_terminate, NULL);
    24. if (wpa_cli_open_connection(ctrl_ifname, 0) < 0) {
    25. fprintf(stderr, "Failed to connect to non-global "
    26. "ctrl_ifname: %s error: %s\n",
    27. ctrl_ifname ? ctrl_ifname : "(nil)",
    28. strerror(errno));
    29. return -1;
    30. }
    31. ret = wpa_request(ctrl_conn, argc - optind,&argv[optind]);
    32. os_free(ctrl_ifname);
    33. eloop_destroy();
    34. wpa_cli_cleanup();
    35. return ret;
    36. }

    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

    函数结构如下:

    1. //wpa_supplicant\wpa_cli.c
    2. static struct wpa_ctrl *ctrl_conn;
    3. static const char *global = NULL;
    4. //wpa_supplicant\wpa_cli.c
    5. static int wpa_cli_open_global_ctrl(void)
    6. {
    7. ctrl_conn = wpa_ctrl_open(global);
    8. return 0;
    9. }
    10. //src\common\wpa_ctrl.c
    11. struct wpa_ctrl * wpa_ctrl_open(const char *ctrl_path)
    12. {
    13. return wpa_ctrl_open2(ctrl_path, NULL);
    14. }
    15. //src\common\wpa_ctrl.c
    16. struct wpa_ctrl * wpa_ctrl_open2(const char *ctrl_path, const char *cli_path)
    17. {
    18. struct wpa_ctrl *ctrl;
    19. if (ctrl_path == NULL)
    20. return NULL;
    21. }

    因为在命令中没有指定-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,表示只建立连接,不附加到接口

    函数结构如下:

    1. //wpa_supplicant\wpa_cli.c
    2. #ifndef CONFIG_CTRL_IFACE_DIR
    3. #define CONFIG_CTRL_IFACE_DIR "/var/run/wpa_supplicant"
    4. #endif /* CONFIG_CTRL_IFACE_DIR */
    5. static const char *ctrl_iface_dir = CONFIG_CTRL_IFACE_DIR;
    6. static const char *client_socket_dir = NULL;
    7. static struct wpa_ctrl *ctrl_conn;
    8. //wpa_supplicant\wpa_cli.c
    9. static int wpa_cli_open_connection(const char *ifname, int attach)
    10. {
    11. char *cfile = NULL;
    12. int flen, res;
    13. if (ifname == NULL)
    14. return -1;
    15. if (cfile == NULL) {
    16. flen = os_strlen(ctrl_iface_dir) + os_strlen(ifname) + 2;
    17. cfile = os_malloc(flen);
    18. if (cfile == NULL)
    19. return -1;
    20. res = os_snprintf(cfile, flen, "%s/%s", ctrl_iface_dir,
    21. ifname);
    22. if (os_snprintf_error(flen, res)) {
    23. os_free(cfile);
    24. return -1;
    25. }
    26. }
    27. ctrl_conn = wpa_ctrl_open2(cfile, client_socket_dir);
    28. if (ctrl_conn == NULL) {
    29. os_free(cfile);
    30. return -1;
    31. }
    32. os_free(cfile);
    33. return 0;
    34. }

    该函数仍然调用了wpa_ctrl_open2()函数,并将返回的控制接口指针wpa_ctrl赋值给全局变量ctrl_conn

    ctrl_conn为全局变量,类型为结构体指针wpa_ctrl

    1. //src\common\wpa_ctrl.c
    2. struct wpa_ctrl {
    3. int s;
    4. struct sockaddr_un local;
    5. struct sockaddr_un dest;
    6. };

    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函数结构如下:

    1. //src\common\wpa_ctrl.c
    2. #ifndef CONFIG_CTRL_IFACE_CLIENT_DIR
    3. #define CONFIG_CTRL_IFACE_CLIENT_DIR "/tmp"
    4. #endif /* CONFIG_CTRL_IFACE_CLIENT_DIR */
    5. #ifndef CONFIG_CTRL_IFACE_CLIENT_PREFIX
    6. #define CONFIG_CTRL_IFACE_CLIENT_PREFIX "wpa_ctrl_"
    7. #endif /* CONFIG_CTRL_IFACE_CLIENT_PREFIX */
    8. //src\common\wpa_ctrl.c
    9. struct wpa_ctrl * wpa_ctrl_open2(const char *ctrl_path, const char *cli_path)
    10. {
    11. struct wpa_ctrl *ctrl;
    12. static int counter = 0;
    13. int ret;
    14. size_t res;
    15. int tries = 0;
    16. int flags;
    17. if (ctrl_path == NULL)
    18. return NULL;
    19. ctrl = os_zalloc(sizeof(*ctrl));
    20. if (ctrl == NULL)
    21. return NULL;
    22. ctrl->s = socket(PF_UNIX, SOCK_DGRAM, 0);
    23. if (ctrl->s < 0) {
    24. os_free(ctrl);
    25. return NULL;
    26. }
    27. ctrl->local.sun_family = AF_UNIX;
    28. counter++;
    29. try_again:
    30. ret = os_snprintf(ctrl->local.sun_path,
    31. sizeof(ctrl->local.sun_path),
    32. CONFIG_CTRL_IFACE_CLIENT_DIR "/"
    33. CONFIG_CTRL_IFACE_CLIENT_PREFIX "%d-%d",
    34. (int) getpid(), counter);
    35. if (os_snprintf_error(sizeof(ctrl->local.sun_path), ret)) {
    36. close(ctrl->s);
    37. os_free(ctrl);
    38. return NULL;
    39. }
    40. tries++;
    41. if (bind(ctrl->s, (struct sockaddr *) &ctrl->local,
    42. sizeof(ctrl->local)) < 0) {
    43. if (errno == EADDRINUSE && tries < 2) {
    44. unlink(ctrl->local.sun_path);
    45. goto try_again;
    46. }
    47. close(ctrl->s);
    48. os_free(ctrl);
    49. return NULL;
    50. }
    51. ctrl->dest.sun_family = AF_UNIX;
    52. res = os_strlcpy(ctrl->dest.sun_path, ctrl_path, sizeof(ctrl->dest.sun_path));
    53. if (res >= sizeof(ctrl->dest.sun_path)) {
    54. close(ctrl->s);
    55. os_free(ctrl);
    56. return NULL;
    57. }
    58. if (connect(ctrl->s, (struct sockaddr *) &ctrl->dest,
    59. sizeof(ctrl->dest)) < 0) {
    60. close(ctrl->s);
    61. unlink(ctrl->local.sun_path);
    62. os_free(ctrl);
    63. return NULL;
    64. }
    65. flags = fcntl(ctrl->s, F_GETFL);
    66. if (flags >= 0) {
    67. flags |= O_NONBLOCK;
    68. if (fcntl(ctrl->s, F_SETFL, flags) < 0) {
    69. perror("fcntl(ctrl->s, O_NONBLOCK)");
    70. }
    71. }
    72. return ctrl;
    73. }

    对该函数说明如下:

    • 调用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个成员的值

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

    相关定义如下

    1. //winsock.h
    2. #define AF_UNIX 1
    3. #define PF_UNIX AF_UNIX
    4. //sys/un.h
    5. struct sockaddr_un {
    6. sa_family_t sun_family; // 地址族,通常设置为 AF_UNIX
    7. char sun_path[UNIX_PATH_MAX]; // 套接字文件的路径
    8. };

    代码运行后,返回的结构体指针wpa_ctrl的相关值如下:

    1. ctrl->s: 3
    2. ctrl->local.sun_family: 1
    3. ctrl->local.sun_path: /tmp/wpa_ctrl_13152-1
    4. ctrl->dest.sun_family: 1
    5. 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"

    该函数结构如下:

    1. //wpa_supplicant\wpa_cli.c
    2. struct wpa_cli_cmd {
    3. const char *cmd;
    4. int (*handler)(struct wpa_ctrl *ctrl, int argc, char *argv[]);
    5. char ** (*completion)(const char *str, int pos);
    6. enum wpa_cli_cmd_flags flags;
    7. const char *usage;
    8. };
    9. //wpa_supplicant\wpa_cli.c
    10. static const struct wpa_cli_cmd wpa_cli_commands[] = {
    11. { "scan", wpa_cli_cmd_scan, NULL,
    12. cli_cmd_flag_none,
    13. "= request new BSS scan" },
    14. }
    15. //wpa_supplicant\wpa_cli.c
    16. static int wpa_request(struct wpa_ctrl *ctrl, int argc, char *argv[])
    17. {
    18. const struct wpa_cli_cmd *cmd, *match = NULL;
    19. int count;
    20. int ret = 0;
    21. ifname_prefix = NULL;
    22. if (argc == 0)
    23. return -1;
    24. count = 0;
    25. cmd = wpa_cli_commands;
    26. while (cmd->cmd) {
    27. if (os_strncasecmp(cmd->cmd, argv[0], os_strlen(argv[0])) == 0)
    28. {
    29. match = cmd;
    30. if (os_strcasecmp(cmd->cmd, argv[0]) == 0) {
    31. /* we have an exact match */
    32. count = 1;
    33. break;
    34. }
    35. count++;
    36. }
    37. cmd++;
    38. }
    39. if (count > 1) {
    40. printf("Ambiguous command '%s'; possible commands:", argv[0]);
    41. cmd = wpa_cli_commands;
    42. while (cmd->cmd) {
    43. if (os_strncasecmp(cmd->cmd, argv[0],
    44. os_strlen(argv[0])) == 0) {
    45. printf(" %s", cmd->cmd);
    46. }
    47. cmd++;
    48. }
    49. printf("\n");
    50. ret = 1;
    51. } else if (count == 0) {
    52. printf("Unknown command '%s'\n", argv[0]);
    53. ret = 1;
    54. } else {
    55. ret = match->handler(ctrl, argc - 1, &argv[1]);
    56. }
    57. return ret;
    58. }

    该函数将参数"scan"与已经定义的数组wpa_cli_commands中的命令元素进行完全匹配

    1. while (cmd->cmd) {
    2. if (os_strncasecmp(cmd->cmd, argv[0], os_strlen(argv[0])) == 0)
    3. {
    4. match = cmd;
    5. if (os_strcasecmp(cmd->cmd, argv[0]) == 0) {
    6. count = 1;
    7. break;
    8. }
    9. }
    10. cmd++;
    11. }

    匹配到相同的命令后调用该命令对应的句柄函数,并将未处理参数个数减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()

    主要函数调用如下:

    1. //wpa_supplicant\wpa_cli.c
    2. static int wpa_cli_cmd_scan(struct wpa_ctrl *ctrl, int argc, char *argv[])
    3. {
    4. return wpa_cli_cmd(ctrl, "SCAN", 0, argc, argv);
    5. }
    6. //wpa_supplicant\wpa_cli.c
    7. static int wpa_cli_cmd(struct wpa_ctrl *ctrl, const char *cmd, int min_args,
    8. int argc, char *argv[])
    9. {
    10. char buf[4096];
    11. if (write_cmd(buf, sizeof(buf), cmd, argc, argv) < 0)
    12. return -1;
    13. return wpa_ctrl_command(ctrl, buf);
    14. }
    15. //wpa_supplicant\wpa_cli.c
    16. static int wpa_ctrl_command(struct wpa_ctrl *ctrl, const char *cmd)
    17. {
    18. return _wpa_ctrl_command(ctrl, cmd, 1);
    19. }
    20. //wpa_supplicant\wpa_cli.c
    21. static int _wpa_ctrl_command(struct wpa_ctrl *ctrl, const char *cmd, int print)
    22. {
    23. char buf[4096];
    24. size_t len;
    25. int ret;
    26. len = sizeof(buf) - 1;
    27. ret = wpa_ctrl_request(ctrl, cmd, os_strlen(cmd), buf, &len,
    28. wpa_cli_msg_cb);
    29. return 0;
    30. }

    wpa_ctrl_request()函数返回1个整型变量,成功返回0,失败返回-1,该函数有6个输入参数,如下

    • 参数ctrl是socket控制接口

    • 参数cmd是发送给wpa_supplicant的命令

    • 参数cmd_len是命令长度

    • 参数reply是wpa_supplicant对命令的回复

    • 参数reply_len是回复的长度

    • 参数msg_cb是绑定的消息回调函数

    该函数结构如下:

    1. //src\common\wpa_ctrl.c
    2. int wpa_ctrl_request(struct wpa_ctrl *ctrl,
    3. const char *cmd,
    4. size_t cmd_len,
    5. char *reply,
    6. size_t *reply_len,
    7. void (*msg_cb)(char *msg, size_t len))
    8. {
    9. struct timeval tv;
    10. struct os_reltime started_at;
    11. int res;
    12. fd_set rfds;
    13. const char *_cmd;
    14. char *cmd_buf = NULL;
    15. size_t _cmd_len;
    16. {
    17. _cmd = cmd;
    18. _cmd_len = cmd_len;
    19. }
    20. errno = 0;
    21. started_at.sec = 0;
    22. started_at.usec = 0;
    23. retry_send:
    24. if (send(ctrl->s, _cmd, _cmd_len, 0) < 0) {
    25. if (errno == EAGAIN || errno == EBUSY || errno == EWOULDBLOCK)
    26. {
    27. if (started_at.sec == 0)
    28. os_get_reltime(&started_at);
    29. else {
    30. struct os_reltime n;
    31. os_get_reltime(&n);
    32. if (os_reltime_expired(&n, &started_at, 5))
    33. goto send_err;
    34. }
    35. os_sleep(1, 0);
    36. goto retry_send;
    37. }
    38. send_err:
    39. os_free(cmd_buf);
    40. return -1;
    41. }
    42. os_free(cmd_buf);
    43. for (;;) {
    44. tv.tv_sec = 10;
    45. tv.tv_usec = 0;
    46. FD_ZERO(&rfds);
    47. FD_SET(ctrl->s, &rfds);
    48. res = select(ctrl->s + 1, &rfds, NULL, NULL, &tv);
    49. if (res < 0 && errno == EINTR)
    50. continue;
    51. if (res < 0)
    52. return res;
    53. if (FD_ISSET(ctrl->s, &rfds)) {
    54. res = recv(ctrl->s, reply, *reply_len, 0);
    55. if (res < 0)
    56. return res;
    57. if ((res > 0 && reply[0] == '<') ||
    58. (res > 6 && strncmp(reply, "IFNAME=", 7) == 0)) {
    59. if (msg_cb) {
    60. if ((size_t) res == *reply_len)
    61. res = (*reply_len) - 1;
    62. reply[res] = '\0';
    63. msg_cb(reply, res);
    64. }
    65. continue;
    66. }
    67. *reply_len = res;
    68. break;
    69. } else {
    70. return -2;
    71. }
    72. }
    73. return 0;
    74. }

    该函数调用send()发送命令到wpa_supplicant

    然后在for循环里调用select()监视使用的socket文件,添加到可读文件集合,超时时间设置为10s

    然后调用recv()接收来自wpa_supplicant的回复

    最后,如果回调函数存在,则调用回调函数msg_cb,因回调函数设置为wpa_cli_msg_cb,所以实际调用了wpa_cli_msg_cb()函数

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

    调用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()函数

    1. //wpa_supplicant\main.c
    2. int main(int argc, char *argv[])
    3. {
    4. int c, i;
    5. struct wpa_interface *ifaces, *iface;
    6. int iface_count, exitcode = -1;
    7. struct wpa_params params;
    8. struct wpa_global *global;
    9. os_memset(¶ms, 0, sizeof(params));
    10. params.wpa_debug_level = MSG_INFO;
    11. iface = ifaces = os_zalloc(sizeof(struct wpa_interface));
    12. iface_count = 1;
    13. for (;;) {
    14. c = getopt(argc, argv,
    15. "b:Bc:C:D:de:f:g:G:hi:I:KLMm:No:O:p:P:qsTtuvW");
    16. if (c < 0)
    17. break;
    18. switch (c) {
    19. case 'c':
    20. iface->confname = optarg;
    21. break;
    22. case 'D':
    23. iface->driver = optarg;
    24. break;
    25. case 'i':
    26. iface->ifname = optarg;
    27. break;
    28. }
    29. }
    30. exitcode = 0;
    31. global = wpa_supplicant_init(¶ms);
    32. wpa_printf(MSG_INFO, "Successfully initialized " "wpa_supplicant");
    33. for (i = 0; exitcode == 0 && i < iface_count; i++) {
    34. struct wpa_supplicant *wpa_s;
    35. wpa_s = wpa_supplicant_add_iface(global, &ifaces[i], NULL);
    36. }
    37. if (exitcode == 0)
    38. exitcode = wpa_supplicant_run(global);
    39. return exitcode;
    40. }

    在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()函数分析如下:

    1. //wpa_supplicant\wpa_supplicant.c
    2. struct wpa_supplicant * wpa_supplicant_add_iface(struct wpa_global *global,
    3. struct wpa_interface *iface,
    4. struct wpa_supplicant *parent)
    5. {
    6. struct wpa_supplicant *wpa_s;
    7. struct wpa_interface t_iface;
    8. struct wpa_ssid *ssid;
    9. wpa_s = wpa_supplicant_alloc(parent);
    10. wpa_s->global = global;
    11. if (wpa_supplicant_init_iface(wpa_s, &t_iface)) {
    12. wpa_printf(MSG_DEBUG, "Failed to add interface %s",
    13. iface->ifname);
    14. wpa_supplicant_deinit_iface(wpa_s, 0, 0);
    15. return NULL;
    16. }
    17. wpa_s->next = global->ifaces;
    18. global->ifaces = wpa_s;
    19. return wpa_s;
    20. }
    21. //wpa_supplicant\wpa_supplicant.c
    22. static int wpa_supplicant_init_iface(struct wpa_supplicant *wpa_s,
    23. const struct wpa_interface *iface)
    24. {
    25. wpa_printf(MSG_DEBUG, "Initializing interface '%s' conf '%s' driver "
    26. "'%s' ctrl_interface '%s' bridge '%s'", iface->ifname,
    27. iface->confname ? iface->confname : "N/A",
    28. iface->driver ? iface->driver : "default",
    29. iface->ctrl_interface ? iface->ctrl_interface : "N/A",
    30. iface->bridge_ifname ? iface->bridge_ifname : "N/A");
    31. wpa_s->ctrl_iface = wpa_supplicant_ctrl_iface_init(wpa_s);
    32. return 0;
    33. }

    接着会跳转到unix的控制接口文件中

    1. //wpa_supplicant\ctrl_iface_unix.c
    2. struct ctrl_iface_priv *wpa_supplicant_ctrl_iface_init(struct wpa_supplicant *wpa_s)
    3. {
    4. struct ctrl_iface_priv *priv;
    5. priv = os_zalloc(sizeof(*priv));
    6. if (priv == NULL)
    7. return NULL;
    8. dl_list_init(&priv->ctrl_dst);
    9. dl_list_init(&priv->msg_queue);
    10. priv->wpa_s = wpa_s;
    11. priv->sock = -1;
    12. if (wpas_ctrl_iface_open_sock(wpa_s, priv) < 0) {
    13. os_free(priv);
    14. return NULL;
    15. }
    16. return priv;
    17. }
    18. static int wpas_ctrl_iface_open_sock(struct wpa_supplicant *wpa_s,
    19. struct ctrl_iface_priv *priv)
    20. {
    21. eloop_register_read_sock(priv->sock, wpa_supplicant_ctrl_iface_receive, wpa_s, priv);
    22. wpa_msg_register_cb(wpa_supplicant_ctrl_iface_msg_cb);
    23. }

    所以当wpa_supplicant程序接收到socket消息时就执行wpa_supplicant_ctrl_iface_receive()函数

    1. //wpa_supplicant\ctrl_iface_unix.c
    2. static void wpa_supplicant_ctrl_iface_receive(int sock,
    3. void *eloop_ctx,
    4. void *sock_ctx)
    5. {
    6. struct wpa_supplicant *wpa_s = eloop_ctx;
    7. struct ctrl_iface_priv *priv = sock_ctx;
    8. char *buf;
    9. int res;
    10. struct sockaddr_storage from;
    11. socklen_t fromlen = sizeof(from);
    12. char *reply = NULL, *reply_buf = NULL;
    13. size_t reply_len = 0;
    14. int new_attached = 0;
    15. buf = os_malloc(CTRL_IFACE_MAX_LEN + 1);
    16. res = recvfrom(sock, buf, CTRL_IFACE_MAX_LEN + 1, 0,
    17. (struct sockaddr *) &from, &fromlen);
    18. buf[res] = '\0';
    19. reply_buf = wpa_supplicant_ctrl_iface_process(wpa_s, buf, &reply_len);
    20. reply = reply_buf;
    21. os_memset(buf, 0, res);
    22. if (!reply && reply_len == 1) {
    23. reply = "FAIL\n";
    24. reply_len = 5;
    25. } else if (!reply && reply_len == 2) {
    26. reply = "OK\n";
    27. reply_len = 3;
    28. }
    29. if (reply) {
    30. wpas_ctrl_sock_debug("ctrl_sock-sendto", sock, reply, reply_len);
    31. if (sendto(sock, reply, reply_len, 0, (struct sockaddr *) &from, fromlen) < 0) {
    32. int _errno = errno;
    33. wpa_dbg(wpa_s, MSG_DEBUG,
    34. "ctrl_iface sendto failed: %d - %s",
    35. _errno, strerror(_errno));
    36. }
    37. }
    38. os_free(reply_buf);
    39. os_free(buf);
    40. }

    在该函数主要中进行以下处理

    • 调用recvfrom()函数接收来自wpa_cli的命令,将接收数据保存在字符指针buf里

    • 调用wpa_supplicant_ctrl_iface_process()函数处理命令,返回结果保存在字符指针reply_buf和字符指针reply中

    • 调用sendto()函数向socket发送回复reply

    进一步分析wpa_supplicant接收wpa_cli的消息入口为wpa_supplicant_ctrl_iface_process()函数

    1. //wpa_supplicant\ctrl_iface.c
    2. char * wpa_supplicant_ctrl_iface_process(struct wpa_supplicant *wpa_s,
    3. char *buf,
    4. size_t *resp_len)
    5. {
    6. char *reply;
    7. const int reply_size = 4096;
    8. int reply_len;
    9. int level = wpas_ctrl_cmd_debug_level(buf);
    10. wpa_dbg(wpa_s, level, "Control interface command '%s'", buf);
    11. reply = os_malloc(reply_size);
    12. os_memcpy(reply, "OK\n", 3);
    13. reply_len = 3;
    14. if (os_strcmp(buf, "SCAN") == 0) {
    15. wpas_ctrl_scan(wpa_s, NULL, reply, reply_size, &reply_len);
    16. } else if (os_strncmp(buf, "SCAN ", 5) == 0) {
    17. wpas_ctrl_scan(wpa_s, buf + 5, reply, reply_size, &reply_len);
    18. }
    19. if (reply_len < 0) {
    20. os_memcpy(reply, "FAIL\n", 5);
    21. reply_len = 5;
    22. }
    23. *resp_len = reply_len;
    24. return reply;
    25. }
    26. //wpa_supplicant\ctrl_iface.c
    27. static void wpas_ctrl_scan(struct wpa_supplicant *wpa_s, char *params,
    28. char *reply, int reply_size, int *reply_len)
    29. {
    30. if (!wpa_s->sched_scanning && !wpa_s->scanning &&
    31. ((wpa_s->wpa_state <= WPA_SCANNING) ||
    32. (wpa_s->wpa_state == WPA_COMPLETED))) {
    33. wpa_supplicant_req_scan(wpa_s, 0, 0);
    34. } else if (wpa_s->sched_scanning) {
    35. wpa_supplicant_req_scan(wpa_s, 0, 0);
    36. }
    37. }

    最终执行到scan.c文件中的wpa_supplicant_req_scan()函数,发起扫描请求

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

    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事件

  • 相关阅读:
    Java:Jar包反编译,解压和压缩
    论文:OIE@OIA: an Adaptable and Efficient Open Information Extraction Framework
    systemverilog运行的时候调用系统函数运行python等
    android 多产品项目搭建与变体的使用
    线性基学习笔记 / 洛谷 P3812 【线性基】【贪心】
    JLPT N2 文法 Day 1
    系统入门到实战学习某项技术、有问题找“百度“、学习优秀的技术博客、找开源代码等资料
    MongoDB入门与实战-第六章-MongoDB分片
    ESP32-arduino,超好玩的定时器!
    项目解决方案:海外门店视频汇聚方案(全球性的连锁店、国外连锁店视频接入和汇聚方案)
  • 原文地址:https://blog.csdn.net/lishan132/article/details/136401423