UDP接收段合并卸载 (URO)

从 Windows 11 版本 24H2 开始,UDP 接收段合并卸载 (URO) 使网络接口卡 (NIC) 能够合并 UDP 接收段。 NIC 可以将来自同一流且符合一组规则的 UDP 数据报组合到逻辑上连续的缓冲区中。 然后,这些组合数据报将作为单个大型数据包指示给 Windows 网络堆栈。

合并 UDP 数据报可降低在高带宽流中处理数据包的 CPU 成本,从而提高吞吐量并减少每个字节的周期数。

以下部分介绍了合并 UDP 数据包的规则以及如何编写 URO 微型端口驱动程序。

合并 UDP 数据包的规则

只能在满足以下所有条件的数据包上尝试 URO 合并:

  • 所有数据包的 IpHeader.Version 完全相同。
  • IpHeader.SourceAddressIpHeader.DestinationAddress 对于所有数据包都是相同的。
  • UdpHeader.SourcePortUdpHeader.DestinationPort 对于所有数据包都是相同的。
  • UdpHeader.Length 对于所有数据包都是相同的,但最后一个数据包可能更少。
  • UdpHeader.Length 必须为非零。
  • UdpHeader.Checksum(如果非零)必须在所有数据包上正确。 这意味着接收检查和卸载必须验证数据包。
  • 对于所有数据包,第 2 层标头 必须相同。

如果数据包为 IPv4,它们还必须满足以下条件:

  • 所有数据包的 IPv4Header.Protocol == 17 (UDP)。
  • 所有数据包的 EthernetHeader.EtherType == 0x0800。
  • 收到的数据包上的 IPv4Header.HeaderChecksum 必须正确。 这意味着接收检查和卸载必须验证报头。
  • 所有数据包的 IPv4Header.HeaderLength == 5(无 IPv4 选项标头)。
  • IPv4Header.ToS 对于所有数据包都是相同的。
  • IPv4Header.ECN 对于所有数据包都是相同的。
  • IPv4Header.DontFragment 对于所有数据包都是相同的。
  • IPv4Header.TTL 对于所有数据包都是相同的。
  • 所有数据包的 IPv4Header.TotalLength == UdpHeader.Length + length (IPv4Header)。

如果数据包为 IPv6,它们还必须满足以下条件:

  • 所有数据包的 IPv6Header.NextHeader == 17 (UDP) (无扩展标头)。
  • 所有数据包的 EthernetHeader.EtherType == 0x86dd (IPv6)。
  • 所有数据包的 IPv6Header.TrafficClassIPv6Header.ECN 都是相同的。
  • 所有数据包的 IPv6Header.FlowLabel 都是相同的。
  • 所有数据包的 IPv6Header.HopLimit 都是相同的。
  • 所有数据包的 IPv6Header.PayloadLength == UdpHeader.Length

URO 数据包结构

生成的单合并单元 (SCU) 必须具有单个 IP 标头和 UDP 标头,后跟所有合并的数据报的 UDP 有效负载连接在一起。

URO 指示必须将 IPv4Header.TotalLength 字段设置为 SCU 的总长度,或 IPv6Header.PayloadLength 字段设置为 UDP 有效负载的长度,并将 UdpHeader.Length 字段设置为合并有效负载的长度。

如果第 2 层 (L2) 标头存在于合并的数据报中,则 SCU 必须包含有效的 L2 标头。 SCU 中的 L2 标头必须与合并的数据报的 L2 标头类似。

校验和验证与指示

URO 指示必须将 IPv4Header.HeaderChecksum UdpHeader.Checksum 字段设置为零,并填写 SCU 上的校验和卸载带外信息,指示 IPv4 和 UDP 校验和成功。

匹配所有合并条件但校验和验证失败的数据包必须单独指示。 在它之后收到的数据包不得与在它之前收到的数据包合并。

例如,假设从同一流接收数据包 1、2、3、4 和 5,但数据包 3 校验和验证失败。 数据包 1 和 2 可以合并在一起,数据包 4 和 5 可以合并在一起,但数据包 3 不得与任一 SCU 合并。 数据包 1 和 2 不得与数据包 4 和 5 合并。 数据包 2 是 SCU 中的最后一个数据包,数据包 4 启动新的 SCU。 此外,在指示数据包 3 之前,必须先指示包含数据包 1 和 2 的 SCU,并且必须在包含数据包 4 和 5 的 SCU 之前指示数据包 3。

数据包合并和流分离

由于硬件和内存允许,来自多个流的数据包可以并行合并。 来自不同流的数据包不得合并在一起。

来自多个接收交错的数据包可以被分离,并与其各自的流合并。 例如,给定流量 A、B 和 C,如果数据包以 A、A、B、C、B、A 的顺序到达,则来自 A 流量的数据包可合并为 AAA,来自 B 流量的数据包可合并为 BB,而来自 C 流量的数据包可正常指示或与来自 C 流量的待处理 SCU 合并。

给定流中的数据包之间不得重新排序。 例如,A 流的数据包必须按接收的顺序合并,而不考虑从 B 和 C 流收到的数据包。

用于控制 URO 的 INF 关键字

以下关键字可用于通过注册表键值设置启用/禁用 URO。

