wpa_supplicant

最简单的运行wpa_supplicant的命令

wpa\_supplicant -iwlan0 -C/var/run/wpa_supplicant -d

-iwlan0表示使用wlan0无线网卡
-C表示控制接口(ctrl interface)所在的目录。控制接口提供了外部程序与wpa_supplicant进行交互的接口,外部程序通过控制接口来获取wpa_supplicant的相关信息,并且能够控制它的行为。
-d表示输出调试信息
程序成功执行后,会在/var/run/wpa_supplicant目录下面生成wlan0的套接字文件
srwxrwx--- 1 root root 0 Dec 15 10:42 wlan0

如果系统中有多个无线网卡,可以开启多个wpa_supplicant进程,每个wpa_supplicant进程控制一个无线网卡。当然,也可以只使用一个wpa_supplicant进程来控制多个无线网卡,每个无线网卡通过-N命令行参数来分隔:

wpa\_supplicant -iwlan0 -C/var/run/wpa_supplicant -N
-iwlan1 -C/var/run/wpa_supplicant -d

此时,会生成两个控制接口

ll /var/run/wpa_supplicant

srwxrwx--- 1 root root 0 Dec 15 10:58 wlan0
srwxrwx--- 1 root root 0 Dec 15 10:58 wlan1

wpa_cli程序的核心是一个事件循环eloop,在事件循环eloop中会循环处理信号事件、定时器事件、文件描述符的读写事件。而这些事件信息都存储在struct eloop_data这样的结构变量中,程序循环处理事件也就是循环处理struct eloop_data结构变量。

1. 信号事件

与信号事件有关的数据结构是struct eloop_signal,具体信息如下:

struct eloop_signal 
{
int sig; /* signal number */
void *user_data;
eloop_signal_handler handler; /* signal handler */
int signaled; /* 进程是否收到该信号的标志 */
};

struct eloop_data中与信号事件有关的数据域如下:

struct eloop_data 
{
/* other fields */

int signal_count; /* 程序初始化过程中注册的信号处理函数的数量 */
struct eloop_signal *signals; /* 程序初始化过程中注册的所有的信号处理函数 */
int signaled; /* 程序运行过程中是否有未处理的信号的标志 */
}

wpa_cli的信号处理过程是这样的:无论进程收到什么信号,都会调用eloop_handle_signal函数来处理信号。在eloop_handle_signal内,只会简单的在eloop_data中记录某个信号发生了,除此之外,没有做任何其他事情。而真正的信号处理过程是推迟到事件循环eloop中去处理的。wpa_cli在初始化的时候仅仅注册了SIGINT和SIGTERM这两个信号的处理函数。

2. 定时器事件

与定时器事件有关的数据结构是struct eloop_timeout,具体信息如下:

struct eloop_timeout 
{
struct dl_list list; // 定时器事件的链表
struct os_reltime time; // 事件的发生时间
void *eloop_data;
void *user_data;
eloop_timeout_handler handler; // 定时器事件的处理函数
};

struct eloop_data中与信号事件有关的数据域如下:

struct eloop_data 
{

/* other fields */

struct dl_list timeout; // 定时器事件的链表头
}

在交互(Interactive)模式下,wpa_cli在进入事件循环函数eloop_run之前,注册了try_connection定时器事件。try_connection定时器事件在eloop_run中被执行,它会调用wpa_cli_open_connection去连接wpa_supplicant程序创建的控制接口,如果连接失败,则把自己再注册为一个1秒后触发的定时器事件;如果连接成功,则会生成两个与wpa_supplicant的连接:struct wpa_ctrl *ctrl_conn和struct wpa_ctrl *mon_conn。与wpa_supplicant进行的命令请求和响应,是通过ctrl_conn这个连接来进行的,但是与它有关的读写事件并不会注册在eloop_data中;而与mon_conn有关的读事件是注册到eloop_data中的,用来读取wpa_supplicant发送的广播事件。连接成功之后,wpa_cli会注册一个每隔一段时间就与wpa_supplicant通讯的心跳事件,还会把标准输入注册到eloop_data中,以便能够读取用户输入的命令。
与wpa_supplicant通讯的心跳事件的处理函数wpa_cli_ping到底做了哪些事情呢?其实,它只是调用了wpa_ctrl_request。那wpa_ctrl_request又是何许人也呢?它是wpa_cli用来向wpa_supplicant发送命令请求并接收响应数据的通用接口。在wpa_ctrl_request中,程序利用struct wpa_ctrl *ctrl_conn这个连接向wpa_supplicant发送命令请求,然后读取wpa_supplicant的数据响应并打印到标准输出,这与前文中的“与ctrl_conn有关的读写事件并不会注册在eloop_data中”是一致的。

至此,我们可以知道,eloop_data中注册了一个定时器事件(每隔一段时间就与wpa_supplicant通讯的心跳事件)和两个文件描述符读事件(一个是标准输入,用于获取用户的输入;另一个是mon_conn连接的读事件,用于获取wpa_supplicant主动发送的广播事件)。

3. 文件描述符的读写事件

与文件描述符的读写事件有关的数据结构是struct eloop_sock和struct eloop_sock_table,具体信息如下:

struct eloop_sock 
{
int sock; // 文件描述符
void *eloop_data;
void *user_data;
eloop_sock_handler handler; // 处理函数
};
struct eloop_sock_table
{
int count; // 该类型的文件描述符的数量
struct eloop_sock *table; // 该类型的文件描述符集合
eloop_event_type type; // 文件描述符类型:READ,WRITE,EXCEPTION
int changed; // 该类型的文件描述符集合是否发生过改变
};

struct eloop_data中与信号事件有关的数据域如下:

struct eloop_data 
{
/* other fields */

int max_sock;

struct eloop_sock_table readers;
struct eloop_sock_table writers;
struct eloop_sock_table exceptions;
}

通过前面的分析可以知道,eloop_data中只有在readers中注册了两个文件描述符。其中一个是mon_conn连接的读事件,当wpa_supplicant主动发送广播信息时,会调用wpa_cli_mon_receive函数将广播信息打印到标准输出。另一个是标准输入的读事件,当用户输出一个命令的时候,会在wpa_cli中所支持的命令集合wpa_cli_commands中进行匹配,

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;
};

const struct wpa_cli_cmd wpa_cli_commands[] =
{
{ "status", wpa_cli_cmd_status, NULL,
cli_cmd_flag_none,
"[verbose] = get current WPA/EAPOL/EAP status" },

// other commands
}

如果匹配到某一条命令,则调用该命令的的处理函数handler,该handler与定时器心跳事件的处理函数wpa_cli_ping的处理过程是一样的。