Android 之 WiFi wpa_supplicant

Posted by Vector Blog on December 22, 2015

1. 简介

wpa_supplicant是一个开源软件项目,它实现了Station对无线网络进行管理和控制的功能, 下面列举其中几个重要的功能:

  • 支持WPA和IEEE 802.11i(无线安全标准)所定义的大部分功能
  • 支持多种EAP Method
  • 对各种无线网卡和驱动的支持

wpa_supplicant_8,主要有四个子目录:

  • hostapd:当手机进入Soft AP模式时,手机将扮演AP的角色,需要hostapd来提供AP的功能
  • hs20: hotspot2.0
  • wpa_supplicant:Station模式,也叫Managed模式,这是正常使用wifi连接AP的情况
  • src:hostapd和wpa_supplicant中都包含一些通用的数据结构和处理方法,这些内容都放在此src目录中

wpa_supplicant包含两个主要的可执行工具:wpa_supplicant 和 wpa_cli 。wpa_supplicant是核心程序,它和 wpa_cli 的关系就是服务和客户端的关系,在无需显示屏的情况下我们可以直接用 wpa_cli 来操作 wifi.

2. wpa_cli

wpa_cli 的 常用参数有 :

  • -p: ctrl sockets 路径
  • -i: 网络接口名称

常用命令有 :

  • status : 获取当前 WPA/EAPOL/EAP 状态
  • scan
  • scan_result
  • add_network
  • remove_network
  • list_network
  • set_network
  • get_network <network id> <variable>
  • enable_network <network id>
  • disable_network <network id> : 会 disable 其他的 network
  • select_network <network id>
  • disconnect
  • reconnect
  • save_config

使用前需先加载 wifi driver 并且启动 wpas 。

adb shell insmod /system/lib/modules/wlan.ko
adb shell /system/bin/wpa_supplicant -i p2p0 -Dnl80211 -c /data/misc/wifi/p2p_supplicant.conf -N -i wlan0 -Dnl80211 -c /data/misc/wifi/wpa_supplicant.conf -dddd -e /data/misc/wifi/entropy.bin &




3. 架构

接下来在来看一下 wpa_supplicant 的架构 :

WPAS主要包含如下几个重要模块:

  • event loop , WPAS所有工作围绕事件展开,并没有采用多线程所有事件都在主线程完成,主线程等待事件的发生并进行处理。

  • driver i/f , 该接口模块用于隔离和底层驱动直接交互的那些driver控制模块( 图中的wext等,又称为driver wrapper ,driver wrapper会因为平台和芯片驱动不同而不同),这样可以保证其他模块的平台/驱动无关性。

  • EAPPOL sm , EAP sm , 根据这两个协议实现的状态机,另外还有WPAS自己的状态机 : WPA/WPA2 sm .

  • configuration , WPAS支持较多的配置参数,这些参数的处理由configuration模块完成.

wpas 是C/S结构中的 Server 端,它通过 ctrl i/f 模块向客户端提供通信接口。Linux/Unix平台中,Client端利用Unix域socket与其通信。目前常用的Client端wpa_cli(无界面的命令行程序)和wpa_gui(UI用Qt实现)。

4. WPAS的初始化

在 Android 系统中启动 wpas 是通过 “setprop ctrl.start wpa_supplicant” ,高通平台其定义在 init.qcom.rc 中:

service p2p_supplicant /system/bin/wpa_supplicant \
    -ip2p0 -Dnl80211 -c/data/misc/wifi/p2p_supplicant.conf \
    -I/system/etc/wifi/p2p_supplicant_overlay.conf -N \
    -iwlan0 -Dnl80211 -c/data/misc/wifi/wpa_supplicant.conf \
    -I/system/etc/wifi/wpa_supplicant_overlay.conf \
    -O/data/misc/wifi/sockets -puse_p2p_group_interface=1 -dd \
    -e/data/misc/wifi/entropy.bin -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 \
    -O/data/misc/wifi/sockets -dd \
    -e/data/misc/wifi/entropy.bin -g@android:wpa_wlan0
    class main
    socket wpa_wlan0 dgram 660 wifi wifi
    disabled
    oneshot

当支持 P2P 时启动的是 p2p_supplicant ,执行的都是 “/system/bin/wpa_supplicant” ,差别是启动时后面的参数不一样。 下面简要介绍部分参数 :

  • -i: 网络接口名称, 如无线网卡通常为wlan0
  • -c: 配置文件路径
  • -C: ctrl_interface参数 (当不设定 -c 时使用)
  • -d: 提高 debug level , -dd增加更多
  • -D: driver name
  • -e: 随机数文件
  • -g: 全局 ctrl_interface
  • -I: additional 配置文件路径
  • -O: 覆盖ctrl_interface参数
  • -p: 驱动参数
  • -N: 接下来是一个新的接口描述

