Wi-Fi Direct 教程 06:P2P_FIND 进入 P2P Core——Social Channels、P2P_SEARCH 与第一轮扫描决策
摘要:从 p2p_ctrl_find() 追踪 P2P_FIND 参数、wpas_p2p_find()、p2p_find()、扫描模式与 P2P_SEARCH,直到 Core 请求第一轮 scan。
@[toc]
control parser 已经识别 P2P_FIND。本文从 p2p_ctrl_find() 开始:先把文本参数转换为 wpas_p2p_find() 的结构化参数,再进入 P2P Core 的 p2p_find(),解释 Social Channels、P2P_FIND_START_WITH_FULL、enum p2p_scan_type 与 P2P_SEARCH 状态怎样共同决定第一轮扫描请求。
这里关注的是“P2P Core 为什么决定要 scan”,而不是 driver 如何执行 scan。
1. p2p_ctrl_find():把 P2P_FIND 的文本参数变成 wpas_p2p_find() 参数
这段 control parser 与 P2P glue 调用链以 hostap 2.12 ctrl_iface.c、p2p_supplicant.c 为依据。S1
p2p_ctrl_find() 仍然位于:
1 | wpa_supplicant/ctrl_iface.c |
它是 control interface 与 P2P supplicant 层之间的参数适配器。
当前无参数命令:
1 | P2P_FIND |
进入:
1 | p2p_ctrl_find(wpa_s, "") |
后,可以按当前分支投影成下面的执行路径阅读版:
1 | /* 执行路径阅读版:当前 cmd == ""。 */ |
这里不展开 wpas_p2p_find() 内部状态机,只关注参数如何从文本命令变成函数调用。
当前无参数 P2P_FIND 可以整理成:
| 参数 | 当前来源/结果 | 当前含义 |
|---|---|---|
timeout |
atoi("") -> 0 |
没有显式传 timeout |
type |
默认 P2P_FIND_START_WITH_FULL |
使用默认 discovery type |
| requested device type | 未指定 | 不加该过滤条件 |
dev_id |
未指定 | 不指定目标 device ID |
search_delay |
wpas_p2p_search_delay(wpa_s) |
由 supplicant 当前配置/状态 helper 给出 |
seek |
未指定 | 没有 service seek string |
freq |
0 |
没有显式指定频率 |
include_6ghz |
false |
当前 command 没有 include_6ghz 参数 |
如果以后执行:
1 | P2P_FIND 10 type=social freq=2412 |
仍然会先进入同一个 p2p_ctrl_find(),只是这一次 cmd 不再为空,它会解析 timeout、discovery type、frequency 等参数后,再调用同一个:
1 | wpas_p2p_find() |
所以从架构职责看:
1 | wpa_supplicant_ctrl_iface_process() |
三层职责不要混在一起。
2. P2P_FIND 最终到达 wpas_p2p_find()
最终进入:
1 | wpa_supplicant/p2p_supplicant.c |
入口函数签名接收的已经不是字符串 command,而是解析后的结构化参数:
1 | wpa_s |
这说明 control interface 文本协议已经在上一层结束。
wpas_p2p_find() 开头会先处理 supplicant/P2P 当前状态,例如:
1 | /* 执行路径阅读版:只展示进入 P2P core 前的关键 gate。 */ |
到这里,本篇目标已经完成。
因为研究计划的阶段 1 明确停止在:
1 | wpas_p2p_find() |
附近。
接下来的:
1 | p2p_find() |
p2p_find() 内部属于 P2P Core 运行机制,不在本篇展开。这里的停止点是有意的边界:本文只回答 control command 怎样从另一个进程进入 supplicant 并到达 wpas_p2p_find()。
3. 为什么 wpa_cli 很快收到 OK,但这不代表已经发现 Peer
把 parser 的返回路径再补完整,会看到一个非常重要的语义边界。
wpa_supplicant_ctrl_iface_process() 在进入 command dispatch 前,默认准备:
1 | OK\n |
如果当前:
1 | p2p_ctrl_find() |
同步返回成功,那么 reply_len 保持成功状态,wpa_supplicant_ctrl_iface_receive() 会把这个 reply 通过:
1 | sendto() |
发回原来的 client address。
如果 handler 同步返回失败,parser 会把负的 reply_len 转成:
1 | FAIL\n |
所以当前 wpa_cli 看到:
1 | OK |
正确含义是:
P2P_FIND这个 control command 已经被服务端同步接受,并成功进入了 P2P find 启动路径。
它不等价于:
1 | 已经发现另一个 P2P Device |
这些属于 wpas_p2p_find() 返回后的异步 P2P 运行过程,不在本篇范围。
于是 client 和 server 两侧可以形成两个不同时间尺度:
1 | 同步 control request/reply: |
这也解释了为什么上一篇一次性 wpa_cli 能在 wpa_ctrl_request() 中很快收到 OK 并退出,而真正的 Wi-Fi Direct Discovery 仍然由后台运行的 wpa_supplicant 继续执行。
4. 先把“Social Channels”说清楚:它不是“常用 Wi-Fi 信道”的泛称
Wi-Fi Direct 的 Device Discovery 不是每轮都把所有可用信道完整扫描一遍。这里的 peer 就是“本机正在发现、准备与之建立 P2P 关系的另一台 P2P Device”;peer 进入 p2p->devices 后,才成为本机 P2P Core 持有的 peer entry。P2P 规范定义了一组 Social Channels,用于让尚未建立联系的 P2P Device 更容易在有限的公共集合上相遇。经典 2.4 GHz Social Channels 是 1、6、11;Hostap 的 P2P API 说明也明确把 P2P_SCAN_SOCIAL 定义为扫描这三个 social channels。S2 S4
这里的“Social”可以理解为“双方默认都会去的会合信道集合”,不是:
- RSSI 最好的三个信道;
- 当前国家一定最空闲的三个信道;
- 业务数据最终只能工作的三个信道;
- GO 最终 Operating Channel 必须从 1/6/11 选。
它服务的是 Device Discovery 的 rendezvous(会合)效率。
4.1 为什么代码里是 2412、2437、2462,而不是 1、6、11
Driver scan API 使用 MHz 频率,而 P2P 规范/Operating Class 更常用 channel number。Hostap 的 channel-to-frequency 辅助函数在 2.4 GHz Operating Class 81 中按:
1 | freq_MHz = 2407 + 5 * channel |
换算。S5
于是:
| Channel | 计算 | 中心频率 |
|---|---|---|
| 1 | 2407 + 5 * 1 |
2412 MHz |
| 6 | 2407 + 5 * 6 |
2437 MHz |
| 11 | 2407 + 5 * 11 |
2462 MHz |
因此在 2.12 glue 层能直接看到:
1 | int social_channels_freq[] = { 2412, 2437, 2462, 60480 }; |
前三个就是 2.4 GHz channel 1/6/11 的 MHz 表示;60480 对应 60 GHz P2P social channel 支持路径。当前实验使用 mac80211_hwsim 的常规 2.4/5 GHz 能力时,实际最重要的是前三个。S1
4.2 为什么 1、6、11 经常同时被称为“不重叠信道”,但这里不能混为同一个原因
在 20 MHz 2.4 GHz Wi-Fi 规划中,1/6/11 常被用于减少相邻信道重叠;但 Wi-Fi Direct 把 1/6/11 设为 Social Channels 是 P2P Device Discovery 的协议会合规则。两件事最终碰巧使用同一组数字,但解释问题不同。
本篇后面看到:
1 | freq == 2412 || freq == 2437 || freq == 2462 |
时,应优先结合当前 P2P Discovery 上下文理解成“是否位于 2.4 GHz social channel”,不能只解释成普通 WLAN 信道规划。
5. 已初始化的 callback 怎样成为 P2P Core 的“出口”
wpas_p2p_init() 已经在 interface 初始化阶段完成 callback 绑定;Device Discovery 直接使用其中以下几项:S1 S2
1 | p2p.cb_ctx = wpa_s; |
p2p_init(&p2p) 把这份配置保存进 P2P Core context,最终由:
1 | wpa_s->global->p2p |
持有。因此下面这些调用都能确定真实目标:
| P2P Core 中的调用 | 当前 wpa_supplicant 2.12 实际目标 | 本篇职责 |
|---|---|---|
p2p->cfg->p2p_scan(...) |
wpas_p2p_scan() |
把 Core 的 Scan 请求转换成 driver scan |
p2p->cfg->start_listen(...) |
wpas_start_listen() |
请求 remain-on-channel / Listen |
p2p->cfg->stop_listen(...) |
wpas_stop_listen() |
结束 Listen |
p2p->cfg->dev_found(...) |
wpas_dev_found() |
把完整 peer 信息报告给 supplicant/control layer |
所以本篇会反复跨越同一条边界:
1 | flowchart LR |
理解这张图以后,后面的“Core 请求扫描 -> glue 触发 driver -> 结果回来 -> glue 再喂给 Core”就不会被误解成一个同步函数调用栈。
6. 从 wpas_p2p_find() 进入 p2p_find():默认类型必须是 P2P_FIND_START_WITH_FULL
control 层传入无参数 P2P_FIND 时:
1 | P2P_FIND |
进入 p2p_ctrl_find() 时,默认 discovery type 是:
1 | enum p2p_discovery_type type = P2P_FIND_START_WITH_FULL; |
所以默认路径不能只写成“Full”或者模糊的“默认扫描模式”,应明确写成:
1 | P2P_FIND_START_WITH_FULL |
type=social 才会改成:
1 | P2P_FIND_ONLY_SOCIAL |
type=progressive 则是:
1 | P2P_FIND_PROGRESSIVE |
Hostap 的 README-P2P 对这三类控制行为给出了直接说明:默认先做一次 Full Scan,之后主要搜索 Social Channels;type=social 跳过首次 Full Scan;type=progressive 在后续 Search round 中逐步附加其它信道。S6
6.1 p2p_discovery_type 与 p2p_scan_type 是两层枚举
这是很容易混淆的地方。
p2p_discovery_type 描述的是 整次 P2P_FIND 的策略:
| Discovery type | 谁选择 | 整次 Find 的策略 |
|---|---|---|
P2P_FIND_START_WITH_FULL |
无参数 P2P_FIND 默认 |
首次 Full;随后进入 Search/Listen 循环,通常 Social Scan |
P2P_FIND_ONLY_SOCIAL |
type=social |
从第一轮开始就只扫描 Social Channels |
P2P_FIND_PROGRESSIVE |
type=progressive |
首次 Full,后续轮次逐步把其它 channel 加进 Search |
而 p2p_scan_type 是 某一次具体 scan request 的扫描范围。一个 P2P_FIND_START_WITH_FULL 生命周期中可以先出现 P2P_SCAN_FULL,后面又出现很多次 P2P_SCAN_SOCIAL。
7. p2p_find() 做了什么:建立本轮 Find 上下文并进入 P2P_SEARCH
wpas_p2p_find() 在取消可能冲突的 sched scan、检查 p2p_disabled/global->p2p/p2p_in_provisioning 后,最终调用:
1 | p2p_find(global->p2p, ...) |
P2P Core 的 p2p_find() 主要完成四类工作。S3
7.1 保存本轮 Find 的过滤条件和时间基准
Core 会记录:
find_type;- requested device type;
- 指定
dev_id; search_delay;- 首次指定
freq; find_start时间。
find_start 后面非常关键,因为 Linux/cfg80211 或 driver 可能保留旧 BSS scan cache。P2P Core 不希望把“本次 P2P_FIND 开始之前就缓存的旧帧”误报为新发现,所以扫描结果进入 Core 后会把每条 result 的接收时间与 find_start 比较。
7.2 清理上一轮 Discovery 的临时状态
包括停止旧 Listen、取消/覆盖 Find timeout、清掉上一轮 peer 的 reported 状态等。这里的目的不是删除 peer table,而是让新的一轮 Find 可以重新报告仍然存在的设备。
7.3 状态切到 P2P_SEARCH
核心状态变化是:
1 | state = P2P_SEARCH |
这才是 Device Discovery 的 P2P Core 主状态。
7.4 根据 discovery type 发起第一轮 scan
默认 P2P_FIND_START_WITH_FULL 且没有指定首个 freq 时,第一轮请求:
1 | P2P_SCAN_FULL |
所以本篇主线开端应准确理解为:
1 | flowchart TD |
8. Device Discovery 相关状态不能只列名字:看 P2P_IDLE / P2P_SEARCH 与短 Listen 的关系
enum p2p_state 中还包含其它 P2P 运行阶段,但本文只解释 Device Discovery 真正经过或直接影响的状态,避免把其它协议阶段提前混入。
对正常 P2P_FIND 主线而言,核心状态只有:
| 状态 | 本文中的作用 | 进入条件 | 离开条件 |
|---|---|---|---|
P2P_IDLE |
P2P Core 没有正在执行 Find | 初始/Find 结束 | p2p_find() 接受新的 Find 请求 |
P2P_SEARCH |
Device Discovery 正在运行 | p2p_find() 调用 p2p_set_state(p2p, P2P_SEARCH) |
Find timeout、显式停止或其它终止条件 |
P2P_LISTEN_ONLY |
显式 Listen-only 操作 | 只有单独 Listen 请求才进入 | Listen timeout/停止 |
正常 Find 中的 短 Listen 并不会把 p2p->state 改成 P2P_LISTEN_ONLY。它仍然属于 P2P_SEARCH 生命周期,只是 radio 此刻执行 remain-on-channel/Listen 动作。这个区别是理解 Search / Scan / Listen 的关键。S1 S2
1 | stateDiagram-v2 |
这张图只描述本篇 Device Discovery 需要的状态迁移。后文出现的 pending_listen_freq、in_listen、drv_in_listen 等字段用于记录短 Listen 的异步 radio 上下文,而不是创建一个新的 Find state。
8.1 Search / Scan / Listen 不是三个同级状态
必须区分三层概念:
P2P_SEARCH:P2P Core 的 state;P2P_SCAN_*:Search 中下一次 scan 的 scan mode;- short Listen:Search 生命周期里的一次 radio operation。
因此文章不能画成 SEARCH -> SCAN -> LISTEN 三个同级 enum p2p_state。真正的关系是 P2P_SEARCH 持续存在,内部轮换不同 radio operation。
8.2 Search 与短 Listen 的轮转由 callback/timeout 推进
一次 scan 完成后,supplicant glue 逐条提交 scan result,再调用 p2p_scan_res_handled();P2P Core 随后进入 p2p_continue_find(),根据当前 Find 状态决定是否安排短 Listen。Listen 完成后,driver/eloop callback 再推进下一轮 Search。本文后面会把这两处异步桥接按真实调用顺序展开。
9. 四种 p2p_scan_type 到底有什么区别
2.12 glue 层实际处理四种 scan request:S1 S2
1 | enum p2p_scan_type { |
它们应按“扫描范围、触发原因、收益、代价”理解,而不是只翻译枚举名。
| Scan type | wpas_p2p_scan() 的 freqs |
典型触发 | 优点 | 代价/限制 | 适合场景 |
|---|---|---|---|---|---|
P2P_SCAN_FULL |
NULL,交给 driver 扫支持范围 |
默认 Find 的首次扫描 | 覆盖广,能发现不在 social channel 上运行的 GO/BSS | 扫描时间和 radio 占用更高 | P2P_FIND_START_WITH_FULL 首轮 |
P2P_SCAN_SOCIAL |
2412/2437/2462 + 平台支持的 social freq | Find 的常规 Search round | 快,双方容易在公共集合相遇 | 只看 social channels,可能暂时漏掉其它工作信道上的 GO | Discovery 循环的主要 Search |
P2P_SCAN_SPECIFIC |
[freq, 0] |
用户/上层明确指定首个频率 | 最快验证一个已知 channel | 覆盖最窄,频率猜错会漏设备 | P2P_FIND ... freq=<MHz> 的首轮等 |
P2P_SCAN_SOCIAL_PLUS_ONE |
social channels + 一个额外 freq |
progressive search 或已知额外候选频率 | 保留 social rendezvous,同时逐步扩展覆盖 | 每轮比 pure social 多一个 channel | progressive Find / 指定额外 channel |
因此默认 Find 的典型扫描序列不是:
1 | FULL -> FULL -> FULL -> FULL |
而更接近:
1 | 首次:FULL |
这正是“为什么既有 P2P_FIND_START_WITH_FULL,又有 P2P_SCAN_FULL”的答案:前者控制整个 Find 策略,后者只是某一轮 scan 的实际范围。
10. P2P Core 请求 scan 后,怎样真正进入 wpas_p2p_scan()
Core 不直接调用 driver。它执行的是初始化时绑定的:
1 | p2p->cfg->p2p_scan(...) |
初始化阶段已把该函数指针绑定为:
1 | wpas_p2p_scan() |
所以真实跨层调用是:
1 | flowchart LR |
这里还没有立即调用 wpa_drv_scan()。wpas_p2p_scan() 先构造本轮主动扫描需要的 SSID、WPS IE、P2P IE、频率表,然后把真正的 driver scan 放进 radio work scheduler。
11. 本篇边界:P2P Core 已经决定“需要扫描”
到这里,文本命令参数已经转换为 wpas_p2p_find() 参数,P2P Core 已建立本轮 Find 上下文、进入 P2P_SEARCH,并通过已绑定的 p2p_scan callback 请求 supplicant 发起第一轮 scan。本文停在这个 callback 边界:扫描帧内容怎样构造、radio work 怎样排队以及 driver 何时真正收到 scan request,属于下一层运行机制,不在本篇展开。
关键源码索引
| 关键对象 / 符号 | 作用 | Git 源码 |
|---|---|---|
p2p_ctrl_find() |
解析 P2P_FIND 参数 |
ctrl_iface.c |
wpas_p2p_find() |
supplicant P2P Find 入口 | p2p_supplicant.c |
p2p_find() |
P2P Core Find 状态初始化 | p2p.c |
enum p2p_scan_type |
扫描策略 | p2p.h |
wpas_p2p_scan() callback |
Core 请求 supplicant scan 的桥接 | p2p_supplicant.c |
资料来源
[S1] hostap 2.12 Git:P2P_FIND parser、supplicant glue 与 scan callback
- 版本:
hostap_2_12/831364bf02710ad09c2f27d3efa92abeeb5634c0 - 文件:ctrl_iface.c、p2p_supplicant.c、wpa_supplicant.c
- 使用位置:命令参数转换、
wpas_p2p_find()与 scan callback 边界 - 支撑内容:
P2P_FIND参数如何进入 supplicant/P2P Core,以及 Core 怎样通过 callback 请求扫描。
[S2] hostap 2.12 Git:P2P module design
- 来源:doc/p2p.doxygen
- 使用位置:第 2、5、7、25~29 节
- 支撑内容:P2P Core 与 glue callback 契约、scan results 提交与
p2p_scan_res_handled()结束通知的设计。
[S3] hostap 2.12 Git:P2P Core / WPS implementation
- 文件:p2p.c、p2p.h、p2p_i.h、p2p_utils.c、wps.c、wps_attr_build.c
- 使用位置:第 3~12、18~30 节
- 支撑内容:
p2p_find()、scan modes、peer table、p2p_add_device()、Find Continue/Listen 以及 WPS IE builder 的真实 2.12 实现。
[S4] Wi-Fi Alliance Wi-Fi Direct Specification v1.9
- URL/文档:Wi-Fi Direct Specification v1.9
- 版本:2021-10-25 Final Public release
- 使用位置:第 1、5、27~29 节
- 支撑内容:In-band Device Discovery 的 Search/Listen 概念、2.4 GHz Social Channels 1/6/11 与 Listen Channel 规则。
[S5] Linux Wireless channel/cfg80211 + hostap channel helper
- URL/文档:Linux Wireless Channel List、cfg80211
- 来源:hostap p2p_utils.c
- 使用位置:第 1.1、10、22.4 节
- 支撑内容:Channel 1/6/11 到 2412/2437/2462 MHz 的对应及 hostap operating class/channel-to-frequency helper。
[S6] hostap 2.12 README-P2P
- 来源:wpa_supplicant/README-P2P
- 使用位置:第 3、6、29 节
- 支撑内容:
p2p_find默认、type=social、type=progressive的对外命令语义。










