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 节点正常通信时,所有节点都只做被动扫描——没人会闲到去回复扫描请求。

抓包小技巧: 用 nRF Sniffer 或 Ellisys 抓 Mesh 包时,建议开启「主动扫描 + 广播信道全捕获」模式。这样既能拿到完整的 AD Type 数据,又不会漏掉三个信道上的重传包。

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 跟其他节点不一致,说明这个节点可能掉线太久,或者被单独更新过密钥。
注意: Secure Network Beacon 的 MIC 覆盖了 Flags、Network ID 和 IV Index。如果抓包工具显示 MIC 校验失败,大概率是 Network Key 不匹配,或者包在传输过程中被篡改了。别急着怀疑抓包器,先检查一下你的密钥配置。

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 包时,重点关注下面几个时间点和数据:

  1. Invite → Capabilities:看设备回复的 Capabilities 里支持的算法列表。如果 Provisioner 选了 ECDH P-256,但设备只支持 P-192,那就会卡在 Public Key 交换阶段。
  2. Public Key 交换:公钥是 64 字节(X + Y 坐标各 32 字节)。如果抓包发现公钥长度不对,或者校验和失败,基本可以断定是 ECC 硬件加速器驱动有问题。
  3. Confirmation + Random:这两个阶段是安全握手的关键。我曾经遇到一个 case,设备在 Confirmation 阶段总是回复 Failed,错误码是 0x04(Confirmation Failed)。查了两天,最后发现是设备端的随机数生成器种子没初始化,每次生成的 Random 都一样,导致 Confirmation 值校验不通过。
  4. Provisioning Data:这个包是加密的,抓包工具如果没有 Network Key 就解不开。但你可以看包长度——正常是 25 字节(含 MIC),如果长度不对,说明分片重组有问题。
实战经验: 在做多设备并发 Provisioning 时,抓包会发现大量的 Transaction Number 冲突。这是因为多个 Provisioner 同时给不同设备发消息,Transaction Number 都是从 0 开始递增的。解决办法是在 Provisioner 端实现 Transaction Number 的随机初始值,或者给每个设备分配独立的会话上下文。

4.4 协议栈分层关系总览

说了这么多,我们来画一张图,把这几层的关系理清楚。这张图是我自己调试时经常参考的,每次抓包遇到问题,我都会回到这张图上,问自己:我现在看到的数据,是哪一层?

蓝牙 Mesh 协议栈分层与抓包视角 BLE 物理层(PHY) 1M PHY / 2M PHY / Coded PHY(125kbps / 500kbps) BLE 链路层(LL) Advertising PDU / Scanning PDU / 信道选择 #37 #38 #39 Mesh 承载层(Bearer Layer) PB-ADV(广播承载) / PB-GATT(代理承载) Mesh 网络层(Network Layer) Mesh Beacon / Provisioning PDU / Network PDU Mesh 应用层(Access Layer + Model Layer) 抓包器可见范围(Sniffer 视角) 本章重点

这张图里,抓包器能看到的是从物理层一直到 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
我的习惯: 每次拿到一个新的抓包文件,我会先用 Wireshark 的「Bluetooth Mesh」过滤规则把 Mesh 包单独拎出来。然后按 Beacon Type 分组,先看有没有 Unprovisioned Device Beacon,再看 Secure Network Beacon 的 IV Index 是否一致。这两步走完,网络的基本健康状况就心里有数了。