嵌入式设备WIFI应用开发(2) - wpa_cli源码分析
概述
使用wpa_cli可执行程序和相关的命令可以做到连接wifi,在实际的项目中更进一步的想法是怎么样将wpa_cli的源代码整合到自己的实际项目中来。为了达到这个目的,需要从wap_cli的源码开始分析,了解清楚了wpa_cli的工作过程中后,就可以开始进行源代码层面的整合了。
wpa_cli源码分析
wpa_cli的源码包含在了wpa_supplicant的源码包中,解压wpa_supplicant后,在wpa_supplicant目录下找到wap_cli.c这个文件,该文件就是wpa_cli的主要实现问题,在该文件中能找wpa_cli的入口main函数。
wpa_cli的main函数分析
通过对main函数执行流程的分析,最后得到的结论我们进一步分析的只要两个函数:wpa_cli_open_connection()和wpa_request()
wpa_cli_open_connection()函数分析
wpa_request() 函数分析
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;
};
static 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" },
{ "ifname", wpa_cli_cmd_ifname, NULL,
cli_cmd_flag_none,
"= get current interface name" },
{ "ping", wpa_cli_cmd_ping, NULL,
cli_cmd_flag_none,
"= pings wpa_supplicant" },
{ "relog", wpa_cli_cmd_relog, NULL,
cli_cmd_flag_none,
"= re-open log-file (allow rolling logs)" },
{ "note", wpa_cli_cmd_note, NULL,
cli_cmd_flag_none,
"<text> = add a note to wpa_supplicant debug log" },
{ "mib", wpa_cli_cmd_mib, NULL,
cli_cmd_flag_none,
"= get MIB variables (dot1x, dot11)" },
{ "help", wpa_cli_cmd_help, wpa_cli_complete_help,
cli_cmd_flag_none,
"[command] = show usage help" },
{ "interface", wpa_cli_cmd_interface, NULL,
cli_cmd_flag_none,
"[ifname] = show interfaces/select interface" },
{ "level", wpa_cli_cmd_level, NULL,
cli_cmd_flag_none,
"<debug level> = change debug level" },
....
//后面还有很多
wpa_cli_commands数组中每个元素对应一个wpa_cli_cmd ,wpa_cli_cmd结构体定义了wpa_cli命令及命令执行函数。已
{
"status",
wpa_cli_cmd_status,
NULL,
cli_cmd_flag_none,
"[verbose] = get current WPA/EAPOL/EAP status"
}
为例:
“status”:命令字,
wpa_cli_cmd_status:执行status命令的函数,
进入wpa_cli_cmd_status函数:
static int wpa_cli_cmd_status(struct wpa_ctrl *ctrl, int argc, char *argv[])
{
if (argc > 0 && os_strcmp(argv[0], "verbose") == 0)
return wpa_ctrl_command(ctrl, "STATUS-VERBOSE");
if (argc > 0 && os_strcmp(argv[0], "wps") == 0)
return wpa_ctrl_command(ctrl, "STATUS-WPS");
if (argc > 0 && os_strcmp(argv[0], "driver") == 0)
return wpa_ctrl_command(ctrl, "STATUS-DRIVER");
#ifdef ANDROID
if (argc > 0 && os_strcmp(argv[0], "no_events") == 0)
return wpa_ctrl_command(ctrl, "STATUS-NO_EVENTS");
#endif /* ANDROID */
return wpa_ctrl_command(ctrl, "STATUS");
}
最终进入
static int wpa_ctrl_command(struct wpa_ctrl *ctrl, char *cmd)
{
return _wpa_ctrl_command(ctrl, cmd, 1);
}
下一层
static int _wpa_ctrl_command(struct wpa_ctrl *ctrl, char *cmd, int print)
{
char buf[4096];
size_t len;
int ret;
if (ctrl_conn == NULL) {
printf("Not connected to wpa_supplicant - command dropped.\n");
return -1;
}
if (ifname_prefix) {
os_snprintf(buf, sizeof(buf), "IFNAME=%s %s",
ifname_prefix, cmd);
buf[sizeof(buf) - 1] = '\0';
cmd = buf;
}
len = sizeof(buf) - 1;
ret = wpa_ctrl_request(ctrl, cmd, os_strlen(cmd), buf, &len,
wpa_cli_msg_cb);
if (ret == -2) {
printf("'%s' command timed out.\n", cmd);
return -2;
} else if (ret < 0) {
printf("'%s' command failed.\n", cmd);
return -1;
}
if (print) {
buf[len] = '\0';
printf("%s", buf);
if (interactive && len > 0 && buf[len - 1] != '\n')
printf("\n");
}
return 0;
}
下一层
#ifdef CTRL_IFACE_SOCKET
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;
#ifdef CONFIG_CTRL_IFACE_UDP
if (ctrl->cookie) {
char *pos;
_cmd_len = os_strlen(ctrl->cookie) + 1 + cmd_len;
cmd_buf = os_malloc(_cmd_len);
if (cmd_buf == NULL)
return -1;
_cmd = cmd_buf;
pos = cmd_buf;
os_strlcpy(pos, ctrl->cookie, _cmd_len);
pos += os_strlen(ctrl->cookie);
*pos++ = ' ';
os_memcpy(pos, cmd, cmd_len);
} else
#endif /* CONFIG_CTRL_IFACE_UDP */
{
_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)
{
/*
* Must be a non-blocking socket... Try for a bit
* longer before giving up.
*/
if (started_at.sec == 0)
os_get_reltime(&started_at);
else {
struct os_reltime n;
os_get_reltime(&n);
/* Try for a few seconds. */
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)
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] == '<') {
/* This is an unsolicited message from
* wpa_supplicant, not the reply to the
* request. Use msg_cb to report this to the
* caller. */
if (msg_cb) {
/* Make sure the message is nul
* terminated. */
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;
}
#endif /* CTRL_IFACE_SOCKET */
最终走到wpa_ctrl_request,wpa_ctrl_request完成与wpa_supplicant之间的通行,发送命令,接收命令执行结果。
接口封装
明确了wpa_cli命令执行过程后,从后往前倒推执行过程,在连接上一篇《嵌入式设备WIFI应用开发(1)- wpa_supplicant和wpa_cli的移植和使用》中,我们使用了“scan”,“scan_r”,“add_network”,“set_network”,“enable_network”,“status”命令完成了wifi的整个连接过程。现在只需要针对上面的命令封装出调用接口就可以了。
scan命令封装
int my_wpa_cmd_scan(struct wpa_ctrl *ctrl, char *buf, size_t bufsize)
{
return my_wpa_ctrl_command(ctrl, "SCAN", buf, bufsize);
}
int my_wpa_scan(const char *ifname)
{
int ret = 0;
char reply[64] = {0};
if (!ifname)
return -1;
if (wpa_cli_open_connection(ifname, 0) < 0)
{
TRACE("> failed to connect to ifname: %s error: %s. %s %d\r\n", ifname ? ifname : "(nil)",
strerror(errno), MDL);
return -1;
}
my_wpa_cmd_scan(ctrl_conn, reply, sizeof(reply));
if (os_strncmp(reply, "OK", 2) != 0)
{
TRACE("> failed to scan network in wpa_supplicant, %s. %s %d\r\n", reply, MDL);
ret = -1;
if (os_strncmp(reply, "FAIL-BUSY", os_strlen("FAIL-BUSY")) == 0)
{
ret = -2;
}
}
wpa_cli_cleanup();
return ret;
}
scan_r命令封装
int my_wpa_cmd_scan_results(struct wpa_ctrl *ctrl, char *buf, size_t bufsize)
{
return my_wpa_ctrl_command(ctrl, "SCAN_RESULTS", buf, bufsize);
}
int my_wpa_scan_results(const char *ifname, struct my_wpa_scan_result *result)
{
int ret = 0;
char reply[4096] = {0};
size_t reply_len = sizeof(reply);
if (ifname == NULL || result == NULL)
return -1;
if (wpa_cli_open_connection(ifname, 0) < 0)
{
TRACE("> failed to connect to ifname: %s error: %s. %s %d\r\n", ifname ? ifname : "(nil)",
strerror(errno), MDL);
return -1;
}
ret = my_wpa_cmd_scan_results(ctrl_conn, reply, reply_len);
reply[reply_len - 1] = '\0';
if (0 == ret)
{
/* reply保存所有扫描的结果 */
ret = my_wpa_parse_scan_result(reply, result);
}
wpa_cli_cleanup();
return ret;
}
其它指令可以按同样的方法进行封装。
libwifi.a
通过wpa_cli的源码分析,更进一步,将相关的源文件抽取出来,编译成一个libwifi.a文件,移植到自己的项目中。
通过libwifi.h对外提供调用接口。
libwifi.h
#ifdef __cplusplus
extern "C" {
#endif
#define MAX_SCAN_RESULT_NUM 50
typedef struct wpa_cli_scan_result
{
char bssid[24]; /* wifi热点MAC地址 */
int freq; /* wifi信道值 */
int level; /* 信号强度,值越大信号越强 */
char flags[128]; /* 加密相关信息 */
char ssid[128]; /* ssid */
} wpa_cli_scan_result;
typedef struct my_wpa_scan_result
{
int count; /* 扫描wifi总数 */
wpa_cli_scan_result results[MAX_SCAN_RESULT_NUM]; /* 扫描wifi结果 */
} my_wpa_scan_result;
/* ssid 搜素 */
int wifi_connect_scan(const char *ifname, const char *utf8ssid, int *auth, int *encr, int *signal);
/* 获取指定ssid WiFi连接状态 */
int wifi_connect_status(const char *ifname, const char *utf8ssid);
/* 连接指定ssid WiFi */
int wifi_connect_by_ssid(const char* ifname, const char* ssid, const char* passwd, int auth, int encr);
#ifdef __cplusplus
}
#endif
#endif /* LIB_WIFI_H */