Scan in WAPS

Posted by Vector Blog on December 22, 2015

回顾一下 wpas 创建 socket 之后监听连接

=>wpa_supplicant_add_iface
 ==>wpa_supplicant_init_iface
     wpa_s->ctrl_iface = wpa_supplicant_ctrl_iface_init(wpa_s);
     ===>wpas_ctrl_iface_open_sock
           eloop_register_read_sock(priv->sock, wpa_supplicant_ctrl_iface_receive, wpa_s, priv);

来看一下 eloop_register_read_sock 的实现

int eloop_register_read_sock(int sock, eloop_sock_handler handler,
			     void *eloop_data, void *user_data)
{
	return eloop_register_sock(sock, EVENT_TYPE_READ, handler,
				   eloop_data, user_data);
}

int eloop_register_sock(int sock, eloop_event_type type,
			eloop_sock_handler handler,
			void *eloop_data, void *user_data)
{
	struct eloop_sock_table *table;

	// 获取到全局变量 struct eloop_data eloop 中该事件对应的 eloop_sock_table
	table = eloop_get_sock_table(type);
	return eloop_sock_table_add_sock(table, sock, handler,
					 eloop_data, user_data);
}

可以看到这里将 socket 根据事件类型加入到 eloop 的可读事件 table (readers) 中 ,这里需要看一下 eloop_sock_table 的结构:

struct eloop_sock_table {
	int count;		//所有监听 fd 的个数
	//fd句柄及相关handler 的数组指针, 严格来说这里 table 指向的是一个eloop_sock 数组
	struct eloop_sock *table;	
	eloop_event_type type;	//该 table 监听事件类型
	int changed;
};

struct eloop_sock {
	int sock;	//fd句柄
	void *eloop_data;	
	void *user_data;
	eloop_sock_handler handler;	//event 处理函数
	WSAEVENT event;
};

因为 wpas 的 socket 类型为基于 packages 的 DGRAM 方式,server 端只需监听单个的 socket 即可, 这里所有写到 server socket 的event 都会由 wpa_supplicant_ctrl_iface_receive 来处理。

static void wpa_supplicant_ctrl_iface_receive(int sock, void *eloop_ctx,void *sock_ctx)
{
	struct wpa_supplicant *wpa_s = eloop_ctx;
	struct ctrl_iface_priv *priv = sock_ctx;
	char buf[4096]; int res; struct sockaddr_un from;
	socklen_t fromlen = sizeof(from);
	char *reply = NULL; size_t reply_len = 0; int new_attached = 0;
	res = recvfrom(sock, buf, sizeof(buf) - 1, 0,(struct sockaddr *) &from, &fromlen);
	......
	buf[res] = '\0';
	// 客户端在调用 wpa_ctrl_attach 时, 会首先发送"ATTACH"命令
	if (os_strcmp(buf, "ATTACH") == 0) {
		......// 略过相关处理
	}
	......// "DETACH"和"LEVEL"命令处理
	else {
		// 除了上面3个命令其他的命令处理都在wpa_supplicant_ctrl_iface_process函数中
		reply = wpa_supplicant_ctrl_iface_process(wpa_s, buf,&reply_len);
	} 
	if (reply) {// 回复客户端
		sendto(sock, reply, reply_len, 0, (struct sockaddr *) &from,fromlen);
		os_free(reply);
	} ......

	/*
	Client成功ATTACH后, 将通知EAPOL模块。 因为有些认证流程需要用户的参与( 例如输入密码之类的) ,
	所以当客户端连接上后, EAPOL模块将判断是否需要和客户端交互。 读者可阅读
	eapol_sm_notify_ctrl_attached函数。
	*/
	if (new_attached)
		eapol_sm_notify_ctrl_attached(wpa_s->eapol);
}

绝大部分命令都由wpa_supplicant_ctrl_iface_process函数处理 , 在搜索时上层会发送 “SCAN” , 来看一下其处理:

char * wpa_supplicant_ctrl_iface_process(struct wpa_supplicant *wpa_s,
					 char *buf, size_t *resp_len)
{
	char *reply;
	const int reply_size = 4096;
	int reply_len;

	......

	reply = os_malloc(reply_size);
	if (reply == NULL) {
		*resp_len = 1;
		return NULL;
	}

	os_memcpy(reply, "OK\n", 3);
	reply_len = 3;

	if (os_strcmp(buf, "PING") == 0) {
		os_memcpy(reply, "PONG\n", 5);
		reply_len = 5;
	} else if (os_strcmp(buf, "IFNAME") == 0) {
		reply_len = os_strlen(wpa_s->ifname);
		os_memcpy(reply, wpa_s->ifname, reply_len);
	}
         ......
	} else if (os_strcmp(buf, "SCAN") == 0) {
		wpas_ctrl_scan(wpa_s, NULL, reply, reply_size, &reply_len);
         }
         ......

	if (reply_len < 0) {
		os_memcpy(reply, "FAIL\n", 5);
		reply_len = 5;
	}

	*resp_len = reply_len;
	return reply;         

