4. 帧格式详解:管理帧、控制帧、数据帧结构
说实话,搞WiFi协议适配这么多年,我见过太多人在帧格式上栽跟头了。很多人觉得不就是几个字节嘛,对着文档抄就行了。但实际调起来,往往是一个bit没对齐,整个链路就断了。今天咱们就把这三种帧——管理帧、控制帧、数据帧——彻底掰开揉碎了讲清楚。
4.1 三种帧的宏观区别
802.11标准把所有的帧分成了三大类。你想想看,就像我们平时发快递,有包装盒、有运单、有里面的货物。WiFi帧也是这个道理:
- 管理帧:负责建立和维护连接。比如Beacon、Probe Request/Response、Association Request/Response。说白了就是“握手”用的。
- 控制帧:负责协调信道访问。比如RTS、CTS、ACK。它们不携带用户数据,只负责“指挥交通”。
- 数据帧:真正承载上层数据的帧。我们上网浏览网页、看视频,数据都封装在这里面。
我个人习惯,拿到一个抓包文件,先看Frame Control字段里的Type和Subtype,马上就能判断这是哪一类帧。这个习惯帮我省了不少排查时间。
核心记忆点:Type字段占2bit,00=管理帧,01=控制帧,10=数据帧。11是保留值,目前还没用到。
4.2 通用帧头结构
所有802.11帧都共享一个通用的帧头。嗯,这里要注意,不管你是管理帧还是数据帧,前两个字节一定是Frame Control。我见过有人拿数据帧的解析逻辑去解析管理帧,结果全乱了。
| 字段 | 长度(字节) | 说明 |
|---|---|---|
| Frame Control | 2 | 帧类型、子类型、标志位 |
| Duration/ID | 2 | NAV设置或关联ID |
| Address 1 | 6 | 接收端MAC地址 |
| Address 2 | 6 | 发送端MAC地址 |
| Address 3 | 6 | BSSID或目标地址 |
| Sequence Control | 2 | 序列号和分片号 |
| Address 4 | 6 | 仅用于WDS模式 |
| Frame Body | 可变 | 实际载荷 |
| FCS | 4 | CRC校验 |
我在项目中遇到过一个问题:某个AP老是丢包,抓包一看,FCS校验失败率高达30%。后来发现是射频干扰导致帧尾被截断了。所以FCS这个字段,别看它只有4个字节,它是帧完整性的最后一道防线。
4.3 Frame Control字段深度解析
Frame Control字段是整个帧的“身份证”。它只有2个字节,但里面塞了11个子字段。我刚开始学的时候也觉得头大,后来自己画了个位图,一下子就清楚了。
我的小技巧:把Frame Control字段的16个bit画成一张表贴在工位上。调试的时候对照着看,效率翻倍。
咱们按bit顺序来拆解:
- Bit 0-1 (Protocol Version):协议版本,目前固定为00。802.11标准从1997年到现在,这个字段就没变过。你想想看,兼容性就是这么来的。
- Bit 2-3 (Type):帧类型。00=管理,01=控制,10=数据。
- Bit 4-7 (Subtype):子类型。比如管理帧里,1000=Beacon,0100=Probe Request。
- Bit 8 (To DS):发往分布式系统。1表示帧要去AP。
- Bit 9 (From DS):来自分布式系统。1表示帧来自AP。
- Bit 10 (More Frag):还有分片。1表示后面还有分片。
- Bit 11 (Retry):重传帧。1表示这是重传的。
- Bit 12 (Power Management):省电模式。1表示站点要进入省电状态。
- Bit 13 (More Data):还有数据。AP用这个告诉站点还有缓存数据。
- Bit 14 (Protected Frame):加密帧。1表示帧体被加密了。
- Bit 15 (Order):严格顺序。1表示帧必须按顺序处理。
// Frame Control字段位图示例(C语言结构体)
typedef struct {
uint16_t protocol_version : 2;
uint16_t type : 2;
uint16_t subtype : 4;
uint16_t to_ds : 1;
uint16_t from_ds : 1;
uint16_t more_frag : 1;
uint16_t retry : 1;
uint16_t power_mgmt : 1;
uint16_t more_data : 1;
uint16_t protected_frame : 1;
uint16_t order : 1;
} __attribute__((packed)) frame_control_t;
我曾经调试过一个兼容性问题:某个老款网卡发出来的帧,Retry位总是置1,但实际并没有重传。后来发现是驱动bug,把Retry位当成了保留位在用。所以你看,协议规范是一回事,实际实现又是另一回事。
4.4 Duration/ID字段的作用
这个字段有点意思。它有两个身份:
- 作为Duration:在大多数帧里,它表示NAV(Network Allocation Vector)的值。单位是微秒。其他站点看到这个值,就知道要等多久才能用信道。
- 作为ID:在PS-Poll帧里,它表示站点的AID(Association ID)。取值范围是1-2007。
怎么区分?看bit 15。如果bit 15=0,那就是Duration模式;如果bit 15=1,那就是ID模式。嗯,这里要注意,ID模式下bit 14也必须为1,否则帧格式无效。
避坑指南:我曾经在写驱动时,把Duration字段直接当NAV值用了,没检查bit 15。结果PS-Poll帧来了,解析出来的Duration值大得离谱,导致整个网络卡死。后来加了bit 15判断才解决。
Duration值的计算也有讲究。比如在RTS/CTS握手里,RTS帧的Duration要覆盖整个交换过程:CTS+SIFS+Data+SIFS+ACK。少算一个SIFS,其他站点就可能提前抢信道。
// Duration计算示例(RTS/CTS场景)
uint16_t calculate_duration_for_rts(void) {
// CTS: 14字节 * 8bit / 数据速率 + SIFS
// Data: 帧体长度 * 8bit / 数据速率 + SIFS
// ACK: 14字节 * 8bit / 数据速率 + SIFS
uint16_t cts_time = (14 * 8) / data_rate + sifs;
uint16_t data_time = (frame_body_len * 8) / data_rate + sifs;
uint16_t ack_time = (14 * 8) / data_rate + sifs;
return cts_time + data_time + ack_time;
}
4.5 三种帧的帧体结构差异
帧体(Frame Body)是三种帧差异最大的地方。咱们分别看看:
管理帧的帧体
管理帧的帧体由固定字段和Tagged Parameters组成。固定字段比如Beacon帧里的Timestamp、Beacon Interval、Capability Information。Tagged Parameters就是那些TLV(Type-Length-Value)格式的IE(Information Element)。
- 固定字段:每个管理帧子类型都有自己的固定字段。比如Beacon帧有8字节的Timestamp,2字节的Beacon Interval。
- Tagged Parameters:SSID、Supported Rates、RSN(WPA2)等。每个IE的格式都是:1字节Tag + 1字节Length + 可变Value。
调试经验:抓包时如果发现管理帧解析失败,90%是某个IE的Length字段写错了。我曾经遇到一个AP,它的Vendor Specific IE的Length比实际值大了2,导致后续所有IE都解析错位。
控制帧的帧体
控制帧的帧体最简单,甚至有些控制帧没有帧体。比如RTS帧只有帧头,CTS帧也只有帧头。ACK帧更是只有帧头加FCS。控制帧的帧体长度是固定的,不需要解析可变字段。
| 控制帧类型 | 帧体长度(字节) | 说明 |
|---|---|---|
| RTS | 0 | 只有帧头+FCS |
| CTS | 0 | 只有帧头+FCS |
| ACK | 0 | 只有帧头+FCS |
| PS-Poll | 0 | 只有帧头+FCS |
| CF-End | 0 | 只有帧头+FCS |
| BlockAckReq | 2 | BAR Control + BAR Information |
| BlockAck | 可变 | BA Control + BA Information |
数据帧的帧体
数据帧的帧体就是LLC/SNAP封装后的上层数据。说白了,你的IP包、TCP段都塞在这里面。数据帧的帧体长度可以从0到2304字节(不含加密开销)。
数据帧还有一个特殊变种:QoS Data帧。它在帧头里多了2字节的QoS Control字段,用于标记优先级(TID)。这个在802.11e里引入,用来支持语音视频等实时业务。
// 数据帧帧体解析示例
typedef struct {
uint8_t dsap; // Destination Service Access Point
uint8_t ssap; // Source Service Access Point
uint8_t control; // LLC Control
uint8_t org_code[3]; // SNAP Organization Code
uint16_t ether_type; // Ethernet Type (e.g., 0x0800 for IP)
uint8_t payload[]; // 实际数据
} __attribute__((packed)) llc_snap_header_t;
4.6 帧格式核心逻辑图
下面这张图把三种帧的关系和帧头结构串起来了。我建议你把它存下来,调试的时候对照着看。
4.7 实战中的帧格式调试技巧
说了这么多理论,咱们来点实际的。我总结了几条调试帧格式时的经验:
- 先看FCS:如果FCS校验失败,后面的解析都是白费功夫。先排查物理层问题。
- 再读Frame Control:确定Type和Subtype,选对解析模板。我见过有人拿数据帧模板解析管理帧,结果全乱码。
- 检查Duration/ID的bit15:别把AID当NAV用,否则NAV值会异常大,导致信道被长时间占用。
- 管理帧的IE解析要逐字节核对:一个IE的Length写错,后面全错。我习惯用Wireshark的“Export Packet Bytes”功能导出原始数据,然后自己写脚本逐字节比对。
- 数据帧注意加密:如果Protected Frame位为1,帧体是加密的,直接看是乱码。需要先解密再解析LLC/SNAP头。
曾经踩过的坑:有一次客户反馈说某个AP连不上,我抓包发现Association Request帧的帧体里,Supported Rates IE的Length字段写成了0。AP收到后直接丢弃了关联请求。后来查出来是驱动里一个结构体没初始化。所以啊,帧格式的每一个字节都不能马虎。
好了,帧格式这块就讲到这里。记住一句话:帧头是骨架,帧体是血肉,FCS是灵魂。搞懂了这三者的关系,WiFi协议适配你就入门了。