卸载大型 TCP 数据包的段
NDIS 微型端口驱动程序可以卸载大于网络介质最大传输单元 (MTU) 的大型 TCP 数据包的分段。 支持对大型 TCP 数据包进行分段的 NIC 还必须能够:
为包含 IP 选项的发送数据包计算 IP 校验和。
为包含 TCP 选项的发送数据包计算 TCP 校验和。
NDIS 版本 6.0 及更高版本支持大型发送卸载版本 1 (LSOv1),它与 NDIS 5.x 中的大型发送卸载 (LSO) 类似。 NDIS 版本 6.0 及更高版本还支持大型发送卸载版本 2 (LSOv2),该版本提供增强的大型数据包分段服务,包括支持 IPv6。
支持 LSOv2 和 LSOv1 的微型端口驱动程序必须从 NET_BUFFER_LIST 结构 OOB 信息中确定卸载类型。 驱动程序可以使用 NDIS_TCP_LARGE_SEND_OFFLOAD_NET_BUFFER_LIST_INFO 结构中的 Type 成员来确定驱动程序栈使用的是 LSOv2 还是 LSOv1,并执行相应的卸载服务。 任何包含 LSOv1 或 LSOv2 OOB 数据的 NET_BUFFER_LIST 结构也包含一个 NET_BUFFER 结构。 有关 NDIS_TCP_LARGE_SEND_OFFLOAD_NET_BUFFER_LIST_INFO 的更多信息,请参阅获取 TCP/IP 卸载 NET_BUFFER_LIST 信息。
但是,如果微型端口收到 OID_TCP_OFFLOAD_PARAMETERS 以关闭微型端口的 LSO 功能,并且微型端口已成功完成 OID、微型端口应删除包含任何非零 LSOv1 或 LSOv2 OOB 数据 (NDIS_TCP_LARGE_SEND_OFFLOAD_NET_BUFFER_LIST_INFO) 的所有 NET_BUFFER_LIST。
TCP/IP 传输只卸载符合以下条件的大型 TCP 数据包:
该数据包是一个 TCP 数据包。 TCP/IP 传输不会卸载大型 UDP 数据包进行分段。
数据包必须至少能被微型端口驱动程序指定的最小段数进行分段。 有关详细信息,请参阅报告 NIC 的 LSOv1 TCP 数据包分段功能和报告 NIC 的 LSOv2 TCP 数据包分段功能。
该数据包不是环回数据包。
数据包不会通过隧道发送。
在分段卸载大型 TCP 数据包之前,TCP/IP 传输:
- 更新与 NET_BUFFER_LIST 结构相关的大数据包分段信息。 该信息是 NDIS_TCP_LARGE_SEND_OFFLOAD_NET_BUFFER_LIST_INFO 结构,是与 NET_BUFFER_LIST 结构相关联的 NET_BUFFER_LIST 信息的一部分。 有关 NET_BUFFER_LIST 信息的更多信息,请参阅访问 TCP/IP 卸载 NET_BUFFER_LIST 信息。 TCP/IP 传输将 MSS 值设置为最大段大小 (MSS)。
对于 LSOv1,将大型 TCP 数据包的总长度写入数据包的 IP 标头的“总长度”字段。 总长度包括 IP 标头的长度、IP 选项(如有)的长度、TCP 标头的长度、TCP 选项(如有)的长度以及 TCP 有效负载的长度。 对于 LSOv2,将数据包 IP 标头的“总长度”字段设置为 0。 微型端口驱动程序应根据 NET_BUFFER_LIST 结构中第一个 NET_BUFFER 结构的长度来确定数据包的长度。
计算 TCP 伪标头的补数和,并将此和写入 TCP 标头的校验和字段。 TCP/IP 传输会计算伪标头中以下字段的补数和:源 IP 地址、目标 IP 地址和协议。 TCP/IP 传输所提供的伪标头的补数和使 NIC 可以提前开始计算每个数据包的真正 TCP 校验和,NIC 可以从大型 TCP 数据包中推导出这些数据包,而无需检查 IP 标头。 请注意,RFC 793 规定伪标头校验和是根据源 IP 地址、目标 IP 地址、协议和 TCP 长度来计算的。 (TCP 长度是 TCP 标头的长度加上 TCP 有效负载的长度。TCP 长度不包括伪标头的长度。)但是,由于基础微型端口驱动程序和 NIC 会从 TCP/IP 传输所传递的大数据包中生成 TCP 段,因此传输不知道每个 TCP 段的 TCP 有效负载的大小,也就无法在伪标头中包含 TCP 长度。 相反,如下所述,NIC 会扩展 TCP/IP 传输提供的伪标头校验和,以覆盖每个生成的 TCP 段的 TCP 长度。
将正确的序列号写入 TCP 标头的序列号字段。 序列号标识 TCP 有效负载的第一个字节。
微型端口驱动程序在其 MiniportSendNetBufferLists 或 MiniportCoSendNetBufferLists 函数中获取 NET_BUFFER_LIST 结构后,它可以调用 NET_BUFFER_LIST_INFO 宏(_Id 为 TcpLargeSendNetBufferListInfo)来获取 TCP/IP 传输写入的 MSS 值。
微型端口驱动程序从数据包的 IP 标头中获取大型数据包的总长度,并使用 MSS 值将大 TCP 数据包分成较小的数据包。 每个较小的数据包包含 MSS 或更少的用户数据字节。 请注意,只有从分段大数据包创建的最后一个数据包所含的用户数据字节数应少于 MSS。 从分段数据包创建的所有其他数据包都应包含 MSS 用户数据字节。 如果未遵循这一规则,创建和传输不必要的额外数据包可能会降低性能。
微型端口驱动程序会将 MAC、IP 和 TCP 标头粘贴到从大数据包派生出来的每个段上。 微型端口驱动程序必须计算这些派生数据包的 IP 和 TCP 校验和。 为计算从大型 TCP 数据包衍生出的每个数据包的 TCP 校验和,NIC 会计算 TCP 校验和(TCP 标头和 TCP 有效负载)的变量部分,将此校验和与 TCP/IP 传输计算的伪标头的 1 的补数和相加,然后计算校验和的 16 位 1 的补数。 有关计算此类校验和的详细信息,请参阅 RFC 793 和 RFC 1122。
下图显示了大数据包的分段情况。
大型 TCP 数据包中的 TCP 用户数据长度应等于或小于微型端口驱动程序分配给 MaxOffLoadSize 值的值。 有关 MaxOffLoadSize 值的详细信息,请参阅报告 NIC 的 LSOv1 TCP 数据包分段功能和报告 NIC 的 LSOv2 TCP 数据包分段功能。
在驱动程序发出状态指示以表明 MaxOffLoadSize 值发生变化后,如果收到使用先前 MaxOffLoadSize 值的 LSO 发送请求,则驱动程序不得崩溃。 相反,驱动程序可能会让发送请求失败。
独立发布状态指示以报告 MaxOffLoadSize 值变化的中间驱动程序必须确保未发布状态指示的底层微型端口适配器不会收到任何大小大于微型端口适配器报告的 MaxOffLoadSize 值的数据包。
响应 OID_TCP_OFFLOAD_PARAMETERS 以关闭 LSO 服务的微型端口中间驱动程序必须为 LSO 发送请求仍可到达微型端口驱动程序的一小段时间窗口做好准备。
段数据包中 TCP 用户数据的长度必须小于或等于 MSS。 MSS 是 TCP 传输通过使用与 NET_BUFFER_LIST 结构关联的 LSO NET_BUFFER_LIST 信息向下传递的 ULONG 值。 请注意,只有从分段大数据包创建的最后一个数据包所含的用户数据字节数应少于 MSS。 从分段数据包创建的所有其他数据包都应包含 MSS 用户数据字节。 如果未遵循这一规则,创建和传输不必要的额外数据包可能会降低性能。
从大型 TCP 数据包派生的段数据包数量必须等于或大于微型端口驱动程序指定的 MinSegmentCount 值。 有关 MinSegmentCount 值的详细信息,请参阅报告 NIC 的 LSOv1 TCP 数据包分段功能和报告 NIC 的 LSOv2 TCP 数据包分段功能。
以下假设和限制适用于处理任何支持 LSO 的微型端口驱动程序(无论版本如何)的 IP 和 TCP 标头:
TCP/IP 传输卸载的大型 TCP 数据包的 IP 标头中的 MF 位将不会被设置,IP 标头中的碎片偏移将为零。
大型 TCP 数据包 TCP 标头中的 URG、RST 和 SYN 标志将不会被设置,TCP 标头中的紧急偏移量(指针)将为零。
如果设置了大数据包 TCP 标头中的 FIN 位,则微型端口驱动程序必须在从大 TCP 数据包创建的最后一个数据包的 TCP 标头中设置该位。
如果设置了大 TCP 数据包 TCP 标头中的 PSH 位,则微型端口驱动程序必须在从大 TCP 数据包创建的最后一个数据包的 TCP 标头中设置该位。
如果设置了大 TCP 数据包 TCP 标头中的 CWR 位,则微型端口驱动程序必须在从大 TCP 数据包创建的第一个数据包的 TCP 标头中设置该位。 微型端口驱动程序可选择在它从大型 TCP 数据包创建的最后一个数据包的 TCP 标头中设置该位,但这样做不太可取。
如果大型 TCP 数据包包含 IP 选项或 TCP 选项(或两者),微型端口驱动程序会将这些选项原封不动地复制到从大型 TCP 数据包派生出来的每个数据包中。 具体来说,NIC 不会递增时间戳选项。
所有数据包标头(以太网、IP、TCP)都将位于数据包的第一个 MDL 中。 标头不会在多个 MDL 之间分割。
提示
启用 LSO 时,这一假设有效。 否则,当未启用 LSO 时,微型端口驱动程序无法假定 IP 标头与以太网标头位于同一个 MDL 中。
微型端口驱动程序必须按照从 TCP/IP 传输接收 NET_BUFFER_LIST 结构的顺序发送 NET_BUFFER_LIST 结构中的数据包。
在处理大型 TCP 数据包时,微型端口适配器只负责对数据包进行分段,并将 MAC、IP 和 TCP 标头粘贴到从大型 TCP 数据包派生出来的数据包上。 TCP/IP 传输会执行所有其他任务(如根据远程主机的接收窗口大小调整发送窗口大小)。
在完成大数据包的发送操作之前(如使用 NdisMSendNetBufferListsComplete 或 NdisMCoSendNetBufferListsComplete 时)、微型端口驱动程序会写入 NDIS_TCP_LARGE_SEND_OFFLOAD_NET_BUFFER_LIST_INFO 值(用于大型发送卸载的 NET_BUFFER_LIST 信息),其中包含从大型 TCP 数据包创建的所有数据包中成功发送的 TCP 用户数据字节总数。
除了之前的 LSO 要求外,支持 LSOv2 的微型端口驱动程序还必须:
支持 IPv4 或 IPv6,或同时支持 IPv4 和 IPv6。
支持在网络接口卡 (NIC) 生成的每个段数据包中复制大数据包中的 IPv4 选项。
支持在每个 TCP 段数据包中复制 TCP 大数据包中的 IPv6 扩展标头。
支持在微型端口驱动程序生成的每个 TCP 段数据包中复制 TCP 选项。
使用 NET_BUFFER_LIST 结构中的 IP 和 TCP 标头作为模板,为每个段数据包生成 TCP/IP 标头。
使用 0x0000 至 0x7FFF 范围内的 IP 标识 (IP ID) 值。 (0x8000 至 0xFFFF 的范围是为具有 TCP 烟囱卸载功能的设备保留的。)例如,如果模板 IP 标头以标识字段值 0x7FFE 开始,则第一个 TCP 段数据包的 IP ID 值必须为 0x7FFE,然后依次为 0x7FFF、0x0000、0x0001,以此类推。
使用 NDIS_TCP_LARGE_SEND_OFFLOAD_NET_BUFFER_LIST_INFO 的 TcpHeaderOffset 成员中的字节偏移量来确定 TCP 标头的位置,从数据包的第一个字节开始。
将与每个 LSOv2 NET_BUFFER_LIST 结构关联的 NET_BUFFER 结构的数量限制为一个。
注意
这是对支持 LSOv2 的微型端口驱动程序的新要求。 LSOv1 微型端口驱动程序没有明确执行这一规则,但建议执行。
根据 NET_BUFFER_LIST 结构中第一个 NET_BUFFER 结构的长度确定数据包的总长度。 这不同于 LSOv1 使用的方法驱动程序。
支持 TCP 选项、IP 选项和 IP 扩展标头。
发送操作完成后,微型端口驱动程序必须将 NDIS_TCP_LARGE_SEND_OFFLOAD_NET_BUFFER_LIST_INFO 结构中的 LsoV2TransmitComplete.Reserved 成员设置为零,并将 LsoV2TransmitComplete.Type 成员设置为 NDIS_TCP_LARGE_SEND_OFFLOAD_V2_TYPE。