这里 “SCAN” 由 wpas_ctrl_scan 来处理

=> wpas_ctrl_scan
 ==> wpa_supplicant_req_scan(struct wpa_supplicant *wpa_s, int sec, int usec)
{
	// 参数sec 和 usec 代表延迟多长时间开始扫描, 这里上面传下来的都是 0
	
	/* 查询当前 timeout 队列里已经有的相同任务,
	    如果延迟时间较新 request 长时以新任务取代,返回1,
	    当队列中的任务时间更早时则什么都不做, 返回0,
	    当找不到相同任务时, 返回-1,
	*/
	res = eloop_deplete_timeout(sec, usec, wpa_supplicant_scan, wpa_s,
				    NULL);
	if (res == 1) {
		wpa_dbg(wpa_s, MSG_DEBUG, "Rescheduling scan request: %d.%06d sec",
			sec, usec);
	} else if (res == 0) {
		wpa_dbg(wpa_s, MSG_DEBUG, "Ignore new scan request for %d.%06d sec since an earlier request is scheduled to trigger sooner",
			sec, usec);
	} else {
		wpa_dbg(wpa_s, MSG_DEBUG, "Setting scan request: %d.%06d sec",
			sec, usec);
		eloop_register_timeout(sec, usec, wpa_supplicant_scan, wpa_s, NULL);
	}
}

scan request 最后会在 timeout 事件触发时由相应的 handler 来处理, 这里是由 wpa_supplicant_scan 来处理 ,这个函数内容非常的多, 会根据传进来的 wpa_s 中参数做不同的处理, 例如:当某些条件则不扫描,当正在扫描时延迟1s进行扫描, 设定扫描频段等。 最后执行的是 wpa_supplicant_trigger_scan :

    ====>wpa_supplicant_trigger_scan(wpa_s, scan_params);
    	  // 实际上也是 timeout 事件
          radio_add_work(wpa_s, 0, "scan", 0, wpas_trigger_scan_cb, ctx)
     =====>wpas_trigger_scan_cb
     	======>wpa_drv_scan(wpa_s, params);

wpa_drv_scan 会去调用 driver interface 中的 scan2

static inline int wpa_drv_scan(struct wpa_supplicant *wpa_s,
			       struct wpa_driver_scan_params *params)
{
	if (wpa_s->driver->scan2)
		return wpa_s->driver->scan2(wpa_s->drv_priv, params);
	return -1;
}

从 Android.mk 中可以看到定义的是 CONFIG_DRIVER_NL80211 ,也就是 nl80211 接口 :

int wpa_driver_nl80211_scan(struct i802_bss *bss,
			    struct wpa_driver_scan_params *params)
{
	struct wpa_driver_nl80211_data *drv = bss->drv;
	int ret = -1, timeout;
	struct nl_msg *msg = NULL;

	wpa_dbg(drv->ctx, MSG_DEBUG, "nl80211: scan request");
	drv->scan_for_auth = 0;

         // 生成 nl_msg 并填充相应的参数 ,cmd 为 NL80211_CMD_TRIGGER_SCAN
	msg = nl80211_scan_common(bss, NL80211_CMD_TRIGGER_SCAN, params);

	if (params->p2p_probe) {
		struct nlattr *rates;

		wpa_printf(MSG_DEBUG, "nl80211: P2P probe - mask SuppRates");

		rates = nla_nest_start(msg, NL80211_ATTR_SCAN_SUPP_RATES);
		if (nla_put(msg, NL80211_BAND_2GHZ, 8,
			    "\x0c\x12\x18\x24\x30\x48\x60\x6c"))
			goto fail;
		nla_nest_end(msg, rates);

		if (nla_put_flag(msg, NL80211_ATTR_TX_NO_CCK_RATE))
			goto fail;
	}

         // 发送请求给wlan驱动。 返回值只是表示该命令是否正确发送给了驱动。 扫描结束事件将通过 driver event返回给WPAS。
	ret = send_and_recv_msgs(drv, msg, NULL, NULL);
	......

