7.4.2 P2P Device Discovery流程分析
根据7.3.2节中对DISCOVER_PEERS命令的代码分析可知,P2pStateMachine将发送"P2P_FIND 120"命令给WPAS以触发P2P Device Discovery流程。处理该命令的代码如下所示。
[—>ctrl_iface.c::wpa_supplicant_ctrl_iface_process]
- 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 ctrl_rsp = 0; int reply_len;
- ......
- #ifdef CONFIG_P2P
- // 处理带参数的P2P_FIND命令
- } else if (os_strncmp(buf, "P2P_FIND ", 9) == 0) {
- // 注意“P2P_FIND ”多了一个空格
- if (p2p_ctrl_find(wpa_s, buf + 9)) reply_len = -1;
- } else if (os_strcmp(buf, "P2P_FIND") == 0) {
- // 处理不带参数的P2P_FIND命令
- if (p2p_ctrl_find(wpa_s, "")) reply_len = -1;
- } ......// 其他P2P命令处理
- #endif
- ......
- }
不论P2P_FIND命令是否携带参数,其最终的处理函数都将对应为p2p_ctrl_find,如下所示。
[—>ctrl_iface.c::p2p_ctrl_find]
- static int p2p_ctrl_find(struct wpa_supplicant *wpa_s, char *cmd)
- {
- unsigned int timeout = atoi(cmd);
- enum p2p_discovery_type type = P2P_FIND_START_WITH_FULL;
- // 搜索方式,见下文解释
- u8 dev_id[ETH_ALEN], *_dev_id = NULL;
- char *pos;
- // 设置搜索方式,见下文解释
- if (os_strstr(cmd, "type=social")) type = P2P_FIND_ONLY_SOCIAL;
- else if (os_strstr(cmd, "type=progressive")) type = P2P_FIND_PROGRESSIVE;
- pos = os_strstr(cmd, "dev_id=");// dev_id代表peer端device的MAC地址
- if (pos) {......}
- // wpas_p2p_find内部将调用p2p_find,下文将直接分析p2p_find
- return wpas_p2p_find(wpa_s, timeout, type, 0, NULL, _dev_id);
- }
P2P_FIND支持三种不同的Discovery Type,分别如下。
·P2P_FIND_START_WITH_FULL:默认设置。表示先扫描所有频段,然后再扫描social channels。这种搜索方式如图7-3所示。
·P2P_FIND_ONLY_SOCIAL:只扫描social channels。它将跳过“扫描所有频段”这一过程。这种搜索方式能加快搜索的速度。
·P2P_FIND_PROGRESSIVE:它和P2P_FIND_START_WITH_FULL类似,只不过在Search State阶段将逐个扫描所有频段。为什么在search state阶段会扫描所有频段呢?请读者参考图7-22中的状态切换路线14。当周围已经存在Group的时候,如果在最初的“扫描所有频段”这一过程中没有发现Group,则在后续的search state逐个扫描频段过程中就有可能发现之前那些没有找到的Group。
注意 GO将工作在Operation Channel,而Listen Channel只在最初的P2P Device Discovery阶段使用。
1.P2P设备扫描流程
P2P设备扫描流程从wpas_p2p_find开始,其代码如下所示。
[—>p2p_supplicant.c::wpas_p2p_find]
- int wpas_p2p_find(struct wpa_supplicant *wpa_s, unsigned int timeout,
- enum p2p_discovery_type type,unsigned int num_req_dev_types,
- const u8 *req_dev_types, const u8 *dev_id)
- {
- /*
- 取消还未发送的Action帧数据。WPAS中,待发送的Action帧数据保存在wpa_supplicant对象的
- pending_action_tx变量中,它指向一块数据缓冲区。
- */
- wpas_p2p_clear_pending_action_tx(wpa_s);
- wpa_s->p2p_long_listen = 0;
- /*
- 如果wifi driver能直接处理P2P管理,则主要工作将由wifi driver来完成。Galaxy Note 2
- 不支持WPA_DRIVER_FLAGS_P2P_MGMT。
- */
- if (wpa_s->drv_flags & WPA_DRIVER_FLAGS_P2P_MGMT)
- return wpa_drv_p2p_find(wpa_s, timeout, type);
- ......
- // 取消计划扫描任务
- wpa_supplicant_cancel_sched_scan(wpa_s);
- // 调用p2p_find函数
- return p2p_find(wpa_s->global->p2p, timeout, type,
- num_req_dev_types, req_dev_types, dev_id);
- }
来看p2p_find函数,其代码如下所示。
[—>p2p.c::p2p_find]
- int p2p_find(struct p2p_data *p2p, unsigned int timeout, enum p2p_discovery_type type,
- unsigned int num_req_dev_types, const u8 *req_dev_types, const u8 *dev_id)
- {
- int res;
- p2p_free_req_dev_types(p2p);
- if (req_dev_types && num_req_dev_types) {......// 本例没有设置request dev type属性}
- if (dev_id) {
- os_memcpy(p2p->find_dev_id_buf, dev_id, ETH_ALEN);
- p2p->find_dev_id = p2p->find_dev_id_buf;
- } else p2p->find_dev_id = NULL;
- // 注意下面这个P2P_AFTER_SCAN_NOTHING标志,它表示P2P设备完成scan动作后,无须做其他动作
- p2p->start_after_scan = P2P_AFTER_SCAN_NOTHING;
- p2p_clear_timeout(p2p);
- p2p->cfg->stop_listen(p2p->cfg->cb_ctx);// 停止监听
- p2p->find_type = type;
- p2p_device_clear_reported(p2p);
- p2p_set_state(p2p, P2P_SEARCH); // 设置P2P模块的状态为P2P_SEARCH
- eloop_cancel_timeout(p2p_find_timeout, p2p, NULL);
- p2p->last_p2p_find_timeout = timeout;
- // 注册一个扫描超时处理任务
- if (timeout) eloop_register_timeout(timeout, 0, p2p_find_timeout,p2p, NULL);
- switch (type) {
- case P2P_FIND_START_WITH_FULL:
- case P2P_FIND_PROGRESSIVE: // p2p_scan函数指针指向wpas_p2p_scan
- res = p2p->cfg->p2p_scan(p2p->cfg->cb_ctx, P2P_SCAN_FULL, 0,
- p2p->num_req_dev_types,p2p->req_dev_types, dev_id);
- break;
- case P2P_FIND_ONLY_SOCIAL:
- res = p2p->cfg->p2p_scan(p2p->cfg->cb_ctx, P2P_SCAN_SOCIAL, 0,
- p2p->num_req_dev_types,p2p->req_dev_types, dev_id);
- break;
- default:
- return -1;
- }
- if (res == 0) {
- // 设置p2p_scan_running值为1,该变量后面用到的地方比较多,请读者注意
- p2p->p2p_scan_running = 1;
- eloop_cancel_timeout(p2p_scan_timeout, p2p, NULL);
- eloop_register_timeout(P2P_SCAN_TIMEOUT, 0, p2p_scan_timeout,p2p, NULL);
- } ...... // 略去res为其他值的处理情况
- return res;
- }
上述代码中p2p_config对象的p2p_scan函数指针变量将指向p2p_supplicant.c中的wpas_p2p_scan,马上来看它,代码如下所示。
[—>p2p_supplicant.c::wpas_p2p_scan]
- static int wpas_p2p_scan(void *ctx, enum p2p_scan_type type, int freq,
- unsigned int num_req_dev_types, const u8 *req_dev_types, const u8 *dev_id)
- {
- struct wpa_supplicant *wpa_s = ctx;
- // 扫描参数
- struct wpa_driver_scan_params params;
- int ret; struct wpabuf *wps_ie, *ies;
- int social_channels[] = { 2412, 2437, 2462, 0, 0 };
- size_t ielen; int was_in_p2p_scan;
- os_memset(¶ms, 0, sizeof(params));
- params.num_ssids = 1; // 设置SSID参数,P2P_WILDCARD_SSID的值为“DIRECT-”
- params.ssids[0].ssid = (u8 *) P2P_WILDCARD_SSID;
- params.ssids[0].ssid_len = P2P_WILDCARD_SSID_LEN;
- wpa_s->wps->dev.p2p = 1;
- // 构造Probe Request帧中WSC IE信息
- wps_ie = wps_build_probe_req_ie(0, &wpa_s->wps->dev, wpa_s->wps->uuid,
- WPS_REQ_ENROLLEE,num_req_dev_types, req_dev_types);
- ielen = p2p_scan_ie_buf_len(wpa_s->global->p2p);
- ies = wpabuf_alloc(wpabuf_len(wps_ie) + ielen);
- ......
- // 构造P2P IE信息,感兴趣的读者不妨自行阅读p2p_scan_ie函数
- p2p_scan_ie(wpa_s->global->p2p, ies, dev_id);
- params.p2p_probe = 1;
- params.extra_ies = wpabuf_head(ies); params.extra_ies_len = wpabuf_len(ies);
- switch (type) {
- case P2P_SCAN_SOCIAL: // 只扫描social channels的话,将设置params.freqs变量
- params.freqs = social_channels;
- break;
- case P2P_SCAN_FULL:
- break;
- ......// 其他扫描频段控制
- }
- was_in_p2p_scan = wpa_s->scan_res_handler == wpas_p2p_scan_res_handler;
- // 设置P2P扫描结果处理函数
- wpa_s->scan_res_handler = wpas_p2p_scan_res_handler;
- // 发起P2P设备扫描,该函数内部将调用driver_nl80211.c的wpa_driver_nl80211_scan函数
- ret = wpa_drv_scan(wpa_s, ¶ms);
- wpabuf_free(ies);
- ......
- return ret;
- }
读者可比较本节和4.5.3节无线网络扫描流程分析的内容。总体而言,P2P设备扫描的代码逻辑比无线网络扫描的代码逻辑要简单得多。图7-31为WPAS中P2P设备扫描所涉及的几个重要函数调用。
图7-31 P2P Device扫描流程
下面来看P2P设备扫描结果的处理流程。
2.P2P设备扫描结果处理流程
由4.5.3节“_wpa_supplicant_event_scan_results分析之二”中的代码可知,当scan_res_handler不为空的时候,扫描结果将交给scan_res_handler来处理。由图7-31可知,对P2P设备扫描时将设置scan_res_handler为wpas_p2p_scan_res_handler,其代码如下所示。
[—>p2p_supplicant.c::wpas_p2p_scan_res_handler]
- static void wpas_p2p_scan_res_handler(struct wpa_supplicant *wpa_s,
- struct wpa_scan_results *scan_res)
- {
- size_t i;
- ......
- for (i = 0; i < scan_res->num; i++) {
- struct wpa_scan_res *bss = scan_res->res[i];
- // ①对每一个扫描结果调用p2p_scan_res_handler函数
- if (p2p_scan_res_handler(wpa_s->global->p2p, bss->bssid,// 处理扫描结果
- bss->freq, bss->level,(const u8 *) (bss + 1), bss->ie_len) > 0)
- break;
- }
- p2p_scan_res_handled(wpa_s->global->p2p);// ②处理完毕后调用p2p_scan_res_handled
- }
wpas_p2p_scan_res_handler中有两个关键函数,先来看第一个。
(1)p2p_scan_res_handler函数
p2p_scan_res_handler的代码如下所示。
[—>p2p.c::p2p_scan_res_handler]
- int p2p_scan_res_handler(struct p2p_data *p2p, const u8 *bssid, int freq,
- int level, const u8 *ies, size_t ies_len)
- {
- // 添加一个P2P Device,详情见下文代码分析
- p2p_add_device(p2p, bssid, freq, level, ies, ies_len);
- /*
- go_neg_peer代表GON的对端设备,如果go_neg_peer不为空而且设备扫描时由发现了它,则直接通过
- p2p_connect_send向其发送GON Request帧以开展GON流程。
- */
- if (p2p->go_neg_peer && p2p->state == P2P_SEARCH &&
- os_memcmp(p2p->go_neg_peer->info.p2p_device_addr, bssid, ETH_ALEN) == 0) {
- p2p_connect_send(p2p, p2p->go_neg_peer);
- return 1;
- }
- return 0;
- }
上述代码中最重要的是p2p_add_device函数,其代码如下所示。
[—>p2p.c::p2p_add_device]
- int p2p_add_device(struct p2p_data *p2p, const u8 *addr, int freq, int level,
- const u8 *ies, size_t ies_len)
- {
- struct p2p_device *dev; struct p2p_message msg;
- const u8 *p2p_dev_addr; int i;
- os_memset(&msg, 0, sizeof(msg));
- // 解析扫描结果中的IE信息,解析完的结果保存在一个p2p_message对象中
- if (p2p_parse_ies(ies, ies_len, &msg)) {......// 解析扫描结果}
- // p2p device info中的属性
- if (msg.p2p_device_addr) p2p_dev_addr = msg.p2p_device_addr;
- else if (msg.device_id) p2p_dev_addr = msg.device_id;
- ......
- // 过滤那些被阻止的P2P Device
- if (!is_zero_ether_addr(p2p->peer_filter) &&
- os_memcmp(p2p_dev_addr, p2p->peer_filter, ETH_ALEN) != 0) {......}
- // 构造一个p2p_device对象,并将其加入p2p_data结构体的devices链表中
- dev = p2p_create_device(p2p, p2p_dev_addr);// 创建一个P2P Device对象
- ......
- os_get_time(&dev->last_seen);// 设置发现该P2P Device的时间
- // p2p_device的flags变量代表该p2p_device的一些信息
- dev->flags &= ~(P2P_DEV_PROBE_REQ_ONLY | P2P_DEV_GROUP_CLIENT_ONLY);
- if (os_memcmp(addr, p2p_dev_addr, ETH_ALEN) != 0)
- os_memcpy(dev->interface_addr, addr, ETH_ALEN);
- ......// 处理ssid、listen channel等内容
- dev->listen_freq = freq;
- // 如果对端P2P Device是GO,它回复的Probe Response帧P2P IE信息中将包含Group Info属性
- if (msg.group_info) dev->oper_freq = freq;
- dev->info.level = level;
- p2p_copy_wps_info(dev, 0, &msg);// 复制WSC IE
- ......// 处理Vendor相关的IE信息
- // 根据Group Info信息添加Client。就本例而言,周围还不存在GO
- p2p_add_group_clients(p2p, p2p_dev_addr, addr, freq, msg.group_info,msg.group_info_len);
- p2p_parse_free(&msg);
- // 判断是否有Service Discovery Request,如果有,需要为flags设置P2P_DEV_SD_SCHEDULE标志位
- if (p2p_pending_sd_req(p2p, dev)) dev->flags |= P2P_DEV_SD_SCHEDULE;
- // P2P_DEV_REPORTED表示WPAS已经向客户端汇报过该P2P Device信息了
- if (dev->flags & P2P_DEV_REPORTED) return 0;
- // P2P_DEV_USER_REJECTED表示用户拒绝该P2P Device信息
- if (dev->flags & P2P_DEV_USER_REJECTED) {return 0;}
- /*
- dev_found函数指针指向wpas_dev_found,该函数将向WifiMonitor发送消息以告知我们找到了一个
- P2P Device,该消息也称为P2P Device Found消息。
- */
- p2p->cfg->dev_found(p2p->cfg->cb_ctx, addr, &dev->info,
- !(dev->flags & P2P_DEV_REPORTED_ONCE));
- // 下面这两个标志表示该P2P Device已经向客户端汇报过并且汇报过一次了
- dev->flags |= P2P_DEV_REPORTED | P2P_DEV_REPORTED_ONCE;
- return 0;
- }
图7-32为WPAS向其客户端汇报的P2P Device Found消息的格式示例。
图7-32 P2P Device Found消息
(2)p2p_scan_res_handled函数
接着来看第二个关键函数p2p_scan_res_handled。
[—>p2p.c::p2p_scan_res_handled]
- void p2p_scan_res_handled(struct p2p_data *p2p)
- {
- ......
- p2p->p2p_scan_running = 0; // 设置p2p_scan_running值为0
- eloop_cancel_timeout(p2p_scan_timeout, p2p, NULL);// 取消扫描超时处理任务
- /*
- 还记得p2p_find函数中介绍的P2P_AFTER_SCAN_NOTHING标志吗(参考7.4.2节)?它将用在
- 下面这个p2p_run_after_scan函数中。由于指定了P2P_AFTER_SCAN_NOTHING标志,所以下面这个
- 函数返回0。感兴趣的读者可自行研究p2p_run_after_scan函数。
- */
- if (p2p_run_after_scan(p2p)) return;
- if (p2p->state == P2P_SEARCH) // 就本例而言,将满足此if条件
- p2p_continue_find(p2p);
- }
p2p_continue_find的代码如下所示。
[—>p2p.c::p2p_continue_find]
- void p2p_continue_find(struct p2p_data *p2p)
- {
- struct p2p_device *dev;
- #ifdef ANDROID_P2P
- int skip=1;
- #endif
- p2p_set_state(p2p, P2P_SEARCH);
- ......// Service Discovery和Provision Discovery处理。就本例而言,这部分代码逻辑意义不大
- p2p_listen_in_find(p2p); // 进入listen state。来看此函数的代码
- }
[—>p2p.c::p2p_listen_in_find]
- static void p2p_listen_in_find(struct p2p_data *p2p)
- {
- unsigned int r, tu;
- int freq;
- struct wpabuf *ies;
- // 根据p2p_supplicant.conf中listen_channel等配置参数获取对应的频段
- freq = p2p_channel_to_freq(p2p->cfg->country,
- p2p->cfg->reg_class,p2p->cfg->channel);
- ......
- // 计算需要在listen state等待的时间
- os_get_random((u8 *) &r, sizeof(r));
- tu = (r % ((p2p->max_disc_int - p2p->min_disc_int) + 1) +
- p2p->min_disc_int) * 100;
- p2p->pending_listen_freq = freq;
- p2p->pending_listen_sec = 0;
- p2p->pending_listen_usec = 1024 * tu;
- /*
- 构造P2P Probe Response帧,当我们在Listen state收到其他设备发来的Probe Request帧后,wifi
- 驱动将直接回复此处设置的P2P Probe Response帧。
- */
- ies = p2p_build_probe_resp_ies(p2p);
- // start_listen指向wpas_start_listen函数
- if (p2p->cfg->start_listen(p2p->cfg->cb_ctx, freq, 1024 *tu/1000,ies)<0){...... }
- wpabuf_free(ies);
- }
由上述代码可知,p2p_listen_in_find的内部实现完全遵循了7.2.2节介绍的和listen state相关的理论知识。
下面来看wpas_start_listen函数。
[—>p2p_supplicant.c::wpas_start_listen]
- static int wpas_start_listen(void *ctx, unsigned int freq,
- unsigned int duration, const struct wpabuf *probe_resp_ie)
- {
- struct wpa_supplicant *wpa_s = ctx;
- /*
- 调用driver_nl80211.c的wpa_driver_set_ap_wps_p2p_ie函数,它用于将Probe Response帧信息和
- Association Response帧信息。此函数为Android新增的功能,由厂商实现。
- */
- wpa_drv_set_ap_wps_ie(wpa_s, NULL, probe_resp_ie, NULL);
- /*
- 调用driver_nl80211.c的wpa_driver_nl80211_probe_req_report函数,其目的是让wifi driver
- 收到Probe Request帧后,返回一个EVENT_RX_PROBE_REQ netlink消息给WPAS。
- */
- if (wpa_drv_probe_req_report(wpa_s, 1) < 0) {......}
- wpa_s->pending_listen_freq = freq;
- wpa_s->pending_listen_duration = duration;
- /*
- 调用driver_nl80211.c的wpa_driver_nl80211_remain_on_channel函数,其目的是让
- wlan设备在指定频段(第二个参数freq)上停留duration毫秒。注意,这个函数的返回值只是表示
- wifi driver是否成功处理了这个请求,它不能用于判断wifi driver是否已经切换到了指定频段。
- 如果一切正常,当wifi driver切换到指定频段后,它将发送一个名为EVENT_REMAIN_ON_CHANNEL的
- netlink消息给WPAS。
- */
- if (wpa_drv_remain_on_channel(wpa_s, freq, duration) < 0) {......}
- wpa_s->off_channel_freq = 0;
- wpa_s->roc_waiting_drv_freq = freq;
- return 0;
- }
wpas_start_listen比较简单,不过有一些知识点请读者注意。
·wpa_drv_set_ap_wps_ie为wifi driver设置了P2P IE信息。如果wifi driver自己处理Probe Resquest帧(即不发送EVENT_RX_PROBE_REQ消息给WPAS),则wifi driver将把此处设置的P2P IE信息填写到Probe Response帧中。
·wpa_drv_probe_req_report要求wifi driver收到Probe Request帧后,发送EVENT_RX_PROBE_REQ消息给WPAS。WPAS内部将处理此消息,最终会回复一个Probe Response帧。这部分代码请读者自行阅读。
·wpa_drv_remain_on_channel要求wifi driver在指定频段工作一段时间。当wifi driver切换到指定频段后,会发送EVENT_REMAIN_ON_CHANNEL消息给WPAS,WPAS内部将处理一些事情。这部分代码也请读者自行阅读。
提醒 笔者感觉wpa_drv_set_ap_wps_ie和wpa_drv_probe_req_report功能重复。不过由于driver.h对set_ap_wps_ie的功能描述不是特别清晰,请了解细节的读者和我们分享相关的知识。另外,请读者认真阅读EVENT_RX_PROBE_REQ和EVENT_REMAIN_ON_CHANNEL消息的处理代码。
(3)P2P设备扫描结果处理流程总结
图7-33展示了P2P设备扫描结果处理的流程。
图7-33 P2P设备扫描结果处理流程
注意 图7-33中,只有满足一定条件,第6个及以后的函数才能执行。请读者结合p2p_scan_res_handled的代码来加深对图7-32的体会。另外,由于Android平台中wpa_driver_set_ap_wps_p2p_ie由厂商实现,故图7-33没有列出该函数。
设备找到以后,下一步工作就是发起Provision Discovery流程,马上来看它。