第3章 Mesh协议栈初探:分层结构与PDU格式
各位同学,今天我们来聊聊蓝牙Mesh的协议栈。说实话,我第一次接触Mesh协议栈时,也被那一堆层吓到了——Bearer、Network、Lower/Upper Transport、Access、Model……感觉像在爬一栋五层楼。但后来我发现,只要理解了每一层“管什么事”,这栋楼其实很好爬。
我个人习惯把协议栈比作一个快递系统。你想想看,Model层是“我要寄什么”,Access层是“写快递单”,Transport层是“打包分箱”,Network层是“贴路由标签”,Bearer层是“交给哪个快递员”。每一层只关心自己的事,不越界。这就是分层设计的精髓。
3.1 协议栈全景:五层架构
蓝牙Mesh协议栈从下到上分别是:
- Bearer层:负责物理传输,比如通过BLE广播或GATT连接收发数据。
- Network层:负责消息的路由、中继、加密和重放保护。
- Lower Transport层:负责将大消息分片成小段,或把小段重组回大消息。
- Upper Transport层:负责应用层加密、传输确认和心跳。
- Access层:负责应用数据的格式化和操作码解析。
- Model层:定义具体的行为,比如开关灯、传感器读数。
嗯,这里要注意:Model层其实不算严格意义上的“协议层”,它更像是一套标准行为模板。但为了方便理解,我们通常把它放在最上面。
核心观点:每一层只处理自己的PDU(协议数据单元),下层对上层的数据是“透明”的。你在Wireshark里看到的每一层头部,都是这一层自己加的。
3.2 Bearer层:数据怎么出门
Bearer层是协议栈的最底层,它决定了数据通过什么物理通道发送。蓝牙Mesh支持两种Bearer:
- Advertising Bearer(PB-ADV):通过BLE广播信道发送,是Mesh的主要承载方式。节点之间不需要建立连接,直接广播。
- GATT Bearer(PB-GATT):通过BLE GATT连接发送,主要用于手机App与Mesh节点通信,或者不支持广播的设备。
我在项目中遇到过一个问题:有些设备只支持GATT Bearer,但Mesh网络里大部分节点都用广播。这时候就需要一个“代理节点”来做协议转换。说白了,就是让一个节点同时支持两种Bearer,帮GATT设备转发消息到广播网络里。
实战技巧:抓包时,如果你看到的是广播包(ADV),那大概率是PB-ADV;如果你看到的是ATT Write/Notify,那就是PB-GATT。Wireshark里可以直接过滤 btmesh 协议,它会自动解析。
3.3 Network层:路由与安全的第一道门
Network层是Mesh协议栈的“交通警察”。它负责三件事:
- 消息中继:判断是否需要转发给邻居节点。
- 路由:根据TTL(生存时间)和源/目标地址决定消息走向。
- 网络层加密:使用Network Key对PDU进行加密和认证。
Network层PDU的格式是这样的:
Network PDU = IVI (1bit) + NID (7bit) + CTL (1bit) + TTL (7bit) + SEQ (24bit) + SRC (16bit) + DST (16bit) + Transport PDU (可变) + NetMIC (32bit或64bit)
嗯,这里要注意:IVI是IV Index的最低有效位,用于防止重放攻击。NID是Network ID,用来标识使用的是哪个Network Key。CTL位表示后面跟的是控制消息还是访问消息。
我曾经调试过一个奇怪的问题:某个节点总是收不到中继消息。抓包一看,TTL=0。原来配置工具把TTL设成了0,导致消息发不出去。你想想看,TTL=0意味着“别转发”,那中继节点当然不会理它。
避坑指南:我曾经在抓包时发现,有些设备发送的Network PDU里,SEQ号是乱序的。后来排查发现,是设备固件里的序列号计数器没做持久化,每次重启都从0开始。这会导致重放攻击防护失效。所以,SEQ号必须保存在非易失性存储里。
3.4 Lower Transport层:大消息的分片与重组
Lower Transport层解决一个实际问题:Network层一次最多只能传15字节的Payload(因为BLE广播包的限制)。如果你的应用数据超过15字节,怎么办?
答案就是分片。Lower Transport层把大消息切成多个Segment,每个Segment单独发送。接收端再把这些Segment重组回原始消息。
分片消息的格式如下:
Segmented Message:
SEG (1bit) = 1 表示这是分片消息
AKF (1bit) = 应用密钥标志
AID (6bit) = 应用密钥ID
SeqZero (13bit) = 序列号零,用于标识属于同一消息的所有分片
SegO (5bit) = 当前分片编号
SegN (5bit) = 总分片数减1
SegmentData (可变)
非分片消息就简单多了:
Unsegmented Message:
SEG (1bit) = 0
AKF (1bit)
AID (6bit)
Upper Transport PDU (可变)
我记得有一次,客户反映某个灯控指令有时灵有时不灵。抓包后发现,指令被分成了3个Segment,但第2个Segment丢了。接收端等不到所有分片,超时后直接丢弃了。这就是典型的“分片丢失”问题。解决方案是降低分片数量,或者启用传输层确认。
3.5 Upper Transport层:应用层加密与传输控制
Upper Transport层是“应用数据的保险柜”。它负责:
- 使用Application Key对Access Payload进行加密和认证。
- 处理传输层确认(如果消息需要可靠传输)。
- 处理心跳消息(Heartbeat)。
Upper Transport PDU的格式:
Upper Transport PDU = TransMIC (32bit) + Access PDU (可变)
这里要注意:TransMIC是传输层消息完整性校验码,用于验证数据是否被篡改。它和Network层的NetMIC不同,NetMIC保护的是整个Network PDU,而TransMIC只保护Access PDU。
关键点:Upper Transport层使用的密钥是Application Key,而Network层使用的是Network Key。这两把钥匙不一样。Network Key保护的是“谁在说话”,Application Key保护的是“说了什么”。
3.6 Access层:应用数据的格式化
Access层是协议栈的“翻译官”。它把上层Model发来的数据,格式化成标准的Access PDU,然后交给Transport层。
Access PDU的格式:
Access PDU = Opcode (1-3字节) + Parameters (可变)
Opcode分为两种:
- 单字节Opcode:0x00-0x7F,用于标准Mesh模型。
- 双字节Opcode:0x8000-0xBFFF,用于厂商自定义模型。
- 三字节Opcode:0xC00000-0xFFFFFF,用于特殊用途。
举个例子,开关灯的命令:
Opcode: 0x8201 (Generic OnOff Set)
Parameters: 0x01 (表示开灯)
我在Wireshark里经常看到新手把Opcode和Parameters搞混。记住:Opcode告诉设备“你要干什么”,Parameters告诉设备“具体怎么干”。
3.7 Model层:行为定义
Model层是协议栈的“顶层应用”。它定义了设备的行为,比如:
- Generic OnOff Server:开关灯。
- Generic Level Server:调亮度。
- Sensor Server:上报传感器数据。
Model层本身不产生PDU,它只是调用Access层的接口。说白了,Model层就是一套API规范,告诉开发者“你的设备应该支持哪些功能”。
实战建议:在Wireshark里分析Mesh流量时,我建议你从下往上逐层看。先看Bearer层确认物理通道,再看Network层确认路由和加密,最后看Access层确认具体操作。这样不容易漏掉关键信息。
3.8 各层PDU格式对比
为了方便你记忆,我把各层PDU的核心字段整理成了一张表:
| 协议层 | 核心字段 | 长度 | 作用 |
|---|---|---|---|
| Network | IVI, NID, CTL, TTL, SEQ, SRC, DST | 9字节 + 可变 | 路由、中继、网络层加密 |
| Lower Transport | SEG, AKF, AID, SeqZero, SegO, SegN | 2-3字节 + 可变 | 分片与重组 |
| Upper Transport | TransMIC | 4字节 + 可变 | 应用层加密、传输确认 |
| Access | Opcode, Parameters | 1-3字节 + 可变 | 应用数据格式化 |
嗯,这张表你最好收藏一下。每次抓包分析时,对照着看,能快速定位问题出在哪一层。
3.9 小结
这一章我们走了一遍Mesh协议栈的五层结构。从Bearer层的物理传输,到Network层的路由加密,再到Transport层的分片和加密,最后到Access和Model层的应用处理。每一层都有自己的PDU格式,每一层都只关心自己的事。