
1. 为什么讲完CSMA/CA必须单独把虚拟载波监听拎出来说做Wi-Fi协议栈或者无线网络优化的朋友应该都有体会DCF这套机制表面上就是“先听后说、随机退避”面试也好、项目评审也好讲完物理载波监听和退避算法很多人就觉得DCF已经聊透了。但真正踩过吞吐量坑、处理过隐藏节点问题的人会告诉你DCF能在大规模部署下没有彻底崩掉靠的恰恰是被很多人一句话带过的虚拟载波监听机制也就是NAVNetwork Allocation Vector网络分配向量那套逻辑。我见过不止一个刚接触802.11 MAC层的工程师抓包看到某个节点的NAV值被设成几百微秒第一反应是“这不是还没发数据吗为什么要等这么久”第二反应是“Duration字段到底是谁填的填的数字怎么算出来的”。这两个问题如果没搞明白后面不管是做协议分析、写驱动、调无线性能还是排查“为什么某个终端老是不发数据”之类的诡异问题都会卡壳。这篇文章就是打算把虚拟载波监听这件事彻底聊透。我会从DCF的整体框架说起解释为什么在已经有了物理载波监听的情况下协议还要再设计这么一套虚拟的“预约”机制然后拆解NAV的更新规则、Duration字段的解析方式再结合帧交换流程讲清楚不同帧类型下NAV是怎么设置的最后把我在实际抓包和问题排查中遇到过的一些典型场景整理出来希望能帮大家省掉一些自己摸索的时间。2. DCF的基本接入逻辑与虚拟载波监听在其中的位置2.1 CSMA/CA的先天不足信道检测只能覆盖物理层DCF的核心接入方式是CSMA/CA也就是载波侦听多路访问/冲突避免。这里的“载波侦听”字面上指的是物理层检测信道是否忙通过能量检测或者载波状态判断当前信道上有没有人在传输。这套机制在一个“所有人都能听到所有人”的理想环境里是够用的。但现实无线环境比这复杂得多最典型的就是隐藏节点问题站点A在给接入点AP发数据站点B因为距离较远或者中间有遮挡完全感知不到A的传输。B如果这时候也发起传输两个站的帧就会在AP处撞在一起。物理载波监听解决不了这个问题因为B确实“听不到”A的传输。怎么办协议设计者的思路很巧妙既然B听不到A的发送那就让A把自己的发送意图“告诉”所有可能影响到这次传输的节点。怎么告诉就是在帧头里携带持续时间信息让收到帧的节点即使听不到真正的数据发送方也能根据这个信息知道信道接下来一段时间会被占用从而推迟自己的发送。广播覆盖指令信息而这套依赖MAC层Header中的Duration/ID字段来协调信道占用时间的机制就是虚拟载波监听。它相当于让每个节点维护一个小黑板上面写着信道什么时候会空闲在这个时间到达之前谁也不能去碰信道。这个黑板就是NAV。2.2 NAV的本质信道忙不忙不再只靠耳朵听NAV的运作方式可以这样理解每个站点内部帮有一个计时器计时器的值表示信道被预定的剩余时间。站点收到一个不是发给自己的帧时会读取该帧MAC头的Duration字段如果这个值比自己当前的NAV大就更新NAV到该值。然后每个时隙把NAV递减直到归零。NAV不为零站点就认为信道忙即便物理载波监听显示信道是空闲的也不会启动发送。这个机制把“信道是否忙”的判断从单一的物理层扩展到了MAC层物理载波监听判断的是当前的瞬时状态而虚拟载波监听判断的是未来一段时间的预定状态。两者结合起来才是802.11标准中定义的完整载波监听。这里有个细节值得注意NAV并不是在收到任何帧的时候都更新。标准规定只有接收到的帧的地址匹配某些条件时才更新NAV比如帧的接收地址不是本机、也不是广播地址时才会去读Duration字段。如果帧是发给自己的那么说明这是协议交换流程的一部分Duration字段往往代表着当前这个帧交换序列还需要占用信道多长时间这时候站点通常不需要更新NAV去阻碍自己参与交换。2.3 两类载波监听的分工与对比物理载波监听和虚拟载波监听在DCF里的分工可以用一句话概括物理监听管现在虚拟监听管未来。物理监听管的是“现在这一刻信道上有没有能量”它需要接收机持续工作比较耗电虚拟监听管的是“从当前到未来某个时刻信道会不会被占用”它不需要持续检测信道只需要维护一个计时器。两者的协作关系也很有讲究。站点要发送数据时先看NAV是否为0NAV不为0就直接进入退避状态NAV为0时再启动物理载波监听信道空闲DIFS时间后才能进入退避或发送。这在实现上是两个条件同时满足才允许发送的“与”关系。对比维度物理载波监听虚拟载波监听NAV判断依据射频能量、前导码、信号特征MAC头Duration字段表示内容信道当前是否有信号信道未来被预定的时长实现方式物理层持续检测开销较大MAC层计时器维护开销小解决的核心问题当前时刻的冲突隐藏节点带来的未来冲突典型应用场景信道空闲检测RTS/CTS、帧聚合保护需要说明的是这两种载波监听并不总是同时生效。在一些特殊场景下站点即使没有听到物理层信号也会因为NAV不为零而不敢发送这就出现了“明明信道里没有信号但站点就是不发送”的现象。这类问题我在后面的排查实录里会专门展开。3. Duration字段与NAV更新机制的核心细节3.1 Duration/ID字段到底填什么数怎么读所有802.11数据帧、控制帧、管理帧的MAC头里都有一个Duration/ID字段长度是16比特。这个字段在不同帧类型下有两种含义当作为Duration使用时它表示当前帧交换序列需要占用信道的微秒数当作为ID使用时它用于PS-Poll帧中指定站点电源管理标识。绝大多数情况下我们是把它的值当作时间来读的。这个时间的计算方式与帧的类型和所处的交换流程密切相关。对于最常见的单播数据传输一个完整的数据ACK交换Duration字段的典型取值等于一个SIFS时间加上一个ACK帧的传输时间。例如在802.11a/g环境下SIFS为16微秒ACK帧以最低速率6 Mbps发送时需要大约44微秒那么在数据帧里的Duration值就约等于60微秒左右。RTS帧里的Duration则要覆盖更长的周期SIFS加上CTS的传输时间加上SIFS加上数据帧的传输时间加上SIFS再加上ACK的传输时间。CTS帧中的Duration是在RTS的Duration基础上减去SIFS和CTS自身传输时间得出的目的是让接收方周围的节点预定的信道释放时间与实际完成时间对齐。这两处细节在做协议分析时很容易算错尤其是在抓包工具显示的时间与实际帧间隔存在微小时差的情况下更要理解每个数字的来源。3.2 NAV的更新条件与“不大于不更新”原则NAV更新的核心原则是收到帧中Duration值大于本地NAV当前值时更新否则不更新。这条原则在实际实现中非常关键它保证了NAV不会因为乱序帧或链路层重传而不断回拨也就是不能因为收到一个Duration较小的老帧就把本来预定的信道释放时间提前。举个例子站点C已经通过一个CTS帧把NAV设置成200微秒过了一会儿它又收到一个来自其他节点的RTS帧其中Duration字段只有100微秒。按照规则这个100微秒不会覆盖已有的200微秒NAV保持200微秒不变。只有收到Duration大于200的帧时NAV才会被推到更晚的释放时间。这个设计的合理性在于NAV代表的是一个保护时长的“上界”。所有节点在参与信道预定的时候都会尽量把自己能计算的最长占用时间广播出去收到方取其中一个最大值来维护这样才能确保在这一轮帧交换真正结束之前不会有节点插进来。如果采用“覆盖式”更新一个Duration较短的帧就可能把另一个节点早已宣布的更长占用时间抹掉保护窗就会出现漏洞隐藏节点问题就会重新冒出来。3.3 帧地址与NAV更新之间的微妙关系前面提到并非所有帧都会触发NAV更新具体规则与帧头里的地址有关。这里详细展开一下。对于单播帧如果一个节点收到一个帧但帧的接收地址不是自己它就会根据Duration更新NAV。这一条逻辑很容易理解这帧是别人之间的通信与自己无关那就按对方预定的时间避让。如果帧的接收地址是自己那么这就是一个正常通信流程中的环节自己需要参与响应不能设NAV把自己挡在门外。比如AP给站点发了一个数据帧站点需要回复ACK如果站点把这个数据帧的Duration写进NAV那它连自己的ACK都没法发出去了。对于广播帧和多播帧情况有些特殊。广播/多播帧的接收地址是一个组地址理论上所有属于这个组的站点都是接收者。标准对这类帧的NAV更新规则在不同版本中有过调整。在802.11-2012之前标准要求收到广播/多播帧后更新NAV之后的版本对部分场景进行了细化。实际开发中很多协议栈对广播帧的Duration字段处理得比较保守要么不更新NAV要么只在特定条件下更新。原因是广播帧本身无法通过ACK机制确认误设NAV可能导致一段时间的信道空转。3.4 加密帧对NAV解析的影响这里要提一个做协议分析时经常会遇到的问题加密帧。在WPA2/WPA3环境下MAC头的Duration字段是明文传输的受保护的是帧体部分。所以即使一个节点解不了加密帧的内容它依然可以读取Duration字段并更新NAV。这是虚拟载波监听机制能在加密环境中正常工作的前提。但有一种情况下Duration字段会变得不可靠就是当某个帧使用了非标准的Duration取值或者驱动在填充Duration字段时因为帧聚合、重传等场景计算失误填了一个不合理的值。这类问题属于实际设备中的实现缺陷排查起来比较麻烦因为抓包工具只会如实显示数值不会告诉你这个值填得对不对。建议在分析这类问题时把同一交换流程中RTS、CTS、数据帧和ACK的Duration值对照起来看通常能发现问题所在。4. 虚拟载波监听在帧交换流程中的完整应用4.1 基础数据交换中的NAV设定最简单的场景是两台设备之间的单播数据发送。发送方在发送数据帧时把Duration字段设置为SIFS加ACK时长。接收方收到这个数据帧后由于帧的接收地址是自己不会更新NAV而是在SIFS时间之后回复ACK。ACK帧自身的MAC头也有Duration字段这个值通常设为0因为这个交换序列已经结束不需要再预留信道。此时其他节点的情况是它们收到了数据帧因为不是发给自己的会根据数据帧的Duration值更新NAV。这样在数据帧还没传完、接收方还没回复ACK的这个时间段内周围节点会因为NAV的存在而保持沉默。这就是最基本的虚拟载波监听保护场景。如果没有这种保护周围的节点只靠物理载波监听很可能在数据帧发送完毕后、ACK回复之前的SIFS间隔内抢入信道导致ACK被破坏。我知道有人会问SIFS只有十几微秒其他节点物理载波监听难道检测不到信道忙吗答案是可以但在高密度场景下信道从忙变闲的瞬间是竞争最激烈的时候多个节点同时退出退避、同时发起发送的概率并不低。NAV的作用就是把SIFS和ACK所占用的窗口也一并保护起来在整个帧交换序列完成之前不给其他节点任何机会。4.2 RTS/CTS机制中的NAV链路RTS/CTS可选的机制说明了虚拟载波监听为什么是解决隐藏节点问题的关键手段。当发送方A要发送一个较长数据帧时先发一个RTS帧RTS的Duration字段覆盖整个交换序列SIFS加CTS时间加SIFS加数据帧时间加SIFS加ACK时间。接收方B收到RTS后回复CTSCTS中的Duration字段等于RTS的Duration减去SIFS和CTS自身的传输时长也就是只覆盖数据帧、SIFS和ACK这段后续时间。关键点在于A周边的节点可能因为距离或遮挡听不到B的CTS但B周边的节点听得到这些节点会被CTS的Duration锁住。反过来B周边的节点可能听不到A的RTS但A周边的节点听得到这些节点会被RTS的Duration锁住。这样A和B各自的“可听范围”通过RTS和CTS两次广播形成了一个覆盖交换双方的保护区隐藏节点问题因此被大幅缓解。听不到RTS但能听到CTS的节点可能根本不知道是谁在发送数据只知道这个信道上某个方向正在进行通信自己需要沉默多久这就是虚拟载波监听的典型价值所在不需要理解通信内容只需要知道“信道什么时候能空出来”。4.3 帧聚合与Block ACK中的NAV计算到了802.11n/802.11ac/802.11ax时代帧聚合让单次信道占用时间变得很长NAV的计算也从“数据ACK”变成了“多个MPDU加Block ACK”。发送方聚合了大量子帧后需要计算整个A-MPDU的传输时间加上SIFS和Block ACK的时间填入第一个帧的Duration字段。这里有个实现细节需要注意在一个A-MPDU中并非所有子帧的Duration字段都会被正确填充。很多硬件实现只在第一个子帧中设置有效的Duration值后续子帧可能全部填0。只要第一个子帧的Duration能被接收方正确解析并用于NAV更新就足够保护整个聚合传输了。但如果有工具或协议分析器把每个子帧单独解析就会看到一部分子帧Duration为0容易误判为异常。我在实际测试中遇到过聚合帧中部分子帧Duration填写错误导致周围节点过早解除NAV的情况表现为链路吞吐下降。排查时需要把硬件驱动和网卡固件的版本、聚合参数都纳入考虑因为这类问题往往不是协议本身的问题而是设备实现层面的缺陷。4.4 管理帧与NAV的互动管理帧在802.11协议栈中经常被忽视其实管理帧同样参与NAV的设定。典型的例子是Beacon帧Beacon中的Duration字段通常为0表示它不参与预定信道。但一些特定的管理帧比如Channel Switch Announcement、Extended Channel Switch Announcement等在特定场景下可能会携带非零Duration值。更值得注意的是有些设备在发送管理帧重传时Duration字段的填充逻辑可能与数据帧不同。在做无线网络侦测时如果发现某个管理帧的Duration值异常大建议先确认这个帧是否来自某厂商设备的特定固件版本不要一上来就判定为攻击行为。当然对于常见的deauth flood攻击攻击者会把Duration字段填入较大值来长时间虚占信道这种场景下正确识别Duration的异常行为反而是检测攻击的重要手段之一。5. 常见问题与排查技巧实录5.1 场景一NAV被异常拉高信道明明空闲却不发送这个场景我遇到过多次。现场表现是某个无线终端的上行速率突然掉得很厉害抓包发现它一直在退避但物理信道其实很干净几乎没有其他传输。进一步抓包发现这个终端的NAV被设置成了一个较大数值导致它长期处于虚拟忙状态。排查思路是这样的先看是谁把NAV拉高的。方法是在终端附近抓包找到Duration值异常大的帧检查这个帧的来源。常见原因有两种一是某个设备发送了带大Duration值的帧但这个帧没有被正确接收导致只有部分节点更新了NAV二是某个节点常见的是一些老旧网卡设备在发送管理帧或空数据帧时Duration字段填充错误把保护时间填成了一个过大的值。解决办法是在AP侧和终端侧同时抓包对照两边的NAV变化时间点定位到具体是什么帧把NAV拉高的。如果是某个设备固件问题常规手段是升级固件如果是攻击行为则需要在AP上启用帧过滤或入侵检测规则。5.2 场景二加密环境下看不到Duration内容严格来说加密环境下Duration字段是看得到的这个问题实际上出在抓包工具身上。部分抓包工具在解析加密帧时如果无法解出帧体内容就把整个帧标记为“解析失败”导致Duration字段信息不容易直接读取。这时候需要把配置改成“即使无法解密也显示MAC层信息”或者使用支持Raw 802.11模式的抓包工具手动读取Frame Control和Duration字段。另外提醒一句在Wireshark中NAV的值通常显示在802.11头部信息里看起来像“NAV327”这样的格式。但Wireshark的NAV字段是它根据Duration值计算结果不一定是真实芯片寄存器中的NAV值。对芯片内部逻辑做调试时还是得用芯片厂商提供的调试工具读取实际寄存器值不能只依赖抓包工具的显示。5.3 场景三RTS/CTS开启后部分老设备无法通信开启RTS/CTS后出现兼容性问题的场景也不少见。有些老设备对CTS帧中的Duration解析有bug更新NAV时算错了时间导致长时间不发数据。另一种情况是某些设备在收到RTS后回复CTS时会把自身NAV设置为RTS的Duration值而不是像标准要求的那样先清除再处理后续帧出现NAV叠加计算错误。这类问题最有效的排查方法是先关掉RTS/CTS确认问题是否消失。如果确认是RTS/CTS导致的问题再尝试调整RTS阈值让只有大帧才走RTS/CTS小帧仍然走基本接入方式。这样做既能兼容老设备也能保留RTS/CTS对隐藏节点环境的保护能力。5.4 场景四漫游场景下NAV残留导致接入异常终端从一个AP漫游到另一个AP后如果之前所在信道的NAV计时器没有正确清零可能导致终端在新信道上长时间不发数据。这种问题在跨信道漫游时更容易出现因为终端需要切换到新信道物理层检测和MAC层状态都需要重新初始化。排查时重点看终端的日志关联到新AP之后是否出现了长时间的Tx暂停或MAC层退避记录。如果确认是NAV残留问题通常需要驱动在信道切换时强制清除NAV。很多商用驱动已经做了这个处理但一些嵌入式平台的驱动实现不完整需要手动打补丁。5.5 抓包验证NAV变化的一个可用小技巧跟大家分享一个直接有效的方法准备两台电脑一台运行抓包工具并设置为监听模式另一台作为普通站点连接AP并持续发送数据。在抓包端把过滤条件设为带Duration字段的帧观察一系列帧的Duration值变化。重点看RTS和CTS的Duration值是否与数据帧长度、速率匹配如果不匹配通常说明发送方驱动在计算传输时间时出了问题。这个方法还可以用来验证一个AP的EDCA参数是否配置正确。因为不同接入类别的帧在Duration上的计算逻辑虽然一样但实际发送时使用的速率和退避参数不同会导致Duration值的分布存在差异。当然这个进阶话题涉及QoS机制的细节以后有机会单独写一篇展开聊。6. 关于虚拟载波监听底层实现与性能权衡的几点个人体会聊到这里虚拟载波监听的原理和实际应用已经讲得比较全面了。最后再分享几点我在阅读802.11协议标准和做实际调试时的一些体会。第一标准里关于NAV的表述是经过精心设计的每一处看似冗余的规则背后几乎都有实际场景的考量。比如“不大于不更新”这个原则就是在多节点竞争环境下保证保护窗口完整性的基础。读标准的时候如果只看字面逻辑很容易觉得某些规则规定得过于繁琐只有结合实际的问题场景去读才能理解设计者的意图。第二虚拟载波监听虽然能有效缓解隐藏节点问题但并非万能。当节点密度极高时NAV的频繁更新会让很多节点进入长时间的虚忙状态反而浪费了信道的空闲资源。这也是为什么后来的802.11ax引入了OFDMA和BSS Coloring机制这些机制在某种程度上就是要降低虚拟载波监听的“保守度”提高空间复用率。理解了这一层演进逻辑再去看802.11ax的那些新特性思路会清晰很多。第三做协议分析时一定要区分“协议标准的规定”和“现实设备的实现”抓包工具显示的信息并不等于芯片内部真实的寄存器状态。很多看似诡异的现象最终追下去都是设备驱动或固件的实现差异引起的。遇到此类问题时多拿两台不同品牌设备交叉验证比单凭抓包数据下结论可靠得多。无线协议这条路上的坑确实不少但每踩一个坑、再把对应的机制弄明白对整个网络的理解就会深一层。希望这篇文章能帮你在虚拟载波监听这块省下一些摸索的时间也欢迎有实际工程经验的朋友多交流一起把这一块的理解补得更扎实。