*UdpRsc

枚举标准化 INF 关键字具有以下属性:

SubkeyName
必须在 INF 文件中指定且出现在注册表中的关键字名称。

ParamDesc
与 SubkeyName 关联的显示文本。


与列表中的每个选项关联的枚举整数值。 此值存储在 NDI\params\ SubkeyName\中。

EnumDesc
与菜单中显示的每个值关联的显示文本。

默认
菜单的默认值

SubkeyName ParamDesc EnumDesc
*UdpRsc URO 0 已禁用
1(默认值) Enabled

有关使用枚举关键字的详细信息,请参阅枚举关键字

编写 URO 微型端口驱动程序

从 NDIS 6.89 开始,URO 的 NDIS 接口可促进 TCP/IP 与 NDIS 微型端口驱动程序之间的通信。

报表 URO 功能

微型端口驱动程序在 NDIS_OFFLOAD 结构的 UdpRsc 成员中播发对 URO 的支持,并将其传递给 NdisMSetMiniportAttributes 函数。

查询 URO 功能

若要检查微型端口驱动程序是否支持 URO,NDIS 驱动程序和其他应用程序可以查询返回 NDIS_OFFLOAD 结构的 OID_TCP_OFFLOAD_HARDWARE_CAPABILITIES OID。

查询 URO 状态

若要确定当前的 URO 状态,NDIS 驱动程序和其他应用程序可以查询 OID_TCP_OFFLOAD_CURRENT_CONFIG OID 请求。 NDIS 处理此 OID,不会将其传递到微型端口。

更改 URO 状态

可以通过发出 OID_TCP_OFFLOAD_PARAMETERS OID 请求来启用或禁用 URO。 此 OID 使用 NDIS_OFFLOAD_PARAMETERS 结构。 在此结构中,UdpRsc.Enabled 成员可以具有以下值:

含义
NDIS_OFFLOAD_PARAMETERS_UDP_RSC_NO_CHANGE
0
微型端口驱动程序不应更改当前设置。
NDIS_OFFLOAD_PARAMETERS_UDP_RSC_DISABLED
1
URO 已禁用。
NDIS_OFFLOAD_PARAMETERS_UDP_RSC_ENABLED
2
URO 已启用。

当驱动程序使用标志集处理 OID_TCP_OFFLOAD_PARAMETERS OID 请求 NDIS_OFFLOAD_PARAMETERS_UDP_RSC_DISABLED 时,NIC 必须等待完成请求,直到指示所有现有的合并段和未完成的 URO 指示。 这可确保跨 NDIS 组件同步 URO 启用/禁用事件。

微型端口驱动程序处理 OID_TCP_OFFLOAD_PARAMETERS OID 请求后,微型端口驱动程序必须发出具有更新卸载状态的 NDIS_STATUS_TASK_OFFLOAD_CURRENT_CONFIG状态指示。

NDIS_OFFLOAD_PARAMETERS_SKIP_REGISTRY_UPDATENDIS_OFFLOAD_PARAMETERS 中的标志允许仅禁用 URO 的运行时。 使用此标志所做的更改不会保存到注册表。

在 NDIS 6.89 及更高版本中选择退出 URO

面向 NDIS 6.89 及更高版本的驱动程序应了解 URO 数据包并正常处理它们。 若要选择退出 URO:

  • 轻型筛选器 (LWF) 驱动程序在 NDIS_FILTER_DRIVER_CHARACTERISTICS 结构中设置 NDIS_FILTER_DRIVER_UDP_RSC_NOT_SUPPORTED 标志。
  • 协议驱动程序在 NDIS_PROTOCOL_DRIVER_CHARACTERISTICS 结构中设置 NDIS_PROTOCOL_DRIVER_UDP_RSC_NOT_SUPPORTED标志

此方法可确保不熟悉 URO 的组件不会收到 URO NBL。 如果存在不支持 URO 的 LWF 或协议驱动程序,则 NDIS 在绑定期间禁用微型端口上的 URO。

URO 驱动程序的编程注意事项

在实现支持 URO 的微型端口驱动程序时,请考虑以下问题。

Winsock URO API

有关 Winsock URO API 的信息,请参阅 IPPROTO_UDP 套接字选项。 请参阅有关 UDP_RECV_MAX_COALESCED_SIZE2 UDP_COALESCED_INFO 的信息。

Windows TCP/IP 堆栈更新

Microsoft TCP/IP 传输在与 NDIS 绑定时启用 URO,除非配置阻止它这样做。

WFP 标注可以使用 FWPS_CALLOUT2 中的 FWP_CALLOUT_FLAG_ALLOW_URO 来宣传其对 URO 的支持。 如果在 URO 敏感层注册了不兼容的 WFP 标注,则在注册标注时,OS 将禁用 URO。

如果套接字选择加入 URO,其最大合并大小大于或等于硬件卸载大小,则堆栈会将 NBL 从未经修改的硬件传送到套接字。 如果套接字选择加入较小的最大合并大小,堆栈会将合并接收分解为套接字的较小大小。

如果套接字未选择加入 URO,则堆栈将重新分段该套接字的接收。 如果没有硬件 URO,现有软件 URO 功能将继续可用。