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 帧格式核心逻辑图

下面这张图把三种帧的关系和帧头结构串起来了。我建议你把它存下来,调试的时候对照着看。

WiFi帧格式核心逻辑图 WiFi帧格式核心逻辑图 通用帧头(30字节) Frame Control Duration/ID Address 1-4 Seq Control QoS Control HT Control 管理帧 Type=00 子类型:Beacon, Probe Req/Resp Association Req/Resp, Auth 帧体:固定字段 + Tagged IE 控制帧 Type=01 子类型:RTS, CTS, ACK PS-Poll, CF-End 帧体:通常为0(无帧体) 数据帧 Type=10 子类型:Data, QoS Data Null Data, CF-ACK 帧体:LLC/SNAP + IP载荷 关键说明 1. 所有帧共享通用帧头,但帧体结构差异巨大 2. Frame Control的Type字段决定帧类别,Subtype决定具体行为 3. Duration/ID字段:bit15=0时为NAV值,bit15=1时为AID 4. 管理帧帧体最复杂(固定字段+TLV),控制帧最简单(通常无帧体)

4.7 实战中的帧格式调试技巧

说了这么多理论,咱们来点实际的。我总结了几条调试帧格式时的经验:

  1. 先看FCS:如果FCS校验失败,后面的解析都是白费功夫。先排查物理层问题。
  2. 再读Frame Control:确定Type和Subtype,选对解析模板。我见过有人拿数据帧模板解析管理帧,结果全乱码。
  3. 检查Duration/ID的bit15:别把AID当NAV用,否则NAV值会异常大,导致信道被长时间占用。
  4. 管理帧的IE解析要逐字节核对:一个IE的Length写错,后面全错。我习惯用Wireshark的“Export Packet Bytes”功能导出原始数据,然后自己写脚本逐字节比对。
  5. 数据帧注意加密:如果Protected Frame位为1,帧体是加密的,直接看是乱码。需要先解密再解析LLC/SNAP头。

曾经踩过的坑:有一次客户反馈说某个AP连不上,我抓包发现Association Request帧的帧体里,Supported Rates IE的Length字段写成了0。AP收到后直接丢弃了关联请求。后来查出来是驱动里一个结构体没初始化。所以啊,帧格式的每一个字节都不能马虎。

好了,帧格式这块就讲到这里。记住一句话:帧头是骨架,帧体是血肉,FCS是灵魂。搞懂了这三者的关系,WiFi协议适配你就入门了。