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]

  1. char * wpa_supplicant_ctrl_iface_process(struct wpa_supplicant *wpa_s,
  2. char *buf, size_t *resp_len)
  3. {
  4. char *reply; const int reply_size = 4096;
  5. int ctrl_rsp = 0; int reply_len;
  6. ......
  7. #ifdef CONFIG_P2P
  8. // 处理带参数的P2P_FIND命令
  9. } else if (os_strncmp(buf, "P2P_FIND ", 9) == 0) {
  10. // 注意“P2P_FIND ”多了一个空格
  11. if (p2p_ctrl_find(wpa_s, buf + 9)) reply_len = -1;
  12. } else if (os_strcmp(buf, "P2P_FIND") == 0) {
  13. // 处理不带参数的P2P_FIND命令
  14. if (p2p_ctrl_find(wpa_s, "")) reply_len = -1;
  15. } ......// 其他P2P命令处理
  16. #endif
  17. ......
  18. }

不论P2P_FIND命令是否携带参数,其最终的处理函数都将对应为p2p_ctrl_find,如下所示。

[—>ctrl_iface.c::p2p_ctrl_find]

  1. static int p2p_ctrl_find(struct wpa_supplicant *wpa_s, char *cmd)
  2. {
  3. unsigned int timeout = atoi(cmd);
  4. enum p2p_discovery_type type = P2P_FIND_START_WITH_FULL;
  5. // 搜索方式,见下文解释
  6. u8 dev_id[ETH_ALEN], *_dev_id = NULL;
  7. char *pos;
  8. // 设置搜索方式,见下文解释
  9. if (os_strstr(cmd, "type=social")) type = P2P_FIND_ONLY_SOCIAL;
  10. else if (os_strstr(cmd, "type=progressive")) type = P2P_FIND_PROGRESSIVE;
  11.  
  12. pos = os_strstr(cmd, "dev_id=");// dev_id代表peer端device的MAC地址
  13. if (pos) {......}
  14. // wpas_p2p_find内部将调用p2p_find,下文将直接分析p2p_find
  15. return wpas_p2p_find(wpa_s, timeout, type, 0, NULL, _dev_id);
  16. }

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]

  1. int wpas_p2p_find(struct wpa_supplicant *wpa_s, unsigned int timeout,
  2. enum p2p_discovery_type type,unsigned int num_req_dev_types,
  3. const u8 *req_dev_types, const u8 *dev_id)
  4. {
  5. /*
  6. 取消还未发送的Action帧数据。WPAS中,待发送的Action帧数据保存在wpa_supplicant对象的
  7. pending_action_tx变量中,它指向一块数据缓冲区。
  8. */
  9. wpas_p2p_clear_pending_action_tx(wpa_s);
  10. wpa_s->p2p_long_listen = 0;
  11. /*
  12. 如果wifi driver能直接处理P2P管理,则主要工作将由wifi driver来完成。Galaxy Note 2
  13. 不支持WPA_DRIVER_FLAGS_P2P_MGMT。
  14. */
  15. if (wpa_s->drv_flags & WPA_DRIVER_FLAGS_P2P_MGMT)
  16. return wpa_drv_p2p_find(wpa_s, timeout, type);
  17. ......
  18. // 取消计划扫描任务
  19. wpa_supplicant_cancel_sched_scan(wpa_s);
  20. // 调用p2p_find函数
  21. return p2p_find(wpa_s->global->p2p, timeout, type,
  22. num_req_dev_types, req_dev_types, dev_id);
  23. }

来看p2p_find函数,其代码如下所示。

