即兴的,我猜你不会打电话来NdisCopySendNetBufferListInfo
在 TX 路径中,这意味着校验和卸载元数据正在丢失。
如果 NIC 声称支持校验和卸载(即 NIC 硬件可以插入 IPv4、TCPv4 和/或 TCPv6 校验和),则 TCPIP 驱动程序不会尝试将有效校验和放入 IPv4/TCP 标头中。 (实际上,它明确地将部分校验和放在那里,这在软件中很容易计算,但在硬件中计算起来有点困难。)然后 TCPIP 驱动程序将在 NBL 的信息字段中设置一些标志,指示硬件准确地如何计算将校验和插入数据包有效负载中。
当您克隆 NBL 时,默认情况下,克隆不会继承任何元数据。因此,克隆的 NBL 在数据包有效负载中具有不完整的校验和,但缺少 NIC 硬件插入校验和的指令。
修复方法很简单:NdisCopySendNetBufferListInfo
复制与 TX 路径相关的所有数据包元数据。 (RX 路径有一个类似的 NdisCopyRecieveNetBufferListInfo`,您还应该考虑从驱动程序调用它。)每当您克隆 NBL 时,都应该调用这些例程之一,克隆最终将属于同一个数据包“流”和原来的NBL一样。
当您调用 NdisAllocateCloneNetBufferList 时,为什么 NDIS 不自动复制元数据?表面上的问题是 NDIS 不知道我们正在执行 TX 路径还是 RX 路径。但更深层次的问题是 NDIS 不知道您打算多么严重地破坏数据包。例如,如果您的驱动程序重写了 RX 数据包上的 TCP 标头,则仅简单地复制 NIC 的 TCP 校验和计算和 RSS 哈希值可能是不合适的。
所以打电话NdisCopySendNetBufferListInfo
实际上意味着您声称您没有对数据包进行太多破坏,以至于它们看起来与任何硬件卸载不同。例如,您没有插入协议标头、更改 TCP 端口号等(如果您are做这些事情,那么您要么必须额外编写一些代码来平滑卸载,要么完全禁用它们。)
顺便说一句,这是一个有趣而微妙的问题,每个人的直觉都会出错:
TCP/IP 驱动程序是否获得数据包已传输且没有任何错误的信息?
Ndis[F|M]SendNetBufferListsComplete 确实not表示数据包已传输且没有任何错误。这意味着一件事:数据包有效负载、MDL、NB 和 NBL 不再使用,协议驱动程序可以重新调整它们的用途。
当传输到典型的 PCIe 硬件时,这意味着到 NIC 板载 RAM 的 DMA 已完成,并且 NIC 承诺不再接触数据包有效负载缓冲区。
这是一个简单的答案,但它提出了一个直接的后续问题:如果 SendComplete 并不意味着数据包已成功传输,那么如何does协议判断数据包是否传输成功?
答案是协议不在乎数据包是否传输到下一跳。他们真正关心的是远程端点是否收到数据包。找出答案的唯一方法是某种 ACK 系统。因此,没有人真正费心去构建一个信号,表明 NIC 硬件实际上已将 NBL 传输到下一跳,因为协议无论如何都无法对这些信息做太多事情。
(数据包时间戳 (IEEE15888/PTP/NTP) 是上述讨论的一个例外。但即使在这种情况下,我们也不actually想知道数据包何时离开本地主机。我们actually想知道数据包何时到达远程端点。但物理定律就是这样,后者是不可知的,所以我们必须满足于知道 TX 数据包何时离开本地主机。)
请注意,如果您确定数据包没有传输,那么您可以在NET_BUFFER_LIST::Status
,并且某些协议(例如 UDP + Winsock)会将错误向上冒泡到应用程序。但在这种情况下,您只是针对更快的错误路径进行优化——应用程序本质上仍然有义务构建网络级反馈机制(例如 ACK),以了解数据包是否一路到达目的地。