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流程,马上来看它。
';