2. 数据包结构:MAC层帧格式、帧头(FHDR)与端口(FPort)详解
好,咱们接着聊。上一章我们把LoRaWAN的整体架构捋了一遍,这一章我带你钻进数据包内部,看看MAC层到底长什么样。
说实话,我刚接触LoRaWAN那会儿,最头疼的就是看帧结构。一堆字段,什么MHDR、FHDR、FPort,看着就晕。后来在项目里调了几个月,才真正搞明白每个bit的用途。嗯,今天我就把这些经验掰开揉碎讲给你听。
2.1 MAC层帧格式概览
先看整体结构。LoRaWAN的MAC层帧,说白了就是一层套一层的盒子。最外层是物理层,里面包着MAC层,MAC层里面又包着应用层。咱们今天重点拆MAC层。
一个完整的MAC帧长这样:
| 前导码 | PHDR | PHDR_CRC | PHYPayload | CRC |
其中PHYPayload才是我们关心的MAC层内容:
| MHDR(1字节) | MACPayload(可变) | MIC(4字节) |
你看,MAC层就三个部分:头部、载荷、校验。简单吧?但别急,真正的门道在MACPayload里面。
核心要点:MIC是消息完整性校验码,不是CRC。它用AES-CMAC算法计算,能防篡改。我在项目里见过有人把MIC当CRC用,结果数据被篡改了都不知道——这坑我踩过。
2.2 MHDR:帧头里的秘密
MHDR只有1个字节,但信息量不小。它分成三个部分:
| 位域 | 长度 | 说明 |
|---|---|---|
| MType | 3 bit | 消息类型 |
| RFU | 3 bit | 保留位,必须为0 |
| Major | 2 bit | LoRaWAN版本号,目前是0b00 |
MType定义了6种消息类型,我列个表给你看:
| MType值 | 消息类型 | 方向 |
|---|---|---|
| 000 | Join-Request | 终端 → 服务器 |
| 001 | Join-Accept | 服务器 → 终端 |
| 010 | Unconfirmed Data Up | 终端 → 服务器 |
| 011 | Unconfirmed Data Down | 服务器 → 终端 |
| 100 | Confirmed Data Up | 终端 → 服务器 |
| 101 | Confirmed Data Down | 服务器 → 终端 |
| 110 | RFU | 保留 |
| 111 | Proprietary | 厂商自定义 |
我个人习惯,上行数据用Confirmed,下行用Unconfirmed。为什么?因为终端上行数据丢了可以重传,但服务器下行数据丢了,终端可以通过超时机制发现。你想想看,如果服务器也用Confirmed,那ACK风暴能把网络打爆。
2.3 MACPayload:真正的数据载体
MACPayload是帧的核心,结构如下:
| FHDR(7~22字节) | FPort(1字节,可选) | FRMPayload(可变) |
这里有个细节:FPort是可选的。如果FRMPayload为空,FPort就不存在。我刚开始做开发时,以为FPort必须带,结果发空包时服务器解析报错——嗯,后来查了规范才明白。
2.3.1 FHDR:帧头详解
FHDR固定7字节,加上可选的选项,最多22字节:
| 字段 | 长度 | 说明 |
|---|---|---|
| DevAddr | 4字节 | 终端设备地址 |
| FCtrl | 1字节 | 帧控制字节 |
| FCnt | 2字节 | 帧计数器 |
| FOpts | 0~15字节 | MAC命令选项 |
DevAddr是终端的网络标识,32位。注意,这个地址不是全球唯一的,是网络服务器分配的。同一个网络内唯一就行。
FCtrl这个字节很有意思,它里面塞了很多控制信息:
| 位 | 名称 | 说明 |
|---|---|---|
| 7 | ADR | 自适应数据速率使能 |
| 6 | ADRACKReq | ADR确认请求 |
| 5 | ACK | 确认位,收到Confirmed帧时置1 |
| 4 | FPending | 服务器还有更多数据待下发 |
| 3~0 | FOptsLen | FOpts字段的长度,单位字节 |
这里我要特别说一下FPending位。这个位只有下行帧才有用。服务器告诉你:「我还有数据没发完,你赶紧再开个接收窗口。」我在做智能水表项目时,就靠这个位实现了大数据量的分包下发。
FCnt是帧计数器,16位。每次上行或下行,计数器加1。服务器用这个来检测重放攻击。但有个坑:FCnt溢出后怎么办?规范说可以重置,但实际项目中,我建议用32位扩展计数器,否则一年发65536个包,有些场景真不够用。
FOpts是MAC命令的快速通道。它和FRMPayload里的MAC命令不同,FOpts不加密,但长度限制在15字节以内。我一般把链路检查、设备状态这类不敏感的命令放FOpts里,省得加解密折腾。
经验之谈:FOpts和FPort不能同时存在。如果FOptsLen > 0,FPort必须为0。这个规则很多人不知道,导致设备入网后无法通信——我排查过好几次这种问题。
2.3.2 FPort:端口号的意义
FPort只有1字节,取值范围0~255。它的作用类似于TCP/UDP的端口号,用来区分上层应用:
| FPort值 | 用途 |
|---|---|
| 0 | MAC命令传输(仅限FRMPayload) |
| 1~223 | 应用数据 |
| 224~255 | 保留给未来标准化应用 |
为什么要有FPort?说白了,一个终端可能跑多个应用。比如我的一个项目,终端同时采集温湿度和电量,我就用FPort=1传温湿度,FPort=2传电量。服务器收到后,根据FPort分发到不同的处理模块。
这里要注意:FPort=0时,FRMPayload里装的是MAC命令,而且是加密的。这和FOpts里的MAC命令不同,FOpts不加密但长度受限,FPort=0的MAC命令加密但长度不受限。怎么选?看你的安全需求。
2.4 FRMPayload:应用数据的最终归宿
FRMPayload是应用层数据,最大长度取决于数据速率(DR)。以最常见的SF12/125kHz为例,FRMPayload最多51字节。如果开了ADR,速率高了,长度还能更大。
加密方式是这样的:FRMPayload用AES-128进行加密,密钥是AppSKey(应用会话密钥)。注意,MAC命令如果走FPort=0,用的是NwkSKey(网络会话密钥)加密。这两个密钥不能混用,否则解密失败。
重要提醒:FRMPayload的加密是端到端的。也就是说,网络服务器只负责转发,它看不到应用数据的内容。这在物联网安全中非常重要——我曾经帮一个客户做安全审计,发现他们把敏感数据明文放在FRMPayload里,吓得我赶紧让他们改了。
2.5 实际抓包分析
光说不练假把式。我给你看一个实际的上行数据包:
MHDR: 0x40 (MType=Unconfirmed Data Up, Major=0)
FHDR:
DevAddr: 0x26011F4D
FCtrl: 0x80 (ADR=1, ACK=0, FOptsLen=0)
FCnt: 0x0001
FPort: 0x01
FRMPayload: 0x12345678 (加密后的数据)
MIC: 0xAABBCCDD
这个包什么意思?终端地址是0x26011F4D,开启了ADR,帧计数是1,用FPort=1传应用数据。MIC校验通过后,服务器用AppSKey解密FRMPayload,得到原始数据。
我在调试时,经常用这个结构来排查问题。比如MIC校验失败,先看FCnt对不对;解密失败,先看FPort和密钥是否匹配。90%的问题都能定位。
2.6 本章小结
好了,这一章的内容就到这儿。我们拆解了MAC层的帧结构,从MHDR到FRMPayload,每个字段的作用和注意事项都讲了一遍。下一章,我们聊聊重传机制——什么时候重传?怎么重传?重传多少次?这些都是实战中必须搞明白的问题。
最后送你一句话:理解帧结构,是调试LoRaWAN网络的第一步。把每个bit的含义刻在脑子里,遇到问题你就能快速定位。