UDP 分段卸载 (USO)
UDP 分段卸载 (USO) 是在 Windows 10 2004 及更高版本中支持的一项功能,它让网络接口卡 (NIC) 能够卸载大于网络介质最大传输单元 (MTU) 的 UDP 数据报分段。 这样,Windows 就能减少与每个数据包 TCP/IP 处理相关的 CPU 占用率。 对 USO 的要求与针对 TCP 传输协议的 LSOv2 类似。
USO 的要求
本部分主要涉及 NDIS 协议和微型端口驱动程序。 NDIS 轻型过滤器驱动程序 (LWF) 在修改或发送数据包时必须遵循协议驱动程序要求,并且还可以假定提供给其 FilterSendNetBufferLists 处理程序的任何数据包都符合协议驱动程序要求。
微型端口驱动程序可以卸载大于网络介质 MTU 的大型 UDP 数据包的分段。 支持对大型 UDP 数据包进行分段的 NIC 还必须能够执行以下操作:
- 为包含 IPv4 选项的发送数据包计算 IP 校验和
- 计算已发送数据包的 UDP 校验和
支持 USO 的微型端口驱动程序必须根据 NET_BUFFER_LIST 结构的带外 (OOB) 信息确定卸载类型。 如果 NDIS_UDP_SEGMENTATION_OFFLOAD_NET_BUFFER_LIST_INFO 结构的值非零,则微型端口驱动程序必须执行 USO。 任何包含 USO OOB 数据的 NET_BUFFER_LIST 也包含一个 NET_BUFFER 结构。 但是,如果微型端口驱动程序收到了关闭 USO 的 OID_TCP_OFFLOAD_PARAMETERS 文件,微型端口驱动程序在成功完成 OID 后,应拒绝并返回任何设置了 USO OOB 字段的 NET_BUFFER_LIST 文件。
TCP/IP 传输只卸载符合以下条件的 UDP 数据包:
- 该数据包是一个 UDP 数据包。
- 数据包长度必须大于最大段大小 (MSS) * (MinSegmentCount - 1)。
- 如果微型端口驱动程序未设置 SubMssFinalSegmentSupported 功能,那么传输卸载的每个大型 UDP 数据包必须具有 Length % MSS == 0。 也就是说,大型数据包可分为 N 个数据包,而每个数据包段正好包含 MSS 用户字节。 如果微型端口驱动程序设置了 SubMssFinalSegmentSupported 功能,则传输上的数据包长度可分性条件将不适用。 换句话说,最后的分段可以小于 MSS。
- 该数据包不是环回数据包。
- UDP/IP 传输卸载的大型 TCP 数据包的 IP 标头中的 MF 位将不会被设置,IP 标头中的碎片偏移将为零。
- 应用程序指定了 UDP_SEND_MSG_SIZE/WSASetUdpSendMessageSize。
在分段卸载大型 UDP 数据包之前,TCP/IP 传输会进行以下操作:
- 更新与 NET_BUFFER_LIST 结构相关的大数据包分段信息。 此信息是一种 NDIS_UDP_SEGMENTATION_OFFLOAD_NET_BUFFER_LIST_INFO 结构,而该结构是 NET_BUFFER_LIST 结构的 OOB 信息的一部分。 TCP/IP 传输将 MSS 值设置为所需的 MSS。
- 计算 UDP 伪首部的补数和,并将此和写入 UDP 标头的校验和字段。 TCP/IP 传输会计算伪首部中以下字段的补数和:源 IP 地址、目标 IP 地址和协议。
TCP/IP 传输所提供的伪首部的补数和使 NIC 可以提前开始计算每个数据包的真正 UDP 校验和,NIC 可以从大型 UDP 数据包中推导出这些数据包,而无需检查 IP 标头。
请注意,RFC 768 和 RFC 2460 规定,伪标头文件是根据源 IP 地址、目标 IP 地址、协议和 UDP 长度(UDP 标头文件的长度加上 UDP 有效负载的长度,不包括伪标头文件的长度)计算的。 但是,由于基础微型端口驱动程序和 NIC 从 TCP/IP 传输所传递的大数据包中生成 UDP 数据报,传输不知道每个 UDP 数据报的 UDP 有效负载的大小,因此无法在伪标头文件计算中包含 UDP 长度。 相反,如下部分所述,NIC 会扩展 TCP/IP 传输提供的伪标头文件校验和,以覆盖每个生成的 UDP 数据报的 UDP 长度。
重要
如果 TCP/IP 传输提供的 UDP 标头校验和字段为零,则 NIC 不应执行 UDP 校验和计算。
使用 USO 发送数据包
微型端口驱动程序在其 MiniportSendNetBufferLists 回调函数中获取 NET_BUFFER_LIST 后,可以调用 NET_BUFFER_LIST_INFO 宏(_Id 为 UdpSegmentationOffloadInfo)来获取 MSS 值和 IP 协议。
微型端口驱动程序从第一个 NET_BUFFER 结构的长度中获取大数据包的总长度,并使用 MSS 值将大 UDP 数据包分成较小的 UDP 数据包。 每个较小的数据包包含 MSS 或更少的用户数据字节。 请注意,只有从分段大数据包创建的最后一个数据包所含的用户数据字节数应少于 MSS。 从分段数据包创建的所有其他数据包都必须包含 MSS 用户数据字节。 如果微型端口驱动程序不遵守这一规则,UDP 数据报将会被错误地传送。 如果微型端口驱动程序未设置 SubMssFinalSegmentSupported 功能,则数据包长度除以 MSS,并且每个分段数据包都包含 MSS 用户字节。
微型端口驱动程序会将 MAC、IP 和 UDP 标头粘贴到从大数据包派生出来的每个段上。 微型端口驱动程序必须计算这些派生数据包的 IP 和 UDP 校验和。 为计算从大型 UDP 数据包衍生出的每个数据包的 UDP 校验和,NIC 会计算 UDP 校验和(UDP 标头和 UDP 有效负载)的变量部分,将此校验和与 TCP/IP 传输计算的伪标头的 1 的补数和相加,然后计算校验和的 16 位 1 的补数。 有关计算此类校验和的详细信息,请参阅 RFC 768 和 RFC 2460。
大型 UDP 数据包中的 UDP 用户数据长度必须等于或小于微型端口驱动程序分配给 MaxOffLoadSize 值的值。
在驱动程序发出状态指示以表明 MaxOffLoadSize 值发生变化后,如果收到使用先前 MaxOffLoadSize 值的 LSO 发送请求,则驱动程序不得导致错误检查。 相反,驱动程序必须让发送请求失败。 驱动程序必须出于任何原因(包括大小、最小段数、IP 选项等)而无法执行任何发送请求。 如果功能发生更改,则驱动程序必须尽快发送状态指示。
独立发布状态指示以报告 MaxOffLoadSize 值变化的中间驱动程序必须确保未发布状态指示的底层微型端口适配器不会收到任何大于微型端口适配器报告的 MaxOffLoadSize 值的数据包。
响应 OID_TCP_OFFLOAD_PARAMETERS 以关闭 USO 服务的微型端口中间驱动程序必须为 USO 请求仍可到达微型端口驱动程序的一小段时间窗口做好准备。
从大型 UDP 数据包派生的分段数据包数量必须等于或大于微型端口驱动程序指定的 MinSegmentCount 值。
在处理大型 UDP 数据包时,微型端口驱动程序只负责对数据包进行分段,并将 MAC、IP 和 UDP 标头粘贴到从大型 UDP 数据包派生出来的数据包上。 如果微型端口未能发送至少一个分段数据包,则 NBL 最终必须以失败状态完成。 微型端口可以继续发送后续数据包,但并非必须这样做。 在所有分段数据包传输完成或失败之前,NBL 无法完成返回 NDIS。
支持 USO 的微型端口驱动程序还必须执行以下操作:
- 同时支持 IPv4 和 IPv6。
- 支持在 NIC 生成的每个分段数据包中复制大数据包中的 IPv4 选项。
- 使用 NET_BUFFER_LIST 结构中的 IP 和 UDP 标头作为模板,为每个分段数据包生成 UDP 和 IP 标头。
- 使用 0x0000 至 0xFFFF 范围内的 IP 标识 (IP ID) 值。 例如,如果模板 IP 标头以标识字段值 0xFFFE 开始,则第一个 UDP 数据报的值必须是 0xFFFE,然后是 0xFFFF、0x0000、0x0001,依此类推。
- 如果大型 UDP 数据包包含 IP 选项,则微型端口驱动程序会将这些选项原封不动地复制到从大型 UDP 数据包派生出来的每个数据包中。
- 使用 NDIS_UDP_SEGMENTATION_OFFLOAD_NET_BUFFER_LIST_INFO 的 UdpHeaderOffset 成员中的字节偏移量来确定 UDP 标头的位置,从数据包的第一个字节开始。
- 根据分段数据包递增发送统计信息。 例如,包括每个数据包段的以太网、IP 和 UDP 标头字节数,数据包计数是 MSS 大小的数据包段数,而不是 1 大小的数据包段数。
- 根据每个分段数据报大小设置 UDP 总长度和 IP 长度字段。
NDIS 接口更改
本部分介绍 NDIS 6.83 中的更改,这些更改使主机 TCP/IP 驱动程序堆栈能够利用微型端口驱动程序提供的 USO 功能。
NDIS 和微型端口驱动程序执行以下操作:
- 播发 NIC 支持 USO 功能
- 启用或禁用 USO
- 获取当前的 USO 功能状态
播发 USO 功能
微型端口驱动程序通过填写 NDIS_OFFLOAD 结构的 UdpSegmentation 字段来播发 USO 功能,该字段在 NdisMSetMiniportAttributes 的参数中传递。 NDIS_OFFLOAD 结构中的 Header.Revision 字段必须设置为 NDIS_OFFLOAD_REVISION_6,Header.Size 字段必须设置为 NDIS_SIZEOF_NDIS_OFFLOAD_REVISION_6。
查询 USO 状态
可以使用 OID_TCP_OFFLOAD_CURRENT_CONFIG 查询当前的 USO 状态。 NDIS 会处理该 OID,而不会将其传递给微型端口驱动程序。
更改 USO 状态
可以使用 OID_TCP_OFFLOAD_PARAMETERS 启用或禁用 USO。 微型端口驱动程序处理完 OID 后,必须发送更新了卸载状态的 NDIS_STATUS_TASK_OFFLOAD_CURRENT_CONFIG 状态指示。
USO 关键字
USO 枚举关键字如下所示:
- *UsoIPv4
- *UsoIPv6
这些值描述了 USO 对于特定 IP 协议是启用还是禁用。 USO 设置与 NDIS_TCP_IP_CHECKSUM_OFFLOAD 配置无关。 例如,禁用 *UDPChecksumOffloadIPv4 不会隐式禁用 *UsoIPv4。
子项名称 | 参数说明 | 值 | 枚举说明 |
---|---|---|---|
*UsoIPv4 | UDP 分段卸载 (IPv4) | 0 | 已禁用 |
1 | 已启用 | ||
*UsoIPv6 | UDP 分段卸载 (IPV6) | 0 | 已禁用 |
1 | 已启用 |