目录 [隐藏] TCP 丢包检测：为什么重要？SACK：Selective Acknowledgment（选择性确认）FACK：Forward Acknowledgment（前向确认）RACK：Recent Acknowledgment（基于时间的确认）TLP：Tail Loss Probe（尾部丢包探测）RACK-TLP：现代 TCP 的黄金组合对比总结 在传输控制协议（TCP）数十年的演进中，丢包检测与恢复机制始终是影响传输性能的核心环节。从最初依赖序列号判断丢包的 Reno 模式，到引入选择性确认的 SACK，再到基于时间判定的 RACK，乃至进一步强化的 RACK-TLP，TCP 的恢复能力经历了逐步现代化的升级。本文将带你从理论层面，深入理解 TCP 的四种关键丢包检测机制：FACK、SACK、RACK 与 RACK-TLP，并对比它们的工作原理、适用场景与演进关系。TCP 丢包检测：为什么重要？丢包是 TCP 面临的常见网络问题。能否及时发现丢包并快速恢复，直接决定了 TCP 在以下场景中的表现：弱网或移动网络（丢包率高、时延抖动大）；数据中心高吞吐场景（对恢复速度敏感）；中间设备存在乱序、ACK 压缩行为。传统 TCP 采用 RTO（Retransmission Timeout） 机制来兜底触发重传。但初始 RTO 延迟高达 1 秒，误判多，恢复慢，成为瓶颈。因此，TCP 社区不断探索更快、更精准的丢包检测算法。SACK：Selective Acknowledgment（选择性确认）原理：接收方在 ACK 中显式告诉发送方：“我已经收到哪些数据段”，而不是仅告诉“收到到哪了”。例如：如果收到了第 1、2、5 段，SACK 可以告诉发送方：我收到了 [1-2] 和 [5]，中间 [3-4] 丢失。优点：精确指出哪些段被接收；适用于多段丢包场景；避免不必要的重复重传。局限：虽然能知道“哪里没收到”，但仍依赖序列号判断丢包；容易受到网络乱序、ACK 压缩的干扰，误判丢包或延迟重传。FACK：Forward Acknowledgment（前向确认）原理：FACK 在 SACK 的基础上进一步演化。它不仅看 SACK 报告了哪些段，还估算：“接收方到底收到了多少字节？”通过前向确认值（Forward ACK），FACK 推测数据流的最前进位置，从而判断哪些段在这个位置之前却没被确认，更可能丢失。优点：提升丢包恢复的粒度与速度；可以更准确判断哪些段未收到。局限：依然依赖序列号推断；在中间设备导致乱序或 ACK 抖动时仍可能误判。RACK：Recent Acknowledgment（基于时间的确认）定义于 RFC 8985原理：RACK 完全抛弃了“序列号先后”来判断丢包，转而使用“发送时间”作为核心依据。核心思想：如果一个数据段比其他段更早发送，但对方先确认了后发的数据，那么早发未确认的很可能丢了。举个例子：第 10、11、12 段几乎同时发送；ACK 收到 11 和 12，唯独没收到 10；RACK 判断：第 10 段被丢了，不等 RTO，直接重传。优点：更快发现丢包；不受乱序、ACK 压缩等影响；大幅提升弱网或数据中心 TCP 的表现。前提：TCP 栈需准确记录每个段的发送时间；依赖 SACK 辅助判断哪些段被确认。TLP：Tail Loss Probe（尾部丢包探测）原理：TLP 设计是为了解决“尾部最后几个数据段被丢，但连接进入沉默”的问题。在传统 TCP 中，这种场景只能等待 RTO 超时，可能导致 1 秒以上延迟！TLP 的做法是：检测连接是否在“尾部”安静阶段；主动发一个探测包（probe）来“敲门”；如果 ACK 回来，一切正常；如果没有，尽快触发重传。优点：避免长时间挂起；恢复速度远快于 RTO。RACK-TLP：现代 TCP 的黄金组合RACK + TLP = 快速、鲁棒的 TCP 丢包检测与恢复体系RACK 提供 时间驱动的准确丢包检测，TLP 提供 尾部沉默保护，两者组合应对大多数网络复杂场景。这种组合是目前 Google、Facebook、AWS 等大规模生产网络中的默认选择，也正逐步成为现代 TCP 栈的主流机制。对比总结特性SACKFACKRACKRACK-TLP丢包判断依据序号 + SACK序号 + 前向确认发送时间发送时间 + 探测包抗乱序能力一般一般强强响应速度中等较快快快速且稳健实现复杂度中较高高高是否现代推荐（已基础）（中间形态）（现代默认）（最佳实践）TCP 的丢包检测机制已经从最早的基于序号推测，演进到现在以“发送时间 + 探测包”为核心的智能模型：FACK 是对 SACK 的增强，更好地衡量未确认数据；RACK 是范式转移，开创“时间驱动的丢包检测”；TLP 补齐了边界条件，保护连接不被挂起；RACK-TLP 组合，代表了现代 TCP 的最佳丢包恢复路径。
目录 [隐藏] 一 特性剖析1. TLS 1.3 VS 1.22. 安全增强2.1 PSK KE VS PSK ECHDE2.2 密钥派生3. 2-RTT VS 1-RTT4. 0-RTT Early Data5. 会话恢复二 测试分析1. 编译使用2. 测试汇总3. VS Code 调试4. Wireshark 抓包分析4.1 TLS 1.2 完整握手4.2 TLS 1.3 0-RTT Early Data4.3 会话恢复 这里结合 RFC8446，MbedTLS 源码， Wireshark 抓包对 TLS 1.3 几个重要特性做深入剖析。一 特性剖析1. TLS 1.3 VS 1.2https://datatracker.ietf.org/doc/html/rfc8446#section-1.2 。TLS 1.3 对比 TLS 1.2 的主要差异。项目条目TLS 1.2TLS 1.3加密与算法对称加密算法支持多种，包括已废弃的 CBC、RC4仅支持 AEAD 算法（如 AES-GCM、ChaCha20-Poly1305） 密钥派生算法自定义复杂结构 KDF使用统一的 HKDF（HMAC-based Extract-and-Expand） 密钥交换机制支持 RSA、DH、ECDHE，包括静态密钥移除静态 RSA/DH，统一使用 ECDHE，具备前向安全 签名算法支持RSA、DSA、ECDSARSA、DSA、ECDSA安全改进前向安全（PFS）可选，取决于是否使用 (EC)DHE所有公钥交换均具备前向安全 报文完整性保护加密和 MAC 分离（MAC-then-encrypt）使用 AEAD（集成加密+认证） 证书扩展等隐私保护扩展字段如 SNI 以明文发送扩展通过 EncryptedExtensions 加密传输握手机制握手消息加密ServerHello 后部分消息仍明文ServerHello 后全部握手消息加密 握手流程结构含 ServerHelloDone、ChangeCipherSpec精简状态机，移除冗余消息 0-RTT 数据支持不支持支持 0-RTT，允许部分早期数据发送（有安全限制）会话恢复会话恢复机制Session ID / Session ticket 两种方式统一为 PSK-based 会话恢复，使用 SessionTicket + Binder PSK 模式较少使用明确支持 PSK-only / PSK + ECDHE兼容性机制版本协商通过 ClientHello.version，存在兼容问题使用 extension 提供版本列表，增强兼容性 压缩与 DSA支持 TLS-level 压缩、支持 DSA移除压缩与 DSA，避免压缩攻击重点介绍全新特性前。介绍下 TLS 1.3 的完整握手流程。https://datatracker.ietf.org/doc/html/rfc8446#section-2如上完整握手流程，包含密钥交换（Key Exchange）和双向认证（Authentication，可选）流程。对于密钥交换仅支持 ECDHE。对于认证支持证书认证和 PSK 认证，当然也可以不做认证。如上 KE 和 Auth 流程组合，TLS 1.3 共支持如下5种模式。握手模式描述完整握手ECDHE使用 ECDHE 密钥交换，无认证。 PSK ECHDE使用预设密钥 PSK 快速认证 + ECDHE 密钥交换 证书 ECHDE使用证书进行身份验证 + ECDHE 密钥交换会话恢复PSK ECDHE基于 Session Ticket 快速恢复会话，流程与 PSK-ECDHE 类似，区别在于 PSK 来源不同：完整 PSK-ECDHE 使用预共享凭据；会话恢复则使用前次握手后协商出的临时 PSK。 PSK Only基于 Session Ticket 快速恢复会话，但是不做 ECHDE。需要说明的是，尽管不做 ECHDE，但是还有一个简单 KE，同 TLS 1.2 PSK KE 原理相似。在后面章节会详细介绍。2. 安全增强2.1 PSK KE VS PSK ECHDETLS 1.2 PSK KE 对比 TLS 1.3 PSK ECDHE 一个安全特性就是前向安全。 前向安全确保了长期密钥泄露不会影响历史会话的安全性。以下是 TLS 1.2 PSK KE 与 TLS 1.3 PSK ECDHE 的握手流程对比。在密钥交换过程中，最大的区别在于是否引入了临时椭圆曲线 Diffie-Hellman 的密钥交换。注意：如上为简化公式。完整的密钥派生会在后面详细介绍。在 TLS 1.2 PSK KE 模式下，会话密钥完全依赖预共享密钥（PSK），如果 PSK 泄露，攻击者可以离线解密之前或当前的通信内容。提示：ClientRandom 和 ServerRandom 为明文交换，所以 PSK 泄漏，MasterSecret 就会泄漏。在 TLS 1.3 PSK ECDHE 模式下，引入了临时的 ECDHE ClientShare 和 ServerShare 公钥交换，即使 PSK 被泄露，也无法恢复会话密钥，因此提供了前向安全性。提示：尽管 ClientShare、ServerShare、ClientRandom、ServerRandom 都是明文交换，但是 ECDH 共享密钥计算需要私钥参与。所以 MasterSecret 不会直接泄漏。2.2 密钥派生https://datatracker.ietf.org/doc/html/rfc8446#section-7.1如下流程为 TLS 1.3 完整的密钥派生流程。HKDF-Extract 为熵压缩器，可以看作是一个安全的“种子生成器” 。需要输入 salt 和 IKM（Input Key Material）。如下框图为从上方获取 Salt 参数，从左侧获取 IKM 参数，输出结果向下，其输出变量名写在右边。Derive-Secret 为密钥派生器，可以理解为如上“种子”长出多种不同用途的“树”。需要输入：Secret 和 Label 和 Messages。Derive-Secret(Secret, Label, Messages) = HKDF-Expand-Label(Secret, Label, Transcript-Hash(Messages), Hash.length)如下框图为从上方获取 Secret 参数，Label 和 Messages 已经包含在 Derive-Secret() 函数参数中。其中 ClientHello...ServerHello 表示两条消息的 hash 值。ClientHello...server Finished 表示从 ClientHello 到 Finished 所有消息的 hash 值。0 表示一个长度为 Hash.length 字节的全 0 字节串。首先需要具体说明每一个密钥的使用场景。阶段密钥描述Early Secret 早期密钥binder_key用于验证 PSK 的合法性（在 ClientHello 中计算 binder HMAC） client_early_traffic_secret用于 加密 0-RTT 数据（仅客户端发送） early_exporter_master_secret用于早期阶段的数据导出（如 token 等）Handshake Secret 握手阶段密钥client_handshake_traffic_secret加密客户端握手阶段消息，如 Certificate、Finished server_handshake_traffic_secret加密服务端握手阶段消息，如 Certificate、FinishedMaster Secret 主密钥server_application_traffic_secret_0加密服务端应用层数据 exporter_master_secret用于导出额外密钥材料（给上层协议或 token 用） resumption_master_secret会话恢复时用于生成 Session Ticket 中的 PSK如上密钥派生流程，主要输入信息为 PSK 和 ECDHE。上面我们已经讨论了 TLS 1.3 的5种不同模式。接下来我们看看不同模式的 PSK 和 ECDHE 信息。握手模式描述PSK完整握手ECDHE使用 ECDHE 密钥交换，无认证。无 PSK，有 ECHDE PSK ECHDE使用预设密钥 PSK 快速认证 + ECDHE 密钥交换预设 PSK，有 ECHDE 证书 ECHDE使用证书进行身份验证 + ECDHE 密钥交换无 PSK，有 ECDHE会话恢复PSK ECDHE基于 Session Ticket 快速恢复会话，流程与 PSK-ECDHE 类似，区别在于 PSK 来源不同：常规 PSK-ECDHE 使用预共享凭据；会话恢复则使用前次握手后协商出的临时 PSK。有 PSK，PSK = HKDF-Expand-Label(resumption_master_secret, "resumption", ticket_nonce, Hash.length)PSK 由 resumption_master_secret（上次会话派生出） 和 ticket_nonce（上次会话临时临时生成，通过 NewSessionTicket 发送给客户端）。有 ECDHE PSK Only基于 Session Ticket 快速恢复会话，但是不做 ECHDE。需要说明的是，尽管不做 ECHDE，但是还有一个简单 KE，同时 TLS 1.2 PSK KE 原理一致。在后面章节会详细介绍。有 PSK，PSK 同上。无 ECDHE。3. 2-RTT VS 1-RTTTLS 1.3 的一个重要特性是优化了握手阶段的消息交互效率，将原本的 2-RTT 优化为 1-RTT，主要通过以下两个机制实现：在 TLS 1.2 中，ClientKeyExchange 和 ServerKeyExchange 是独立消息；而在 TLS 1.3 中，这些密钥交换参数被整合进 ClientHello 和 ServerHello 的 key_share 和 pre_shared_key 扩展中，避免了额外的往返消息。对于身份验证相关的 Finished 消息，TLS 1.2 是由客户端先发送，TLS 1.3 则调整为服务端先发送，这使得 Finished 可以合并在服务端的响应路径中发出。另外对于 RTT 计算你可能还会有如下疑问。TLS 1.3 最后还需要发送 Finished 消息，应该算 1.5 RTT？对于客户端 Application Data 可以和 Finished 可以一起发送。对于 ServerHello 和 CertificateRequest 以及 Certificate 是单独发送为什么 RTT 算在一起？在 TLS 1.3 中，服务端从 ServerHello 开始，接下来可以批量发出：EncryptedExtensions → Certificate → CertificateVerify → Finished，中间不需要等待客户端的响应，因此这整个过程都算作第 1 个 RTT 内的服务端响应部分。如果 TCP RWND = 1 MSS，并且不支持 Trickle ，RTT 如何计算？在特定网络条件下（RWND = 1 MSS，不支持 Trickle），TLS 1.3 中服务端确实无法一次性发完所有握手消息，需要多个 TCP ACK 往返来推动 TLS 消息发出。在网络层面上 RTT 被拉长了：虽然 TLS 协议逻辑上是一轮 RTT完成握手，但实际 由于 TCP 的传输能力受限，ServerHello 后的证书链等可能被拆分成多个 TCP 分段发送。这个时候，从“时延体验”上看类似于 2-RTT，但协议本身仍然是 1-RTT 的握手逻辑。4. 0-RTT Early Datahttps://datatracker.ietf.org/doc/html/rfc8446#section-2.3首先说明应用场景。0-RTT Early Data 只能用以有 PSK 的握手流程。基于 Resumption PSK 会话恢复流程基于 External PSK 的完整握手流程不能在首次基于证书的完整握手流程中使用。提示：如图所示绿色握手消息表示 Early Data 特定流程。如上图所示ClientHello客户端发起握手，并尝试发送 Early Data，包含以下关键扩展：字段描述+ early_data告诉服务端：“我打算发送 0-RTT 数据”。仅作为信号，无内容。+ pre_shared_key包含 PSK identity（用于识别会话）和 binder（对 ClientHello 做 MAC 认证）。用于认证服务器并恢复密钥。(Application Data*)如果客户端在 ClientHello 中附带了 early_data 扩展，它可以立即附加加密的 Early Application Data（比如一个 HTTP 请求或 MQTT CONNECT）。该数据使用 client_early_traffic_secret 派生出的密钥进行加密。ServerHello服务端响应握手，并选择一个 PSK：字段描述+ pre_shared_key告诉客户端：“我接受了你提供的哪个 PSK identity”。这一决定决定了后续密钥派生路径。{EncryptedExtensions}这是 TLS 1.3 新增的加密消息，用于传输额外扩展字段：字段描述+ early_data*如果服务端接受了 0-RTT 数据，则必须发送该扩展，告知客户端 early data 被接受；否则不发送，表示拒绝 early data。(EndOfEarlyData)如果客户端确实发送了 Early Data，那么它在收到服务端 EncryptedExtensions 后，必须发送 EndOfEarlyData 消息。它是握手中的一个标记，告知服务端：早期应用数据（0-RTT）已经发送完毕。它也意味着后续将切换到 Handshake Secret 加密上下文。[Application Data*]这是服务端在握手完成后发送的正常 Application Data：使用 server_application_traffic_secret 加密；它可能包含服务端响应，例如 HTTP 响应或 MQTT CONNACK。总结：消息加密上下文作用说明ClientHello明文携带 PSK、声明 early_data 意图(Application Data*)使用 early secret 加密早期发送的业务数据ServerHello明文响应 ClientHello，指定使用哪个 PSK{EncryptedExtensions}Handshake Secret服务端确认是否接受 early_dataEndOfEarlyDataHandshake Secret客户端结束 early data 发送[Application Data*]Application Secret正式数据传输阶段开始5. 会话恢复https://datatracker.ietf.org/doc/html/rfc8446#section-2.2https://datatracker.ietf.org/doc/html/rfc5077会话恢复是基于完整 1-RTT 握手协议进一步优化的机制，旨在减少连接建立时的延迟开销。TLS 1.2 支持两种会话恢复机制：Session ID（服务端有状态）和 Session Ticket（服务端无状态）。TLS 1.3 延续了 Session Ticket 的会话恢复机制，但不再直接恢复 Master Secret，而是将 Resumption Secret 用作 PSK，重新进行密钥派生。该过程支持可选的 ECDHE 交换（PSK+ECDHE），进一步增强前向安全性。看看3种完整握手流程的在2种会话恢复下的收益。完整握手握手模式会话恢复恢复模式收益完整握手ECDHE会话恢复PSK ECDHE无收益 PSK Only省略 ECDHE 流程 PSK ECHDE会话恢复PSK ECDHE无收益，还增加 PSK 解密流程 PSK Only省略 ECDHE 流程 证书 ECHDE会话恢复PSK ECDHE省略证书如流程 PSK Only省略证书认证和 ECDHE 流程提示：如图所示绿色握手消息表示 PSK 会话恢复特定流程。https://datatracker.ietf.org/doc/html/rfc5077#section-4TLS 1.3 会话恢复的关键路径是：完成完整握手后，服务端通过 NewSessionTicket 向客户端下发一个加密的 Ticket。该 Ticket 由服务端的 Ticket Protection Key 加密，服务端不保存 Ticket 内容，因此这种机制被称为“无状态的会话恢复”。在后续会话恢复阶段，客户端将该 Ticket 原样通过 ClientHello 的 pre_shared_key 扩展回传，服务端解密后取出其中的 PSK，重新执行密钥派生流程，从而恢复会话。详细看看 Ticket 数据结构。https://datatracker.ietf.org/doc/html/rfc8446#section-4.6.1struct { uint32 ticket_lifetime; // 客户端可使用该 Ticket 的有效时间（单位：秒） uint32 ticket_age_add; // 随机扰动值，防止重放攻击，服务端生成 opaque ticket_nonce<0..255>; // 用于导出 PSK 的 nonce 值（参与 HKDF） opaque ticket<1..2^16-1>; // 实际 Ticket 内容（密文） Extension extensions<0..2^16-2>; // 可选扩展，例如 early_data 支持等 } NewSessionTicket;https://datatracker.ietf.org/doc/html/rfc5077#section-4struct { opaque key_name[16]; // 标识用于加密该 ticket 的密钥名称 opaque iv[16]; // AEAD 加密使用的初始化向量 opaque encrypted_state<0..2^16-1>; // 加密的 session state（服务端状态） opaque mac[32]; // AEAD 加密产生的认证标签（MAC） } ticket;提示：而对于 PSK 加密的 encrypted_state 数据结构在 RFC 8446 和 RFC 5077 均没有详细定义。需要结合 https://github.com/Mbed-TLS/mbedtls/blob/v3.6.3/library/ssl_tls.c#L3818 代码。struct { uint32_t ticket_age_add; // 与 NewSessionTicket 中一致，用于计算实际 age uint8_t ticket_flags; // 标志位，如是否支持 0-RTT 等 uint8_t resumption_key_len; // PSK 长度（通常是 32） uint8_t resumption_key[32]; // 客户端用于恢复 early_secret 的 PSK uint32_t ticket_lifetime; // Ticket 生命周期，服务端控制 uint16_t ticket_len; // ticket ID 长度 uint8_t ticket[ticket_len]; // 唯一标识该 Session Ticket 的 ID // 以下为可选字段（根据编译选项启用） uint64_t ticket_creation_time; // Ticket 创建时间（用于服务端过期检查） uint32_t max_early_data_size; // 客户端支持的最大 Early Data 大小 uint16_t record_size_limit; // 最大记录大小限制 uint16_t hostname_len; // 主机名长度（客户端 SNI） uint8_t hostname[hostname_len]; // 主机名内容（如有） uint16_t alpn_len; // ALPN 协议长度 uint8_t alpn[alpn_len]; // ALPN 协议内容 } encrypted_state;TLS 1.3 的会话恢复不同于 TLS 1.2 的直接恢复 Master Secret。其核心增强在使用 resumption_key 作为 PSK，重新执行一轮完整的密钥派生，从而获得前向安全。分别看看客户端和服务端如何持有 PSK 。客户端在接收到 NewSessionTicket 后，根据 ticket_nonce 和本地的 resumption_master_secret 派生出会话恢复用的 PSK：对于 Server 端，并不存储 Ticket，而是等待客户端在 ClientHello 的 pre_shared_key 直接将加密 ticket 回传给服务端，使用 Ticket Protection Key 解密后直接取出 encrypted_state 中的 resumption_key 作为 PSK。对于 Ticket Protection Key 的管理，不再 RFC 讨论中，Mbed TLS 做了双密钥的轮换管理。https://github.com/Mbed-TLS/mbedtls/blob/22098d41c6620ce07cf8a0134d37302355e1e5ef/library/ssl_ticket.c#L151-L209/* * Rotate active session ticket encryption key */ int mbedtls_ssl_ticket_rotate(mbedtls_ssl_ticket_context *ctx, const unsigned char *name, size_t nlength, const unsigned char *k, size_t klength, uint32_t lifetime)二 测试分析1. 编译使用这里使用 Mbed TLS v3.6.3 官方仓库 programs/ssl/ssl_client2.c 和 programs/ssl/ssl_server2.c 完成测试。直接参考工程 README.md 编译 ssl_client2 和 ssl2_server2。例如 Make 直接在根目录运行 make 即可。git clone https://github.com/Mbed-TLS/mbedtls.git git submodule update --init cd mbedtls make对于不同测试可以参考 Mbed TLS 的测试用例，比如 0-RTT （Early Data）。测试脚本会告诉你使能哪些宏，使用哪些参数，以及输出日志。tests/opt-testcases/tls13-misc.sh#L235-L266requires_config_enabled MBEDTLS_SSL_PROTO_TLS1_3 requires_config_enabled MBEDTLS_SSL_CLI_C requires_config_enabled MBEDTLS_SSL_SRV_C requires_config_enabled MBEDTLS_SSL_SESSION_TICKETS requires_config_enabled MBEDTLS_HAVE_TIME requires_config_enabled MBEDTLS_SSL_EARLY_DATA requires_config_enabled MBEDTLS_DEBUG_C requires_config_enabled MBEDTLS_SSL_TLS1_3_KEY_EXCHANGE_MODE_EPHEMERAL_ENABLED requires_any_configs_enabled MBEDTLS_SSL_TLS1_3_KEY_EXCHANGE_MODE_PSK_ENABLED \ MBEDTLS_SSL_TLS1_3_KEY_EXCHANGE_MODE_PSK_EPHEMERAL_ENABLED run_test "TLS 1.3 m->m: resumption with early data" \ "$P_SRV debug_level=4 early_data=1 crt_file=../framework/data_files/server5.crt key_file=../framework/data_files/server5.key" \ "$P_CLI debug_level=3 early_data=1 new_session_tickets=1 reco_mode=1 reconnect=1" \ 0 \ -c "Protocol is TLSv1.3" \ -c "Saving session for reuse... ok" \ -c "Reconnecting with saved session" \ -c "HTTP/1.0 200 OK" \ -c "received max_early_data_size" \ -c "NewSessionTicket: early_data(42) extension received." \ -c "ClientHello: early_data(42) extension exists." \ -c "EncryptedExtensions: early_data(42) extension received." \ -c "bytes of early data written" \ -C "0 bytes of early data written" \ -s "Protocol is TLSv1.3" \ -s "key exchange mode: psk" \ -s "Select PSK ciphersuite" \ -s "Sent max_early_data_size" \ -s "NewSessionTicket: early_data(42) extension exists." \ -s "ClientHello: early_data(42) extension exists." \ -s "EncryptedExtensions: early_data(42) extension exists." \ -s "early data bytes read"如上宏配置可以通过 scripts/config.py 检查和设置当前配置。scripts/config.py --file include/mbedtls/mbedtls_config.h get MBEDTLS_SSL_EARLY_DATA ;echo $? #1表示未使能，0表示使能 scripts/config.py --file include/mbedtls/mbedtls_config.h set MBEDTLS_SSL_EARLY_DATA scripts/config.py --file include/mbedtls/mbedtls_config.h get MBEDTLS_SSL_EARLY_DATA ;echo $? #1表示未使能，0表示使能对于当前需要测试的会话恢复。其他宏都默认使能（当前你可以通过 get 检查），你只需要使能如上 MBEDTLS_SSL_EARLY_DATA。scripts/config.py --file include/mbedtls/mbedtls_config.h set MBEDTLS_SSL_EARLY_DATA make ls programs/ssl/ssl_*2TLS 1.2 PSK KE 本地连接测试。服务端programs/ssl/ssl_server2 debug_level=3 nbio=0 min_version=tls12 max_version=tls13 auth_mode="required" tickets=0 server_addr=127.0.0.1 force_ciphersuite=TLS-PSK-WITH-AES-128-GCM-SHA256 psk=6162636465666768696a6b6c6d6e6f70 psk_identity="PSK Identity"客户端programs/ssl/ssl_client2 nss_keylog=1 nss_keylog_file=tls_client_nss_key.log debug_level=3 nbio=0 server_name="localhost" tickets=0 server_addr=127.0.0.1 force_version=tls12 psk=6162636465666768696a6b6c6d6e6f70 psk_identity="PSK Identity"TLS 1.3 PSK ECDHE 本地连接测试。服务端programs/ssl/ssl_server2 debug_level=3 nbio=0 min_version=tls12 max_version=tls13 auth_mode="required" force_ciphersuite=TLS1-3-AES-128-GCM-SHA256 tickets=1 server_addr=127.0.0.1 psk=6162636465666768696a6b6c6d6e6f70 psk_identity="PSK Identity"客户端programs/ssl/ssl_client2 nss_keylog=1 nss_keylog_file=tls_client_nss_key.log debug_level=3 nbio=0 server_name="localhost" tickets=0 new_session_tickets=0 reco_mode=0 server_addr=127.0.0.1 server_addr=127.0.0.1 force_version=tls13 psk=6162636465666768696a6b6c6d6e6f70 psk_identity="PSK Identity"TLS 1.3 双向证书 ECDHE 本地连接测试。服务端programs/ssl/ssl_server2 debug_level=3 nbio=0 min_version=tls12 max_version=tls13 auth_mode="required" crt_file=framework/data_files/server5.crt key_file=framework/data_files/server5.key客户端programs/ssl/ssl_client2 nss_keylog=1 nss_keylog_file=tls_client_nss_key.log debug_level=3 nbio=0 server_name="localhost" tickets=0 new_session_tickets=0 reco_mode=0 server_addr=127.0.0.1 server_addr=127.0.0.1 force_version=tls13 auth_mode="required" crt_file=framework/data_files/server5.crt key_file=framework/data_files/server5.key key_opaque_algs=ecdsa-sign,noneTLS 1.3 开启 Session Tickets，进行会话恢复。服务端programs/ssl/ssl_server2 debug_level=3 nbio=0 min_version=tls12 max_version=tls13 auth_mode="required" force_ciphersuite=TLS1-3-AES-128-GCM-SHA256 tickets=1 server_addr=127.0.0.1 psk=6162636465666768696a6b6c6d6e6f70 psk_identity="PSK Identity"客户端programs/ssl/ssl_client2 nss_keylog=1 nss_keylog_file=tls_client_nss_key.log debug_level=3 nbio=0 server_name="localhost" tickets=1 new_session_tickets=1 reco_mode=0 server_addr=127.0.0.1 server_addr=127.0.0.1 force_version=tls13 psk=6162636465666768696a6b6c6d6e6f70 psk_identity="PSK Identity" reconnect=1 TLS 1.3 0-RTT Early Data 测试。服务端programs/ssl/ssl_server2 debug_level=3 nbio=0 min_version=tls12 max_version=tls13 auth_mode="required" force_ciphersuite=TLS1-3-AES-128-GCM-SHA256 tickets=1 server_addr=127.0.0.1 psk=6162636465666768696a6b6c6d6e6f70 psk_identity="PSK Identity" early_data=1 max_early_data_size=200客户端programs/ssl/ssl_client2 nss_keylog=1 nss_keylog_file=tls_client_nss_key.log debug_level=3 nbio=0 server_name="localhost" tickets=1 new_session_tickets=1 reco_mode=0 server_addr=127.0.0.1 server_addr=127.0.0.1 force_version=tls13 psk=6162636465666768696a6b6c6d6e6f70 psk_identity="PSK Identity" reconnect=1 early_data=1 2. 测试汇总如下测试日志汇总通过此脚本自动生成 run_tls_test.sh。测试用例抓包文件客户端日志服务端日志Key LogTLS 1.2 PSK KEtls12_psk_capture.pcapngtls12_psk_client.logtls12_psk_server.logtls12_psk_nss_key.logTLS 1.3 PSK ECDHEtls13_psk_ecdhe_capture.pcapngtls13_psk_ecdhe_client.logtls13_psk_ecdhe_server.log TLS 1.3 证书 ECDHEtls13_cert_capture.pcapngtls13_cert_client.logtls13_cert_server.log TLS 1.3 会话恢复tls13_session_resumption_capture.pcapngtls13_session_resumption_client.logtls13_session_resumption_server.log TLS 1.3 0-RTT Early Datatls13_0rtt_capture.pcapngtls13_0rtt_client.logtls13_0rtt_server.log 3. VS Code 调试为了方便 VS Code 单步调试走断点阅读代码，你需要编译配置优化和调试等级。scripts/config.py --file include/mbedtls/mbedtls_config.h set MBEDTLS_SSL_EARLY_DATA make make CFLAGS='--coverage -g3 -O0' LDFLAGS='--coverage'参考 Using C++ on Linux in VS Code 或者 Using Clang in Visual Studio Code 进行 VSCode 调试。这里把如上所有测试命令配置 launch.json。直接保存在 根目录 .vscode 即可以调试。4. Wireshark 抓包分析因为 Wireshark 暂时还不支持 TLS 1.3 SSLKEYLOGFILE https://wiki.wireshark.org/TLS 解密。所以 TLS 1.3 信息有限。但是对于一些关键扩展和握手消息还是值得分析。4.1 TLS 1.2 完整握手因为 TLS 1.3 握手消息都有加密，这里解释 TLS 1.2 PSK KE 抓包分析。使用 Wireshark 打开 tls12_psk_capture.pcapng 抓包文件。Wireshark -> Preferences... -> (Pre)-Master-Secret log filename -> tls12_psk_nss_key.logWireshark -> Statistics -> Flow Graph4.2 TLS 1.3 0-RTT Early Datatls13_0rtt_capture.pcapngEarly Data 指示Early Data 数据4.3 会话恢复tls13_session_resumption_capture.pcapng完整握手初次会话，预设 PSK。会话恢复，同时包含预设 PSK 和会话恢复的 PSK，前面一个为 Ticket。
目录 [隐藏] 1 错误模型2 MAC 验证3 SpectrumWiFiPhy3.1 饱和性能3.2 包误码率性能3.3 干扰性能4 Bianchi 验证5 多用户传输验证 本文翻译自，https://www.nsnam.org/docs/release/3.43/models/html/wifi-testing.html#spectrumwifiphy 。目前，大多数关于测试和验证的文档都存在于出版物中，以下列出了一些参考文献。1 错误模型802.11b 错误模型的验证结果可在以下 技术报告 中查看。需要注意以下两点说明： 上述参考文献中的图 1-4 对应的是 ns-3 NIST 比特错误率（BER）模型。在该论文附录的程序（80211b.c）中，有两个用于生成数据的常量。第一个是分组大小，设置为 1024 字节。第二个是“噪声”，其值设为 7 dB；这一值是通过经验选取的，以使曲线与 CMU 测试床的报告数据最契合。虽然值为 1.55 dB 更符合 CMU 论文报告的 -99 dBm 噪声底限，但噪声系数 7 dB 最适合与 CMU 实验数据拟合。这一默认值 7 dB 是 ns3::YansWifiPhy 模型中的 RxNoiseFigure。更改噪声系数会使曲线向左或向右移动，但不会改变其斜率。可以通过运行 examples/wireless/wifi-clear-channel-cmu.cc 示例程序重现这些曲线。当启用 GNU 科学库（GSL）时，会生成以下图表：802.11b 的清晰信道（AWGN）错误模型。802.11b 的清晰信道（AWGN）错误模型802.11a/g OFDM 错误模型的验证结果可在以下 技术报告 中查看。 可以通过运行 examples/wireless/wifi-ofdm-validation.cc 示例程序重现这些曲线，以下图表展示了 802.11a/g（OFDM）Wi-Fi 的帧错误率（NIST 模型）。802.11a/g（OFDM）Wi-Fi 的帧错误率（NIST 模型）对于 802.11n/ac/ax，可以通过运行 wifi-ofdm-ht-validation.cc、wifi-ofdm-vht-validation.cc 和 wifi-ofdm-he-validation.cc 示例程序生成类似曲线。以下分别展示了 802.11n（HT OFDM）Wi-Fi 的帧错误率（NIST 模型）、802.11ac（VHT OFDM）Wi-Fi 的帧错误率（NIST 模型） 和 802.11ax（HE OFDM）Wi-Fi 的帧错误率（NIST 模型）。但这些曲线目前尚无验证。802.11b 的清晰信道（AWGN）错误模型802.11n（HT OFDM）Wi-Fi 的帧错误率（NIST 模型）802.11ax（HE OFDM）Wi-Fi 的帧错误率（NIST 模型）2 MAC 验证802.11 DCF MAC 层的验证已在 [baldo2010] 中完成。802.11 PCF 的操作通过运行 wifi-pcf 示例（启用 PCAP 文件生成），并使用 Wireshark 观察帧交换得以验证。3 SpectrumWiFiPhySpectrumWifiPhy 实现已通过饱和和分组错误率程序（下文描述）进行验证，其结果与传统的 YansWifiPhy 一致。测试中通过切换这两种物理层实现进行比较。一个基本单元测试通过手动构造的分组注入接收的 PHY 对象，控制每个分组到达的时间和接收功率，并检查接收结果。然而，大多数对 SpectrumWifiPhy 的测试是使用下文描述的示例程序，以及一个独立的 LTE/Wi-Fi 共存研究中完成的（未在此文档中记录）。3.1 饱和性能程序 examples/wireless/wifi-spectrum-saturation-example.cc 允许用户选择 SpectrumWifiPhy 或 YansWifiPhy 进行饱和测试。用户可以通过参数 '--wifiType=ns3::YansWifiPhy' 或 '--wifiType=ns3::SpectrumWifiPhy' 切换物理层类型。输出结果没有差异，这符合预期，因为该测试主要验证的是 DCF，而不是物理层。默认情况下，程序将使用 SpectrumWifiPhy，并在 10 秒内发送饱和 UDP 数据，启用 802.11n 功能。输出显示主流 802.11n 速率（短保护间隔和长保护间隔）的结果：wifiType: ns3::SpectrumWifiPhy distance: 1m index MCS width Rate (Mb/s) Tput (Mb/s) Received 0 0 20 6.5 5.81381 4937 1 1 20 13 11.8266 10043 2 2 20 19.5 17.7935 15110 3 3 20 26 23.7958 20207 4 4 20 39 35.7331 30344 5 5 20 52 47.6174 40436 6 6 20 58.5 53.6102 45525 7 7 20 65 59.5501 50569 ... 63 15 40 300 254.902 216459上述输出显示了前 8 种模式（共 32 种）及最后一种模式的结果。前 8 种模式对应未启用短保护间隔和信道绑定的情况。后续 24 种模式分别为启用短保护间隔（第 9 至 16 种模式），然后启用信道绑定，再依次禁用和启用短保护间隔（第 17 至 32 种模式）。第 33 至 64 种模式在两条空间流（MIMO 抽象）下重复上述配置。运行以下命令可使用 YansWifiPhy：./ns3 run "wifi-spectrum-saturation-example --wifiType=ns3::YansWifiPhy"输出结果：wifiType: ns3::YansWifiPhy distance: 1m index MCS width Rate (Mb/s) Tput (Mb/s) Received 0 0 20 6.5 5.81381 4937 1 1 20 13 11.8266 10043 2 2 20 19.5 17.7935 15110 3 3 20 26 23.7958 20207 ...这符合预期，因为在这种情况下，YansWifiPhy 和 SpectrumWifiPhy 使用相同的误码率模型。3.2 包误码率性能程序 examples/wireless/wifi-spectrum-per-example.cc 允许用户选择 SpectrumWifiPhy 或 YansWifiPhy，并选择节点间的距离，同时记录接收统计数据和接收 SNR（通过 WifiPhy::MonitorSnifferRx 跟踪源观察）。程序使用 Friis 传播损耗模型。将发送功率从默认的 40 mW（16 dBm）降低到 1 dBm，以降低基线 SNR；用户还可以通过更改节点间的距离进一步调整 SNR。默认情况下，程序会在 50 米距离下，针对 10 秒的仿真时间，对从 0 到 31 的速率索引逐步测试，生成如下输出：wifiType: ns3::SpectrumWifiPhy distance: 50m; time: 10; TxPower: 1 dBm (1.3 mW) index MCS Rate (Mb/s) Tput (Mb/s) Received Signal (dBm) Noise (dBm) SNR (dB) 0 0 6.50 5.77 7414 -79.71 -93.97 14.25 1 1 13.00 11.58 14892 -79.71 -93.97 14.25 2 2 19.50 17.39 22358 -79.71 -93.97 14.25 3 3 26.00 22.96 29521 -79.71 -93.97 14.25 4 4 39.00 0.00 0 N/A N/A N/A 5 5 52.00 0.00 0 N/A N/A N/A 6 6 58.50 0.00 0 N/A N/A N/A 7 7 65.00 0.00 0 N/A N/A N/A如上饱和性能测试中的情况，使用 YansWifiPhy 运行此程序将生成相同的输出。3.3 干扰性能程序 examples/wireless/wifi-spectrum-per-interference.cc 基于上述包误码率示例，但从未许可 LTE 干扰测试中引入了 WaveformGenerator，允许用户通过命令行（使用 --waveformPower 参数）注入非 Wi-Fi 信号。与包误码率示例程序相比，此程序的发送功率恢复为默认的 40 mW（16 dBm）。默认情况下，干扰发生器关闭，程序行为类似于其他包误码率示例；通过添加少量功率（例如 --waveformPower=0.001），可以观察到 SNR 降低和帧丢失。没有干扰时的部分输出如下：./ns3 run "wifi-spectrum-per-interference" wifiType: ns3::SpectrumWifiPhy distance: 50m; time: 10; TxPower: 16 dBm (40 mW) index MCS Rate (Mb/s) Tput (Mb/s) Received Signal (dBm)Noi+Inf(dBm) SNR (dB) 0 0 6.50 5.77 7414 -64.69 -93.97 29.27 1 1 13.00 11.58 14892 -64.69 -93.97 29.27 2 2 19.50 17.39 22358 -64.69 -93.97 29.27 3 3 26.00 23.23 29875 -64.69 -93.97 29.27 4 4 39.00 34.90 44877 -64.69 -93.97 29.27 5 5 52.00 46.51 59813 -64.69 -93.97 29.27 6 6 58.50 52.39 67374 -64.69 -93.97 29.27 7 7 65.00 58.18 74819 -64.69 -93.97 29.27 ...添加少量波形功率后，较高阶调制可能因较低 SNR 出现帧丢失：./ns3 run "wifi-spectrum-per-interference --waveformPower=0.001" wifiType: ns3::SpectrumWifiPhy distance: 50m; sent: 1000 TxPower: 16 dBm (40 mW) index MCS Rate (Mb/s) Tput (Mb/s) Received Signal (dBm)Noi+Inf(dBm) SNR (dB) 0 0 6.50 5.77 7414 -64.69 -80.08 15.38 1 1 13.00 11.58 14892 -64.69 -80.08 15.38 2 2 19.50 17.39 22358 -64.69 -80.08 15.38 3 3 26.00 23.23 29873 -64.69 -80.08 15.38 4 4 39.00 0.41 531 -64.69 -80.08 15.38 5 5 52.00 0.00 0 N/A N/A N/A 6 6 58.50 0.00 0 N/A N/A N/A 7 7 65.00 0.00 0 N/A N/A N/A ...选择 ns3::YansWifiPhy 时，不会启用波形发生器，因为只有 YansWifiPhy 类型的发送器可以连接到 YansWifiChannel。4 Bianchi 验证程序 src/wifi/examples/wifi-bianchi.cc 允许用户将 ns-3 模拟结果与 [bianchi2000] 和 [bianchi2005] 提出的 Bianchi 模型进行比较。MATLAB 脚本用于生成 Bianchi 模型的代码及其输出，可在 src/wifi/examples/reference 文件夹中找到。用户可运行 MATLAB 脚本 generate_bianchi.m 以重新生成 Bianchi 结果。默认情况下，程序 src/wifi/examples/wifi-bianchi.cc 模拟一个 802.11a 自组网络场景，PHY 速率设置为 54 Mbit/s，并从 5 个站点循环到 50 个站点（每次增加 5 个站点）。程序生成一个 plt 文件，用户可以使用 Gnuplot 快速生成 eps 文件并可视化图形。5 多用户传输验证OFDMA 支持的实现已通过理论模型 [magrin2021mu] 进行验证。在非饱和条件下的延迟、饱和条件下的吞吐量以及 UL OFDMA 的传输范围，已在 [avallone2021wcm] 中提供初步评估。
目录 [隐藏] 1. 模型概览1.1. 高层 MAC 模型1.2. 低层 MAC 模型1.3. 物理层模型2. 范围与限制3. 设计细节3.1. WifiPhy 和相关模型3.1.1. PhyEntity3.1.2. WifiPpdu3.1.3. YansWifiPhy 和 WifiPhyStateHelper3.1.4. InterferenceHelper3.1.5. ErrorRateModel3.1.6. TableBasedErrorRateModel3.1.7. Legacy ErrorRateModels3.1.8. SpectrumWifiPhy3.1.9. MU-MIMO PHY 支持3.2. MAC 模型3.2.1. 基础架构模式中的关联3.2.2. 漫游3.2.3. 信道访问3.2.4. Frame Exchange Managers3.2.5. MAC 队列3.2.6. 多用户传输3.2.7. 多用户调度器3.2.8. 轮询多用户调度器3.2.9. 增强的多链路单无线电操作（EMLSR）3.2.10. 确认管理器3.2.11. WifiDefaultAckManager3.2.12. 保护管理器3.2.13. WifiDefaultProtectionManager3.2.14. 速率控制算法3.2.15. ConstantRateWifiManager3.2.16. IdealWifiManager3.2.17. ThompsonSamplingWifiManager3.2.18. MinstrelWifiManager3.2.19. MinstrelHtWifiManager3.2.20. 802.11ax OBSS PD 空间复用3.2.21. OBSS PD 算法3.2.22. 恒定 OBSS PD 算法3.2.23. 修改 Wi-Fi 模型 最近太想进步了，开始研究 NS-3 Wi-Fi 仿真模型。本文翻译自，https://www.nsnam.org/docs/models/html/wifi-design.html 。ns-3 节点可以包含多个 NetDevice 对象，就像一台实际的计算机可以包含多个独立的网络接口卡（如以太网、Wi-Fi、蓝牙等）。本章节描述了 ns-3 的 WifiNetDevice 及其相关模型。通过将 WifiNetDevice 对象添加到 ns-3 节点中，可以创建基于 802.11 的基础设施网络和自组织（Ad Hoc）网络模型。1. 模型概览WifiNetDevice 模拟了基于 IEEE 802.11 标准的无线网络接口控制器 [ieee80211]。我们将在后续章节中详细说明，但简单来说，ns-3 提供了以下 802.11 相关模型：基本的 802.11 分布式协调功能（DCF），支持 基础设施模式 和 自组织模式802.11a、802.11b、802.11g、802.11n（支持 2.4 GHz 和 5 GHz 频段）、802.11ac、802.11ax（支持 2.4 GHz、5 GHz 和 6 GHz 频段）以及 802.11be 的物理层MSDU 聚合 和 MPDU 聚合 的 802.11n 扩展，并支持两者组合（两级聚合）802.11ax 的 下行链路 OFDMA（DL OFDMA） 和 上行链路 OFDMA（UL OFDMA）（包括多用户增强分布式协调功能（MU EDCA）参数集的支持）802.11be 的 多链路 发现与设置基于 QoS 的增强分布式协调功能（EDCA）以及 802.11e 的队列扩展支持使用不同的传播损耗模型和传播延迟模型，详细信息请参见 Propagation 章节经验证的分组错误模型和帧检测模型，与链路仿真和其他参考一致多种速率控制算法，包括 Aarf、Arf、Cara、Onoe、Rraa、ConstantRate、Minstrel 和 Minstrel-HT802.11s（网状网络），另见其他章节802.11p 和 WAVE（车联网），另见其他章节ns-3 提供的 802.11 模型尝试准确实现 802.11 规范的 MAC 层功能，同时为 802.11a/b/e/g/n/ac/ax/be 物理层提供基于分组的抽象。在 ns-3 中，节点可以在不同的信道上拥有多个 WifiNetDevice，且 WifiNetDevice 能与其他设备类型共存。通过使用 SpectrumWifiPhy 框架，还可以创建涉及跨信道干扰或单个信道上多种无线技术共存的场景。WifiNetDevice 及其模型的源代码位于目录 src/wifi。实现是模块化的，主要包括三个子层模型：物理层模型（PHY layer models）：模拟具体修正案的物理层操作及通用物理层功能。低层 MAC 模型（lower MAC models）：模拟诸如介质访问（DCF 和 EDCA）、帧保护（RTS/CTS）以及确认（ACK/BlockAck）等功能。在 ns-3 中，低层 MAC 包括一个 帧交换管理器（Frame Exchange Manager） 层级结构、一个 信道访问管理器（Channel Access Manager）、Txop 对象、BlockAckManager 对象和一个 MAC 中间实体（MAC middle entity）。高层 MAC 模型（upper MAC models）：实现 Wi-Fi 中非时间关键的过程，如 MAC 层信标生成、探测和关联状态机，以及一组 速率控制算法。在文献中，这一子层有时称为 高层 MAC（upper MAC），相较于时间关键的硬件实现，它更偏向软件实现。接下来，我们将概述每一层的设计，具体结构如图 WifiNetDevice 架构 所示。对于 802.11be 的多链路设备（MLDs），WifiPhy、FrameExchangeManager 和 ChannelAccessManager 的实例数量与链路数量相同。WifiNetDevice 架构1.1. 高层 MAC 模型目前，高层 MAC 的父类 ns3::WifiMac 有三种变体：接入点（AP）（ns3::ApWifiMac）、非 AP 站点（STA）（ns3::StaWifiMac）以及独立基本服务集（IBSS）中的 STA（通常称为自组织网络）（ns3::AdhocWifiMac）。其中最简单的是 ns3::AdhocWifiMac，它实现了一个不执行信标生成、探测或关联的 Wi-Fi MAC。ns3::StaWifiMac 类实现了一个主动探测和关联状态机，能够在错过过多信标时自动重新关联。而 ns3::ApWifiMac 则实现了一个生成周期性信标并接受所有关联请求的 AP。这三种高层 MAC 模型共享一个共同的父类 ns3::WifiMac。该类提供了一些 MAC 配置选项，其中包括一个 QosSupported 属性，用于配置 802.11e/WMM 风格的 QoS 支持。此外，还可以在低层 MAC 中使用多种 速率控制算法。这些算法均实现为 ns3::WifiRemoteStationManager 的子类。完整的可用速率控制算法列表在单独的章节中提供。1.2. 低层 MAC 模型低层 MAC 分为三个主要组件：ns3::FrameExchangeManager：这是一个类层次结构，负责实现支持的 IEEE 802.11 修正案中引入的帧交换序列。它还处理帧聚合、帧重传、保护和确认。ns3::ChannelAccessManager：实现分布式协调功能（DCF）和增强分布式协调功能（EDCAF）。ns3::Txop 和 ns3::QosTxop：负责分组队列管理。ns3::Txop 对象用于不支持 QoS 的高层 MAC，以及标准规定应通过 DCF 访问介质的帧（例如管理帧）的传输。而 ns3::QosTxop 则用于支持 QoS 的高层 MAC。1.3. 物理层模型物理层模型主要负责对分组的接收进行建模以及跟踪能量消耗。分组接收通常包含以下三个主要组件：每个接收的分组都会通过概率方法评估其接收是否成功或失败。成功概率取决于分组的调制方式、信噪比（以及干扰比），以及物理层的状态（例如在传输或休眠期间无法进行接收）。一个对象用于记录所有接收到的信号，以便在做出接收决策时计算每个分组的正确干扰功率。一个或多个与调制方式和标准相对应的错误模型用于查找接收成功的概率。ns-3 提供了两种物理层模型供用户选择，其基本接口定义在 ns3::WifiPhy 类中。YansWifiPhy 类实现了一个简单的物理层模型，该模型在题为 Yet Another Network Simulator 的论文中有所描述。“Yans” 这一缩写源自该论文标题。SpectrumWifiPhy 类是一个基于 ns-3 用于其他无线模型的 Spectrum 框架的更高级实现。Spectrum 框架允许对信号进行精细的频率分解，并支持多种技术在单一信道上共存的场景。2. 范围与限制IEEE 802.11 标准 [ieee80211] 是一个庞大的规范，ns-3 并未涵盖其所有方面；仅记录 ns-3 的合规性本身就可能构成一份很长的文档。本节尝试总结 ns-3 对标准的符合性以及与实际行为的对比。物理层和信道模型以每个分组为基础进行操作。当使用默认的 YansWifiPhy 模型时，未包含频率选择性传播或干扰效应。目前也不支持定向天线。在加性白高斯噪声（AWGN）场景或宽带干扰场景下，性能由对接收信号的信噪比应用分析模型（基于调制方式和信道宽度等因素）决定，噪声包括热噪声以及其他 Wi-Fi 分组的干扰。当使用 SpectrumWifiPhy 时，其他无线技术的干扰才会被建模。以下是物理层和信道模型的具体限制：不支持 802.11n/ac/ax/be 波束成形不支持 802.11n RIFS未实现 802.11 PCF/HCF/HCCA不支持信道切换公告缺少认证和加密功能未建模处理延迟不支持使用 HT/VHT/HE/EHT 格式传输 RTS/CTS 和 ACK能量消耗模型未考虑 MIMO物理层支持 802.11ax 前导符打孔，但 MAC 层目前尚未利用该功能仅支持最小化的 MU-MIMO（假设理想的物理层，没有 MAC 层实现）在 MAC 层，大多数 802.11a/b/e/g/n/ac/ax/be 的主要功能已被实现，但模型中仍有一些零星的限制。对 802.11n、ac、ax 和 be 的支持仍在演进中。一些未被标准强制要求的实现选择如下：对于 802.11b，假定 BSSBasicRateSet 为 1-2 Mbit/s。对于 802.11a/g，假定 BSSBasicRateSet 为 6-12-24 Mbit/s。假定 OperationalRateSet 包含所有强制速率（参见 问题 183）。Wi-Fi 管理器总是为管理帧选择最低的基本速率。如果 STA（AP 或非 AP）支持 VHT，则一旦第一个分组排队，就会始终设置 Block Ack 协议，无论该分组是否将在 A-MPDU 中传输。一旦创建了 A-MSDU，它就不会再被修改，即使是在首次传输之前。这意味着该 A-MSDU 无法与其他 MSDU 结合使用 A-MSDU 聚合。3. 设计细节本节其余部分将深入描述一些 Wi-Fi 模型的设计细节。若用户希望直接跳转到 Wi-Fi 模块的使用章节（用户文档），可以跳过此部分。我们按照自下而上的分层方式组织了这些详细描述，首先介绍信道和物理层模型，然后是 MAC 层模型。我们首先关注物理层框架的选择。ns-3 支持一个仅 Wi-Fi 的物理层模型 YansWifiPhy，该模型不提供信号的频率级别分解。对于仅涉及 Wi-Fi 信号且不涉及与频率相关的传播损失或衰落模型的仿真，默认的 YansWifiPhy 框架是合适的选择。而对于包含同一信道上混合技术或频率相关效应的仿真，SpectrumWifiPhy 更为适合。这两个框架的配置方式非常相似。SpectrumWifiPhy 框架使用 Spectrum 模块 信道框架。YansWifiChannel 是 ns-3 Wi-Fi 模块中唯一的具体信道模型类。ns3::YansWifiChannel 的实现使用了 ns-3 Propagation 模块中提供的传播损失和传播延迟模型。特别是，可以将多个传播模型添加到信道对象（如果添加了多个损失模型，则可以链式组合），还可以添加传播延迟模型。从 ns3::YansWifiPhy 对象发送到信道的分组会以特定的信号强度发送，之后信号强度会因传播损失模型而衰减，同时引入传输（序列化）延迟以及由于任何信道传播延迟模型（通常由设备之间的光速传播延迟引起）的传播延迟，然后被复制到所有其他 ns3::YansWifiPhy 对象。仅允许将 ns3::YansWifiPhy 对象附加到 ns3::YansWifiChannel；因此，不允许模拟其他技术（如 LTE）的对象。此外，来自不同信道的分组不会相互影响；例如，如果信道被逻辑配置为信道 5 和 6，尽管它们的信道编号可能重叠，但分组不会引起相邻信道干扰。3.1. WifiPhy 和相关模型ns3::WifiPhy 是一个抽象基类，表示 802.11 物理层功能。传递给该对象的分组（通过 Send() 方法）会被发送到信道对象，在接收时，接收 PHY 对象根据信号强度和干扰决定分组是否接收成功。此类还提供了多个回调，用于通知物理层事件，公开了一个状态机的概念，可供监控 MAC 层的流程（如载波侦听），并处理休眠/唤醒/关闭模型以及能量消耗。ns3::WifiPhy 挂接到 WifiNetDevice 中的 ns3::FrameExchangeManager 对象。目前 WifiPhy 有两个实现：ns3::YansWifiPhy 和 ns3::SpectrumWifiPhy。它们与以下五个对象协同工作：PhyEntity：包含物理层处理的修订特定部分。WifiPpdu：对修订特定的物理协议数据单元（PPDU）进行建模。WifiPhyStateHelper：维护物理层状态机。InterferenceHelper：跟踪信道上观察到的所有分组。ErrorModel：根据信噪比（SNR）计算错误的概率。3.1.1. PhyEntity3.1.1.1. 背景简介考虑到相关文件的大小和复杂性，对 ns3::WifiPhy 和 ns3::WifiMode（以及其他文件）进行了一些重构。此外，添加和维护新的 PHY 修订已成为一项复杂的任务（尤其是那些实现于其他模块内部的任务，如 DMG）。所采用的解决方案是引入 PhyEntity 类，这些类包含 PHY 处理的“修订”特定部分（即 HT/VHT/HE/EHT 等）。在标准中，每个 PHY 层描述章节的开头都有“PHY 实体”的概念，例如 IEEE 802.11-2016 第 21.1.1 节：:: 第 21 条规定了一个非常高吞吐量（VHT）正交频分复用（OFDM）系统的 PHY 实体。注意，在 wave 模块中已经存在一个同名概念（例如 WaveNetDevice::AddPhy），用于指代每个 11p 信道上的 WifiPhy，但该术语仅在类中使用，并且没有任何文件使用该名称，因此使用该名称来表示 802.11 修订不会引起歧义。3.1.1.2. 架构抽象基类 ns3::PhyEntity 提供了一组唯一的 API，可供每个 PHY 实体使用，对应于 IEEE 802.11 标准的不同修订。目前已实现的 PHY 实体包括：ns3::DsssPhy：DSSS 和 HR/DSSS（11b）的 PHY 实体ns3::OfdmPhy：OFDM（11a 和 11p）的 PHY 实体ns3::ErpOfdmPhy：ERP-OFDM（11g）的 PHY 实体ns3::HtPhy：HT（11n）的 PHY 实体ns3::VhtPhy：VHT（11ac）的 PHY 实体ns3::HePhy：HE（11ax）的 PHY 实体ns3::EhtPhy：EHT（11be）的 PHY 实体它们的继承图见图 PhyEntity 继承结构，并紧密遵循标准的逻辑，例如 IEEE 802.11-2016 第 21.1.1 节：:: VHT PHY 基于 第 19 条中定义的 HT PHY，而 HT PHY 又基于 第 17 条中定义的 OFDM PHY。PhyEntity 继承结构这种架构可以以修订特定的方式处理以下操作：WifiMode 的处理和数据/PHY 速率计算，PPDU 字段大小和持续时间计算，以及发送和接收路径。3.1.2. WifiPpdu与 PhyEntity 类似，ns3::WifiPpdu 基类被具体化为以下修订特定的 PPDU：ns3::DsssPpdu：DSSS 和 HR/DSSS（11b）的 PPDUns3::OfdmPpdu：OFDM（11a 和 11p）的 PPDUns3::ErpOfdmPpdu：ERP-OFDM（11g）的 PPDUns3::HtPpdu：HT（11n）的 PPDUns3::VhtPpdu：VHT（11ac）的 PPDUns3::HePpdu：HE（11ax）的 PPDUns3::EhtPpdu：EHT（11be）的 PPDU它们的继承图如图 WifiPpdu hierarchy 所示，并紧密遵循标准的逻辑。例如，IEEE 802.11-2016 第 21.3.8.1 节指出：:: 为了与非 VHT STA 兼容，定义了特定的非 VHT 字段，这些字段可由符合 Clause 17（OFDM）或 Clause 19（HT）的非 VHT STA 接收。WifiPpdu 继承结构3.1.3. YansWifiPhy 和 WifiPhyStateHelper类 ns3::YansWifiPhy 负责接收从 MAC（ns3::FrameExchangeManager 对象）传递来的分组，将其发送到所连接的 ns3::YansWifiChannel，并从信道接收分组。如果接收成功，则将分组传递回 MAC。信号接收的能量根据发送功率计算，并基于发射机的发射增益、接收机的接收增益和适用的路径损失传播模型进行调整。类 ns3::WifiPhyStateHelper 管理 PHY 层的状态机，并允许其他对象作为 监听器 挂钩以监控 PHY 状态。监听器的主要用途是让 MAC 层知道 PHY 是否忙碌（用于传输和避免冲突）。PHY 层可以处于以下状态之一：TX：PHY 当前正在为其关联的 MAC 发送信号。RX：PHY 已同步信号，正在等待接收最后一位以将其转发到 MAC。CCA_BUSY：PHY 正在为主信道发出 PHY-CCA.indication(BUSY) 指示。IDLE：PHY 未处于 TX、RX 或 CCA_BUSY 状态。SWITCHING：PHY 正在切换信道。SLEEP：PHY 处于节能模式，无法发送或接收帧。OFF：PHY 已关闭，无法发送或接收帧。接收分组的过程如下：对于 YansWifiPhy，大部分逻辑在 WifiPhy 基类中实现。YansWifiChannel 调用 WifiPhy::StartReceivePreamble()，后者调用适当 PHY 实体的 PhyEntity::StartReceivePreamble() 开始接收分组，但首先会检查分组的信号功率是否高于 WifiPhy::RxSensitivity 中存储的阈值。任何低于该阈值的分组将被丢弃，默认值为 -101 dBm（20 MHz 信号在室温下的热噪声底）。此属性的目的包括：弱信号对仿真结果无影响，但会消耗内存和处理资源，因此应丢弃；可将阈值调高以模拟基本载波侦听的限制，用于涉及空间复用的实验。在 StartReceivePreamble() 中，分组被立即添加到干扰助手中以进行信噪跟踪，随后根据 PHY 的状态决定接收步骤。如果 PHY 处于发送状态，分组将被丢弃；如果 PHY 为空闲或正在接收，且使用了 FrameCaptureModel（且分组在捕获窗口内），则调用 PhyEntity::StartPreambleDetectionPeriod()。PhyEntity::StartPreambleDetectionPeriod() 通常会调度一个事件 PhyEntity::EndPreambleDetectionPeriod()，用于检查前导码是否被检测到。自 ns-3.30 版本起，从空闲状态的状态机转换会被抑制直到前导码检测事件完成。PhyEntity::EndPreambleDetectionPeriod() 方法使用前导码检测模型检查信号是否足够强以被接收。若检测到前导码，则调度事件 PhyEntity::EndReceiveField() 到前导码结束时，并将 PHY 置于 CCA_BUSY 状态。目前 ns-3 中仅提供一个简单的阈值前导码检测模型 ThresholdPreambleDetectionModel。如果没有前导码检测模型，则假定前导码已被检测到。自 ns-3.30 起，默认添加 ThresholdPreambleDetectionModel，其阈值为 -82 dBm 的 RSSI 和 4 dB 的 SNR，只有两者均超过阈值时，前导码才会成功检测到。在 PhyEntity::EndReceiveField() 中，检查当前前导码和头部字段是否接收正确。若接收成功，则调用 PhyEntity::StartReceiveField() 处理下一个字段，否则中止接收。该流程在处理单个 MPDU 时运行。对于 MPDU 聚合（A-MPDU），StartReceivePayload 为每个 MPDU 调度一个接收事件 ScheduleEndOfMpdus，成功接收的 MPDU 被逐一传递到 FrameExchangeManager，并在 A-MPDU 完成后通知成功接收的 MPDU 数量。3.1.4. InterferenceHelperInterferenceHelper 是一个用于跟踪所有接收分组并计算其错误概率的对象，同时还评估信道上的能量是否以及何时超过给定阈值。错误概率的基本计算过程如图 SNIR function over time 所示。在 ns-3 模型中，分组以比特（而非符号）表示。InterferenceHelper 将分组分解为一个或多个具有不同信噪比（SNIR）的“片段”。每个片段会使用错误模型独立计算指定比特数的错误概率。InterferenceHelper 根据这些片段及其持续时间，生成一个聚合的“错误概率”值，并将其返回给 WifiPhy 以决定是否接收分组。SNIR 随时间的变化通过 SNIR 函数，我们可以推导出所使用调制和编码方案的比特错误率（BER）和分组错误率（PER）。如果使用 MIMO 且接收天线数量多于空间流数量，则计算的 SNIR 会获得如下增益（假设未使用 STBC）：对于加性高斯白噪声（AWGN），发送天线数量的影响可以忽略。增益值如下：antennas NSS gain 2 x 1 1 0 dB 1 x 2 1 3 dB 2 x 2 1 3 dB 3 x 3 1 4.8 dB 3 x 3 2 1.8 dB 3 x 3 3 0 dB 4 x 4 1 6 dB 4 x 4 2 3 dB 4 x 4 3 1.2 dB 4 x 4 4 0 dB 3.1.5. ErrorRateModelns-3 根据输入帧的信噪比（SNR）以及可能重叠的干扰帧（即信噪加干扰比，SINR）决定分组是成功还是错误。分组错误率（PER）与 SINR 的关系由 ns3::ErrorRateModel 定义，目前有多个模型可用。PER 是帧的调制和编码方案（MCS）、其 SINR 和配置的具体 ErrorRateModel 的函数。ns-3 的默认 ErrorRateModel 随版本更新不断改进。目前（截至 ns-3.33 版本）针对现代 OFDM 标准（如 802.11n/ac/ax）的默认模型是 ns3::TableBasedErrorRateModel。对于 802.11a/g，默认使用 ns3::YansErrorRateModel；对于 802.11b，默认使用 ns3::DsssErrorRateModel。在 ns-3.33 版本之前，现代标准的默认模型是 ns3::NistErrorRateModel。不同错误模型的详细说明可参考外部文献。当前的 OFDM 模型基于 [patidar2017] 中的工作，使用 MATLAB WLAN 工具箱进行链路仿真，并与 IEEE TGn 结果进行验证 [erceg2004]。有关其他错误模型的更多文献，请参见 [pei80211ofdm]、[pei80211b]、[lacage2006yans] 等。当前的 ns-3 错误率模型仅适用于加性高斯白噪声（AWGN）信道，未建模任何潜在的频率选择性衰落效应。错误模型总结如下：ns3::TableBasedErrorRateModel：用于 OFDM 模式，同时复用 ns3::DsssErrorRateModel 处理 802.11b 模式。这是 802.11n/ac/ax 的默认模型。ns3::YansErrorRateModel：用于 OFDM 模式，同时复用 ns3::DsssErrorRateModel 处理 802.11b 模式。这是 802.11a/g 的默认模型。ns3::DsssErrorRateModel：包含 802.11b 模式的模型。1 Mbps 和 2 Mbps 基于经典调制分析。5.5 Mbps 和 11 Mbps 使用 GSL 时基于 [pursley2009] 提供的 CCK 调制模型；否则，使用基于 MATLAB 的备选模型。ns3::NistErrorRateModel：用于 OFDM 模式，同时复用 ns3::DsssErrorRateModel 处理 802.11b 模式。用户可以在 OFDM 模式下选择 NIST、YANS 或基于表格的模型，而 DSSS 模式下始终使用 DsssErrorRateModel。3.1.6. TableBasedErrorRateModelns3::TableBasedErrorRateModel 是针对 802.11n/ac/ax 新增的默认模型，而 ns3::YansErrorRateModel 是 802.11a/g 的默认模型。与基于误差界的分析模型不同，TableBasedErrorRateModel 包含 AWGN 信道的端到端链路仿真表格（PER vs SNR）。由于为所有可能的分组大小和输入 SNR 生成查找表在计算上不可行，该模型采用 IEEE P802.11 TGax [porat2016] 提出的建议，使用参考长度 32 字节（小于 400 字节）和 1458 字节（大于或等于 400 字节）进行外推估计。LDPC 编码仅需单一参考长度。MATLAB WLAN 工具箱用于生成每种调制和编码方案的表格。对 SNR 的每个值，模拟运行直到总计 40000 个分组被模拟。结果与 TGax 曲线非常接近，如图 Comparison of table-based OFDM Error Model with TGax results.。基于表格的 OFDM 错误模型与 TGax 结果对比3.1.7. Legacy ErrorRateModels原始错误率模型 ns3::YansErrorRateModel 基于分析结果。对于 802.11b 调制，1 Mbps 模式基于 DBPSK；2 Mbps 模式基于 DQPSK。详细信息参见 [lacage2006yans]。ns3::NistErrorRateModel 后来加入。DSSS 调制的 1 Mbps 和 2 Mbps 模式与 YansErrorRateModel 一致，而 5.5 Mbps 和 11 Mbps 模式基于 [pursley2009] 的研究。OFDM 调制模式的结果基于 NIST [miller2003]。当 GSL 可用时，802.11b 的 5.5 Mbps 和 11 Mbps 模式使用 [pursley2009]；否则，使用 MATLAB 模拟的备选模型。基于分析的错误模型在高 MCS 时逐渐偏离链路仿真结果，如图 YANS and NIST error model comparison with TGn results 所示。这推动了基于链路仿真的新错误模型的开发。YANS 和 NIST 错误模型与 TGn 结果的比较3.1.8. SpectrumWifiPhy本节描述了 SpectrumWifiPhy 类的实现，代码位于 src/wifi/model/spectrum-wifi-phy.{cc,h}。此外，该实现还使用了以下同目录中的类：wifi-spectrum-phy-interface.{cc,h}wifi-spectrum-signal-parameters.{cc,h}以及 spectrum 模块中的类：wifi-spectrum-value-helper.{cc,h}SpectrumWifiPhy 类重用了最初为 YansWifiPhy 构建的干扰管理器和错误率模型，但在第一步中允许将外部（非 Wi-Fi）信号视为附加噪声。为使 Spectrum 框架适配 Wi-Fi，进行了两项主要改动。首先，物理层必须发送与 Spectrum 信道框架兼容的信号，特别是允许不同技术信号共存的 MultiModelSpectrumChannel（对于纯 Wi-Fi 仿真中 5 GHz 和 6 GHz 频段，也可以使用 SingleModelSpectrumChannel，但不适用于 2.4 GHz 频段。如果在使用 SingleModelSpectrumChannel 时遇到错误，请切换到 MultiModelSpectrumChannel）。其次，InterferenceHelper 必须扩展以支持插入非 Wi-Fi 信号并将其接收功率添加到噪声中，就像意外 Wi-Fi 信号（可能来自不同的 SSID 或隐藏节点）添加到噪声中一样。与 YansWifiPhy 不同，在 SpectrumWifiPhy 中，超过 CcaEdThreshold 的外部信号会触发 CCA_BUSY 状态（见 802.11-2012 标准中 16.4.8.5 节定义的 CCA 模式 1）。因此，WifiPhy::CcaEdThreshold 属性在该模型中可能比在 YansWifiPhy 模型中更为重要。为了支持 Spectrum 信道，YansWifiPhy 的发送和接收方法被适配以使用 Spectrum 信道 API。这需要开发一些 SpectrumModel 相关的类。例如，WifiSpectrumValueHelper 用于使用 Spectrum 框架创建 Wi-Fi 信号，并将其能量分布到各个频段。频谱被细分为子频段（宽度等于 OFDM 子载波，取决于技术）。分配给特定信道的功率分布在子频段上，近似于标准中定义的 OFDM 发射频谱掩码。WifiBandwidthFilter 类用于在传输过程的早期阶段丢弃信号，通过忽略发射带宽（包括保护带）与当前操作信道不重叠的任何 Wi-Fi PPDU。这样可以绕过信号传播/损失计算，从而减少计算负担并提高仿真性能。要启用 WifiBandwidthFilter，用户可以使用以下代码进行对象聚合：Ptr<WifiBandwidthFilter> wifiFilter = CreateObject<WifiBandwidthFilter> (); Ptr<MultiModelSpectrumChannel> spectrumChannel = CreateObject<MultiModelSpectrumChannel> (); spectrumChannel->AddSpectrumTransmitFilter(wifiFilter);为了提供更方便的用户配置体验，现有的 YansWifi 帮助类（位于 src/wifi/helper）被复制并调整，用于提供等效的 SpectrumWifi 帮助类。最后，为了避免 C++ 多重继承问题，插入了一个名为 WifiSpectrumPhyInterface 的小型转发类，作为 SpectrumWifiPhy 和 Spectrum 通道之间的桥梁。WifiSpectrumPhyInterface 调用了不同的 SpectrumWifiPhy::StartRx() 方法以启动接收过程。此方法会根据 WifiPhy::RxSensitivity 属性检查信号强度，并丢弃弱信号。同时，它还会验证信号是否为 Wi-Fi 信号；非 Wi-Fi 信号会添加到 InterferenceHelper 中，在这里它们可能会触发 CCA_BUSY，但不会在接收链中进一步处理。有效的 Wi-Fi 信号会触发调用 WifiPhy::StartReceivePreamble，随后按照之前描述的流程继续处理。此外，为了支持更灵活的通道切换，SpectrumWifiPhy 可以管理多个 WifiSpectrumPhyInterface 实例（多 RF 接口概念）。每个实例处理频谱的特定频率范围，通过 MHz 定义起始和结束频率，且这些范围不能重叠。只有一个实例对应于 SpectrumWifiPhy 的活动 RF 接口，其他实例被称为非活动 RF 接口，可能会与频谱通道断开连接。多 RF 接口概念如果将 SpectrumWifiPhy::TrackSignalsFromInactiveInterfaces 属性设置为 true（默认值），非活动 RF 接口会连接到各自的频谱通道。当非活动 RF 接口接收到属于其覆盖频率范围配置部分的信号时，SpectrumWifiPhy 也会接收这些信号。非活动接口监控的频谱部分由中心频率和信道宽度指定，并无缝设置为使用该频率范围的 Spectrum PHY 的活动信道的等效配置。SpectrumWifiPhy 将这些来自非活动接口的接收信号转发给 InterferenceHelper，而不进一步处理它们。这样做的好处是，当信号在新信道上开始传输但切换尚未完成时，能够生成更准确的 PHY-CCA.indication，这些信号否则会被忽略。这在图 通道切换期间信号跟踪的示例 中进行了说明，其中红色部分仅在 SpectrumWifiPhy::TrackSignalsFromInactiveInterfaces 设置为 true 时生成。通道切换期间信号跟踪的示例3.1.9. MU-MIMO PHY 支持在 ns-3.40 版本中，为 PHY 层引入了 MU-MIMO 的基础支持。当前模型可用于下行链路和上行链路的 MU-MIMO 传输。MU-MIMO 和 OFDMA 的多用户传输依赖于 MAC 层中 TXVECTOR 的填充方式（尚未实现）。由于不支持混合 OFDMA 和 MU-MIMO 配置，TXVECTOR 会在所有用户分配相同 RU 时标识为 MU-MIMO，否则它表示为 OFDMA 传输。在 PHY 层，OFDMA 和 MU-MIMO 的传输方式相似，主要区别在于 MU-MIMO 在上行链路方向上允许多个发射器同时共享相同的频谱。当前的 PHY 抽象模型假设完美条件，其中干扰管理器能够检测属于同一 MU-MIMO 传输的信号，并确保它们不会相互干扰。模型仍然支持与其他信号（包括其他 MU-MIMO 传输）的干扰。3.2. MAC 模型3.2.1. 基础架构模式中的关联在基础架构模式中，关联是一种高层 MAC 功能，由关联管理器（WifiAssocManager 基类和默认子类 WifiDefaultAssocManager）实现。站点 MAC、关联管理器基类和子类之间的交互如图 扫描过程 所示。扫描过程STA 的 Wi-Fi MAC 请求关联管理器启动具有指定参数的扫描过程，包括扫描类型（主动或被动）、目标 SSID、扫描的信道列表等。在扫描结束时，STA Wi-Fi MAC 期望收到最佳 AP 的通知以进行关联。在扫描期间接收到的每个 Beacon 或 Probe Response 帧都会被转发到关联管理器，该管理器会维护一个与扫描参数匹配的候选 AP 列表。默认关联管理器会按照接收到的 Beacon/Probe Response 帧的 SNR 降序排列这些 AP。当通知开始扫描过程时，默认关联管理器会调度一个方法调用，该方法处理在调用时已接收到的帧中包含的信息。当 AP 和 STA 都具有多个链接（即它们是 802.11be MLDs）时，默认关联管理器尝试尽可能多地建立链接。这涉及在 STA 的一些链接上切换工作信道以匹配与 AP MLD 关联的 AP 所运行的信道。如果 AP 因某种原因拒绝关联，STA 将尝试与下一个最佳 AP 关联，直到候选列表耗尽，这时 STA 将进入 “REFUSED” 状态。在这种情况下，模拟用户需要通过某种方式强制重新尝试关联，例如更改配置（即 STA 不会在拒绝后持续尝试关联）。一旦关联完成，如果模拟用户更改了配置，STA 将尝试重新关联到现有的 AP。如果丢失的 Beacon 数量超过阈值，STA 将通知设备其他部分链路已断开（关联丢失），并重新启动扫描过程。需要注意的是，当关联请求失败且未明确被拒绝时（即 AP 未响应关联请求），也可能发生这种情况。在非 AP MLD 的情况下，要丢失关联，必须在任何链接的最大 Beacon 丢失次数乘以两次连续 Beacon 帧间隔的时间间隔内未接收到 Beacon。3.2.2. 漫游当前不支持第 2 层漫游（即 STA 将其关联从一个 AP 切换到另一个 AP）。因此，IEEE 802.11 标准中描述的最小/最大信道驻留时间实现也被省略，因为它仅在信道漫游的上下文中有意义。3.2.3. 信道访问802.11 分布式协调功能（DCF）用于计算何时允许访问传输介质。虽然实现 DCF 使用周期性定时器（每个时隙到期触发）会相对简单，但我们选择了 ji2004sslswn(https://www.nsnam.org/docs/models/html/wifi-references.html#ji2004sslswn) 中描述的方法，该方法仅在需要时惰性计算退避定时器持续时间，据称性能明显优于更简单的周期性定时器解决方案。DCF 基本访问的描述详见 ieee80211-2016(https://www.nsnam.org/docs/models/html/wifi-references.html#ieee80211-2016) 的第 10.3.4.2 节：“当 STA 确定传输队列中有帧时，若介质空闲且持续空闲一个 DIFS 时间（或一个 EIFS 时间，从上一忙碌事件结束算起，以较大者为准），并且退避定时器为零，则可以传输 MPDU。否则，应遵循 10.3.4.3 节描述的随机退避过程。”因此，当以下所有条件都满足时，STA 可以不调用退避过程：在帧排队等待传输时，介质处于空闲状态；从帧排队开始到以下事件的较新者为止，介质持续空闲：一个 DIFS 时间；从上一个介质忙碌事件（与接收的错误帧相关联）结束开始的一个 EIFS 时间；退避定时器为零。EDCA 的退避过程与 DCF 的稍有不同，其描述详见 ieee80211-2016(https://www.nsnam.org/docs/models/html/wifi-references.html#ieee80211-2016) 的第 10.22.2.2 节。在信道访问方面，ns-3 默认配置下，QoS 和非 QoS AP 的 Beacon 在 PIFS 时间（SIFS 加上一个时隙时间）后访问信道，且无需退避（CWmin 和 CWmax 都设置为零），这使得 Beacon 比其他访问类别具有更高的优先级。3.2.4. Frame Exchange Managers随着 IEEE 802.11 标准的不断演进，添加的功能越来越多，因此让单个组件处理所有允许的帧交换序列变得愈发困难。为保持代码的清晰和可扩展性，同时避免代码重复，ns-3 引入了一系列 FrameExchangeManager 类。这些类分别处理不同修正案引入的帧交换序列。FrameExchangeManager 类的继承关系如图 FrameExchangeManager hierarchy 所示。FrameExchangeManager 层次结构每个 FrameExchangeManager 类支持的功能如下：FrameExchangeManager：基类，处理非 QoS 站点的基本序列，包括 MPDU 和普通确认帧、RTS/CTS 和 CTS-to-self、NAV 设置和重置、MPDU 分片等。QosFrameExchangeManager：添加了 TXOP 支持，包括多种保护设置、通过 CF-End 进行 TXOP 截断、TXOP 恢复、在响应 TXOP 持有者发送的 RTS 时忽略 NAV。HtFrameExchangeManager：支持压缩型 Block Ack、A-MSDU 和 A-MPDU 聚合以及隐式 Block Ack 请求策略。VhtFrameExchangeManager：支持 S-MPDUs。HeFrameExchangeManager：支持通过 DL OFDMA 和 UL OFDMA 传输和接收多用户帧。FrameExchangeManager 类可以通过属性控制其处理的帧交换序列。例如，FrameExchangeManager 基类具有 ProtectedIfResponded 属性，用于启用/禁用 RTS/CTS 保护，适用于已响应需要确认的帧的站点，即使该帧未受到 RTS/CTS 的保护。3.2.5. MAC 队列在 QoS 站点，每个 EDCA 功能，以及在非 QoS 站点，DCF，都有自己的 MAC 队列（WifiMacQueue 类的一个实例），用于存储来自上层的待传输数据包。在 QoS 站点，每个接收到的数据包根据套接字优先级分配一个用户优先级（例如，可参考 wifi-multi-tos 或 wifi-mac-ofdma 示例），该优先级决定处理数据包的接入类别（Access Category）。默认情况下，Wi-Fi MAC 队列支持流控制，因此如果对应的 MAC 队列已满，上层不会向下转发数据包。Wi-Fi MAC 队列不支持动态队列限制（如字节队列限制）；因此，只有当某接入类别的 WifiMacQueue 完全满时，才会对流量控制层产生反压（即，当队列深度达到 MaxSize 属性值时，默认值为 500 个数据包）。TCP 小队列（TSQ）[corbet2012] 是 Linux 的一项功能，用于从 Wi-Fi 设备向套接字层提供反馈，以控制 Wi-Fi 层队列中的数据量。ns-3 的 TCP 尚未实现 TSQ，WifiNetDevice 也未提供此类具体反馈（尽管使用现有的追踪源可能足以支持）。无论如何，实验测试表明，TSQ 会干扰 Wi-Fi 上行传输中的聚合 [grazia2022]。数据包在 Wi-Fi MAC 队列中保留，直到被确认或丢弃。数据包可能因以下原因被丢弃，例如，其生命周期到期（即在队列中停留时间过长）或达到最大重试次数。数据包的最大生命周期可以通过 WifiMacQueue 的 MaxDelay 属性配置。可以使用多种追踪源来跟踪数据包传输的结果（请参阅对应的 Doxygen 文档）：WifiMac 追踪源：AckedMpdu、NAckedMpdu、DroppedMpdu、MpduResponseTimeout、PsduResponseTimeout、PsduMapResponseTimeout。WifiMacQueue 追踪源：Expired。内部，Wi-Fi MAC 队列由多个子队列组成，每个子队列存储特定类型的帧（例如，数据帧或管理帧），并具有特定的接收方地址和 TID。对于单用户传输，下一站由 Wi-Fi MAC 队列调度器（由 WifiMac 实例持有）确定。Wi-Fi MAC 队列调度器由一个基类（WifiMacQueueScheduler）及其定义特定调度策略的子类实现。默认调度器（FcfsWifiQueueScheduler）为管理帧分配更高优先级，并以先进先出（FCFS）方式处理数据帧。对于多用户传输（见下文），调度由多用户调度器执行，该调度器可以选择是否咨询 Wi-Fi MAC 队列调度器，以确定通过多用户下行链路（DL）或上行链路（UL）传输需要服务的站点。3.2.6. 多用户传输自 IEEE 802.11ax 修正案引入以来，多用户（MU）传输成为可能，通过 OFDMA 和/或 MU-MIMO 实现下行链路（DL）和上行链路（UL）传输。目前，ns-3 仅支持通过 OFDMA 进行的多用户传输。以下为 DL OFDMA 的三种确认序列。第一种确认序列由多个 BlockAckRequest/BlockAck 帧组成，这些帧作为单用户帧发送，如图 单用户格式的 DL MU 帧确认 所示。单用户格式的 DL MU 帧确认第二种确认序列使用 MU-BAR 触发帧发送（作为单用户帧），以请求在 TB PPDUs 中发送的 BlockAck 响应，如图 通过单用户帧发送的 MU-BAR 触发帧的 DL MU 帧确认 所示。通过单用户帧发送的 MU-BAR 触发帧的 DL MU 帧确认第三种确认序列将 MU-BAR 触发帧聚合到 DL MU PPDU 中的每个 PSDU，BlockAck 响应在 TB PPDUs 中发送，如图 通过聚合的 MU-BAR 触发帧的 DL MU 帧确认 所示。通过聚合的 MU-BAR 触发帧的 DL MU 帧确认对于 UL OFDMA，支持 BSRP 触发帧和基本触发帧，如图 使用 UL OFDMA 的帧交换序列 所示。AP 发送 BSRP 触发帧，以请求站点发送包含缓冲状态报告（Buffer Status Reports）的 QoS Null 帧。AP 发送基本触发帧，以请求站点在 TB PPDUs 中发送数据帧，这些帧由 AP 通过 Multi-STA BlockAck 帧确认。需要注意的是，为了使两次帧交换序列以 SIFS 间隔分隔（如图所示），必须满足以下条件：传输接入类别具有非零 TXOP 限制；TXOP 中有足够剩余时间进行基本触发帧启动的帧交换序列；多用户调度器选择在 BSRP 触发帧之后发送基本触发帧。使用 UL OFDMA 的帧交换序列3.2.7. 多用户调度器一个新的组件，称为 MultiUserScheduler，负责确定聚合 AP 在获得 TXOP 时执行的帧交换序列（DL OFDMA、UL OFDMA 或 BSRP 触发帧），以及执行所选帧交换序列所需的信息（例如，在 DL OFDMA 的情况下需要发送的 PSDU 集合）。TXOP 是在请求信道访问后（通常由 DCF/EDCA 执行）获得的。如果设备有帧需要传输，MultiUserScheduler 可以协调 UL MU 传输，即使没有 DL 流量。访问请求间隔可以通过 AccessReqInterval 属性设置为非零值。此访问请求间隔是 MultiUserScheduler 两次连续请求信道访问的间隔。这些请求独立于 AP 队列中是否有帧存在。还可以设置 MultiUserScheduler 请求信道访问的接入类别（通过 AccessReqAc 属性），并选择访问请求间隔是否从 MultiUserScheduler 上次请求信道访问或上次由 DCF/EDCA 获得信道访问的时间开始计算（通过 DelayAccessReqUponAccess 属性）。MultiUserScheduler 是一个抽象基类。目前，唯一可用的子类是 RrMultiUserScheduler。默认情况下，没有多用户调度器聚合到 AP（因此未启用 OFDMA）。3.2.8. 轮询多用户调度器轮询多用户调度器动态分配每个站点的优先级，以确保在选择站点进行 DL 多用户传输时的空中时间公平性。NStations 属性允许设置可成为 DL 多用户帧接收方的最大站点数。因此，每当 HE AP 访问信道以传输 DL 多用户帧时，调度器确定 AP 需要发送帧的站点数（以该属性指定的值为上限），并尝试为尽可能多的站点分配相等大小的 RUs，而不留有未使用的相同大小的 RUs。例如，如果信道带宽为 40 MHz，且确定的站点数为 5，则前 4 个站点（按优先级排序）将各分配一个 106-tone RU（如果分配了 52-tone RU，将会有三个 52-tone RUs 未使用）。如果可以分配中央 26-tone RU（由 UseCentral26TonesRus 属性确定），尚未分配 RU 的站点可能会被分配这些 RU。在上述示例中，第五个站点将分配一个可用的中央 26-tone RU。当启用 UL OFDMA（通过 EnableUlOfdma 属性）时，每个 DL OFDMA 帧交换之后都会有一个 UL OFDMA 帧交换，涉及相同的站点集和与之前 DL 多用户帧相同的 RU 分配。可以选择（取决于 EnableBsrp 属性的值）在发送基本触发帧之前发送 BSRP 触发帧，以便 AP 收集站点的缓冲状态信息。3.2.9. 增强的多链路单无线电操作（EMLSR）IEEE 802.11be 修正案引入了 EMLSR 操作模式，允许非 AP MLD 在一组设置的链路（称为 EMLSR 链路）上交替进行帧交换（参见 IEEE 802.11be D4.1 第 35.3.17 节）。ns-3 支持以下描述的 EMLSR 操作。3.2.9.1. 支持 EMLSR 操作模式的非 AP MLD 架构支持 EMLSR 操作模式的非 AP MLD 架构基于以下假设：只有一个 PHY 实例（称为 主 PHY）具有完整的 TX/RX 能力，而其他 PHY 实例（简称为 辅助 PHY 或 aux PHYs）的 TX/RX 能力有限。因此，只有主 PHY 能够与 AP MLD 进行帧交换。由于帧交换可以发生在任何 EMLSR 链路上，主 PHY 所操作的链路会在模拟过程中动态切换，具体如以下所述。3.2.9.2. 启用/禁用 EMLSR 模式在支持 EMLSR 操作模式的非 AP MLD 上，可以启用 EMLSR 模式的链路（至少两条），并与支持 EMLSR 操作模式的 AP MLD 执行多链路设置。MLD 的 EHT 配置中的 EmlsrActivated 属性决定了该 MLD 是否支持 EMLSR 操作模式。如果非 AP MLD 的 EmlsrActivated 属性被设置为 true，则 WifiMacHelper 将通过 SetEmlsrManager 方法配置的类型和属性值安装一个 EMLSR 管理器。可以通过 EMLSR 管理器基类的 EmlsrLinkSet 属性启用或禁用非 AP MLD 链路上的 EMLSR 模式（默认情况下，多链路设置后禁用 EMLSR 模式）。设置 EmlsrLinkSet 属性会触发 EML 操作模式通知帧的发送，用于向 AP 通知新的 EMLSR 链路集（如果多链路设置已完成）。否则，EMLSR 链路集将被存储，并在多链路设置完成后立即发送 EML 操作模式通知帧。因此，用户可以通过在初始化时设置 EmlsrLinkSet 属性，选择在多链路设置完成后立即启用某些链路上的 EMLSR 模式；或者可以在初始化时将 EmlsrLinkSet 属性留空，并在运行时设置它以在特定的模拟时间启用某些链路上的 EMLSR 模式（多链路设置完成后）。非 AP MLD 用于发送 EML 操作模式通知帧的链路由 EMLSR 管理器子类选择。默认的 EMLSR 管理器子类 DefaultEmlsrManager 选择主 PHY 操作的链路。当非 AP MLD 收到 EML 操作模式通知帧的确认后，会启动一个计时器，其持续时间为 AP MLD 通知的切换超时时间。当计时器过期，或者非 AP MLD 收到来自 AP MLD 的 EML 操作模式通知帧时，EMLSR 模式将在请求的链路集上启用（如果该集合不为空）或禁用（否则）。请求启用 EMLSR 模式的链路集必须包括主 PHY 操作的链路；在其他启用 EMLSR 模式的链路上操作的 PHY 实例被认为是辅助 PHY。主 PHY 的 PHY 实例通过 EMLSR 管理器基类的 MainPhyId 属性进行配置。该类还允许定义辅助 PHY 的 TX/RX 能力：AuxPhyMaxModClass 属性表示辅助 PHY 支持的最大调制类别。AuxPhyChannelWidth 属性表示辅助 PHY 支持的最大信道宽度（MHz）。该属性的值可能会根据支持的最大调制类别自动限制。AuxPhyTxCapable 属性表示辅助 PHY 是否能够发送帧。3.2.9.3. 下行链路 TXOPEMLSR 操作：下行链路 TXOP当支持 EMLSR 操作模式的 AP MLD 需要在与以 EMLSR 模式运行的非 AP MLD 的链路上启动帧交换时，它会发送一个 MU-RTS 触发帧，作为该交换的初始控制帧（ICF），请求非 AP MLD（以及可能的其他设备）响应（见图 EMLSR 操作：下行链路 TXOP）。MU-RTS 触发帧以非 HT 重复 PPDU 形式传输，传输速率为 6 Mbps、12 Mbps 或 24 Mbps。当初始控制帧的传输开始时，AP MLD 会阻止 EMLSR 链路上受 MU-RTS 触发帧影响的客户端的其他链路上的传输，以避免在这些链路上启动新的帧交换。当 EMLSR 模式启用时，可以通过 EMLSR 管理器基类的 EmlsrPaddingDelay 和 EmlsrTransitionDelay 属性分别设置填充延迟和切换延迟。3.2.9.4. 上行链路 TXOPEMLSR 操作：上行链路 TXOPEMLSR 客户端可以在任何 EMLSR 链路上启动 UL TXOP。当在辅助 PHY 操作的链路上获得信道访问时，辅助 PHY 会发送 RTS 帧，然后主 PHY 接管 TXOP（见图 EMLSR 操作：上行链路 TXOP）。通过 EmlsrManager 类的 MediumSyncDuration、MsdMaxNTxops 和 MsdOfdmEdThreshold 属性可以分别配置同步延迟计时器的持续时间、最大 TXOP 尝试次数和主 20 MHz 信道的 OFDM ED 阈值。3.2.10. 确认管理器自 IEEE 802.11e 修正案引入以来，可用的确认策略编码在 QoS 数据帧的 QoS 控制字段中的确认策略子字段中。WifiAckManager 是为多种确认管理器提供接口的抽象基类，目前默认确认管理器是 WifiDefaultAckManager。3.2.11. WifiDefaultAckManagerWifiDefaultAckManager 根据以下属性值确定使用的确认策略：UseExplicitBar：决定当需要接收方响应且当前传输包括多个帧（A-MPDU）时是否使用 显式 Block Ack 请求 策略。BaThreshold：确定 Block Ack 协议的发起方需要请求接收方响应的条件。DlMuAckSequenceType：选择 DL MU 帧的确认序列（单用户格式确认、通过 MU-BAR 触发帧确认或通过聚合到数据帧的 MU-BAR 触发帧确认）。3.2.12. 保护管理器保护管理器负责在发送帧时决定是否使用保护机制。WifiProtectionManager 是一个抽象基类，为多种保护管理器提供接口。目前默认的保护管理器是 WifiDefaultProtectionManager。3.2.13. WifiDefaultProtectionManagerWifiDefaultProtectionManager 根据远程站点管理器提供的信息选择合适的保护机制。3.2.14. 速率控制算法ns-3 提供多种速率控制算法，有些基于真实设备中的实现，另一些来自学术文献。以下是 MAC 低层可用的速率控制算法：真实设备中的算法：ArfWifiManagerOnoeWifiManagerConstantRateWifiManagerMinstrelWifiManagerMinstrelHtWifiManager文献中的算法：IdealWifiManager（WifiHelper 的默认算法）AarfWifiManager [lacage2004aarfamrr]AmrrWifiManager [lacage2004aarfamrr]CaraWifiManager [kim2006cara]RraaWifiManager [wong2006rraa]AarfcdWifiManager [maguolo2008aarfcd]ParfWifiManager [akella2007parf]AparfWifiManager [chevillat2005aparf]ThompsonSamplingWifiManager [krotov2020rate]3.2.15. ConstantRateWifiManager固定速率控制算法始终为每个数据包使用相同的传输模式。用户可以为所有单播包设置所需的 DataMode，以及为所有请求控制包（例如 RTS）设置 ControlMode。如果希望为非单播包指定不同的数据模式，用户需设置 WifiRemoteStationManager 的 NonUnicastMode 属性。否则，将默认使用最低速率模式。可用属性：DataMode（默认值：WifiMode::OfdmRate6Mbps）：指定所有单播包的模式。ControlMode（默认值：WifiMode::OfdmRate6Mbps）：指定所有请求控制包的模式。3.2.16. IdealWifiManager理想速率控制算法根据先前发送数据包的信噪比（SNR）选择最佳模式。当节点 A 向节点 B 发送单播包，且 B 成功接收后，B 会记录接收包的 SNR 并通过 ACK 将其发送回 A。这样，A 可以通过一个额外的机制（因而被称为“理想”）获取发送给 B 的数据包的 SNR，从而根据一组 SNR 阈值选择传输模式。可用属性：BerThreshold（默认值：1e-6）：用于计算每种模式的 SNR 阈值的最大比特错误率（BER）。3.2.17. ThompsonSamplingWifiManagerThompson 采样（TS）是多臂老虎机问题的经典解决方案。ThompsonSamplingWifiManager 实现了一种基于 TS 的速率控制算法，其目标是提供一个简单的统计算法，且参数较少。3.2.18. MinstrelWifiManagerMinstrel 是一种源自 madwifi 项目的速率控制算法，当前是 Linux 内核的默认速率控制算法。3.2.19. MinstrelHtWifiManagerMinstrel 的扩展，支持 802.11n/ac/ax。3.2.20. 802.11ax OBSS PD 空间复用802.11ax 模式支持 OBSS PD 空间复用功能。OBSS PD 表示重叠基本服务集前导检测（Overlapping Basic Service Set Preamble-Detection），在特定条件下允许 STA 忽略其他 BSS 的 PPDU。3.2.21. OBSS PD 算法ObssPdAlgorithm 是 OBSS PD 算法的基类，实现了通用功能，包括设置回调并在算法请求时执行 PHY 重置。3.2.22. 恒定 OBSS PD 算法恒定 OBSS PD 算法由 ConstantObssPdAlgorithm 类实现。3.2.23. 修改 Wi-Fi 模型修改默认的 Wi-Fi 模型是研究中常见的任务。本节概述了如何修改默认的 Wi-Fi 模型，常见任务包括但不限于：创建或修改 Wi-Fi 帧/头部，修改 wifi-mac-header.*。修改 MAC 低层，例如处理新控制帧（RTS/CTS/ACK/Block ACK）、更改双向/四向事务，通常修改 frame-exchange-manager.* 或其子类。修改 MAC 高层，例如处理新管理帧（Beacon/Probe），修改 wifi-mac.*、sta-wifi-mac.*、ap-wifi-mac.* 或 adhoc-wifi-mac.*。Wi-Fi 队列管理：修改 txop.* 和 qos-txop.*。通道接入管理：修改 channel-access-manager.* 文件，这些文件负责授予对 Txop 和 QosTxop 的访问。修改分片和 RTS 阈值，这些由 Wi-Fi 远程站点管理器处理。修改或创建新速率控制算法，需创建 Wi-Fi 远程站点管理器的子类或修改现有的算法。
目录 [隐藏] 1. 问题分析2. 问题结论3. 测试用例3.1. 创建 libtest.c3.2. 创建 main.c3.3. 编译并测试3.4. 预期输出 1. 问题分析日常被 GCC 折磨，GCC 链接报错，提示未定义符号。ld: Undefined symbols: _ssl_print_string, referenced from: _tls13_get_quic_secret in packet-tls.c.o _tls13_get_quic_secret in packet-tls.c.o排查编译流程确认该符号其最终被链接进入 third_party/wireshark/build/run/libwireshark.dylib。而我最终又链接了 libwireshark.dylib。usr/bin/cc -lwireshark packet-tls.c.o -o tls_dissector.so并且确认该符号并非 static。extern void ssl_print_string(const gchar* name, const StringInfo* data); void ssl_print_string(const gchar* name, const StringInfo* data) { ssl_print_data(name, data->data, data->data_len); }直接查找二进制文件也能够确认其符号已经被链接进入 dylib。 grep "packet-tls-utils.c.o" -r . --include=*.dylib Binary file ./third_party/wireshark/build/run/libwireshark.17.0.5.dylib matches通过 nm 确认符号属性发现其为 t。$ nm third_party/wireshark/build/run/libwireshark.17.0.5.dylib|grep _ssl_print_string 0000000000ad2108 t _ssl_print_string符号类型描述U未定义符号 (undefined)A绝对符号 (absolute)T全局可见的文本段符号 (global text section symbol)t本地文本段符号 (local text section symbol)D全局可见的数据段符号 (global data section symbol)d本地数据段符号 (local data section symbol)B全局可见的 BSS 段符号 (global bss section symbol)b本地 BSS 段符号 (local bss section symbol)C公共符号 (common symbol)c本地公共符号 (local common symbol)-调试符号表条目 (for debugger symbol table entries)S全局可见的其他段中的符号 (global symbol in a section other than those above)s本地其他段中的符号 (local symbol in a section other than those above)I全局可见的间接符号 (global indirect symbol)i本地间接符号 (local indirect symbol)u动态共享库中的小写 u 表示对同一库中另一个模块的私有外部未定义引用 (an undefined reference to a private external in another module in the same library)再次查看对应的 .o 确认其为 T。$ nm third_party/wireshark/./build/epan/dissectors/CMakeFiles/dissectors.dir/packet-tls-utils.c.o |grep _ssl_print_string 0000000000001c00 T _ssl_print_string2. 问题结论那么该文本符号从 T 变成了 t 只能够是在链接阶段了。最后确认了 -fvisibility=hidden。这个选项会将所有符号的默认可见性设置为隐藏，除非显式地声明为 default。这意味着，尽管在编译 .o 文件时符号是全局可见的，但在链接生成 .dylib 文件时符号变成了局部可见。那为什么要这么做呢？https://gitlab.com/wireshark/wireshark/-/blob/4aa814ac25a18ea48d4002351ed45d4b245b0c08/CMakeLists.txt#L1070-1104# # Try to have the compiler default to hiding symbols, so that only # symbols explicitly exported with WS_DLL_PUBLIC will be visible # outside (shared) libraries; that way, more UN*X builds will catch # failures to export symbols, rather than having that fail only on # Windows. # # We don't need that with MSVC, as that's the default. # if( NOT CMAKE_C_COMPILER_ID MATCHES "MSVC") # # Try the GCC-and-compatible -fvisibility-hidden first. # check_c_compiler_flag(-fvisibility=hidden FVHIDDEN) if(FVHIDDEN) set(CMAKE_C_FLAGS "-fvisibility=hidden ${CMAKE_C_FLAGS}") 主要目的是设置编译器默认隐藏符号，确保只有显式导出的符号才会在共享库（shared library）中可见。这可以帮助在更多的 Unix 系统上捕捉到符号导出失败的问题，而不仅仅是在 Windows 上。https://gitlab.com/wireshark/wireshark/-/blob/4aa814ac25a18ea48d4002351ed45d4b245b0c08/include/ws_symbol_export.h#L131-134 /* * Symbols exported from libraries. */ #define WS_DLL_PUBLIC_DEF __attribute__ ((visibility ("default")))3. 测试用例下面是一个简单的示例代码，用于测试 -fvisibility=hidden 和 __attribute__((visibility("default"))) 的效果。我们将创建两个文件：一个是库文件 libtest.c，另一个是测试程序 main.c。在库文件中，我们将定义两个函数，其中一个函数使用 __attribute__((visibility("default"))) 明确导出，而另一个函数不使用任何可见性属性。3.1. 创建 libtest.c// libtest.c #include <stdio.h> // 这个函数将被默认隐藏，因为我们使用 -fvisibility=hidden 编译 void hidden_function() { printf("This is a hidden function.\n"); } // 这个函数将被显式导出，即使我们使用 -fvisibility=hidden 编译 __attribute__((visibility("default"))) void visible_function() { printf("This is a visible function.\n"); }3.2. 创建 main.c// main.c #include <stdio.h> // 声明从库中导入的函数 void hidden_function(); void visible_function(); int main() { // 调用这两个函数 hidden_function(); visible_function(); return 0; }3.3. 编译并测试首先编译库文件 libtest.c，使用 -fvisibility=hidden 来隐藏默认符号：gcc -fvisibility=hidden -c libtest.c -o libtest.o然后创建静态库 libtest.a：ar rcs libtest.a libtest.o接着编译主程序 main.c 并链接静态库：gcc main.c libtest.a -o main最后运行测试程序：./main3.4. 预期输出如果一切正常，你应该看到以下输出：This is a hidden function. This is a visible function.在编译和链接过程中，可以使用 nm 命令来查看库中的符号，验证符号的可见性：nm libtest.o你应该看到类似以下的输出：0000000000000000 t hidden_function 000000000000001a T visible_function其中，hidden_function 是本地可见的（小写 t），而 visible_function 是全局可见的（大写 T）。
目录 [隐藏] -m64-fpack-struct_attrribute_结论 聊聊 GCC 中的对齐最近看到一个问题，在 64 位操作系统中。实际测试环境 Ubuntu 22.04，GCC 11。struct test{ long a int b; char c }问 sizeof(stuct test) =？当然我知道这道题想考察的 int/long 定义以及对齐问题，之前编码也不会太在意这类似问题，直接打印出来就行，但是作为考题这里有诸多不严谨地方。我们好好探索下。不管三七二十一，直接编译打印。$ gcc test.c $ ./a.out 16显然地，这里 int 为4个字节，char 被4字节对齐。看几个 GCC 选项-m64https://gcc.gnu.org/onlinedocs/gcc-9.5.0/gcc/x86-Options.html#x86-Options-m32 -m64 -mx32 -m16 -miamcu Generate code for a 16-bit, 32-bit or 64-bit environment. The -m32 option sets int, long, and pointer types to 32 bits, and generates code that runs on any i386 system. The -m64 option sets int to 32 bits and long and pointer types to 64 bits, and generates code for the x86-64 architecture. For Darwin only the -m64 option also turns off the -fno-pic and -mdynamic-no-pic options. The -mx32 option sets int, long, and pointer types to 32 bits, and generates code for the x86-64 architecture. The -m16 option is the same as -m32, except for that it outputs the .code16gcc assembly directive at the beginning of the assembly output so that the binary can run in 16-bit mode. The -miamcu option generates code which conforms to Intel MCU psABI. It requires the -m32 option to be turned on.看起来这里默认最大设置都是4字节对齐。$ gcc test.c -m16 #编译失败 $ gcc test.c -m32 $ ./a.out 12 $ gcc test.c -m64 $ ./a.out 16-fpack-structhttps://gcc.gnu.org/onlinedocs/gcc-9.5.0/gcc/Code-Gen-Options.html#Code-Gen-Options-fpack-struct[=n] Without a value specified, pack all structure members together without holes. When a value is specified (which must be a small power of two), pack structure members according to this value, representing the maximum alignment (that is, objects with default alignment requirements larger than this are output potentially unaligned at the next fitting location.显然地，这里的编译选项能够设置 long 类型和对齐。先看默认值。$ gcc test.c -fpack-struct=1 $ ./a.out 13 $ gcc test.c -fpack-struct=2 $ ./a.out 14 $ gcc test.c -fpack-struct=4 $ ./a.out 16 $ gcc test.c -fpack-struct=8 $ ./a.out 16_attrribute_这里能够设置 -m32 将 long 和指针类型设置为 4字节，但是对齐方式还是 4字节。https://gcc.gnu.org/onlinedocs/gcc/Common-Variable-Attributes.htmlstruct test_a { char a; //1 int b; //4 long c; //8 } __attribute__ ((packed)) ; //13 //__attribute__ ((aligned(1))) ; //16 //__attribute__ ((aligned(2))) ; //16 //__attribute__ ((aligned(4))) ; //16 //__attribute__ ((aligned(8))) ; //16 //__attribute__ ((aligned(16))) ; //16packed 为字节对齐，aligned 不管设置多少都是4自己对齐。结论如下结论。选项描述-m32 -m64 -mx32 -m16 -miamcu设置 long 和指针数据类型长度，-m32为4个字节，-m64 为8个字节。-fpack-struct设置对齐，支持 1、2、4字节对齐。_attrribute_设置对齐，支持 1、4字节对齐。
目录 [隐藏] 5.9.1 路由数据库5.9.1.1 路由器 ID 集5.9.1.2 链路集5.9.2 路由集5.9.3 Leader 数据库5.9.3.1 ID 分配集5.9.3.2 Leader 数据库丢失5.9.4 链路裕量和链路指标5.9.5 路由成本和下一跳5.9.6 循环和循环检测5.9.7 发送 Advertisement5.9.8 处理 Route64 TLVs5.9.9 路由器 ID 管理5.9.10 路由器 ID 分配5.9.10.1 ADDR_SOL.req – 地址请求5.9.10.2 ADDR_SOL.rsp – 地址请求响应5.9.10.3 ADDR_REL.ntf – 地址释放通知5.9.11 在链路超时之前发送链路请求5.10.1 Thread 网络内的单播数据包转发5.10.1.1 完整 Thread 设备转发5.10.1.2 简化 Thread 设备转发5.10.1.3 使用 Mesh Address Headers 转发数据包5.10.2 在 Thread 网络之外的单播数据包转发5.10.3 Thread 网络内的任播数据包转发5.10.3.1 Leader ALOC5.10.3.2 DHCPv6 Agent ALOC5.10.3.3 Service ALOC5.10.3.4 Commissioner ALOC5.10.3.5 Neighbor Discovery Agent ALOC5.10.3.6 Primary Backbone Router ALOC5.10.4 链路层目的地地址的选择 5.9 路由协议本文为 Thread 路由协议相关规范，翻译自Thread Specification->5.9 Routing Protocol，作为理论储备。这里未做任何解释，接下来会结合其他系列文档从抓包、代码实现结合协议规范去理解该路由协议。本节描述了 Thread 网络中使用的路由协议。Thread 路由协议是一个简单的距离向量路由协议。协议的主要目标是最大化地可以在单个消息中传输的路由信息量。因此，该协议限制了包含最多 MAX_ROUTERS 或更少路由器的网络。注意：可以使用超过 MAX_ROUTERS 的路由地址，以便在重新使用路由器地址之前等待超时。路由器定期向 Thread 网络分区中的所有其他路由器广播其路由成本以及与邻居之间的单跳链路质量。到达特定目的地的路由成本是所有邻居对该目的地广播的成本中的最小值加上到该邻居的成本。 Advertisement 发送的速率由 Trickle 算法的一个实例确定RFC 6206。提示：Trickle 算法是一种用于网络协议中的算法，旨在有效地维护和更新网络中的信息。它通常用于自组织网络（例如传感器网络）中，以确保网络中的路由信息、邻居信息或其他状态信息得到及时更新，同时尽量减少网络中的控制消息的数量。Trickle 算法的核心思想是在保持低开销的同时，仍然能够快速地传播重要的变更。该算法通过引入慢速的消息传播机制和快速的消息传播机制来实现这一目标。具体来说，Trickle 算法的工作方式如下：慢速传播： 定期（例如每分钟）发送一条慢速消息，其中包含节点当前的信息。这有助于保持网络中的信息更新。快速传播： 在检测到信息变更时，立即发送一条快速消息。这有助于迅速传播关键信息。定时器机制： 每个节点都维护一个定时器，定期触发慢速消息的发送。当接收到慢速或快速消息时，可以重置定时器。Trickle 算法通过这种方式实现了一种自适应机制，可以根据网络的实际变化来动态调整消息的传播频率。这有助于在网络拓扑结构变化较小的情况下，降低消息传播的开销，提高网络的效率。 Trickle 算法在许多自组织网络协议中都得到了应用，例如RPL（适用于低功耗和有限能量的网络中的IPv6路由协议）。5.9.1 路由数据库路由器维护一个路由数据库，记录使用 Thread 距离向量路由的每个单独接口的信息。这些数据库包含以下各小节中描述的数据集。5.9.1.1 路由器 ID 集接口的路由器 ID 集包含两个值：ID_set 是当前有效路由器 ID 的集合。ID_sequence_number 是分配给当前 ID_set 的序列号。5.9.1.2 链路集接口的链路集记录了与该接口上的其他路由器是或曾是邻居的信息。路由器为与每个邻居的链接维护一个元组，如下所示：(L_router_id, L_link_margin, L_incoming_quality, L_outgoing_quality, L_age)其中：L_router_id 是分配给该邻居的路由器 ID。L_link_margin 是从该邻居接收到的消息的测得链路余量。L_incoming_quality 是根据 L_link_margin 计算得到的传入链路质量度量。L_outgoing_quality 是由该邻居报告的从本路由器发送消息的传出链路质量度量。L_age 是自上次从该邻居接收到 MLE Advertisement 以来经过的时间。5.9.2 路由集接口的路由集记录了关于其他路由器的路由成本和下一跳信息，这些路由器是从该路由器出发可达（具有有限路由成本）的或最近可达的。路由器为每个可达路由器维护一个元组，如下所示：(R_destination, R_next_hop, R_route_cost)其中：R_destination 是该路由的目的地分配的路由器 ID。R_next_hop 是沿路由的下一跳分配的路由器 ID。R_route_cost 是由 R_next_hop 报告给 R_destination 的路由成本。5.9.3 Leader 数据库作为 Leader 的路由器维护一个额外的数据库，用于跟踪路由器 ID 的分配情况。与路由数据库类似，Leader 为设备作为 Leader 的每个接口维护一个单独的 Leader 数据库。5.9.3.1 ID 分配集接口的分配集记录了每个路由器 ID 的状态。Leader 为每个路由器 ID 维护一个元组，如下所示：(ID_id, ID_owner, ID_reuse_time)其中：ID_id 是一个路由器 ID。ID_owner 是当前分配给该路由器 ID 的 IEEE 802.15.4 扩展地址，或者如果未分配，则是最近分配给它的路由器的 IEEE 802.15.4 扩展地址。ID_reuse_time 是可以重新分配此路由器 ID 的时间，如果它当前未分配。Leader 记录一个路由器 ID 是否当前分配在路由器 ID 集中。5.9.3.2 Leader 数据库丢失在重置事件中， Leader 可能会丢失其 Leader 数据库、路由集和路由数据库的信息。如果 Leader 在重置前确定其 Thread 分区仍然处于活动状态，就必须按以下描述重新构建 Leader 数据库、路由集和路由数据库的丢失状态，并继续作为该 Thread 分区的 Leader 运行。详细信息请参阅第4章第4.7.1.3节《重置后的路由器同步》以确定这一点。路由数据库（第5.9.1节，路由数据库）和路由集（第5.9.2节，路由集）通过整合收到的相邻路由器的 MLE Link Accept、MLE Link Accept 和 Request 以及 MLE Advertisement 消息的信息进行重建。具体而言，ID_sequence_number 是通过选择具有最新 ID Sequence 值的入站 MLE Route64 TLV 并使用其中的 ID Sequence 值来重建的。然后，Router ID Set 是根据在所选的 MLE Route64 TLV 中指示的活动 Router IDs 进行重建。详细的 MLE Route64 TLV 处理细节请参阅第5.9.8节《处理 Route64 TLVs》。在 Leader 数据库 ID 分配集中，对于收到的 Route64 TLVs 中指示的每个活动 Router ID，创建一个元组，并将 ID_id 设置为 Router ID。对于无法确定 MAC 扩展地址的 Router IDs，元组的 ID_owner 字段设置为“invalid”（或“unknown”）。对于所有相邻路由器，可以通过接收的 MLE 消息的 link-local IPv6 源地址确定 MAC 扩展地址。ID_owner 字段将设置为相应的 MAC 扩展地址。ID_reuse_time 字段不设置，因为当前未激活的 Router IDs 不会创建元组。5.9.4 链路裕量和链路指标L_link_margin 值是来自相邻路由器的接收相对信号强度的运行平均值。相对信号强度记录为高于本地噪声底线的 dB，称为链路裕量。平均链路裕量根据表5-5转换为单向链路质量。表5-5 平均链路裕量转换为单向链路质量链路裕量(Link Margin)链路质量(Link Quality)链路成本(Link Cost)>20 dB31>10 dB22>2 dB14≤ 2 dB0无穷大单向链路质量通过 MLE Advertisements 中的 Route64 TLVs 与相邻设备共享。链路成本由链路的两个单向链路质量的最小值确定。例如，一个链路在一个方向上的链路质量为2，在另一个方向上的链路质量为1，因此最小链路质量为1，因此链路成本为4。只有四种可能的链路质量值可以最小化向相邻设备通信链路质量的开销。链路裕量可能直接由测量传入信号强度和噪声底线确定。或者，它可以从已确定与链路裕量存在相关性的一个或多个其他链路质量测量中派生。测量和相关性取决于硬件平台。无论链路裕量如何确定，都必须使用平均化来平滑短期波动。Thread 设备必须针对每个相邻设备维护链路裕量的指数加权移动平均值，每当从相邻设备接收到新的 MAC 帧时更新（采样），新样本值的加权为 1/8 或 1/16。这种权重选择是特定于实现的。例如，如果相邻设备的平均链路裕量为 A，并且从该相邻设备收到的链路裕量为 X，则新的平均值为 (A 7/8) + (X 1/8)。确定链路质量时还必须应用迟滞，以便接近两种质量之间边界的链路裕量不会频繁更改 L_incoming_quality。迟滞值为 2 dB，用于链路质量 1-2 和链路质量 2-3 之间的阈值。迟滞值为 1 dB，用于链路质量 0-1 之间的阈值。例如，平均链路裕量必须至少增加到 12 dB，才能将现有质量 1 的链路更改为质量 2 的链路，并且至少增加到 3 dB，才能将现有质量 0 的链路更改为质量 1 的链路。如果路由器连续接收到特定路由器邻居的 802.15.4 状态 NO_ACK 的 FAILED_ROUTER_TRANSMISSIONS 次单播 MCPS.DATA-Requests，则必须将与该路由器邻居的链路质量设置为“0”。如第5.9.7节《发送 Advertisement 》所述，路由器随后将重置其 Trickle 计时器。请注意，由于失败的 clear channel assessments 导致的传输故障不算作未收到确认。5.9.5 路由成本和下一跳对于路由器 ID（R），路由成本是直接发送到 R 的成本和最小多跳路由成本中的最小值。更具体地说，如果在 Link Set 中存在 L_router_id = R 的条目，则 Cost_direct 是与 L_incoming_quality 和 L_outgoing_quality 的最小值关联的链路成本。如果没有这样的条目，则 Cost_direct 是无穷大。如果在 Route Set 中存在 R_destination = R 的条目，并且 R_route_cost 加上到 R_next_hop 的链路成本小于或等于 MAX_ROUTE_COST，则 Cost_multihop 就是这个总和。如果没有这样的条目，或者总和大于 MAX_ROUTE_COST，则 Cost_multihop 就是无穷大。到达 R 的路由成本是 Cost_direct 和 Cost_multihop 的最小值。如果 Cost_direct 和 Cost_multihop 都是无穷大，则 R 是不可达的。如果 Cost_direct 是有限的且小于或等于 Cost_multihop，则路由的下一跳是 R 本身。否则，下一跳是 R_next_hop。5.9.6 循环和循环检测到某些目的地的路由图中存在循环会导致到达该目的地的成本高于 MAX_ROUTE_COST。没有“分割视野”循环避免。如果路由器从邻居处收到一个数据包，而该邻居是数据包目的地的下一跳，则必须从 Route Set 中删除目的地的条目。这样可以检测并移除两跳循环。5.9.7 发送 Advertisement路由器按照本节描述发送 MLE Advertisement 。REED 按照第5.16.6节《子节点》的描述定期发送 MLE Advertisement 。所有 MLE Advertisement 必须发送到链路本地所有节点组播地址（FF02::1）。发送 MLE Advertisement 的速率由使用 Trickle 算法 RFC 6206 确定，使用 ADVERTISEMENT_I_MIN 和 ADVERTISEMENT_I_MAX 作为参数 I_MIN 和 I_MAX。Trickle 参数 k 设置为无穷大。 Advertisement 没有一致性检查。当报告的到一个或多个目的地的路由成本（在 Route64 TLV 中）变为或从无穷大（表示为值“0”）时，Trickle 计时器将被重置。如果删除具有非零成本的路由器 ID，则还应将其重置。在发送 Advertisement 时，路由器在 Route64 TLV 中包括 L_incoming_quality、L_outgoing_quality 和其 ID_set 中每个路由器 ID 的路由成本。如果到一个路由器 ID 的路由成本小于 MAX_ROUTE_COST，则 Route 值是该成本。否则，Route 值为无穷大（表示为值“0”）。5.9.8 处理 Route64 TLVs处理传入的 Route64 TLV 的第一步是将其路由器 ID 序列号与 ID_sequence_number 进行比较。如果 Route64 TLV 的序列号较高，则必须用 Route64 TLV 的序列号和 ID 集合替换 ID_sequence_number 和 ID_set 的值。若 Route64 TLV 的序列号比 ID_sequence_number 相同或更低，则 ID_sequence_number 和 ID_set 必须保持不变。如果接收设备具有路由器 ID，并且 MLE Advertisement 包括该 ID 的入链路质量，则发件者的 L_outgoing_quality 必须设置为该入链路质量。最后，对于除发件者 ID 或接收者 ID 外出现在 ID_set 和 Route64 TLV 的已分配路由器 ID 掩码中的每个路由器 ID（R），设 Cost_reported 为 Route64 TLV 中 R 的路由成本。随后：如果 Cost_reported = 0 且 R_next_hop == TLV 发件者 ID，则从路由器集合中删除 R。否则，如果 Cost_reported > 0 并且以下条件满足之一：路由器集合中没有路由器 ID R 的条目R_next_hop == TLV 发件者 ID发件者链接成本 + Cost_reported < R_next_hop 链接成本 + R_route_cost 则通过将 R_next_hop 设置为发件者 ID 并将 R_route_cost 设置为 Cost_reported，添加或更新 R 条目。REED 必须处理 Route64 TLVs，以获取 Thread 网络上的路由器数量，用于确定是否成为活跃路由器（参见第 5.9.9 节，路由器 ID 管理）。它还必须从父节点的 Route64 TLV 中获取到 Leader 的成本。如果此成本为无穷大，则子设备设置一个计时器，时间为 PARENT_ROUTE_TO_LEADER_TIMEOUT 秒。如果在计时器到期时未从父节点收到新的 Route64 TLV，则它尝试寻找并连接到具有有限 Leader 成本的新父节点。5.9.9 路由器 ID 管理Leader 负责管理路由器 ID。与所有路由器一样，Leader 维护一个显示已分配路由器 ID 的 ID_set。与其他路由器不同，Leader 还维护一个 ID_Assignment_Set，其中包含具有已分配 ID 的每个设备的 IEEE 802.15.4 扩展地址，对于当前未分配的 ID，则包含可以重新分配 ID 的时间。每当 Leader 分配或取消分配路由器 ID 时，它会更改其 ID_set，递增其 ID_sequence_number，并更新 ID_Assignment_Set 中路由器 ID 的条目。为了回收未使用的 ID，当 ID_sequence_number 在 ID_SEQUENCE_PERIOD 秒内保持不变时，Leader 还会递增 ID_sequence_number。如果一个路由器在 NETWORK_ID_TIMEOUT 秒内未从邻居处收到新的 ID_sequence_number，则它必须视为与 Leader 断开连接，并停止使用其当前的路由器 ID。Leader 可以随时取消分配路由器 ID。如果路由器 ID 的路由成本已达到 INFINITE_COST_TIMEOUT，则 Leader 应该取消分配路由器 ID。当 Leader 取消分配路由器 ID 时，它会从其 ID_set 中删除该 ID，递增 ID_sequence_number，并将路由器 ID 的 Reuse_Time 设置为当前时间加上 ID_REUSE_DELAY。Leader 不应在至少 ID_REUSE_DELAY 秒内重新分配路由器 ID。如果一个路由器根据新的网络数据确定其路由器 ID 已经取消分配，则它必须假定已与 Leader 断开连接，并按第 5.16.1 节“失去连接”的描述进行处理。如果 Thread 网络中分区的活跃路由器数量少于 ROUTER_UPGRADE_THRESHOLD（邻近节点 Route64 TLV 报告的），REED 必须等待从检测到条件开始到 ROUTER_SELECTION_JITTER 秒之间的随机时间，然后如果条件仍然满足，则必须尝试通过向 Leader 请求路由器 ID 来成为活跃路由器。这有助于防止许多子设备同时请求路由器 ID。如果 Thread 网络分区中的活跃路由器数量至少为 ROUTER_UPGRADE_THRESHOLD，低于 MAX_ROUTERS，并且最多有一个具有外部连接性的边界路由器在路由器角色中发挥作用，那么具有外部连接性的边界路由器应该尝试成为活跃路由器，方法是使用上述包括随机性的过程。这种方法通过边界路由器更好地连接到边网主机。如果网络中活跃路由器的数量超过 ROUTER_DOWNGRADE_THRESHOLD，则符合以下所有条件的活跃路由器必须尝试释放其路由器 ID 并成为 REED。让 M 为满足以下条件的路由器集合的邻居路由器：在 M 集合中至少有 MIN_DOWNGRADE_NEIGHBORS 个邻居。至少有一个邻居与 M 集合中所有路由器具有至少与 D 本身的质量相同的更好质量的链接。拥有的子设备数量少于三倍的多余路由器数量。换句话说，如果有 N 个活跃路由器且路由器有 C 个子设备，则 C < 3 *（N - ROUTER_DOWNGRADE_THRESHOLD）。不是 Leader。不是特定 Provisioning Domain（参见第 5.13.7 节“配置域”）的唯一边界路由器。如果多个边界路由器符合降级条件，则它们可以使用某种外部机制来选择是否进行降级。如果成为子设备的决定是由路由器数量增加触发的，设备必须首先在检测到增加后延迟随机时间，时间为 ROUTER_SELECTION_JITTER 秒，然后重新检查总路由器数量和上述标准。这有助于防止许多路由器同时成为子设备。5.9.10 路由器 ID 分配想要成为路由器的 REED 必须发送地址请求消息以请求 Leader 分配一个路由器 ID。Leader 的路由器 ID 是从共享的 Thread 网络数据中获取的。5.9.10.1 ADDR_SOL.req – 地址请求地址请求消息定义如下：CoAP 请求 URIcoap://[]:MM/a/as交易模式Req+Rsp_Piggybacked 或 Req+Rsp_Separate（请求部分）CoAP 载荷MAC 扩展地址 TLV[RLOC16 TLV]状态 TLVMAC 扩展地址 TLV 包括以便 Leader 可以跟踪 Thread 设备和 RLOC16 之间的映射关系。状态 TLV 包含表 5-10 的状态值之一，并指示发送请求的原因。如果原因是活动路由器数量少于 ROUTER_UPGRADE_THRESHOLD，则包含 TOO_FEW_ROUTERS。如果原因是发送者收到 MLE Child ID 请求消息（参见第 4.7.1 节“附着到父节点”），则包含 HAVE_CHILD_ID_REQUEST。如果一个拥有子设备的路由器移动到不同分区（参见第 5.16.4 节“重置 Thread 网络分区数据”），则包含 PARENT_PARTITION_CHANGE。如果发送者是希望通过成为路由器来提供与边网主机更好连接性的边界路由器，则包含 BORDER_ROUTER_REQUEST。如果先前具有状态 BORDER_ROUTER_REQUEST 的请求不成功，发送者可以使用不同的状态值 HAVE_CHILD_ID_REQUEST 重复请求（在应用抖动后）。但前提是在发送请求时，边界路由器确定当前具有外部连接性的边界路由器最多只有一个是活动路由器。RLOC16 TLV 仅在发送者先前是路由器，可能在另一个分区内，并希望保留其原始路由器 ID 时包含。Leader 不得为多个由 MAC 扩展地址标识的设备分配 RLOC16。如果状态是 HAVE_CHILD_ID_REQUEST 或 PARENT_PARTITION_CHANGE，则 Leader 为 REED 分配路由器 ID（如果有可用的）。如果状态是 BORDER_ROUTER_REQUEST，则 Leader 分配路由器 ID（如果有可用的），并且以下至少满足一个条件：当前具有外部连接性的边界路由器中最多有一个是活动路由器（如 Thread 网络数据所示）。路由器数量低于 ROUTER_UPGRADE_THRESHOLD。 注意：外部连接性的确定在第 5.9.9 节“路由器 ID 管理”中定义。如果状态是 TOO_FEW_ROUTERS，则 Leader 为 REED 分配路由器 ID（如果活动路由器数量少于 ROUTER_UPGRADE_THRESHOLD）。如果状态是未分配的编号或在请求中没有意义的状态编号，则 Leader 发送包含状态 UNRECOGNIZED_STATUS 的地址请求响应。如果 REED 请求了特定的路由器 ID 并且该路由器 ID 可用，则选择该路由器 ID。如果无法为分配使用或者由于请求原因无法分配路由器 ID，则 Leader 发送包含状态 'No Address Available' 的地址请求响应。如果分配了路由器 ID，则 Leader 发送包含状态 'Success' 的响应。5.9.10.2 ADDR_SOL.rsp – 地址请求响应地址请求响应消息格式如下：CoAP 响应代码2.04 Changed交易模式Req+Rsp_Piggybacked 或 Req+Rsp_Separate（响应部分）CoAP 载荷状态 TLV[RLOC16 TLV][路由器掩码 TLV]状态 TLV 包含表 5-10 中标记为有效的状态值，标记为 ADDR_SOL.rsp。RLOC16 TLV 包含分配的 RLOC16。路由器掩码 TLV 包含反映新 ID 分配的更新当前 ID 序列和路由器 ID 掩码。仅当状态为 'Success' 时才包含 RLOC16 TLV 和路由器掩码 TLV。如果一个路由器希望返回成为子设备，它必须首先作为子设备重新连接到网络，然后必须向 Leader 发送一个包含不再使用的路由器 ID 的地址释放通知消息。在释放之前重新连接避免了与 Leader 的争用，因为 Leader 需要同时标记路由器 ID 为未分配并将其用于发送响应。5.9.10.3 ADDR_REL.ntf – 地址释放通知地址释放通知消息是可靠通知，格式如下：CoAP 请求URI coap://[]:MM/a/ar交易模式Ntf/Qry/Ans_CONCoAP载荷MAC 扩展地址 TLVRLOC16 TLV如果一个路由器希望返回成为子设备，它必须首先作为子设备重新连接到网络，然后必须向 Leader 发送一个 ADDR_REL.ntf，其中包含它不再使用的 RLOC16 路由器地址。5.9.11 在链路超时之前发送链路请求路由器必须监视 Link Set 中每个路由器链路的 L_age 值。如果对于给定邻居，此值增加到超过 3 * ADVERTISEMENT_I_MAX，则至少有三个连续的 MLE Advertisement 消息被丢失。在这种情况下，路由器必须向邻居路由器的链路本地地址发送 MLE 链路请求单播消息。该 MLE 链路请求消息必须延迟最多 MLE_MAX_REACTION_TIME，以毫秒为分辨率。该 MLE 链路请求消息必须包含一个 TLV 请求 TLV，用于 Route64 TLV。如果从邻居路由器收到包含 Route64 TLV 的相应 Link Accept 消息，则 Link Set 中该链路的 L_age 值应设置为 '0'。5.10 单播数据包转发Thread 规范支持以下类型的单播和组播数据包转发：同属于 Thread 网络的源设备和目的设备之间的单播数据包转发。源设备或目的设备位于 Thread 网络之外的单播数据包转发。当目的设备位于 Thread 网络之外且被用任播 IPv6 地址寻址时，也包括任播数据包转发。同属于 Thread 网络的源设备和目的设备之间使用任播定位器（ALOC）目的地址的任播数据包转发。5.10.1 Thread 网络内的单播数据包转发在同一时间内，最多会有 MAX_ROUTERS 个路由器共享其路由信息，正如 5.9 节中描述的路由协议。在 Thread 网络内，只有路由器可以执行多跳数据包转发。5.10.1.1 完整 Thread 设备转发在 Thread 网络内部，只使用 RLOC 进行路由。Thread 内部的多跳数据包转发使用 6LoWPAN Mesh Header 和 RLOC16 在链路层进行。FTD 需要解析 EID 到 RLOC 地址映射并维护 EID 到 RLOC 映射缓存。FTD 还直接响应 EID 到 RLOC 映射查询。FTD 执行 IPv6 数据包转发的步骤如下：获取 IPv6 目的地的 RLOC。如果 IPv6 目的地址是 RLOC，则设备使用 IPv6 目的地址直接进行路由。如果 IPv6 目的地址是 ALOC，则设备使用第 5.10.3 节“Thread 网络内部的任播数据包转发”中定义的该类型 ALOC 的查找方法来确定适当的目的地的 RLOC，然后转发消息。如果 IPv6 目的地址在网内但不是 RLOC，则设备应检查其邻居表和 EID 到 RLOC 映射缓存，看看是否已知 RLOC。如果 RLOC 尚未知晓，则设备发起 EID 到 RLOC 查询，使用地址查询以获取 IPv6 目的地地址的 RLOC。如果 IPv6 目的地址在网外，则使用第 5.10.2 节“Thread 网络外的单播数据包转发”的规则确定适当的出口边界路由器的 RLOC。确定 RLOC 是否属于相邻设备。如果 RLOC 属于相邻设备，则 6LoWPAN 层使用 RLOC16 编码 6LoWPAN 帧，而无需使用 6LoWPAN Mesh Header，并设置 IEEE 802.15.4 目的地址为 RLOC16。如果 RLOC 不属于相邻设备，则 6LoWPAN 层使用 6LoWPAN Mesh Header 编码 6LoWPAN 帧，将 V 和 F 位设置为“1”，将 Originator Address 字段使用本地 RLOC16，将 Final Address 字段使用目的地的 RLOC16。6LoWPAN Mesh Header 中的 hopsLft 字段必须设置为大于到达目的地的路由成本。如果设备不是活跃路由器，则 hopsLft 字段必须增加一；如果目标 RLOC16 不是活跃路由器的 RLOC16，则 hopsLft 字段也必须增加一。请注意，将 hopsLft 字段设置为 14 或更小的值会减小 6LoWPAN Mesh Header 的大小。接着，设备使用路由集确定下一跳的 RLOC，并设置 IEEE 802.15.4 目的地址为下一跳的 RLOC16。接收到数据包的下一跳设备会查找路由集/邻居表中的下一跳，减少 6LoWPAN Mesh Header 中的跳数计数，然后将数据包发送给下一跳，作为 IEEE 802.15.4 目的地址。5.10.1.2 简化 Thread 设备转发MTD 作为仅操作主机设备，将所有 IPv6 数据包转发到其父级，而无需使用 6LoWPAN Mesh Header。因此，父级执行 IPv6 转发（而不是 6LoWPAN 网格转发）来处理来自 MTD 子级的数据包。 具体来说，MTD 不解析 EID 到 RLOC，并依赖其 FTD 父级来执行查询并在 6LoWPAN Mesh Header 中包括 RLOC16。对于具有网外目的地的数据包，MTD 不选择边界路由器，并依赖其 FTD 父级选择适当的边界路由器。MTD 也不选择特定的 DHCPv6 代理，而是使用 DHCPv6 代理 ALOC，并依赖其 FTD 父级将数据包传递给适当的 DHCPv6 代理。MTD 不选择特定的服务器，而是使用适当服务类型 ID 的服务 ALOC，并依赖其 FTD 父级将数据包传递给适当的服务器。MTD 也可以以类似的方式使用其他 ALOC 类型。5.10.1.3 使用 Mesh Address Headers 转发数据包RFC 4944 的第 11 节描述了如何使用“链路层路由表”中的信息转发带有 Mesh Addressing headers 的数据包。在 Thread 网络中，该表将 Mesh Addressing 头中的目标 RLOC16 映射到到达目的地的下一跳的 RLOC16。在活跃路由器上，此表格如下填充：如果有到路由器 R 的路由，则链路层路由表具有 R 的 RLOC16 条目和对应于 R 的潜在子级的 RLOC16。这些条目的下一跳是到达 R 的路由的下一跳。对于 Mode 位 D=1 的每个此路由器的子级，有一个将子级的 RLOC16 映射到子级本身的条目。此类条目的下一跳是子级。对于 Mode 位 D=0 的每个此路由器的子级，有一个将子级的 RLOC16 映射到路由器本身的条目。此类条目没有下一跳。路由器移除 Mesh Header，并根据第 5.10.1.1 节“完整 Thread 设备转发”的规定将数据包传递给子级。 请注意：以上规则意味着父级将指示其 Mode 位 D=0 子级的 mesh 目的地视为自己的 RLOC16。 一旦带有 6LoWPAN Mesh Header 的数据包到达 Mesh Header 指示的路由器，该路由器会移除 Mesh Header 并根据第 5.10.1.1 节“完整 Thread 设备转发”的规定将数据包传递给自身或子级。 对于 REEDs 和 FEDs，所有 RLOC16 的下一跳是父级。MTD 不接收或处理 mesh header，并且没有链路层路由表。5.10.2 在 Thread 网络之外的单播数据包转发Thread 边界路由器必须在 Thread 接口和其他接口之间实现 IP 层的数据包转发，以确保与外部网络或互联网的通信。Thread 边界路由器向 Leader 提供关于它们提供的有效前缀集合的默认路由以及对于给定有效前缀提供的任何其他特定路由的信息。Leader 聚合边界路由器信息，并在整个 Thread 网络分区中传播此信息。Thread 网络数据使用 RLOC16 识别边界路由器。从 Thread 网络发出的消息在链路层使用 6LoWPAN Mesh Header 从发起节点根据以下规则转发到适当的边界路由器，然后由边界路由器将数据包转发到其他接口。有关此转发的详细信息请参阅第 9.3.2 节，在第 9 章边界路由器中内部和外部接口之间的数据包转发。如果 IPv6 目标地址不在网内，则路由器使用 IPv6 源地址确定提供与 IPv6 源地址的前缀相同的 Provisioning Domain 的边界路由器集合。在该集合中，路由器按以下方式找到最佳的出口边界路由器：查找与 IPv6 目标地址具有最长前缀匹配的 External Route Set 前缀。如果有多个 External Route 前缀匹配，选择具有更高优先级（R_preference）的前缀。如果没有 External Route 前缀匹配，则选择提供默认路由的边界路由器。如果有多个边界路由器提供默认路由，则选择具有更高优先级（P_preference）的边界路由器。如果有两个或更多优先级相同的边界路由器，则选择具有最低网格路径成本的边界路由器。最后，如果没有边界路由器提供默认路由，则路由此数据包失败。路由器应按照第 5.8 节 ICMPv6 中指定的方式向数据包的 IPv6 源发送 ICMPv6 Destination Unreachable 消息，其中 ICMPv6 Code 为 0。Thread 网络的传入消息基于 GUA 或补充 ULA 地址，边界路由器使用其内部地址表或地址查询来识别最终目的地的 RLOC。提供用于 SLAAC 或 DHCPv6 地址配置的前缀的边界路由器可以为网内或网外使用该前缀发送地址查询（有关更多信息，请参阅第 9 章边界路由器中的第 9.2.3 节全局地址和第 9.2.4 节补充唯一本地地址）。完成此步骤后，边界路由器根据第 5.10.1 节中描述的算法将数据包转发到网格目的地。5.10.3 Thread 网络内的任播数据包转发使用 ALOC 目标的 IPv6 数据包的路由由 FTDs 完成，如第 5.10.1.1 节“完整 Thread 设备转发”所定义，使用本节为每种 ALOC 类型定义的特定查询方法。在以下子部分中，“传递到此 ALOC 失败” 表示路由器必须返回数据包的 IPv6 源，使用 ICMPv6 Destination Unreachable 消息，并将 ICMPv6 Code 设置为“0”（无法到达目的地）。ICMPv6 消息负载必须至少包含原始消息的完整 IPv6 头。5.10.3.1 Leader ALOCLeader ALOC 解析为 Leader Data 中 Leader Router ID 字段中的 RLOC。5.10.3.2 DHCPv6 Agent ALOC如果 IPv6 目标地址是 DHCPv6 代理 ALOC，则路由器使用 ALOC 中编码的上下文 ID 来确定相关前缀。对于此前缀，它考虑提供该服务的 DHCPv6 代理集合（即，提供该前缀且 P_dhcp = true 的边界路由器）。在该集合中，路由器按以下方式找到“最近”的 DHCPv6 代理：如果集合中有多个 DHCPv6 代理，则选择具有最低网格路径成本的代理。如果集合中没有 DHCPv6 代理，则传递到此 ALOC 失败。5.10.3.3 Service ALOC如果 IPv6 目标地址是 Service ALOC，则路由器使用服务类型 ID 查找 Thread 网络数据中提供该服务类型的服务器集合。然后选择具有最低网格路径成本的服务器作为目的地 RLOC。如果没有具有该服务类型 ID 的服务器，则传递到此 ALOC 失败。5.10.3.4 Commissioner ALOC如果 IPv6 目标地址是 Commissioner ALOC，则路由器将此 ALOC 中编码的值 N 与当前 Commissioner 会话 ID 进行比较（从 Commissioner Dataset 获取）。如果相等，则将 ALOC 解析为 Commissioner Dataset 中的 Border Agent Locator RLOC16 字段。在其他情况下，传递到此 ALOC 失败。5.10.3.5 Neighbor Discovery Agent ALOC如果 IPv6 目标地址是 Neighbor Discovery Agent ALOC，则路由器使用 ALOC 中编码的上下文 ID 来确定相关前缀。对于此前缀，它考虑提供该服务的 Neighbor Discovery Agent 集合（即，提供该前缀且 P_nd = true 的边界路由器）。在该集合中，路由器按以下方式找到“最近”的 ND 代理：如果集合中有多个 ND 代理，则选择具有最低网格路径成本的代理。如果集合中没有 ND 代理，则传递到此 ALOC 失败。5.10.3.6 Primary Backbone Router ALOC如果 IPv6 目标地址是 Primary Backbone Router ALOC（PBBR ALOC），则路由器首先选择使用以下算法的 Primary BBR 的 Server TLV。将 S 初始化为 BBR Dataset 中所有 Server TLV 的集合。然后：如果 S 为空，则选择无，并完成此算法。如果 S 的大小为一个，则选择该集合成员并完成此算法。选择具有最高优先级的 S 中成员，规则如下：规则 1：Leader 的 Server TLV 优先级高于非 Leader 的 Server TLV。规则 2：前述规则相同时，具有更高 BBR Sequence Number 的 Server TLV 优先级高。规则 3：前述规则相同时，具有更高 RLOC16 值的 Server TLV 优先级高。 最后，如果选择了 Server TLV，则使用该 Server TLV 中的 RLOC16 作为目的地。否则，传递到此 ALOC 失败。5.10.4 链路层目的地地址的选择发送到 rx-off-when-idle 子设备的单播数据包的链路层目的地可以是该子设备的 RLOC16，而不管 IP 目标如何，并且消息必须使用适当的链路层单播传输发送。如果 IP 目标是链路本地单播地址，则链路层目的地是相应的 MAC 扩展地址，由 IP 目标的 64 位 IID 和通用/本地位取反而得。请注意，Thread 设备不根据其 16 位 MAC 地址配置链路本地地址。在所有其他情况下，数据包的下一跳是一个 RLOC。如果数据包有网格头，则 RLOC 是通向网格头目的地的下一跳。否则，它是由上述第 5.10.1.1 至 5.10.1.3 节中定义的单播/任播转发规则确定的。
目录 [隐藏] 你的 Wi-Fi 6 路由器缺少什么？ OFDMA介绍什么阻碍了 Wi-Fi 6 的发展？更多关于 RUs 的信息OFDMA 测试总结OFDMA 究竟是否有效？第一部分介绍测试平台 V2是否支持 OFDMA？最佳情况无限比特率改变优先级OFDMA 究竟是否有效？第2部分介绍延迟ASUSNETGEAR效率吞吐量总结 免责申明：本文翻译自 smartnetbuilder 系列 OFDMA 测试文档，建议直接查看原文。What’s Missing From Your Wi-Fi 6 Router? OFDMA、Does OFDMA Really Work? Part 1、Does OFDMA Really Work? Part 2 。侵权立删，文中测试结论均为原作者结论。你的 Wi-Fi 6 路由器缺少什么？ OFDMA介绍OFDMA 频谱图来源：Gjermund Raaen我感觉自去年以来我一直在等待。随着 2018 年的结束，广告支持的崩溃迫使我转向付费咨询。尽管这对我在财务上更有利，但主要关注咨询工作意味着在 2019 年几乎没有发布任何关于 SmallNetBuilder 的产品评论。我一直在尽力改变这一点。但在努力制定新的 Wi-Fi 测试方法以替代之前侧重速度测试，进而转向以测量 Wi-Fi 产品的质量、稳健性和效率时，我一直遇到障碍。在完成了 octoScope 平台上的TR-398 Wi-Fi 性能测试套件的自动化后，我一直试图证明 OFDMA，这是 Wi-Fi 6 的一个关键特性，可以产生更高的总吞吐量。简而言之：启用 OFDMA 后，我看到了更高的总吞吐量，但增益是高度可变的。OFDMA 开始看起来很像 MU-MIMO。正如我在Wi-Fi 6 性能汇总：测试了五台路由器中指出的，测试 OFDMA 的主要障碍之一是迄今市场上的大多数消费级 Wi-Fi 6 产品都没有启用 OFDMA。一次又一次，我被告知新的启用 OFDMA 的固件将在一个月左右发布，但一个月过去了，固件并未更新。在没有要测试的东西时，很难制定测试方法。截至 2019 年底，美国有四台启用 OFDMA 的 Wi-Fi 6 路由器，如表 1 所示。这是在几乎宣布了几十个 Wi-Fi 6 路由器或网状系统之中的情况。产品Wi-Fi 6 认证平台OFDMA 2.4 GHz 下行OFDMA 2.4 GHz 上行OFDMA 5 GHz 下行OFDMA 5 GHz 上行验证备注ASUS RT-AX88U是Broadcom否否是是是详见下文NETGEAR RAX120否Qualcomm是否是否是芯片组不支持上行 OFDMAAmplifi Alien否Qualcomm是是是是否可能使用新的 Qualcomm 芯片版本Linksys MX5300 (Velop AX)是Qualcomm是是是是否可能使用新的 Qualcomm 芯片版本Buffalo AirStation WXR-5950AX12是Qualcomm?是是是是否仅限日本。可能使用新的 Qualcomm 芯片版本Buffalo AirStation WXR-5950AX12R是Qualcomm?是是是是否仅限日本。可能使用新的 Qualcomm 芯片版本表1：OFDMA 支持摘要表1中的 验证 列表示我已看到产品实际传输 HE-MU 数据帧，这是 OFDMA 操作所必需的。对于其他产品，您需要相信制造商的话，尽管我不建议这样做。因为 Wi-Fi 6 认证要求在两个频段上都支持下行（DL）和上行（UL）OFDMA，并不保证您实际购买的路由器上将启用 OFDMA。例如，ASUS RT-AX88U。ASUS 首先在去年八月左右发布了一个测试版固件，启用了5 GHz 的 DL OFDMA。然后，今年十一月，ASUS 宣布路由器现在已获得了 Wi-Fi 6 认证。然而，一周左右后发布了更新的固件，但没有为 2.4 GHz 无线电启用OFDMA。ASUS 解释说这是与“传统”设备的兼容性问题。因此，Wi-Fi 6 认证也不能保证完全启用 OFDMA。而且，像160 MHz的信道宽度、DL（AX）MU-MIMO、目标唤醒时间（TWT）和许多与 OFDMA 相关的功能都是可选的 Wi-Fi 6 认证，如下所示在Wi-Fi认证产品查找器中的搜索过滤器所示。Wi-Fi 6 认证选项什么阻碍了 Wi-Fi 6 的发展？所以我们进入了大规模过渡到 Wi-Fi 6 的第二年，高价位的顶级路由器仍在发货，但没有支持（或对支持的保证）关键的 Wi-Fi 6 特性。然而，情况在客户端方面不同，那里没有缺乏支持 OFDMA 的设备正在寻找与其合作的 AP 和路由器。Samsung Galaxy S10 系列在 Wi-Fi 6 路由器上架后不久开始发货。Intel 为笔记本电脑准备了 AX200 M.2 格式的网卡。甚至 Apple 也相对及时地发布了支持 AX 的 iPhone 11，尽管比 Samsung 慢了大约一年。就我看到的数据包捕获情况来看，所有这些设备都支持 OFDMA 和 DL AX MU-MIMO。因此，障碍显然存在于路由器/AP 方面。为什么呢？答案是为 AP 空时调度程序编写代码的人们面临着极为艰巨的任务。在过去，调度程序所要做的一切是使用像轮询这样的简单方法发送数据包。然后在 11n 中出现了数据包聚合，而在 11ac 中更是如此，这意味着数据包不会立即发送出去，而是排队并组合成更大的帧。AC MU-MIMO 进一步复杂化了问题，要求决定何时以何种方式保留数据包并使用 MU-MIMO 波束成形同时传输它们。现在，AX 调度程序具备了以前的所有方法，再加上 OFDMA。OFDMA 是一种像 MU-MIMO 一样的同时传输方法。但与使用时域（相位角）中的波束成形技术不同，OFDMA 是在频域中运行的，将单个 20、40、80 或 160 MHz 信道分成子信道，称为 RUs（资源单元）。这些 RUs 中的每一个都可以分配给 AX 设备（STA），通常在它们关联时进行分配。要进一步复杂化调度程序的任务，OFDMA 也在上行方向上工作。因此，调度程序还必须决定是否要接收来自多个 STAs 的数据。UL OFDMA 需要 AP 和 STA 之间的精确同步，这显然并不容易。在真实世界中是否足够可靠，以提供 OFDMA 所承诺的更高带宽效率和更低的延迟，还有待确定。当然，所有这些决策必须针对每次传输机会（TXOP）进行。正如我所说，这是复杂的东西。更多关于 RUs 的信息由于 RUs 是 OFDMA 的核心，因此值得仔细查看。RUs 分为六种不同的信道宽度，每种宽度支持每流的最大数据/链路速率。下表摘自 IEEE 802.11ax 草案规范，显示了每种信道宽度支持的最大 RU 数。20 MHz 带宽 RU 位置（来自 IEEE 草案 802.11ax）下图显示了如何将 20 MHz 宽的信道分割成不同的 RU 宽度。RU 越宽，其数据/链路速率越高，可用的就越少。换句话说，如果 AP 希望将更多 STAs 塞进一个 OFDMA 帧中，那么每个 STA 的数据速率就会更低。需要注意的是，不是所有的 RU 都必须相同。假设 20 MHz 信道宽度，一个帧可以有四个 STAs 使用 26 stone RU，一个 STA 使用 106 tone RU。此分配可以是动态的，因此下一个帧可以使所有五个 STAs 使用 26 tone RU。AP 通过在 STAs 关联时分配给每个 STA 一个 AID（关联 ID）来跟踪哪个 STA 获得了哪个 RU。20 MHz 带宽 RU 位置（来自 IEEE 草案 802.11ax）需要注意的是，RU 的宽度不会随着 Wi-Fi 信道宽度的增加而改变。随着更宽的信道，支持更多的 RU，如下图所示。较宽的信道还会增加两种 RU 宽度；只有在 40 和 80 MHz 信道宽度时才支持 484 tone RU，最大值 996 tone RU 需要 80 MHz 宽的信道。不同信道宽度中的 RUs（来自 IEEE 草案 802.11ax）可以增加 RU 的吞吐量的因素是空间流的数量。因为大多数 STAs 都是双流，让我们看一下 MCS 表（显示了我们熟知的 26 tone RU 的数据/链路速率，具有两个流）。对于单流，将数据速率值减半。26 tone RU，2 流 MCS 表（来自 IEEE 草案 802.11ax）这个表格突出了 OFDMA 中固有的权衡的现实。为了支持单帧中的最多 STA 数，每个 STA 能够获得的最高数据速率接近 30 Mbps。但由于这是一个 MCS 表，这个数字实际上是链路速率，实际交付的数据速率更低。在看到 11ax 可以支持数百个同时客户端的说法时，请记住这一点。那么在一个双流 80 MHz 信道中支持的单个 996 tone RU 提供了多少数据速率？表 27-97（未显示）给出了 1201 Mbps。这也是 AX 非 OFDMA 80 MHz 信道支持的最大数据速率。最后，让我们回到每个信道宽度的最大 RU 表，看看我们可以在一个 80 MHz 信道中使用四个双流 OFDMA STA 得到什么。表 27-6 上面说我们可以使用 242 tone RU，下面的表 27-81 说我们的四个 STA 中的每一个最多可以获得 287 Mbps 数据速率。还不错，但不如单个双流 80 MHz 带宽 STA 获得的 1201 Mbps。值得注意的是 4 x 287 = 1148。因此，将四个 STA 放入一个传输帧中会产生 4% 的数据速率损失。OFDMA 测试考虑到通常情况下 OFDMA 未启用，路由器制造商并未提供如何测试它的指导。设备制造商也对此守口如瓶，尽管 Broadcom 提供了一些建议，以展示 OFDMA 的最佳性能。而 Qualcomm 尚未与我讨论 OFDMA 测试的时间。Intel 提供了一些帮助，提供了一些 AX200 适配器并回答了一些问题。然而，我从 linux-wireless 邮件列表 的开发者那里得到了更多信息。来自 Intel 德国的 Johannes Berg 在捕获 OFDMA HE-MU 数据包方面提供了关键信息。我还需要感谢两家设备制造商对我的帮助。Arris（现在是 CommScope 的一部分）安排了 Broadcom 安装了一个特殊的固件加载，基本上将他们的 mAX Pro mesh 路由器变成了 Broadcom 参考 AP。这使我完全掌握了涉及 UL 和 DL OFDMA 的各种 AP 参数的控制。如果没有他们的帮助，我将无法使用支持双频 UL 和 DL OFDMA 的 AP。NETGEAR 也非常乐意提供帮助，参与了多次 OFDMA 测试方法讨论，并回答了我提出的任何问题。下图显示的 OFDMA 测试平台本质上与 Wi-Fi 6 RvR 测试使用的相同。一个通过电缆连接的单个 Intel AX200 STA 已被替换为一个内置了四部三星 S10e 智能手机的 octoScope Box18。四根 octoScope 高增益双频天线捕捉 RF 信号，而四通道 90 dB 范围可编程衰减器（quadAtten）用于衰减信号水平。quadAtten 输入到四通道双向 RF 分频器/合并器，其第二端口连接到 octoScope Pal-24 和 Pal-5 合作设备，这些设备具有自己的衰减器。这提供了添加 2.4 GHz b/g/n 和 5 GHz a/n/ac STAs 到测试组合的选项。OFDMA 测试设置这是 Arris mAX Pro 放置在 octoScope Box38 中。用于 OFDMA 测试的四根天线如图所示。Arris mAX Pro 在测试室内这是放置在较小的 Box18 中的四部三星 S10e 手机的情况。手机间距均匀，而且确实靠近天线。实验发现，轻微的倾斜有助于最大化信号水平。这种设置为每个 STA 提供了大约 -50 dBM 的最大 RSSI 水平。第二个房间中的四部三星 S10e我的测试测量启用和禁用 OFDMA 时四部手机的总 UDP 吞吐量。测试由一个使用 octoScope 平台运行测试并报告结果的 Python 脚本运行。Android adb shell 命令用于控制三星 S10 的关联以及在每部手机上运行 Magic Iperf 应用 中的 iperf3 的启动/停止。脚本使用不同的数据包大小运行 UDP 流量的速率 vs. 范围（RvR）测试多次。具体来说，RvR 测试每个步骤运行 30 秒的 UDP 流量，使用 6 dB 步骤，0-36 dB 衰减范围。 RvR 测试使用 128、166、512 和 1500 字节数据包大小重复。 然后整个序列再次重复一次，以检查可重复性。 接着相同的序列上行测试。所有设备在每次 RvR 运行之间重新关联。该测试显示了在一些数据包大小和信号水平组合中的总吞吐量增益。它还显示了在其他信号水平和数据包大小组合中的显著总吞吐量损失。由于 octoScope 系统在向 iperf3 端点运行下行流量时阻止了正常的 UDP 结果报告，所以我只显示了上行结果。图表显示了启用和禁用 OFDMA 时 Arris AP 上的总吞吐量增益，Y 轴显示了结果，结果按数据包大小和应用衰减进行分组。例如，1500 字节（标准）数据包大小和 18 dB 的应用衰减可以获得 33% 的最高增益。显然，这些结果没有逻辑上的进展。主要的一致性是当应用 24 dB 的衰减时吞吐量增益变负（手机报告的 RSSI 在中 -70 dBm 范围内）。我没有显示 30 和 36 dB 的衰减结果，因为这些值导致至少一个 STA 断开连接或报告基本无法使用的吞吐量。OFDMA 增益 – 上行 – 第 1 次运行第二次测试运行显示了结果的高度变异。在产生增益的条件中有大致的相关性，但增益值大多不同。OFDMA 增益 – 上行 – 第 2 次运行请记住，这些数据是由一个实质上是 Broadcom 参考设计的 AP 生成的。虽然你可以购买 Arris mAX Pro，但不能用于此测试的固件。总结推动 Wi-Fi 6 的炒作机器一直像 5G 移动技术背后的机器一样无耻。两者都在推动实际上并没有兑现他们口口声声承诺的更高带宽的产品，至少不带有大量详细说明的附加条件...如果你能找到的话。但炒作和公然误导之间存在差异。当路由器制造商发布不支持关键的 802.11ac 特性——MU-MIMO——的路由器时，他们提供了明确的免责声明，比如 "MU-MIMO 就绪"。他们甚至在产品规格中明确说明，这些产品基于草案 11ac IEEE 规格。而对于 AX，产品限制和关键缺失的功能的明确披露似乎成了过去。正如我在本文的开头所说，制造商正在出售依然不支持工作的 OFDMA 的 Wi-Fi 6 消费者路由器，这仍然是 (仍然是) 草案 802.11ax 标准的关键功能的第二年。而制造商仍然没有明确披露这一事实。OFDMA 在产品营销材料中被突出展示，但产品尚未支持此功能的事实并没有。如果你能找到免责声明，它通常埋在规格表的脚注中，措辞含糊不清，甚至没有直接与所否认的规格相联系。营销材料或规格表也没有明确说明支持哪些 Wi-Fi 6 功能。整个情况令人不悦，标志着 Wi-Fi 营销的新低。因此，路由器制造商，是时候公开并包含产品实际支持的 AX 功能的信息了。是的，我知道这可能会成为一个相当长的列表，而且可能会随着时间的推移而改变。那么为什么不从那些在营销材料中明确宣传的关键功能开始，即 OFDMA DL 和 UL、DL MU-MIMO 和160 MHz 频道呢？这些功能最有可能影响实际消费者，因此它们的支持（或不支持）应该被明确说明。在支持这些功能时更新您的在线规格表，并通过固件更新说明或通过您一直在推动用户安装的应用程序提醒用户有关更改。话虽如此，我仍在继续进行 Wi-Fi 6和其他测试方法的工作，目标是看看 Wi-Fi 6是否真的能够减少延迟，就像它所声称的那样。但如果我有一些真正完全支持（尚处于草案状态的）802.11ax 标准的实际产品来测试，那就太好了。OFDMA 究竟是否有效？第一部分介绍在一月份的文章《你的 Wi-Fi 6 路由器缺少了什么？OFDMA》中，我曾经说 OFDMA 开始看起来很像 MU-MIMO。从那以后，我一直在继续开发 OFDMA 测试方法，尝试不同的方法，正如您很快会看到的。底线是，我仍然无法一致找到所承诺的改善空间效率、使用多个设备的更高总带宽以及较低延迟的证据。测试平台 V2在先前的文章中，测试平台的 STAs 由 octoScope Box18 和四台三星 S10e 智能手机组成。四台 octoScope 高增益双频天线捕获 RF 信号，而四通道 90 dB 范围可编程衰减器 (quadAtten) 降低信号水平。quadAtten 连接到四通道的双向 RF 分频器/组合器，其第二个端口连接到 octoScope Pal-24 和 Pal-5 合作设备，它们具有自己的衰减器。这提供了将 2.4 GHz b/g/n 和 5 GHz a/n/ac STAs 添加到测试中的选项。分频器连接到一个包含受测试设备 (DUT) 的 38 英寸 octoScope 盒中，其中有四个 octoScope 高增益双频天线。OFDMA 测试平台 – 第一个尝试这种测试方法测量了启用 OFDMA 和禁用 OFDMA 时的四部手机的总 UDP 吞吐量。最终，这种方法，即使用 Broadcom 建议的小数据包大小的 UDP 流量，并没有产生显著或可重复的吞吐量增益。这一次，我决定专注于测量延迟，以尝试验证 OFDMA 是否确实显著降低了延迟的说法。新的测试平台使用了四个 octoScope STApals。这些是迷你的 ASUS pico-ITX 格式计算机，配备了 Intel AX200 Wi-Fi 6 网卡。STApal 有两个版本，一个基于 Ubuntu Linux，另一个运行 Windows 10 Pro。选择了 Windows STApal 版本，因为在测试时，其 AX200 驱动程序具有更好的上行和下行吞吐性能，而不是 Linux 驱动程序（octoScope 正在努力纠正这一点）。OFDMA 测试平台 – 第二版本测试平台还配备了一个 octoScope Pal6。它可以用来添加流量负载，但我使用它来监视信道拥塞。稍后再详细介绍。测试是通过一个使用 octoScope octoBox 服务器平台运行测试和报告结果的 Python 脚本来完成的。octoScope 的 multiperf 流量生成器（以 iperf3 为核心）提供流量生成。基本的测试方法受到了“Make Wi-Fi Fast 项目”的 RRUL（负载下实时响应）bufferbloat 测试工具的启发。具体来说，我使用了 Flent 的 rtt_fair_var 测试，将其移植到 octoScope 测试平台上。然而，所有初步探索都是使用 Flent 本地执行的，得益于 Flent 的创始人 Toke Høiland-Jørgensen 的大力帮助。 （感谢，Toke！）作为对 Toke 的额外感谢，这是对他的博士论文 "Bufferbloat and BeyondRemoving Performance Barriers in Real-World Networks" 的一个不知羞耻的推销链接。它更详细地介绍了 Flent 和 Toke 在“终结 wifi 异常”方面的工作。Flent 使用 netperf，而不是 iperf/iperf3 作为其主要的流量生成/基准工具。Netperf 在 Linux 系统上（需要编译）相对容易安装，但在 Windows 上安装较为困难。长话短说，我最终创建了 Windows 版本的 netperf 和 netserver 二进制文件。 （下载包含这两个文件的 zip 文件）。rtt_fair_var 测试是 Realtime Response Under Load (RRUL) 测试的一个变种，它是 Flent 测试套件的核心。对于每个测试 STA，测试同时运行一个 ping，间隔 200 毫秒，以测量往返时延（延迟），并使用 netperf 发送无限带宽的 TCP/IP 流量，以通过流量淹没连接。以下是使用 Flent 进行早期测试的一些典型结果。DUT 是一台 ASUS GT-AX11000，当时只支持 5 GHz 下行（download）流量的 OFDMA。第一个图是 OFDMA 关闭时的结果…Flent RTT Fair 测试 – CDF 延迟图 – BE 优先级 – OFDMA 关闭而第二个图则是开启 OFDMA 时的结果。CDF（累积分布函数）图显示一个量（在这种情况下是 ping 或 RTT）将小于或等于一个值的概率。80%（图中的 0.8）通常用作基准，因此我们可以看到，OFDMA 关闭时，所有三个流量对的平均 ping 时间在 80% 的时间内将低于或等于 30 毫秒，而开启 OFDMA 时，这将增加到 ~ 120 毫秒或更低。不是我们要找的结果！ Flent RTT Fair 测试 – CDF 延迟图 – BE 优先级 – OFDMA 开启是否支持 OFDMA？尽管消费级路由器制造商正在逐渐将 OFDMA 引入其产品中，但大多数 Wi-Fi 6 产品仍然没有启用 OFDMA。即使支持 OFDMA 的产品中，只有少数支持 5 GHz 或双频带的 DL 和 UL OFDMA。我经常向 ASUS 和 NETGEAR 询问 OFDMA 支持的最新情况。我已经放弃向 TP-Link 了。每次我询问时，回复都是他们的 Wi-Fi 6 产品仍然不支持 OFDMA。D-Link 只出售 DIR-X1560，它只支持 5 GHz 上行 + 下行 OFDMA（2.4 GHz 使用 11n 无线电）。Linksys 只有其价格过高的 MX5/MX10 Velop AX Mesh 系统；我没有向他们询问 OFDMA 支持情况。上次与 ASUS 的检查大约一个月前显示，所有其 AX 路由器都支持 5 GHz 下行，同时支持 5 GHz 上行和 2.4 GHz 下行/上行，标记为“即将开放”。但 SNBForums 上 Merlin 的提示让我在当地的 Best Buy 提前取货，购买了 RT-AX58U（又名 RT-AX3000）。对其 GUI 的快速检查显示，它确实在两个频段上提供了 DL、DL/UL 和 DL/UL+MU-MIMO OFDMA 设置。NETGEAR 的列表显示，其入门级的双流 RAX15/20 和中档 RAX45/50 路由器支持完整的 OFDMA（双频段，DL+UL）。在 NETGEAR 的早期 AX 路由器中，RAX80 仍然不支持 OFDMA，而其唯一基于高通芯片的路由器 RAX120 支持双频段的 DL。对于那些喜欢购买昂贵的三频段路由器的人，至少 RAX200 支持完整的 OFDMA。其 Orbi 和 Nighthawk Wi-Fi 6 网络系统都支持完整的 OFDMA。我购买了 RAX15 和 RAX45（Costco 专供款）进行测试，并要求 NETGEAR 发送 RAX200。但由于 Covid-19，他们无法发送，而我目前没有足够多的现金来支付 Amazon 目前的 $488 售价。最佳情况当我在 octoScope 测试平台上运行类似于 Flent rtt_fair_var 测试的等效测试时，一位 octoScope 同事分享了一些他使用类似 TCP/IP 流量加 ping 测试应用于 octoScope 的 Pal6 合作设备（配置为 AX AP）的令人鼓舞的结果。因此，我决定尝试使用 Pal6 配置为四流 AX AP 进行实验。以下所有的图形都是通过将 CSV 测试结果文件导入到 octoScope 的 Expert Analysis 工具生成的。以下四个 WinSTAPals 生成以下图形的 TCP/IP 流量设置如下：50 Mbps 比特率（iperf3 -b）256 字节缓冲区长度（iperf3 -l）DSCP = 192（iperf3 –dscp）DSCP 参数设置了流量的优先级级别。如果您搜索有关 DSCP 的信息，您将发现很多不同的参考文献，提供了很多不同的 DSCP 设置值。对于 octoScope 系统，192 值相当于 CS6 无线 QoS 数据帧的语音优先级。这经过数据包检查进行验证。请注意，当 Pal6 上的 OFDMA 设置为关闭时，MU-MIMO（MU 波束成形设置）仍然启用。Pal6 有单独的下行和上行 OFDMA 启用；在 OFDMA 开启条件下，两者都已启用。第一个图比较了所有四个流量对的 OFDMA 开/关的平均延迟。显然有显著的改进；从约 48 ms 到约 6 ms。Pal6 AP – 平均延迟 CDF 比较 – 下行上行的延迟比 OFDMA 关闭时更低，但与其开启时的下行延迟差不多。Pal6 AP – 平均延迟 CDF 比较 – 上行OFDMA 还宣称能提高多个设备的 总吞吐量。下行吞吐量图似乎也支持这一说法，但不是在图的开始。这种需要时间来稳定的行为在多次运行中都有看到。Pal6 AP – 平均下行吞吐量比较上行吞吐量几乎没有从 OFDMA 中获得收益。**Pal6 AP – 平均上行吞吐量比较**802.11ax 的基本原则是高效率；这已经体现在了标准的名称中，也是其主要任务。如果 AX 能够实现其主要目标，那么无线信道的利用将更加高效。高通芯片组报告信道拥塞情况，因为 Pal6 基于高通的 AX “鹰眼 2.0” 芯片组，所以我们有下面的图。启用 OFDMA 后，拥塞减少了 50% 以上！Pal6 AP – 信道拥塞比较 – 下行上行也显示启用 OFDMA 后拥塞较低，但与下行拥塞相比，增益没有那么明显。Pal6 AP – 信道拥塞比较 – 上行对我来说，这最后两个图是最重要的。因为即使延迟没有减小，总吞吐量没有增加，减少信道拥塞意味着设备竞争较少，这应该会让所有使用同一信道的人获得更好的 Wi-Fi 体验。所以看起来我们终于有了一个基准，可以显示 OFDMA 会有显著的好处（在大多数情况下）！无限比特率但由于 rtt_fair Flent 测试使用了无限比特率，因此我试着看看会发生什么。所有其他流量参数保持不变。对于下行流量来说，OFDMA 开/关的平均延迟并不令人鼓舞。尽管与每个 STA 的 50 Mbps 比特率相比，启用 OFDMA 的变化方向是错误的。Pal6 AP – 平均延迟 CDF 比较 – 无限吞吐量 – 下行上行在两种情况下的延迟更高，但至少在启用 OFDMA 时有所改善。Pal6 AP – 平均延迟 CDF 比较 – 无限吞吐量 – 上行当启用 OFDMA 时，下行总吞吐量也朝着错误的方向发展。Pal6 AP – 平均下行吞吐量比较 – 无限吞吐量 – 下行与之前的有限比特率一样，上行吞吐量显示几乎没有从 OFDMA 中获得的收益。但变化更大。Pal6 AP – 平均上行吞吐量比较 - 无限吞吐量 - 上行最后，启用 OFDMA 时，下行信道拥塞再次朝着错误的方向发展，增加了一倍多。Pal6 AP – 信道拥塞比较 - 无限吞吐量 - 下行至少，上行信道拥塞并没有像启用下行 OFDMA 一样大幅增加。Pal6 AP – 信道拥塞比较 - 无限吞吐量 - 上行我还运行了将每个 STA 的比特率设置为 5 Mbps 和 100 Mbps 的测试，并看到启用 OFDMA 后一些基准改善，而其他基准则变差。因此，似乎这个基准并不像我所希望的那么稳健。改变优先级在 Flent 的基准测试中，有一些使用不同的流量优先级的测试，这是个好主意。从理论上讲，标记为语音和视频的流量应该比较低优先级的“尽力而为”流量具有更低的延迟。但 OFDMA 是否会对流量优先级产生帮助或伤害呢？为了找出答案，我为每个四个流量对分配了不同的流量优先级。一个 STA 使用最佳尽力而为的 CS0（默认值），一个 STA 使用“出色尽力而为” CS3，第三个 STA 使用 CS5 视频，第四个 STA 使用 CS6 语音。Flent RRUL 测试通常不限制吞吐量。之前，我已经通过设置无限比特率来尝试了这一点。但设置缓冲区长度也会对吞吐量进行限制。因此，下一个测试结果中，比特率（iperf3 -b）是无限的，缓冲区长度（iperf3 -l）未设置。下面的图表中的流量优先级关键如下：Ping 对 1: CS0（最佳尽力而为）Ping 对 2: CS3（出色尽力而为）Ping 对 3: CS5（视频）Ping 对 4: CS6（语音）随着信道负载充分（拥塞度约为 90-95%），延迟增加。但关闭 OFDMA 的下行图显示不同流量优先级的延迟存在差异。奇怪的是，最低优先级的最佳尽力而为 STA 具有最低的延迟，而最高优先级的语音 STA 具有最高的延迟。Pal6 AP – 每个 STA 的延迟 - 下行 - 关闭 OFDMA启用 OFDMA 不会显著降低延迟。相反，所有 STA 的延迟都朝着错误的方向移动，视频 STA（Ping 对 3）竟然超越语音，成为延迟最高的。Pal6 AP – 每个 STA 的延迟 - 下行 - 启用 OFDMA关闭 OFDMA 的上行结果更加有趣。较低优先级的 CS0 和 CS3 STA 明显具有较低的延迟，以及较低的延迟差异（曲线越陡，值差异越小），而“较高”优先级的 CS5 和 CS6 STA 则具有较高的延迟。Pal6 AP – 每个 STA 的延迟 - 上行 - 关闭 OFDMA启用 OFDMA 稍微降低了较低优先级 STA 的延迟，但增加了语音和视频的延迟。Pal6 AP – 每个 STA 的延迟 - 上行 - 启用 OFDMA供参考，以下是关闭 OFDMA 的下行图，比特率为 50 Mbps，缓冲区长度为 256 字节。在这种情况下，信道拥塞平均约为 25%。即使水平尺度扩大，也很难看出延迟有明显的差异。Pal6 AP – 每个 STA 的延迟 - 下行 - 关闭 OFDMA - 50 Mbps 比特率 - 256 字节缓冲区长度总结思考我认为你可以看出为什么我正在将 Wi-Fi 行业最新和最大的炒作与 MU-MIMO 放在一起，MU-MIMO 也是802.11ax的一部分，并对这些结果产生了一些影响，OFDMA 在理论上非常出色，但在实际实施中非常困难。我不能说我感到惊讶；空时管理是一个非常困难的任务。而 OFDMA 只会使它变得更加困难。对于每个要发送到等待的 STA 的数据包，AP 必须在以下选择之间进行选择：将其直接放入传输队列为该 STA 的其他数据包保留它以进行聚合保留它，无论是否进行聚合，以通过 MU-MIMO 与其他 STA 的数据一起发送保留它，无论是否进行聚合，以通过 OFDMA 与其他 STA 的数据一起发送这些决策必须考虑客户端类型混合（a/b/g/n/ac/ax）、信号强度和物理位置以及流量类型混合（语音/视频/浏览/文件传输/IoT）和负载的影响。与 MU-MIMO 一样，OFDMA 可能需要多年的时间才能达到实际增加价值的程度。与此同时，我仍在努力弄清楚如何进行 OFDMA 测试，并将在第二部分中呈现结果。我已经在一组我知道已启用 OFDMA 的 AX 路由器上运行了这里描述的基准测试的某个版本。到目前为止，根据所使用的基准测试设置，我已经看到启用 OFDMA 后的空间时间拥塞和延迟变得更糟。如果有人有任何聪明的想法，请分享！OFDMA 究竟是否有效？第2部分介绍在第1部分中，我详细介绍了如何开发我的 OFDMA 基准测试。在第二部分中，我将展示经过测试的两款 Wi-Fi 5（802.11ac）和六款 Wi-Fi 6（802.11ax）路由器的结果。这些路由器都经过了完整测试过程。我使用了第1部分中描述的测试的最后一个变体。总结如下：iperf3 TCP/IP 流量同时运行到四个 STA（Intel AX200，Win 10，21.80.2.1驱动程序）比特率（-b）：50Mbps长度（-l）：256字节DSCP值（-dscp），每个STA一个：0（CS0），96（CS3），160（CS5），192（CS6）从 AP 到每个 STA 的每一对同时运行的 200ms 间隔 ping。Ping 始终是从 DUT 到 STA，用于上行和下行流量。使用与 DUT 关联的 octoScope Pal6（Qualcomm AX基础）来测量拥塞，运行 1bps 的流量以记录统计信息。表1显示了经过测试的路由器，以及其主要属性。产品流平台OFDMA固件版本备注2.4 GHz 下行2.4 GHz 上行5 GHz 下行5 GHz 上行 ASUS RT-AX58U / RT-AX3000 $1802（4，5 GHz仅接收）Broadcom是是是ASUS RT-AX88U $3504Broadcom是是是ASUS GT-AX11000 [$450]4Broadcom是是是Evenroute IQrouter V3 $1392Mediatek不适用不适用不适用NETGEAR R7800 $1704Qualcomm不适用不适用不适用NETGEAR RAX15/20 $1002Broadcom是是是NETGEAR RAX45/50 $3302 - 2.4 GHz 4 - 5 GHzBroadcom是是是NETGEAR RAX120 $4004Qualcomm是否是表1：已测试路由器由于有很多路由器，包括所有产品会变得难以阅读。因此，我将为 ASUS 和 NETGEAR 路由器分别显示不同的图表。这两套图表将包括两个 Wi-Fi 5 路由器和 octoScope 的 Pal6 ，配置为四流 AX AP 供参考。NETGEAR R7800 是一款四流 Wi-Fi 5（802.11ac）路由器，是我“常备”的 AC 参考路由器。每当我有一个表现奇怪的产品时，我都会把R7800 放回测试室，以确保是产品，而不是测试平台出现了问题。基于 OpenWRT 的 Evenroute IQrouter V3 被包含在内，因为它在其 Mediatek 芯片组的无线驱动程序中集成了 MakeWiFiFast 小组的codel 和空时公平性工作。IQrouter 的功能集主要侧重于优化有线路由性能，特别是减少缓冲区过载对路由器的 ISP 连接的影响。但由于它还具有一些减少 Wi-F i延迟的功能，因此将其包含在内，以查看其与 R7800 和 AX 产品的性能差异。（不是那么）有趣的故事；实际上，我写了两篇文章。第一版发布大约15分钟后，我将其删除，因为数据有误。文章发布后不久，我回到测试平台，开始重新配置它，发现其中一个 STApal 的一个通道没有连接。是的，就这样悬在那里。在一番口头的自我抨击和实验室墙壁上多次打击之后，我开始重新采集数据。但在这个过程中，我认为我可能会改进测试配置，以更好地支持 MU-MIMO。经过多年未增加 Wi-Fi 性能价值的历史，我被告知 MU-MIMO 实际上现在有效，至少是 802.11a c下行形式的 MU-MIMO。802.11ax 标准增加了上行形式的 MU-MIMO，但在首批 AX 芯片组中没有支持它。MU-MIMO 使用波束成形，需要空间多样性，即设备必须物理分离。测试平台并不支持这一点，因为所有四个 STApals 都使用相同的两个天线。下面的图表显示了测试平台的新配置。每对 STApals 都有自己的衰减器，连接到它们自己的一对天线，位于38英寸的八面体盒子的角落。1:4 RF 分配器/合并器的未使用端口都有50欧姆终端。理想情况下，每个 STApal 都应该有自己的一对天线，坐在盒子的角落。但即使这种非理想的配置也揭示了在测试运行中启用和禁用 MU-MIMO 时的性能差异。OFDMA测试平台 - 更新后现在，让我们看一下结果。延迟ASUS我们将首先总结在表2中关闭 OFDMA 和开启 OFDMA 时的80%概率下行延迟。产品80%延迟 - 关闭 OFDMA80%延迟 - 开启 OFDMA变化百分比Evenroute IQrouter V310.110.1–NETGEAR R78008.98.9–octoScope Pal624.57.0-71ASUS GT-AX110005.74.2-26ASUS RT-AX58U / RT-AX30004.66.2+35ASUS RT-AX88U4.93.4-31表2：ASUS路由器 OFDMA 延迟摘要 - 下行第一个图展示了 ASUS 路由器和三个“参考”产品的 OFDMA 关闭的下行延迟，使用80%概率基准。尽管所有 ASUS AX 产品都相对密集地聚集在一起，但参考的 octoScope Pal6 不是。请注意，两个 AC 参考产品，IQrouter v3 和 NETGEAR R7800，表现类似。ASUS下行延迟 CDF – OFDMA关闭 – 下行我至少检查了6次 Pal6 的结果。以下是一些结果，其中上面用 黑色粗体 标示的数值。Pal6下行延迟 CDF – OFDMA关闭 – 下行 – 重测启用 OFDMA 的下行延迟 图确认了上面表格中的结果。Pal6 的延迟改善最多，减少了71%。RT-AX58U 是唯一一个表现更差的路由器，延迟增加了35%。尽管如此，所有的 ASUS 路由器的延迟仍然好于 AC 参考路由器。ASUS下行延迟 CDF – OFDMA开启 – 下行转向 上行延迟，80%概率的延迟在表3中总结了 OFDMA 关闭和开启的情况。再次，Pal6 的延迟得到了最大的改善，减少了57%。而且，RT-AC58U 再次是三款ASUS路由器中唯一延迟恶化的路由器。产品OFDMA关闭时的80%延迟OFDMA开启时的80%延迟% 变化Evenroute IQrouter V322.622.6–NETGEAR R780024.924.9–octoScope Pal625.311.0-57ASUS GT-AX1100017.715.9-10ASUS RT-AX58U / RT-AX300015.118.1+20ASUS RT-AX88U17.814.9-16表3：ASUS路由器OFDMA延迟摘要 – 上行关闭 OFDMA 的上行延迟 图清楚地显示了所有的 ASUS 路由器与“参考”产品之间的差异。ASUS上行延迟 CDF – OFDMA关闭 – 上行启用 OFDMA 的上行延迟 图显示了Pal6延迟的显著变化。再次，AX产品通常比AC产品具有更低的延迟。ASUS上行延迟 CDF – OFDMA开启 – 上行NETGEAR表4总结了三款 AX NETGEAR 路由器在关闭 OFDMA 和开启 OFDMA 时的80%概率下行延迟。与三款 ASUS 路由器一样，所有三款NETGEAR 路由器的 OFDMA 关闭延迟都低于两款 AC 参考产品和 Pal6 参考产品。基于 Broadcom 的 RAX15 和 RAX45 的延迟约为Qualcomm-based 的 RAX120 的一半，这是本次测试中唯一一款基于 Qualcomm 的路由器。产品80%延迟 - 关闭OFDMA80%延迟 - 开启OFDMA变化百分比Evenroute IQrouter V310.110.1–NETGEAR R78008.98.9–octoScope Pal624.57.0-71NETGEAR RAX154.23.3-21NETGEAR RAX454.94.90NETGEAR RAX1207.97.7-3表4：NETGEAR路由器OFDMA延迟摘要 - 下行这里是 禁用 OFDMA 的下行延迟 图表...NETGEAR 延迟 CDF – OFDMA 关闭 – 下行还有下面的 启用 OFDMA 的下行延迟，没有太多其他要说的。NETGEAR 延迟 CDF – OFDMA 开启 – 下行转向 上行延迟，80% 概率延迟在表5中分别总结了 OFDMA 关闭和开启的情况。对于 RAX120 来说，与下行相比，延迟改善要好得多。但启用 OFDMA 似乎严重地影响了 RAX15。为了核实，我重复了测试，结果类似。产品80% 延迟 – OFDMA 关闭80% 延迟 – OFDMA 开启% 变化Evenroute IQrouter V322.622.6–NETGEAR R780024.924.9–octoScope Pal625.311.0-57NETGEAR RAX1513.9145.5+947NETGEAR RAX4515.913-18NETGEAR RAX12029.422-25表5：NETGEAR 路由器 OFDMA 延迟摘要 – 上行禁用 OFDMA 的上行延迟 图表显示了相对于下行图表而言的明显向右移动（更高延迟），正如我们在 ASUS 路由器上所看到的。请注意，RAX120 的延迟高于 R7800。NETGEAR 延迟 CDF – OFDMA 关闭 – 上行启用 OFDMA 的上行延迟 图表显示，RAX120 现在比 R7800 更好，与 IQrouter v3 大致相当。RAX15，则像他们说的，破表了，但不是一种好事。NETGEAR 延迟 CDF – OFDMA 开启 – 上行结论：我的关于延迟的看法是，OFDMA似乎能够降低一些延迟。但我怀疑这种效果在实际世界中是不会被注意到的。这不是像打开 OFDMA 就能将延迟从数百毫秒减少到1毫秒那样显著的效果。效率AX 的首要指令是提高空间效率，即使用更少的空间来传输给定数量的数据包。幸运的是，octoScope的 Pals 测量了空间拥塞。因此，通过查看 OFDMA 开启和关闭时的拥塞差异，我们可以看到 AX 是否遵循了其指令。我将使用由与 DUT 相关的 Pal6 测量的空间拥塞的条形图来展示OFDMA的作用。下行拥塞 图表显示了三台ASUS路由器，每对中的第一个条形图表示关闭 OFDMA 时的平均拥塞，第二个条形图表示开启时的拥塞。尽管拥塞相对较低，大约在25%左右，但你会注意到每个产品在启用 OFDMA 时都略微增加了拥塞。然而，就像我们在延迟中看到的那样，你在现实世界中不会注意到这一点。ASUS 空间拥塞 – OFDMA 效应 – 下行我没有在上面的图表中包含 Pal6 和两台AC参考产品，因为Pal6会压缩比例，使细微的空间拥塞变化难以看到。因此，这些产品分开绘制。看起来，Pal6的高延迟和高拥塞是相关的。参考产品空间拥塞 – OFDMA 效应 – 下行以下是 ASUS 三兄弟的上行拥塞 图表。空间使用量是下行的两倍多。这一次，启用 OFDMA 稍微降低了拥塞。但你不会注意到它，而且这很可能只是测量的变异。ASUS 空间拥塞 – OFDMA 效应 – 上行以下是参考产品的上行拥塞。IQrouter v3 和 R7800 在这个方向上的拥塞都较低。但看看Pal6！它几乎减少了20%的拥塞，这是我在所有这些测试中看到的最显著的变化。参考产品空间拥塞 – OFDMA 效应 – 下行现在轮到 NETGEAR 了。以下是 下行...NETGEAR 空间拥塞 – OFDMA 效应 – 下行...和下面是上行。最终，有一款产品脱颖而出，那就是双流的 RAX15。它的拥塞要低于所有其他经过测试的产品，而且启用 OFDMA 时拥塞明显降低。实际上，所有三款NETGEAR路由器在启用 OFDMA 时都显示出较低的拥塞。但对于RAX45来说，降低幅度相当小。正如我们在 ASUS 路由器中所看到的，三台 NETGEAR 路由器的上行拥塞是下行的两倍多。RAX15 在启用 OFDMA 时的拥塞增加足够大，是真实的。但 RAX120 和 RAX45 拥塞的变化可能在测量精度内。NETGEAR 空间拥塞 – OFDMA 效应 – 上行结论：虽然有一些证据表明 OFDMA 会影响空间效率，但当看到效果时，它非常轻微。吞吐量启用 OFDMA 后的总吞吐量改进情况并不比延迟或拥塞要好。OFDMA 在这里的期望效果是更高的总吞吐量。但在这些测试中，如果有任何改进的话，都非常轻微；远远在我处理任何 Wi-Fi 测量时使用的 +/- 5% 的经验法则范围之内。我将使用平均吞吐量的条形图，因为时间图并没有太多帮助。我将使用与空间拥塞相同的绘图方法，首先是 OFDMA 关闭，然后是开启，对每个产品进行绘制。以下是 ASUS 下行。这里没有太多可看的。ASUS 平均总吞吐量 – OFDMA 效应 – 下行以及参考产品的下行平均吞吐量。Pal6 的 OFDMA 关闭时吞吐量较低，与我们之前看到的更高延迟一致。参考产品平均总吞吐量 – OFDMA 效应 – 下行以下是 上行。除了平均吞吐量较低约 20 Mbps 之外，这里也没有太多可看的。平均总吞吐量 – OFDMA 效应 – 上行这是参考产品的上行。对于 Pal6 来说，在 OFDMA 开启/关闭时没有太大的差异。参考产品平均总吞吐量 – OFDMA 效应 – 上行转向 NETGEAR。下行...NETGEAR 平均总吞吐量 – OFDMA 效应 – 下行...和 上行。唯一显著的变化是 RAX15，与期望的方向相反。这似乎与之前观察到的拥塞的巨大变化相一致。NETGEAR 平均总吞吐量 – OFDMA 效应 – 上行结论：OFDMA 对于总吞吐量没有任何改善。关于吞吐量的最后一个观察是，请注意，这个基准测试使用了四个50 Mbps的吞吐流，但没有一个产品达到了200 Mbps的总吞吐量。我进行的实验显示这部分原因是由于流量优先级的混合，部分原因是由于缓冲长度的限制。总结消费级路由器制造商在其 Wi-Fi 6 产品中启用 OFDMA 已经等待了很长时间。在此期间，我尝试了各种 OFDMA 测试方法，有些是由 Broadcom 建议的，有些是由路由器制造商建议的。我目前的结论是，对于普通消费者来说，OFDMA 没有明显的好处。可能会有更多的设备和不同的流量组合显示出延迟、吞吐量和/或空时拥塞的改进。但使用情况非常具体，改进不足以使 OFDMA 成为购买Wi-Fi 6 路由器的理由。目前，我将搁置我的 OFDMA 测试工作，重新设计一个新的 Wi-Fi 测试套件，以便我可以继续产品测试。不幸的是，OFDMA 只是又一个从移动/蜂窝世界借用来的技术，在 Wi-Fi 世界中并没有取得好的效果，这里设备而不是网络才是主导。
目录 [隐藏] 简单请求预检请求结论origin 和referer 跨源资源共享（CORS）这里来聊聊跨源资源共享CORS（Cross Origin Resouce Share）。开局一张图，很多人错误理解跨源资源共享就是浏览器在访问服务器A（domain-a.com）获取的资源如果包含跨源服务器B（domain-a.com）资源的时候那么此时就需要跨源资源共享，所以我们需要配置我们的访问控制策略Access-Control-Allow-Origin 运行所有站点访问。但是为什么这里跨源资源共享是被服务器B控制的？原来这里这里跨源资源共享是浏览器用来保护服务器B的，防止服务器A加载的资源伪造一个请求，但是此时浏览器已经记住了服务器B的Cookie认证，从而完一次对服务器B的攻击，这就是跨源请求伪造（Cross-site request forgery）。假如浏览器没有CORS，会是怎样？我们需要举个栗子：用户在浏览如上购物网站B（domain-b.com），此时已经完成认证，浏览器会自动将记住服务器B的Cookie，方便你继续浏览。此时我们在新的标签页开始浏览网页访问服务器A（domain-a.com），服务器A加载的JavaScript可以直接对服务器B发起了一次请求获取了你的购物信息。所以此时服务器B的同源策略就一项非常重要的安全措施。浏览器有CORS又会是怎样？简单请求对于如上的跨源资源请求浏览器会区分是简单请求还是非简单请求。什么是简单请求？使用下列方法之一：GETHEADPOST仅存在如下头部字段：AcceptAccept-LanguageContent-LanguageContent-Type （需要注意额外的限制）Content-Type的值仅限于下列三者之一：text/plainmultipart/form-dataapplication/x-www-form-urlencoded对于简单请求浏览器直接发起请求，并且带上头部字段Origin，从而把访问控制决策直接交给服务器B，服务器B返回Access-Control-Allow-Origin: xxx 字段指示该资源可以被是否可以被跨源访问。预检请求而对于非简单请求又怎么办呢？浏览器会慎之又慎，先发起一次预检请求（OPTIONS），确认该服务器A被允许跨站点访问后再进行访问。预检请求会获取如下（但不限于）信息。有了这些信息，浏览器就可以判断是否进行跨源请求了。Access-Control-Allow-Methods指示其支持的HTTP请求方法。Access-Control-Allow-Origin当前站点支持跨源资源共享站点。Access-Control-Max-Age 预检请求的最大缓存时间。结论那么对于服务器B如何控制跨源策略呢？首先需要进行开启跨源请求伪造过滤（CORF）。理论上默认是不支持任何跨源访问的，确实需要也应该是白名单机制，避免通配符。origin 和referer所以正常访问的跨站点的链接跳转都会带上referer，不管是同源和跨源，包含完整URL。只有跨源请求的时候才会带上origin，只包含域名，不带URL。
目录 [隐藏] 样例探索结论 GCC 优化过于复杂，这里探索 GCC 对未使用的代码和变量以及文件的优化策略。3.10 Options That Control Optimization6.3.3 Reducing Size of Executables with Unused Subprogram/Data Elimination样例如下两个文件。$ tree ├── main.c └── unused.cmain.c 文件内容如下。#include <stdio.h> // __attribute__((used, section(".linker_entry_list"))) int main_extern_unused_var = 0; // __attribute__((used, section(".linker_entry_list"))) static int main_static_unused_var = 0; // __attribute__((used)) static int main_static_unused_func(void) { printf("static unused function.\n"); return 0; } // __attribute__((used)) int main_extern_unused_func(void) { printf("extern unused function.\n"); return 0; } int main(int argc, char* argv[]) { printf("hello world!\n"); return 0; }unused.c#include <stdio.h> // __attribute__((used, section(".linker_entry_list"))) int other_extern_unused_var = 0; // __attribute__((used, section(".linker_entry_list"))) static int other_static_unused_var = 0; // __attribute__((used)) static int other_static_unused_func(void) { printf("static unused function.\n"); return 0; } // __attribute__((used)) int other_extern_unused_func(void) { printf("extern unused function.\n"); return 0; }探索默认编译选项，通过可执行文件确定所有变量和函数都未优化。$ gcc -o main.app main.c unused.c readelf -a main.app |grep unused 37: 0000000000004018 4 OBJECT LOCAL DEFAULT 26 main_static_unused_var 38: 0000000000001149 27 FUNC LOCAL DEFAULT 16 main_static_unused_func 40: 0000000000004020 4 OBJECT LOCAL DEFAULT 26 other_static_unused_var 41: 00000000000011a5 27 FUNC LOCAL DEFAULT 16 other_static_unused_func 57: 0000000000001164 27 FUNC GLOBAL DEFAULT 16 main_extern_unused_func 68: 00000000000011c0 27 FUNC GLOBAL DEFAULT 16 other_extern_unused_func 69: 000000000000401c 4 OBJECT GLOBAL DEFAULT 26 other_extern_unused_var 70: 0000000000004014 4 OBJECT GLOBAL DEFAULT 26 main_extern_unused_var-O1 -O2 -Os 选项，所有static 链接属性的变量和函数被用优化掉。$ gcc -Os -o main.app main.c unused.c $ readelf -a main.app |grep unused 53: 0000000000001169 21 FUNC GLOBAL DEFAULT 16 main_extern_unused_func 64: 000000000000117e 21 FUNC GLOBAL DEFAULT 16 other_extern_unused_func 65: 0000000000004018 4 OBJECT GLOBAL DEFAULT 26 other_extern_unused_var 66: 0000000000004014 4 OBJECT GLOBAL DEFAULT 26 main_extern_unused_var为 static 链接属性的变量增加 __attribute__((used)) 属性。优化被抑制。__attribute__((used)) static int other_static_unused_var=0; __attribute__((used)) static int other_static_unused_func(void) { printf("static unused function.\n"); return 0; }$ gcc -Os -o main.app main.c unused.c $ readelf -a main.app |grep unused 29: 0000000000001169 21 FUNC LOCAL DEFAULT 16 main_static_unused_func 30: 0000000000004018 4 OBJECT LOCAL DEFAULT 26 main_static_unused_var 40: 0000000000001193 21 FUNC LOCAL DEFAULT 16 other_static_unused_func 41: 0000000000004020 4 OBJECT LOCAL DEFAULT 26 other_static_unused_var 57: 000000000000117e 21 FUNC GLOBAL DEFAULT 16 main_extern_unused_func 68: 00000000000011a8 21 FUNC GLOBAL DEFAULT 16 other_extern_unused_func 69: 000000000000401c 4 OBJECT GLOBAL DEFAULT 26 other_extern_unused_var 70: 0000000000004014 4 OBJECT GLOBAL DEFAULT 26 main_extern_unused_var增加 -Wl,--gc-sections 选项，所有的 unused.c 变量和函数，以及 main.c 中的变量全部被优化。$gcc -Wl,--gc-sections -o main.app main.c unused.c $readelf -a main.app |grep unused 37: 0000000000001149 27 FUNC LOCAL DEFAULT 16 main_static_unused_func 53: 0000000000001164 27 FUNC GLOBAL DEFAULT 16 main_extern_unused_func同时使用 -Wl,--gc-sections 和 -O1。进一步优化掉 main.c 中的 static 函数，只剩下最后一个未使用的 extern 函数。$ gcc -Wl,--gc-sections -O1 -o main.app main.c unused.c $ readelf -a main.app |grep unused 52: 0000000000001149 30 FUNC GLOBAL DEFAULT 16 main_extern_unused_func同时使用 -Wl,--gc-sections 和 -O2 或者 -Os 。所有未使用的函数、变量全部被优化。$ gcc -Wl,--gc-sections -O2 -o main.app main.c unused.c 或者额外指定 -ffunction-sections ，进一步把未使用的函数优化掉。$gcc -Wl,--gc-sections -ffunction-sections -fdata-sections -o main.app main.c unused.c对于--gc-sections ，通过设置 KEEP 链接抑制变量的优化。注意，并不能对函数使用属性。__attribute__((used, section(".linker_entry_list"))) int main_extern_unused_var = 0; __attribute__((used, section(".linker_entry_list"))) static int main_static_unused_var = 0;.linker_entry_list : { KEEP(*(SORT(.linker_entry_list*))); }$gcc -Wl,--gc-sections -ffunction-sections -fdata-sections -o main.app main.c unused.c -T x86_64.lds $ readelf -a main.app |grep unused 25: 3ffbdb64 4 OBJECT LOCAL DEFAULT 13 main_static_unused_var 27: 3ffbdb6c 4 OBJECT LOCAL DEFAULT 13 other_static_unused_var 88: 3ffbdb68 4 OBJECT GLOBAL DEFAULT 13 other_extern_unused_var 98: 3ffbdb60 4 OBJECT GLOBAL DEFAULT 13 main_extern_unused_var结论选项结果默认选项/-O0不优化-O1/2/3/s按链接属性优化，优化未使用的 static 链接属性的变量和函数。可以通过 __attribute__((used)) 抑制该优化。-Wl,--gc-sections按未使用的变量、函数、文件进行优化，默认优化未使用变量（-fdata-sections）和未使用文件，通过指定 （-ffunction-sections）选择未优化函数。对于 Data 可以使用 KEEP 抑制该优化。