[—>p2p.c::p2p_find]

  1. int p2p_find(struct p2p_data *p2p, unsigned int timeout, enum p2p_discovery_type type,
  2. unsigned int num_req_dev_types, const u8 *req_dev_types, const u8 *dev_id)
  3. {
  4. int res;
  5. p2p_free_req_dev_types(p2p);
  6. if (req_dev_types && num_req_dev_types) {......// 本例没有设置request dev type属性}
  7.  
  8. if (dev_id) {
  9. os_memcpy(p2p->find_dev_id_buf, dev_id, ETH_ALEN);
  10. p2p->find_dev_id = p2p->find_dev_id_buf;
  11. } else p2p->find_dev_id = NULL;
  12. // 注意下面这个P2P_AFTER_SCAN_NOTHING标志,它表示P2P设备完成scan动作后,无须做其他动作
  13. p2p->start_after_scan = P2P_AFTER_SCAN_NOTHING;
  14. p2p_clear_timeout(p2p);
  15. p2p->cfg->stop_listen(p2p->cfg->cb_ctx);// 停止监听
  16. p2p->find_type = type;
  17.  
  18. p2p_device_clear_reported(p2p);
  19. p2p_set_state(p2p, P2P_SEARCH); // 设置P2P模块的状态为P2P_SEARCH
  20.  
  21. eloop_cancel_timeout(p2p_find_timeout, p2p, NULL);
  22. p2p->last_p2p_find_timeout = timeout;
  23. // 注册一个扫描超时处理任务
  24. if (timeout) eloop_register_timeout(timeout, 0, p2p_find_timeout,p2p, NULL);
  25. switch (type) {
  26. case P2P_FIND_START_WITH_FULL:
  27. case P2P_FIND_PROGRESSIVE: // p2p_scan函数指针指向wpas_p2p_scan
  28. res = p2p->cfg->p2p_scan(p2p->cfg->cb_ctx, P2P_SCAN_FULL, 0,
  29. p2p->num_req_dev_types,p2p->req_dev_types, dev_id);
  30. break;
  31. case P2P_FIND_ONLY_SOCIAL:
  32. res = p2p->cfg->p2p_scan(p2p->cfg->cb_ctx, P2P_SCAN_SOCIAL, 0,
  33. p2p->num_req_dev_types,p2p->req_dev_types, dev_id);
  34. break;
  35. default:
  36. return -1;
  37. }
  38. if (res == 0) {
  39. // 设置p2p_scan_running值为1,该变量后面用到的地方比较多,请读者注意
  40. p2p->p2p_scan_running = 1;
  41. eloop_cancel_timeout(p2p_scan_timeout, p2p, NULL);
  42. eloop_register_timeout(P2P_SCAN_TIMEOUT, 0, p2p_scan_timeout,p2p, NULL);
  43. } ...... // 略去res为其他值的处理情况
  44.  
  45. return res;
  46. }

上述代码中p2p_config对象的p2p_scan函数指针变量将指向p2p_supplicant.c中的wpas_p2p_scan,马上来看它,代码如下所示。

[—>p2p_supplicant.c::wpas_p2p_scan]

  1. static int wpas_p2p_scan(void *ctx, enum p2p_scan_type type, int freq,
  2. unsigned int num_req_dev_types, const u8 *req_dev_types, const u8 *dev_id)
  3. {
  4. struct wpa_supplicant *wpa_s = ctx;
  5. // 扫描参数
  6. struct wpa_driver_scan_params params;
  7. int ret; struct wpabuf *wps_ie, *ies;
  8. int social_channels[] = { 2412, 2437, 2462, 0, 0 };
  9. size_t ielen; int was_in_p2p_scan;
  10.  
  11. os_memset(&params, 0, sizeof(params));
  12.  
  13. params.num_ssids = 1; // 设置SSID参数,P2P_WILDCARD_SSID的值为“DIRECT-”
  14. params.ssids[0].ssid = (u8 *) P2P_WILDCARD_SSID;
  15. params.ssids[0].ssid_len = P2P_WILDCARD_SSID_LEN;
  16.  
  17. wpa_s->wps->dev.p2p = 1;
  18. // 构造Probe Request帧中WSC IE信息
  19. wps_ie = wps_build_probe_req_ie(0, &wpa_s->wps->dev, wpa_s->wps->uuid,
  20. WPS_REQ_ENROLLEE,num_req_dev_types, req_dev_types);
  21.  
  22. ielen = p2p_scan_ie_buf_len(wpa_s->global->p2p);
  23. ies = wpabuf_alloc(wpabuf_len(wps_ie) + ielen);
  24. ......
  25. // 构造P2P IE信息,感兴趣的读者不妨自行阅读p2p_scan_ie函数
  26. p2p_scan_ie(wpa_s->global->p2p, ies, dev_id);
  27.  
  28. params.p2p_probe = 1;
  29. params.extra_ies = wpabuf_head(ies); params.extra_ies_len = wpabuf_len(ies);
  30.  
  31. switch (type) {
  32. case P2P_SCAN_SOCIAL: // 只扫描social channels的话,将设置params.freqs变量
  33. params.freqs = social_channels;
  34. break;
  35. case P2P_SCAN_FULL:
  36. break;
  37. ......// 其他扫描频段控制
  38. }
  39.  
  40. was_in_p2p_scan = wpa_s->scan_res_handler == wpas_p2p_scan_res_handler;
  41. // 设置P2P扫描结果处理函数
  42. wpa_s->scan_res_handler = wpas_p2p_scan_res_handler;
  43. // 发起P2P设备扫描,该函数内部将调用driver_nl80211.c的wpa_driver_nl80211_scan函数
  44. ret = wpa_drv_scan(wpa_s, &params);
  45. wpabuf_free(ies);
  46. ......
  47. return ret;
  48. }

读者可比较本节和4.5.3节无线网络扫描流程分析的内容。总体而言,P2P设备扫描的代码逻辑比无线网络扫描的代码逻辑要简单得多。图7-31为WPAS中P2P设备扫描所涉及的几个重要函数调用。

7.4.2 P2P Device Discovery流程分析 - 图1

图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]

  1. static void wpas_p2p_scan_res_handler(struct wpa_supplicant *wpa_s,
  2. struct wpa_scan_results *scan_res)
  3. {
  4. size_t i;
  5. ......
  6. for (i = 0; i < scan_res->num; i++) {
  7. struct wpa_scan_res *bss = scan_res->res[i];
  8. // ①对每一个扫描结果调用p2p_scan_res_handler函数
  9. if (p2p_scan_res_handler(wpa_s->global->p2p, bss->bssid,// 处理扫描结果
  10. bss->freq, bss->level,(const u8 *) (bss + 1), bss->ie_len) > 0)
  11. break;
  12. }
  13. p2p_scan_res_handled(wpa_s->global->p2p);// ②处理完毕后调用p2p_scan_res_handled
  14. }

