7.4.2 P2P Device Discovery流程分析

最后更新于:2022-04-02 06:05:09

根据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:默认设置。表示先扫描所有频段,然后再扫描socialchannels。这种搜索方式如图7-3所示。 * P2P_FIND_ONLY_SOCIAL:只扫描social channels。它将跳过“扫描所有频段”这一过程。这种搜索方式能加快搜索的速度。 * P2P_FIND_PROGRESSIVE:它和P2P_FIND_START_WITH_FULL类似,只不过在SearchState阶段将逐个扫描所有频段。为什么在search state阶段会扫描所有频段呢?请读者参考图7-22中的状态切换路线14。当周围已经存在Group的时候,如果在最初的“扫描所有频段”这一过程中没有发现Group,则在后续的search state逐个扫描频段过程中就有可能发现之前那些没有找到的Group。 >[info] 注意 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设备扫描所涉及的几个重要函数调用。 :-: ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/047f76b28481e0e5ba5868126492983f_1251x576.jpg) 图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中有两个关键函数,先来看第一个。 **①、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消息的格式示例。 :-: ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/f038f613f3ec8aaa98406da54b57ebfd_768x97.jpg) 图7-32 P2P Device Found消息 **②、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内部将处理一些事情。这部分代码也请读者自行阅读。 >[info] 提醒 笔者感觉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消息的处理代码。 **③、P2P设备扫描结果处理流程总结** 图7-33展示了P2P设备扫描结果处理的流程。 :-: ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/37c720811f6860b2c6d14ae53414ed24_1000x681.jpg) 图7-33 P2P设备扫描结果处理流程 >[info] 注意 图7-33中,只有满足一定条件,第6个及以后的函数才能执行。请读者结合p2p_scan_res_handled的代码来加深对图7-32的体会。另外,由于Android平台中wpa_driver_set_ap_wps_p2p_ie由厂商实现,故图7-33没有列出该函数。设备找到以后,下一步工作就是发起Provision Discovery流程,马上来看它。
';