4. 协议栈解析:BLE Advertising、Scanning、Mesh Beacon、Mesh Provisioning PDU
抓包分析这件事,说白了就是跟空气里的无线电波打交道。你拿着抓包器,看着一堆十六进制数据在屏幕上滚动,如果不懂底层协议栈的封装关系,那跟看天书没什么区别。我个人习惯,拿到一个陌生的 Mesh 包,先问自己三个问题:这是广播包还是扫描响应包?这是普通的 BLE Beacon 还是 Mesh Beacon?这个包是在 Provisioning 阶段还是已经入网了?
这一章,我们就从最底层的 BLE Advertising 和 Scanning 开始,一路拆到 Mesh Beacon 和 Provisioning PDU。嗯,我会把我在实际项目中踩过的坑也一并抖出来。
4.1 BLE Advertising 与 Scanning:Mesh 的物理基础
蓝牙 Mesh 不是走经典蓝牙的 ACL 连接,它完全建立在 BLE 的广播机制之上。你想想看,一个节点要跟周围所有节点通信,最直接的方式就是站在楼顶拿大喇叭喊——这就是 Advertising。
4.1.1 广播信道与广播 PDU
BLE 在 2.4GHz 频段上划了 40 个信道,其中 37、38、39 三个信道专门留给广播用。为什么是三个?因为要避开 Wi-Fi 的 1、6、11 信道干扰。Mesh 的广播包就在这三个信道上轮着发。
一个标准的 BLE 广播 PDU 长这样:
| Preamble (1B) | Access Address (4B) | PDU Header (2B) | Payload (0-37B) | CRC (3B) |
这里有个关键点:Access Address 在广播模式下固定为 0x8E89BED6。我在项目初期调试时,有一次抓包发现某个设备始终不被发现,排查了半天,结果是固件里把 Access Address 写成了连接用的随机值。嗯,这种低级错误,犯过一次就再也不会忘了。
4.1.2 广播 PDU 类型与 Mesh 的关系
BLE 广播 PDU 分好几种,Mesh 主要用到下面这些:
| PDU 类型 | 代码 | Mesh 中的用途 |
|---|---|---|
| ADV_IND | 0x00 | 可连接无向广播,Mesh 节点入网前使用 |
| ADV_NONCONN_IND | 0x02 | 不可连接无向广播,Mesh 消息的主要承载 |
| ADV_SCAN_IND | 0x06 | 可扫描无向广播,配合扫描请求/响应使用 |
| SCAN_REQ / SCAN_RSP | 0x03 / 0x04 | 扫描请求与响应,Provisioning 发现阶段使用 |
Mesh 网络中的节点在正常工作状态下,发送的都是 ADV_NONCONN_IND 包。为什么?因为 Mesh 消息不需要建立连接,发完就走,谁爱收谁收。我曾经在客户现场遇到一个问题:某个节点入网后一直发 ADV_IND,导致手机 App 总想连它,网络拥塞得一塌糊涂。查到最后,是 SDK 的广播模式配置错了。
4.1.3 Scanning 的两种模式
抓包器本质上就是一个高性能的 Scanner。BLE 扫描分两种模式:
- 被动扫描(Passive Scanning):只监听广播包,不发任何请求。省电,但拿不到扫描响应数据。
- 主动扫描(Active Scanning):收到广播后,发送
SCAN_REQ,等待对方回复SCAN_RSP。信息更全,但会增加空口负载。
在 Mesh Provisioning 的发现阶段,Provisioner 通常使用主动扫描来获取未入网设备的完整广播数据。但 Mesh 节点正常通信时,所有节点都只做被动扫描——没人会闲到去回复扫描请求。
4.2 Mesh Beacon:节点的身份标识
Mesh Beacon 是承载在 BLE 广播 PDU 的 Payload 里的一种特殊数据格式。它告诉周围节点:「我是谁,我在干什么,我需不需要被配置。」
4.2.1 Beacon 的类型与结构
Mesh 规范定义了三种 Beacon:
| Beacon 类型 | Beacon Type 值 | 用途 |
|---|---|---|
| Unprovisioned Device Beacon | 0x00 | 未入网设备广播,等待被 Provisioning |
| Secure Network Beacon | 0x01 | 已入网节点广播,同步网络密钥与 IV Index |
| Mesh Private Beacon | 0x02 | 隐私增强版,加密广播,防止跟踪 |
一个典型的 Unprovisioned Device Beacon 结构如下:
| Beacon Type (1B) | Device UUID (16B) | OOB Information (2B) | URI Hash (可选, 4B) |
这里 Device UUID 是设备的唯一标识,Provisioner 就是靠这个来区分不同设备的。我记得有一次做多设备并发 Provisioning 测试,发现两个开发板的 UUID 一模一样——原来是工厂烧录时 EEPROM 地址写错了,所有设备都读了同一个值。这种问题在抓包里一眼就能看出来:两个设备发着相同的 UUID,Provisioner 直接懵掉。
4.2.2 Secure Network Beacon 的抓包要点
Secure Network Beacon 是已入网节点定期广播的包,默认间隔 10 秒。它的结构:
| Beacon Type (1B) | Flags (1B) | Network ID (8B) | IV Index (4B) | MIC (8B) |
抓包时重点关注两个字段:
- Network ID:网络标识,由 Network Key 派生。不同网络的 Network ID 不同,可以用来区分多个 Mesh 网络是否在同一个物理空间内互相干扰。
- IV Index:网络序列号,用于防止重放攻击。如果抓包发现某个节点的 IV Index 跟其他节点不一致,说明这个节点可能掉线太久,或者被单独更新过密钥。
4.3 Mesh Provisioning PDU:入网流程的通信语言
Provisioning 是设备从「裸机」变成「Mesh 节点」的关键一步。整个流程走的是 BLE 的 GATT 连接(PB-GATT)或广播(PB-ADV),但不管底层用什么,上层跑的都是一套统一的 Provisioning PDU。
4.3.1 Provisioning PDU 的通用格式
每个 Provisioning PDU 都长这样:
| Generic Provisioning PDU (1B) | Transaction Number (1B) | Provisioning PDU (可变) |
其中 Generic Provisioning PDU 的低 6 位表示消息类型,高 2 位用于分片控制。Provisioning 消息一共有 9 种类型:
| 消息类型 | 代码 | 方向 | 说明 |
|---|---|---|---|
| Provisioning Invite | 0x00 | Provisioner → 设备 | 邀请设备开始入网 |
| Provisioning Capabilities | 0x01 | 设备 → Provisioner | 上报设备能力(算法、OOB 方式等) |
| Provisioning Start | 0x02 | Provisioner → 设备 | 确认入网参数 |
| Provisioning Public Key | 0x03 | 双向 | 交换 ECC 公钥 |
| Provisioning Input Complete | 0x04 | 设备 → Provisioner | 用户输入 OOB 完成 |
| Provisioning Confirmation | 0x05 | 双向 | 确认值交换 |
| Provisioning Random | 0x06 | 双向 | 随机数交换 |
| Provisioning Data | 0x07 | Provisioner → 设备 | 下发网络密钥、IV Index、Unicast Address |
| Provisioning Complete | 0x08 | 设备 → Provisioner | 确认入网完成 |
| Provisioning Failed | 0x09 | 双向 | 入网失败及原因 |
4.3.2 抓包分析 Provisioning 流程
我建议你在抓 Provisioning 包时,重点关注下面几个时间点和数据:
- Invite → Capabilities:看设备回复的 Capabilities 里支持的算法列表。如果 Provisioner 选了 ECDH P-256,但设备只支持 P-192,那就会卡在 Public Key 交换阶段。
- Public Key 交换:公钥是 64 字节(X + Y 坐标各 32 字节)。如果抓包发现公钥长度不对,或者校验和失败,基本可以断定是 ECC 硬件加速器驱动有问题。
- Confirmation + Random:这两个阶段是安全握手的关键。我曾经遇到一个 case,设备在 Confirmation 阶段总是回复 Failed,错误码是 0x04(Confirmation Failed)。查了两天,最后发现是设备端的随机数生成器种子没初始化,每次生成的 Random 都一样,导致 Confirmation 值校验不通过。
- Provisioning Data:这个包是加密的,抓包工具如果没有 Network Key 就解不开。但你可以看包长度——正常是 25 字节(含 MIC),如果长度不对,说明分片重组有问题。
4.4 协议栈分层关系总览
说了这么多,我们来画一张图,把这几层的关系理清楚。这张图是我自己调试时经常参考的,每次抓包遇到问题,我都会回到这张图上,问自己:我现在看到的数据,是哪一层?
这张图里,抓包器能看到的是从物理层一直到 Mesh 网络层。再往上,Access Layer 和 Model Layer 的数据已经被加密和分片了,抓包工具只能看到 Network PDU 的头部,Payload 部分如果没有密钥就是一堆乱码。
所以,你在抓包时看到的数据,从外到内依次是:
- 最外层:BLE 广播 PDU 的头部(Preamble、Access Address、PDU Header)
- 第二层:Mesh 承载层的分片信息(如果使用 PB-ADV 或 PB-GATT)
- 第三层:Mesh Beacon 或 Provisioning PDU 的头部
- 最内层:如果是 Provisioning Data 消息,Payload 是加密的;如果是 Secure Network Beacon,能看到 Network ID 和 IV Index