最近在公司开发新产品智能Android机器人,开始使用的是rk3229开发板,在调试wifi的时候经常用到工具wap_cli,开始使用demo板调试的时候wpa_cli可以正常使用。但是由于产品需要支持蓝牙功能,所以换了博通的AP6212的wifi模块。demo使用的rtl8616wifi模块。后来发现新硬件上的wap_cli不能使用。很影响调试进度。报错如下
Failed to connect to non-global ctrl_ifname: wlan0 error: No such file or directory
Could not connect to wpa_supplicant: wlan0 - re-trying
报这个错的原因是找不到相关的waln0这个socket通信节点,正常情况下这个socket会在此目录下建立通信节点 /data/misc/wifi/sockets/wlan0
,但是我发现
在新硬件下面根本没有建立相关节点。通过不断调试添加log信息原来是ctrl_interface没有定义锁导致。只要把params.override_ctrl_interface定义了就ok了。
wifi的 supplicant模块启动在init.rc中触发的,不同wifi芯片 触发的service是不一样的。比如下面是rtl wifi
service rtw_suppl_con /system/bin/wpa_supplicant_rtl \
-ip2p0 -Dnl80211 -c/data/misc/wifi/p2p_supplicant.conf \
-e/data/misc/wifi/entropy.bin -N \
-iwlan0 -Dnl80211 -c/data/misc/wifi/wpa_supplicant.conf \
-O/data/misc/wifi/sockets \
-g@android:wpa_wlan0
class main
socket wpa_wlan0 dgram 660 wifi wifi
disabled
oneshot
这个是博通的
service wpa_supplicant /system/bin/wpa_supplicant \
-iwlan0 -Dnl80211 -c/data/misc/wifi/wpa_supplicant.conf \
-I/system/etc/wifi/wpa_supplicant_overlay.conf \
-e/data/misc/wifi/entropy.bin -g@android:wpa_wlan0
class main
socket wpa_wlan0 dgram 660 wifi wifi
disabled
oneshot
-i 指定网络socket通信节点 这里指定的是 wlan0
-D 指定wifi驱动的的接口 这里是nl80211
-C 指定 wpa_supplicant 初始化的时候需要读取 配置文件
这里是/data/misc/wifi/wpa_supplicant.conf
-I 备用读取配置文件
-g 指定 ctrl_interface = @android:wpa_ 和 -i 指定waln0组合
这里有点不是很明白 ctrl_interface = @android:wpa_wlan0,但是真用在代码中ctrl_interface 会变为ctrl_interface = /data/misc/wifi/sockets。
具体相关转换原因自己没有多关注。有了解的可以可以说一下。
一般情况下配置文件的内容如下:
ctrl_interface=/data/misc/wifi/sockets
update_config=1
ap_scan=1
ctrl_interface=/data/misc/wifi/sockets 和 -g@android:wpa_wlan0 在源码中的体现
case 'g':
params.ctrl_interface = optarg;
break;
这就是疑惑的地方 两个明显不同确实指的同一个ctrl_interface
ap_scan=1
1—wpas负责扫描和选择网络
0、2—驱动负责扫描和连接
update_config=1 允许更新配置文件
int main(int argc, char *argv[])
{
case 'c':
iface->confname = optarg;
break;
case 'C':
iface->ctrl_interface = optarg;
break;
case 'D':
iface->driver = optarg;
break;
case 'd':
#ifdef CONFIG_NO_STDOUT_DEBUG
printf("Debugging disabled with "
"CONFIG_NO_STDOUT_DEBUG=y build time "
"option.\n");
goto out;
#else /* CONFIG_NO_STDOUT_DEBUG */
params.wpa_debug_level--;
break;
#endif /* CONFIG_NO_STDOUT_DEBUG */
case 'e':
params.entropy_file = optarg;
break;
#ifdef CONFIG_DEBUG_FILE
case 'f':
params.wpa_debug_file_path = optarg;
break;
#endif /* CONFIG_DEBUG_FILE */
case 'g':
params.ctrl_interface = optarg;
break;
case 'G':
params.ctrl_interface_group = optarg;
break;
case 'h':
usage();
exitcode = 0;
goto out;
case 'i':
iface->ifname = optarg;
、、、、、、、、
global = wpa_supplicant_init(¶ms);
for (i = 0; exitcode == 0 && i < iface_count; i++) {
struct wpa_supplicant *wpa_s;
if ((ifaces[i].confname == NULL &&
ifaces[i].ctrl_interface == NULL) ||
ifaces[i].ifname == NULL) {
if (iface_count == 1 && (params.ctrl_interface ||
params.dbus_ctrl_interface))
break;
usage();
exitcode = -1;
break;
}
//这里调用wpa_supplicant_add_iface
wpa_s = wpa_supplicant_add_iface(global, &ifaces[i]);
}
if (exitcode == 0)
exitcode = wpa_supplicant_run(global);
wpa_supplicant_init函数功能是初始化struct wpa_global *global这个结构体
wpa_global是一个全局性质的上下文信息。它通过ifaces变量指向一个wpa_supplicant对象。
(1)wpa_interface用于描述一个无线设备。该参数在初始化时用到。
(2)wpa_global是一个全局性质的上下文信息。它通过ifaces变量指向一个wpa_supplicant对象。
(3)wpa_supplicant是wpa_supplicant的核心数据结构。一个interface对应一个wpa_supplicant对象,其内部包含非常多的成员变量。
(4)ctrl_iface_global_priv是全局控制接口的信息,内部包含一个用于通信的socket句柄
wpa_supplicant_init()
{
全局结构体指针 return 返回用
struct wpa_global *global;
global = os_zalloc(sizeof(*global));
if (global == NULL)
return NULL;
dl_list_init(&global->p2p_srv_bonjour);
dl_list_init(&global->p2p_srv_upnp);
global->params.daemonize = params->daemonize;
global->params.wait_for_monitor = params->wait_for_monitor;
global->params.dbus_ctrl_interface = params->dbus_ctrl_interface;
if (params->pid_file)
global->params.pid_file = os_strdup(params->pid_file);
if (params->ctrl_interface)
global->params.ctrl_interface =
os_strdup(params->ctrl_interface);
这句话很重要。如果为空的话。相关的socket就不会建立 就会出现上面的报错
if (params->ctrl_interface_group)
global->params.ctrl_interface_group =
os_strdup(params->ctrl_interface_group);
if (params->override_driver)
global->params.override_driver =
os_strdup(params->override_driver);
if (params->override_ctrl_interface)
global->params.override_ctrl_interface =
os_strdup(params->override_ctrl_interface);
将 params 都复制到global 的wpa_params中
初始化ctrl_iface_global_priv,最重要的是建立socket服务端,并且设置socket节点的地址ctrl_interface。
global->ctrl_iface = wpa_supplicant_global_ctrl_iface_init(global);
}
wpa_supplicant_global_ctrl_iface_init(struct wpa_global *global)
{
struct ctrl_iface_global_priv *priv;
priv = os_zalloc(sizeof(*priv));
if (priv == NULL)
return NULL;
dl_list_init(&priv->ctrl_dst);
priv->global = global;
priv->sock = -1;
if (global->params.ctrl_interface == NULL)
这里判断ctrl_interface是否为空。为空直接返回。所以就不会建立相关socket
return priv;
建立socket服务客户端 用来接收 wpa_cli 和framework传来的命名
if (wpas_global_ctrl_iface_open_sock(global, priv) < 0) {
os_free(priv);
return NULL;
}
wpa_msg_register_cb(wpa_supplicant_ctrl_iface_msg_cb);
return priv;
}
static int wpas_global_ctrl_iface_open_sock(struct wpa_global *global,
struct ctrl_iface_global_priv *priv)
{
struct sockaddr_un addr;
这里应该是ctrl_interface=/data/misc/wifi/sockets
const char *ctrl = global->params.ctrl_interface;
int flags;
#ifdef ANDROID
这里应该是ctrl=@android:wpa_wlan0,所以 ctrl + 9 = wlan0
if (os_strncmp(ctrl, "@android:", 9) == 0) {
建立服务端的 socket
priv->sock = android_get_control_socket(ctrl + 9);
if (priv->sock < 0) {
wpa_printf(MSG_ERROR, "Failed to open Android control "
"socket '%s'", ctrl + 9);
goto fail;
}
wpa_printf(MSG_DEBUG, "Using Android control socket '%s'",
ctrl + 9);
goto havesock;
}
if (os_strncmp(ctrl, "@abstract:", 10) != 0) {
/*
* Backwards compatibility - try to open an Android control
* socket and if that fails, assume this was a UNIX domain
* socket instead.
*/
priv->sock = android_get_control_socket(ctrl);
if (priv->sock >= 0) {
wpa_printf(MSG_DEBUG,
"Using Android control socket '%s'",
ctrl);
goto havesock;
}
}
#endif /* ANDROID */
havesock:
flags = fcntl(priv->sock, F_GETFL);
if (flags >= 0) {
flags |= O_NONBLOCK;
if (fcntl(priv->sock, F_SETFL, flags) < 0) {
wpa_printf(MSG_INFO, "fcntl(ctrl, O_NONBLOCK): %s",
strerror(errno));
/* Not fatal, continue on.*/
}
}
//这里会进行conf文件的保存。之前通过 init.rc service启动时候添加的参数和wpa_supplicant 初始化默认的参数会保存到data/misc/wifi/wpa_supplicant.conf中
eloop_register_read_sock(priv->sock,
wpa_supplicant_global_ctrl_iface_receive,
global, priv);
return 0;
fail:
if (priv->sock >= 0) {
close(priv->sock);
priv->sock = -1;
}
return -1;
}
wpa_supplicant_global_ctrl_iface_receive()可以接受两类命令,接口命令和全局命令。一类命令前面有“IFNAME= ”指明处理该命令的接口,然后调用每个接口对应的 wpa_supplicant_ctrl_iface_process()来进行相应处理。另一类未指定接口,由wpa_supplicant_global_ctrl_iface_process()直接处理。
static void wpa_supplicant_global_ctrl_iface_receive(int sock, void *eloop_ctx,
void *sock_ctx)
{
struct wpa_global *global = eloop_ctx;
struct ctrl_iface_global_priv *priv = sock_ctx;
char buf[4096];
int res;
struct sockaddr_un from;
socklen_t fromlen = sizeof(from);
char *reply = NULL, *reply_buf = NULL;
size_t reply_len;
res = recvfrom(sock, buf, sizeof(buf) - 1, 0,
(struct sockaddr *) &from, &fromlen);
if (res < 0) {
wpa_printf(MSG_ERROR, "recvfrom(ctrl_iface): %s",
strerror(errno));
return;
}
buf[res] = '\0';
if (os_strcmp(buf, "ATTACH") == 0) {
if (wpa_supplicant_ctrl_iface_attach(&priv->ctrl_dst, &from,
fromlen))
reply_len = 1;
else
reply_len = 2;
} else if (os_strcmp(buf, "DETACH") == 0) {
if (wpa_supplicant_ctrl_iface_detach(&priv->ctrl_dst, &from,
fromlen))
reply_len = 1;
else
reply_len = 2;
} else {
//wpa_supplicant_global_ctrl_iface_process分发处理传入的命令
reply_buf = wpa_supplicant_global_ctrl_iface_process(
global, buf, &reply_len);
reply = reply_buf;
}
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) {
if (sendto(sock, reply, reply_len, 0, (struct sockaddr *) &from,
fromlen) < 0) {
wpa_printf(MSG_DEBUG, "ctrl_iface sendto failed: %s",
strerror(errno));
}
}
os_free(reply_buf);
}
char * wpa_supplicant_global_ctrl_iface_process(struct wpa_global *global,
char *buf, size_t *resp_len)
{
if (os_strcmp(buf, "PING") == 0) {
os_memcpy(reply, "PONG\n", 5);
reply_len = 5;
} else if (os_strncmp(buf, "INTERFACE_ADD ", 14) == 0) {
if (wpa_supplicant_global_iface_add(global, buf + 14))
reply_len = -1;
} else if (os_strncmp(buf, "INTERFACE_REMOVE ", 17) == 0) {
if (wpa_supplicant_global_iface_remove(global, buf + 17))
reply_len = -1;
} else if (os_strcmp(buf, "INTERFACE_LIST") == 0) {
reply_len = wpa_supplicant_global_iface_list(
global, reply, reply_size);
} else if (os_strcmp(buf, "INTERFACES") == 0) {
reply_len = wpa_supplicant_global_iface_interfaces(
global, reply, reply_size);
} else if (os_strcmp(buf, "TERMINATE") == 0) {
wpa_supplicant_terminate_proc(global);
} else if (os_strcmp(buf, "SUSPEND") == 0) {
wpas_notify_suspend(global);
} else if (os_strcmp(buf, "RESUME") == 0) {
wpas_notify_resume(global);
} else if (os_strncmp(buf, "SET ", 4) == 0) {
if (wpas_global_ctrl_iface_set(global, buf + 4)) {
#ifdef CONFIG_P2P
if (global->p2p_init_wpa_s) {
os_free(reply);
/* Check if P2P redirection would work for this
* command. */
return wpa_supplicant_ctrl_iface_process(
global->p2p_init_wpa_s,
buf, resp_len);
}
#endif /* CONFIG_P2P */
reply_len = -1;
}
#ifndef CONFIG_NO_CONFIG_WRITE
} else if (os_strcmp(buf, "SAVE_CONFIG") == 0) {
if (wpas_global_ctrl_iface_save_config(global))
reply_len = -1;
#endif /* CONFIG_NO_CONFIG_WRITE */
} else if (os_strcmp(buf, "STATUS") == 0) {
reply_len = wpas_global_ctrl_iface_status(global, reply,
reply_size);
#ifdef CONFIG_MODULE_TESTS
} else if (os_strcmp(buf, "MODULE_TESTS") == 0) {
int wpas_module_tests(void);
if (wpas_module_tests() < 0)
reply_len = -1;
#endif /* CONFIG_MODULE_TESTS */
} else {
os_memcpy(reply, "UNKNOWN COMMAND\n", 16);
reply_len = 16;
}
if (reply_len < 0) {
os_memcpy(reply, "FAIL\n", 5);
reply_len = 5;
}
*resp_len = reply_len;
return reply;
}
wpa_supplicant_global_ctrl_iface_process主要是命令解析,
根据wpa_cli或者framework传递过来的参数。然后执行相关的函数。
在这里我们关注
if (os_strcmp(buf, "SAVE_CONFIG") == 0) {
if (wpas_global_ctrl_iface_save_config(global))
reply_len = -1;
保存我们设置的参数。不论是通过settings.apk还是wpa_cli工具联网,最后相关的ssid和password都会保存到wpa_supplicant.conf中。这也是为啥市面上简单很多共享wifi需要root权限原因。没有root权限根本读取不了你的配置文件。
真正执行保存文件的代码是
int wpa_config_write(const char *name, struct wpa_config *config)
{
f = fopen(tmp_name, "w");
wpa_config_write_global(f, config);
//这里保存network={
// ssid="yzs_test"
// psk="12345ABCDE"
// key_mgmt=WPA-PSK
// priority=10
//}
for (ssid = config->ssid; ssid; ssid = ssid->next) {
if (ssid->key_mgmt == WPA_KEY_MGMT_WPS || ssid->temporary)
continue; /* do not save temporary networks */
if (wpa_key_mgmt_wpa_psk(ssid->key_mgmt) && !ssid->psk_set &&
!ssid->passphrase)
continue; /* do not save invalid network */
fprintf(f, "\nnetwork={\n");
wpa_config_write_network(f, ssid);
fprintf(f, "}\n");
}
fflush(f);
}
wpa_config_write_global会保存除network热点之外的其他属性比如
**ctrl_interface=/data/misc/wifi/sockets
update_config=1
device_name=rk322x_box
manufacturer=rockchip
model_name=rk322x-box
model_number=rk322x-box
serial_number=FNM88CD8WC**
wpa_cli是shell情况下使用的wifi网络配置工具。和framework下的wifi.c是共用一个socket服务。一般情况下使用 示例
wpa_cli -i wlan0 -p /data/misc/wifi/sockets
然后进入交互模式。若此时没有建立相关wpa_supplicant服务,此时就会报
Failed to connect to wpa_supplicant
此行代码在wpa_cli.c下
int main(int argc, char *argv[])
{
int c;
int daemonize = 0;
int ret = 0;
const char *global = NULL;
if (os_program_init())
return -1;
for (;;) {
c = getopt(argc, argv, "a:Bg:G:hi:p:P:v");
if (c < 0)
break;
switch (c) {
case 'a':
action_file = optarg;
break;
case 'B':
daemonize = 1;
break;
case 'g':
global = optarg;
break;
case 'G':
ping_interval = atoi(optarg);
break;
case 'h':
usage();
return 0;
case 'v':
printf("%s\n", wpa_cli_version);
return 0;
case 'i':
os_free(ctrl_ifname);
ctrl_ifname = os_strdup(optarg);
printf("ctrl_ifname is ===== %s \n", ctrl_ifname);
break;
case 'p':
ctrl_iface_dir = optarg;
break;
case 'P':
pid_file = optarg;
break;
default:
usage();
return -1;
}
}
interactive = (argc == optind) && (action_file == NULL);
if (interactive)
printf("%s\n\n%s\n\n", wpa_cli_version, wpa_cli_license);
if (eloop_init())
return -1;
if (global) {
#ifdef CONFIG_CTRL_IFACE_NAMED_PIPE
ctrl_conn = wpa_ctrl_open(NULL);
#else /* CONFIG_CTRL_IFACE_NAMED_PIPE */
ctrl_conn = wpa_ctrl_open(global);
#endif /* CONFIG_CTRL_IFACE_NAMED_PIPE */
if (ctrl_conn == NULL) {
fprintf(stderr, "Failed to connect to wpa_supplicant "
"global interface: %s error: %s\n",
global, strerror(errno));
return -1;
}
if (interactive) {
update_ifnames(ctrl_conn);
mon_conn = wpa_ctrl_open(global);
if (mon_conn) {
if (wpa_ctrl_attach(mon_conn) == 0) {
wpa_cli_attached = 1;
eloop_register_read_sock(
wpa_ctrl_get_fd(mon_conn),
wpa_cli_mon_receive,
NULL, NULL);
} else {
printf("Failed to open monitor "
"connection through global "
"control interface\n");
}
}
}
}
eloop_register_signal_terminate(wpa_cli_terminate, NULL);
if (ctrl_ifname == NULL)
ctrl_ifname = wpa_cli_get_default_ifname();
if (interactive) {
//初始化socket通信的交互接口,这里会打开两个socket ctrl_conn 和mon_conn ,主要用户
wpa_cli_interactive();
} else {
//这里就是报Failed to connect to non-global ctrl_ifname: wlan0 error: No such file or directory 原因
if (!global &&
wpa_cli_open_connection(ctrl_ifname, 0) < 0) {
fprintf(stderr, "Failed to connect to non-global "
"ctrl_ifname: %s error: %s\n",
ctrl_ifname, strerror(errno));
return -1;
}
if (action_file) {
if (wpa_ctrl_attach(ctrl_conn) == 0) {
wpa_cli_attached = 1;
} else {
printf("Warning: Failed to attach to "
"wpa_supplicant.\n");
return -1;
}
}
if (daemonize && os_daemonize(pid_file))
return -1;
if (action_file)
wpa_cli_action(ctrl_conn);
else
ret = wpa_request(ctrl_conn, argc - optind,
&argv[optind]);
}
os_free(ctrl_ifname);
eloop_destroy();
wpa_cli_cleanup();
return ret;
}
wpa_cli_open_connection 打开socket服务失败就会报错。
ctrl_conn 用于wpa_cli往下发命令
monitor_conn 用于接收wpa_supplicant发送过来的信息
static int wpa_cli_open_connection(const char *ifname, int attach)
{
ctrl_conn = wpa_ctrl_open(ifname);
mon_conn = wpa_ctrl_open(ifname)
}
struct wpa_ctrl * wpa_ctrl_open(const char *ctrl_path)
{
//建立本地socket客户端
ctrl->s = socket(PF_UNIX, SOCK_DGRAM, 0);
ctrl->local.sun_family = AF_UNIX;
//这里的addr.sun_path is data/misc/wifi/sockets/wlan0
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);
//绑定socket地址
bind(ctrl->s, (struct sockaddr *) &ctrl->local,
sizeof(ctrl->local)) < 0)
//连接服务端
connect(ctrl->s, (struct sockaddr *) &ctrl->dest,
sizeof(ctrl->dest)) < 0)
}