这里重点看一下 “-c” ,也就是指定 wpa_supplicant启动配置文件:

/data/misc/wifi/p2p_supplicant.conf

ctrl_interface=/data/misc/wifi/sockets    #控制接口Unix域socket的文件名
disable_scan_offload=1
driver_param=use_p2p_group_interface=1
update_config=1            #保存WPAS运行过程中修改的配置信息
......
device_type=10-0050F204-5
config_methods=physical_display virtual_push_button
p2p_disabled=1
p2p_no_group_iface=1
pmf=1
external_sim=1
key_mgmt_offload=1
 
#wpa_supplicant运行过程中得到的无线网络信息都会通过”network“配置项保存到配置文件中,
#如果该信息完整,一旦wpa_supplicant找到该无线网络就会尝试用保存的信息去加入它。
network={
    ssid="Test"
    psk="123456789"
    key_mgmt=WPA-PSK
    priority=2
}

WPAS 的初始化流程可以概括为下图 :

下面从头开始追代码:

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(&params, 0, sizeof(params));
	params.wpa_debug_level = MSG_INFO;

	iface = ifaces = os_zalloc(sizeof(struct wpa_interface));
	iface_count = 1;

         // 输入输出重定向到/dev/null设备
	wpa_supplicant_fd_workaround(1);

         // 参数解析
	for (;;) {
		c = getopt(argc, argv,
			   "b:Bc:C:D:de:f:g:G:hi:I:KLm:No:O:p:P:qsTtuvW");
		if (c < 0)
			break;
		switch (c) {
		......
		case 'c':
			iface->confname = optarg;
			break;
		......
		case 'd':
			// wpa_debug_level 越小开放的 log 越多
			params.wpa_debug_level--;
			break;
		case 'e':
			params.entropy_file = optarg;
			break;
                  ......
		case 'i':
			iface->ifname = optarg;
			break;
		......
		}
	}

	exitcode = 0;

         // 关键函数,根据传入的参数, 创建并初始化一个wpa_global对象
	global = wpa_supplicant_init(&params);
	
         ......

         // WPAS支持操作多个无线网络设备, 此处通过 wpa_supplicant_add_iface 将它们一一添加到WPAS中
	for (i = 0; exitcode == 0 && i < iface_count; i++) {
		struct wpa_supplicant *wpa_s;

		......
		wpa_s = wpa_supplicant_add_iface(global, &ifaces[i], NULL);
		}
	}

         // 运行 event loop
	if (exitcode == 0)
		exitcode = wpa_supplicant_run(global);

	wpa_supplicant_deinit(global);

	fst_global_deinit();

out:
	......
}

如上面的流程图,其中包括3个关键的函数 :

  • wpa_supplicant_init
  • wpa_supplicant_add_iface
  • wpa_supplicant_run

4.1 wpa_supplicant_init

struct wpa_global * wpa_supplicant_init(struct wpa_params *params)
{
	struct wpa_global *global;
	int ret, i;
	......

         // 注册各种EAP方法 ,后面在单独分析
	ret = eap_register_methods();	

         // 创建一个wpa_global对象
	global = os_zalloc(sizeof(*global));

	...... // 初始化global中的其他参数, 其中会将传进来的 params 信息保存到 global 中

         // 初始化了WPAS中事件驱动的核心数据结构体 eloop_data
	if (eloop_init()) {
		......
	}

	random_init(params->entropy_file);

         // 初始化全局控制接口对象
	global->ctrl_iface = wpa_supplicant_global_ctrl_iface_init(global);

	for (i = 0; wpa_drivers[i]; i++)
		global->drv_count++;
	if (global->drv_count == 0) {
		wpa_printf(MSG_ERROR, "No drivers enabled");
		wpa_supplicant_deinit(global);
		return NULL;
	}

         // 分配全局driver wrapper上下文信息数组
	global->drv_priv = os_calloc(global->drv_count, sizeof(void *));

	return global;
}

wpa_supplicant_init函数的主要功能是初始化 wpa_global 以及一些与整个程序相关的资源, 包括随机数资源、 eloop事件循环机制以及设置消息全局回调函数。

4.1.1 eloop_init 函数及 event loop 模块

eloop_init 函数仅初始化了WPAS中事件驱动的核心数据结构体 eloop_data 。
WPAS 支持5种类型的 event :

  • read event
  • write event
  • exception event , 如果 socket 操作错误,则有错误事件处理
  • timeout event , 通过 select 的等待超时机制实现定时事件
  • signal , 来自kernel

与之对应的是 eloop_data 结构体 :

struct eloop_data {
    int max_sock;    //供select使用
  