	drv->scan_state = SCAN_REQUESTED;
	timeout = 10;
	if (drv->scan_complete_events) {
		timeout = 30;
	}
	eloop_cancel_timeout(wpa_driver_nl80211_scan_timeout, drv, drv->ctx);
     // 一般情况下,driver完成扫描后需要通知WPAS一个scan complete事件。如果驱动不通知的话,WPAS就会自己去查询driver以获取扫描到的无线网络信息。
	eloop_register_timeout(timeout, 0, wpa_driver_nl80211_scan_timeout,
			       drv, drv->ctx);

fail:
	......
}

接下来 scan message经由netlink进入到Linux内核当中去处理。

这里的总体流程如下图(截图来自《深入理解Android:WIFI模块…》)

在搜索结束之后,waps 收到driver 的 NL80211_CMD_NEW_SCAN_RESULTS

D/wpa_supplicant( 1331): nl80211: Drv Event 34 (NL80211_CMD_NEW_SCAN_RESULTS) received for wlan0

	case NL80211_CMD_NEW_SCAN_RESULTS:
		wpa_dbg(drv->ctx, MSG_DEBUG,
			"nl80211: New scan results available");
		drv->scan_state = SCAN_COMPLETED;
		drv->scan_complete_events = 1;
		eloop_cancel_timeout(wpa_driver_nl80211_scan_timeout, drv,
				     drv->ctx);
		send_scan_event(drv, 0, tb);
			==> wpa_supplicant_event(drv->ctx, EVENT_SCAN_RESULTS, &event);
		break;

wpa_supplicant_event被driver event模块用来发送driver事件给WPAS

void wpa_supplicant_event(void *ctx, enum wpa_event_type event,
			  union wpa_event_data *data)
{
	case EVENT_SCAN_RESULTS:
		.......

		if (wpa_supplicant_event_scan_results(wpa_s, data))
			==> _wpa_supplicant_event_scan_results(wpa_s, data, 1);
			break; /* interface may have been removed */
		.......
		break;
}

static int _wpa_supplicant_event_scan_results(struct wpa_supplicant *wpa_s,
					      union wpa_event_data *data,
					      int own_request)
{
	struct wpa_scan_results *scan_res = NULL;

	wpa_supplicant_notify_scanning(wpa_s, 0);

	// 获得无线网络扫描结果 
	scan_res = wpa_supplicant_get_scan_results(wpa_s,
						   data ? &data->scan_info :
						   NULL, 1);
	if (scan_res == NULL) {
		.......  //  扫描结果为空, 重新发起扫描
		wpa_supplicant_req_new_scan(wpa_s, 1, 0);
		ret = -1;
		goto scan_work_done;
	}

wpa_supplicant_get_scan_results 中调用到 driver interface 中的 get_scan_results2 , 这个方法会向 driver 发 NL80211_CMD_GET_SCAN 并等待收到结果

D/wpa_supplicant( 1331): nl80211: Received scan results (14 BSSes)

static int _wpa_supplicant_event_scan_results(struct wpa_supplicant *wpa_s,
					      union wpa_event_data *data,
					      int own_request)
{
	struct wpa_scan_results *scan_res = NULL;
	......

	wpa_supplicant_notify_scanning(wpa_s, 0);

	scan_res = wpa_supplicant_get_scan_results(wpa_s,
						   data ? &data->scan_info :
						   NULL, 1);
	.......
	wpas_notify_scan_results(wpa_s);

	wpas_notify_scan_done(wpa_s, 1);	

来看一下获取到的扫描结果 wpa_scan_results 结构体

struct wpa_scan_results {
	struct wpa_scan_res **res;	//指向wpa_scan_res数组
	size_t num;	//res 数组个数
	struct os_reltime fetch_time;
};

struct wpa_scan_res {
	unsigned int flags;
	u8 bssid[ETH_ALEN];	//无线网络的bssid
	int freq;		//工作频率
	u16 beacon_int;
	u16 caps;
	int qual;
	int noise;
	int level;
	u64 tsf;
	unsigned int age;	//距离上一次收到该bss信息过去的毫秒时间
	unsigned int est_throughput;
	int snr;
	size_t ie_len;
	size_t beacon_ie_len;
	/* Followed by ie_len + beacon_ie_len octets of IE data */
};

然后向上层发送 WPA_EVENT_SCAN_RESULTS
D/wpa_supplicant( 1331): wlan0: New scan results available (own=1 ext=0)
D/wpa_supplicant( 1331): CTRL_IFACE monitor sent successfully to /data/misc/wifi/sockets/wpa_ctrl_950-2\x00

上层收到该消息之后会主动下 command 给 waps 获取搜索结果。