wpas_p2p_scan_res_handler中有两个关键函数,先来看第一个。

(1)p2p_scan_res_handler函数

p2p_scan_res_handler的代码如下所示。

[—>p2p.c::p2p_scan_res_handler]

  1. int p2p_scan_res_handler(struct p2p_data *p2p, const u8 *bssid, int freq,
  2. int level, const u8 *ies, size_t ies_len)
  3. {
  4. // 添加一个P2P Device,详情见下文代码分析
  5. p2p_add_device(p2p, bssid, freq, level, ies, ies_len);
  6. /*
  7. go_neg_peer代表GON的对端设备,如果go_neg_peer不为空而且设备扫描时由发现了它,则直接通过
  8. p2p_connect_send向其发送GON Request帧以开展GON流程。
  9. */
  10. if (p2p->go_neg_peer && p2p->state == P2P_SEARCH &&
  11. os_memcmp(p2p->go_neg_peer->info.p2p_device_addr, bssid, ETH_ALEN) == 0) {
  12. p2p_connect_send(p2p, p2p->go_neg_peer);
  13. return 1;
  14. }
  15. return 0;
  16. }

上述代码中最重要的是p2p_add_device函数,其代码如下所示。

[—>p2p.c::p2p_add_device]

  1. int p2p_add_device(struct p2p_data *p2p, const u8 *addr, int freq, int level,
  2. const u8 *ies, size_t ies_len)
  3. {
  4. struct p2p_device *dev; struct p2p_message msg;
  5. const u8 *p2p_dev_addr; int i;
  6.  
  7. os_memset(&msg, 0, sizeof(msg));
  8. // 解析扫描结果中的IE信息,解析完的结果保存在一个p2p_message对象中
  9. if (p2p_parse_ies(ies, ies_len, &msg)) {......// 解析扫描结果}
  10.  
  11. // p2p device info中的属性
  12. if (msg.p2p_device_addr) p2p_dev_addr = msg.p2p_device_addr;
  13. else if (msg.device_id) p2p_dev_addr = msg.device_id;
  14. ......
  15. // 过滤那些被阻止的P2P Device
  16. if (!is_zero_ether_addr(p2p->peer_filter) &&
  17. os_memcmp(p2p_dev_addr, p2p->peer_filter, ETH_ALEN) != 0) {......}
  18. // 构造一个p2p_device对象,并将其加入p2p_data结构体的devices链表中
  19. dev = p2p_create_device(p2p, p2p_dev_addr);// 创建一个P2P Device对象
  20. ......
  21. os_get_time(&dev->last_seen);// 设置发现该P2P Device的时间
  22. // p2p_device的flags变量代表该p2p_device的一些信息
  23. dev->flags &= ~(P2P_DEV_PROBE_REQ_ONLY | P2P_DEV_GROUP_CLIENT_ONLY);
  24.  
  25. if (os_memcmp(addr, p2p_dev_addr, ETH_ALEN) != 0)
  26. os_memcpy(dev->interface_addr, addr, ETH_ALEN);
  27. ......// 处理ssid、listen channel等内容
  28. dev->listen_freq = freq;
  29. // 如果对端P2P Device是GO,它回复的Probe Response帧P2P IE信息中将包含Group Info属性
  30. if (msg.group_info) dev->oper_freq = freq;
  31. dev->info.level = level;
  32. p2p_copy_wps_info(dev, 0, &msg);// 复制WSC IE
  33. ......// 处理Vendor相关的IE信息
  34. // 根据Group Info信息添加Client。就本例而言,周围还不存在GO
  35. p2p_add_group_clients(p2p, p2p_dev_addr, addr, freq, msg.group_info,msg.group_info_len);
  36.  
  37. p2p_parse_free(&msg);
  38. // 判断是否有Service Discovery Request,如果有,需要为flags设置P2P_DEV_SD_SCHEDULE标志位
  39. if (p2p_pending_sd_req(p2p, dev)) dev->flags |= P2P_DEV_SD_SCHEDULE;
  40. // P2P_DEV_REPORTED表示WPAS已经向客户端汇报过该P2P Device信息了
  41. if (dev->flags & P2P_DEV_REPORTED) return 0;
  42. // P2P_DEV_USER_REJECTED表示用户拒绝该P2P Device信息
  43. if (dev->flags & P2P_DEV_USER_REJECTED) {return 0;}
  44. /*
  45. dev_found函数指针指向wpas_dev_found,该函数将向WifiMonitor发送消息以告知我们找到了一个
  46. P2P Device,该消息也称为P2P Device Found消息。
  47. */
  48. p2p->cfg->dev_found(p2p->cfg->cb_ctx, addr, &dev->info,
  49. !(dev->flags & P2P_DEV_REPORTED_ONCE));
  50. // 下面这两个标志表示该P2P Device已经向客户端汇报过并且汇报过一次了
  51. dev->flags |= P2P_DEV_REPORTED | P2P_DEV_REPORTED_ONCE;
  52. return 0;
  53. }

图7-32为WPAS向其客户端汇报的P2P Device Found消息的格式示例。

7.4.2 P2P Device Discovery流程分析 - 图2

图7-32 P2P Device Found消息

(2)p2p_scan_res_handled函数

接着来看第二个关键函数p2p_scan_res_handled。

[—>p2p.c::p2p_scan_res_handled]

  1. void p2p_scan_res_handled(struct p2p_data *p2p)
  2. {
  3. ......
  4. p2p->p2p_scan_running = 0; // 设置p2p_scan_running值为0
  5. eloop_cancel_timeout(p2p_scan_timeout, p2p, NULL);// 取消扫描超时处理任务
  6. /*
  7. 还记得p2p_find函数中介绍的P2P_AFTER_SCAN_NOTHING标志吗(参考7.4.2节)?它将用在
  8. 下面这个p2p_run_after_scan函数中。由于指定了P2P_AFTER_SCAN_NOTHING标志,所以下面这个
  9. 函数返回0。感兴趣的读者可自行研究p2p_run_after_scan函数。
  10. */
  11. if (p2p_run_after_scan(p2p)) return;
  12. if (p2p->state == P2P_SEARCH) // 就本例而言,将满足此if条件
  13. p2p_continue_find(p2p);
  14. }

p2p_continue_find的代码如下所示。

[—>p2p.c::p2p_continue_find]

  1. void p2p_continue_find(struct p2p_data *p2p)
  2. {
  3. struct p2p_device *dev;
  4. #ifdef ANDROID_P2P
  5. int skip=1;
  6. #endif
  7. p2p_set_state(p2p, P2P_SEARCH);
  8. ......// Service Discovery和Provision Discovery处理。就本例而言,这部分代码逻辑意义不大
  9. p2p_listen_in_find(p2p); // 进入listen state。来看此函数的代码
  10. }

[—>p2p.c::p2p_listen_in_find]

  1. static void p2p_listen_in_find(struct p2p_data *p2p)
  2. {
  3. unsigned int r, tu;
  4. int freq;
  5. struct wpabuf *ies;
  6. // 根据p2p_supplicant.conf中listen_channel等配置参数获取对应的频段
  7. freq = p2p_channel_to_freq(p2p->cfg->country,
  8. p2p->cfg->reg_class,p2p->cfg->channel);
  9. ......
  10. // 计算需要在listen state等待的时间
  11. os_get_random((u8 *) &r, sizeof(r));
  12. tu = (r % ((p2p->max_disc_int - p2p->min_disc_int) + 1) +
  13.     p2p->min_disc_int) * 100;
  14.  
  15. p2p->pending_listen_freq = freq;
  16. p2p->pending_listen_sec = 0;
  17. p2p->pending_listen_usec = 1024 * tu;
  18. /*
  19. 构造P2P Probe Response帧,当我们在Listen state收到其他设备发来的Probe Request帧后,wifi
  20. 驱动将直接回复此处设置的P2P Probe Response帧。
  21. */
  22. ies = p2p_build_probe_resp_ies(p2p);
  23. // start_listen指向wpas_start_listen函数
  24. if (p2p->cfg->start_listen(p2p->cfg->cb_ctx, freq, 1024 *tu/1000,ies)<0){...... }
  25. wpabuf_free(ies);
  26. }

由上述代码可知,p2p_listen_in_find的内部实现完全遵循了7.2.2节介绍的和listen state相关的理论知识。

下面来看wpas_start_listen函数。

[—>p2p_supplicant.c::wpas_start_listen]

  1. static int wpas_start_listen(void *ctx, unsigned int freq,
  2. unsigned int duration, const struct wpabuf *probe_resp_ie)
  3. {
  4. struct wpa_supplicant *wpa_s = ctx;
  5. /*
  6. 调用driver_nl80211.c的wpa_driver_set_ap_wps_p2p_ie函数,它用于将Probe Response帧信息和
  7. Association Response帧信息。此函数为Android新增的功能,由厂商实现。
  8. */
  9. wpa_drv_set_ap_wps_ie(wpa_s, NULL, probe_resp_ie, NULL);
  10. /*
  11. 调用driver_nl80211.c的wpa_driver_nl80211_probe_req_report函数,其目的是让wifi driver
  12. 收到Probe Request帧后,返回一个EVENT_RX_PROBE_REQ netlink消息给WPAS。
  13. */
  14. if (wpa_drv_probe_req_report(wpa_s, 1) < 0) {......}
  15.  
  16. wpa_s->pending_listen_freq = freq;
  17. wpa_s->pending_listen_duration = duration;
  18. /*
  19. 调用driver_nl80211.c的wpa_driver_nl80211_remain_on_channel函数,其目的是让
  20. wlan设备在指定频段(第二个参数freq)上停留duration毫秒。注意,这个函数的返回值只是表示
  21. wifi driver是否成功处理了这个请求,它不能用于判断wifi driver是否已经切换到了指定频段。
  22. 如果一切正常,当wifi driver切换到指定频段后,它将发送一个名为EVENT_REMAIN_ON_CHANNEL的
  23. netlink消息给WPAS。
  24. */
  25. if (wpa_drv_remain_on_channel(wpa_s, freq, duration) < 0) {......}
  26. wpa_s->off_channel_freq = 0;
  27. wpa_s->roc_waiting_drv_freq = freq;
  28. return 0;
  29. }

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.4.2 P2P Device Discovery流程分析 - 图3

图7-33 P2P设备扫描结果处理流程

注意 图7-33中,只有满足一定条件,第6个及以后的函数才能执行。请读者结合p2p_scan_res_handled的代码来加深对图7-32的体会。另外,由于Android平台中wpa_driver_set_ap_wps_p2p_ie由厂商实现,故图7-33没有列出该函数。

设备找到以后,下一步工作就是发起Provision Discovery流程,马上来看它。