    int count;     //所有事件表中事件总数
    ......
    struct eloop_sock_table readers;    //read事件表
    struct eloop_sock_table writers;    //write事件表
    struct eloop_sock_table exceptions;    //exception事件表
  
    struct dl_list timeout;    //timeout事件表
  
    int signal_count;    //signal事件个数
    struct eloop_signal *signals;    //signal事件表
    ......
};

先来看一下 eloop_sock_table 这样的结构体:

struct eloop_sock_table {
	int count;
	struct eloop_sock *table;
	
	eloop_event_type type;
		typedef enum {
			EVENT_TYPE_READ = 0,
			EVENT_TYPE_WRITE,
			EVENT_TYPE_EXCEPTION
		} eloop_event_type;
	int changed;
};

针对这5种事件会先注册事件处理函数,与之对应的注册函数为 :

@ eloop.h

// 注册socket读事件处理函数,参数sock代表一个socket句柄。一旦该句柄上有读事件发生,则handler函数将被事件处理循环调用
int eloop_register_read_sock(int sock, eloop_sock_handler handler, void *eloop_data, void *user_data);
   
// 注册socket事件处理函数,具体是哪种事件(只能是读、写或异常)由type参数决定
int eloop_register_sock(int sock, eloop_event_type type, eloop_sock_handler handler,void *eloop_data, void *user_data);
   
// 注册超时事件处理函数
int eloop_register_timeout(unsigned int secs, unsigned int usecs, eloop_timeout_handler handler, void *eloop_data, void *user_data);
   
// 注册信号事件处理函数,具体要处理的信号由sig参数指定
int eloop_register_signal(int sig, eloop_signal_handler handler, void *user_data);

接下来看一下 WPAS事件驱动机制的运行原理,其代码在 eloop_run 函数中 :

@eloop.c

void eloop_run(void)
{
    // fd_set是select中用到的一种参数类型
    fd_set *rfds, *wfds, *efds; 
    struct timeval _tv;
    int res;
    struct os_time tv, now;
    // 事件驱动循环
    while (!eloop.terminate &&(!dl_list_empty(&eloop.timeout) || eloop.readers.count > 0 
    	     || eloop.writers.count > 0 || eloop.exceptions.count > 0)) {
        struct eloop_timeout *timeout;
        // 判断是否有超时事件需要等待
        timeout = dl_list_first(&eloop.timeout, struct eloop_timeout,list);
        if (timeout) {
            os_get_time(&now);
            if (os_time_before(&now, &timeout->time))
                os_time_sub(&timeout->time, &now, &tv);
            else
                tv.sec = tv.usec = 0;
            _tv.tv_sec = tv.sec;
            _tv.tv_usec = tv.usec;
        }
        // 将外界设置的读事件添加到对应的fd_set中
        eloop_sock_table_set_fds(&eloop.readers, rfds);   
        ......// 设置写、异常事件到fd_set中
        // 调用select函数
        res = select(eloop.max_sock + 1, rfds, wfds, efds,timeout ? &_tv : NULL);
        if(res &lt; 0) {......// 错误处理}
        // 先处理信号事件
        eloop_process_pending_signals();
        // 判断是否有超时事件发生
        timeout = dl_list_first(&eloop.timeout, struct eloop_timeout,list);
        if (timeout) {
            os_get_time(&now);
            if (!os_time_before(&now, &timeout->time)) {
                void *eloop_data = timeout->eloop_data;
                void *user_data = timeout->user_data;
                eloop_timeout_handler handler = timeout->handler;
                eloop_remove_timeout(timeout);
                // 注意,超时事件只执行一次
                handler(eloop_data, user_data);
                // 处理超时事件
            }
        }
        ......// 处理读/写/异常事件。方法和下面这个函数类似
        eloop_sock_table_dispatch(&eloop.readers, rfds);
        ......// 处理wfds和efds
    }
    out:
        return;
}

4.2 wpa_supplicant_add_iface

wpa_supplicant_add_iface用于向WPAS添加接口设备。 所谓的添加( add iface) ,其实就是初始化这些设备。

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_supplicant.conf中的每个network项都对应一个wpa_ssid对象

	wpa_s = wpa_supplicant_alloc(parent);

	wpa_s->global = global;
	t_iface = *iface;		
	
         ......

	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;

	wpa_supplicant_set_state(wpa_s, WPA_DISCONNECTED);

	return wpa_s;
}

这里重点需要看一下 wpa_supplicant_init_iface 这个函数 :

static int wpa_supplicant_init_iface(struct wpa_supplicant *wpa_s,
				     struct wpa_interface *iface)
{
	struct wpa_driver_capa capa;
	int capa_res;

	if (iface->confname) {
                  ......
		//解析配置文件,例如 wpa_s->confname 为"/datnl80211_global_inita/misc/wifi/wpa_supplicant.conf"
		wpa_s->conf = wpa_config_read(wpa_s->confname, NULL);
		......
		wpa_s->confanother = os_rel2abs_path(iface->confanother);
		wpa_config_read(wpa_s->confanother, wpa_s->conf);

		//保存driver相关参数
		if (iface->ctrl_interface) {
			os_free(wpa_s->conf->ctrl_interface);
			wpa_s->conf->ctrl_interface =
				os_strdup(iface->ctrl_interface);
		}

		if (iface->driver_param) {
			os_free(wpa_s->conf->driver_param);
			wpa_s->conf->driver_param =
				os_strdup(iface->driver_param);
		}

		if (iface->p2p_mgmt && !iface->ctrl_interface) {
			os_free(wpa_s->conf->ctrl_interface);
			wpa_s->conf->ctrl_interface = NULL;
		}
	} else
		wpa_s->conf = wpa_config_alloc_empty(iface->ctrl_interface,
						     iface->driver_param);

	......

	os_strlcpy(wpa_s->ifname, iface->ifname, sizeof(wpa_s->ifname));
	......
	/* RSNA Supplicant Key Management - INITIALIZE */
	eapol_sm_notify_portEnabled(wpa_s->eapol, FALSE);
	eapol_sm_notify_portValid(wpa_s->eapol, FALSE);

	//初始化相应的driver
	if (wpas_init_driver(wpa_s, iface) < 0)
		return -1;

	//初始化wpa_sm相关的资源
	if (wpa_supplicant_init_wpa(wpa_s) < 0)
		return -1;

	wpa_sm_set_ifname(wpa_s->wpa, wpa_s->ifname,
			  wpa_s->bridge_ifname[0] ? wpa_s->bridge_ifname :
			  NULL);
	wpa_sm_set_fast_reauth(wpa_s->wpa, wpa_s->conf->fast_reauth);

	.......

	wpa_s->hw.modes = wpa_drv_get_hw_feature_data(wpa_s,
						      &wpa_s->hw.num_modes,
						      &wpa_s->hw.flags);
	......

	capa_res = wpa_drv_get_capa(wpa_s, &capa);
	if (capa_res == 0) {
		...... //相关capability信息
	}
	if (wpa_s->max_remain_on_chan == 0)
		wpa_s->max_remain_on_chan = 1000;

	if (wpa_s->drv_flags & WPA_DRIVER_FLAGS_DEDICATED_P2P_DEVICE)
		wpa_s->p2p_mgmt = iface->p2p_mgmt;
	else
		iface->p2p_mgmt = 1;

	if (wpa_s->num_multichan_concurrent == 0)
		wpa_s->num_multichan_concurrent = 1;

	if (wpa_supplicant_driver_init(wpa_s) < 0)
		return -1;


	if (wpa_s->conf->country[0] && wpa_s->conf->country[1] &&
	    wpa_drv_set_country(wpa_s, wpa_s->conf->country)) {
		wpa_dbg(wpa_s, MSG_DEBUG, "Failed to set country");
		return -1;
	}

	if (wpas_wps_init(wpa_s))
		return -1;

	if (wpa_supplicant_init_eapol(wpa_s) < 0)
		return -1;
	wpa_sm_set_eapol(wpa_s->wpa, wpa_s->eapol);

	wpa_s->ctrl_iface = wpa_supplicant_ctrl_iface_init(wpa_s);

	wpa_s->gas = gas_query_init(wpa_s);
	if (wpa_s->gas == NULL) {
		wpa_printf(MSG_ERROR, "Failed to initialize GAS query");
		return -1;
	}

	......

	return 0;
}

这里看一下 wpas_init_driver :

static int wpas_init_driver(struct wpa_supplicant *wpa_s,
			    struct wpa_interface *iface)
{
	const char *ifname, *driver, *rn;

	driver = iface->driver;
next_driver:
	//根据nl80211找到wpa_driver数组中nl80211指定的driver对象 wpa_driver_nl80211_ops ,并调用其 global_init
	//global_init 会创建netlink_socket 来接收kernel 中的消息
	if (wpa_supplicant_set_driver(wpa_s, driver) < 0)
		return -1;

	//调用 wpa_driver_nl80211_ops 的 init2
	wpa_s->drv_priv = wpa_drv_init(wpa_s, wpa_s->ifname);
	......
	if (wpa_drv_set_param(wpa_s, wpa_s->conf->driver_param) < 0) {
		wpa_msg(wpa_s, MSG_ERROR, "Driver interface rejected "
			"driver_param '%s'", wpa_s->conf->driver_param);
		return -1;
	}

	......

	return 0;
}