发送在我的 NDIS 修改过滤器驱动程序中无法正常工作

Send doesn't work properly in my NDIS modifying filter driver

我正在尝试使用 NDIS 实现数据包修改过滤器。 我使用了丢弃数据包并从克隆的 NBL 发起 send/receive 的方法。

msdn 上的文档说这是允许的: https://docs.microsoft.com/en-us/windows-hardware/drivers/ddi/ndis/nc-ndis-filter_send_net_buffer_lists

For each NET_BUFFER structure submitted to FilterSendNetBufferLists, a filter driver can do the following: ...

Copy the buffer and originate a send request with the copy. The send operation is similar to a filter-driver initiated send request. In this case, the driver must return the original buffer to the overlying driver by calling the NdisFSendNetBufferListsComplete function.

我使用以下算法成功实现了 RX 路径:

  1. Filter 获取 FilterReceiveNetBufferLists 中的 NBL
  2. FilterReceiveNetBufferLists 创建 NBL 克隆并入队以进行进一步处理
  3. FilterReceiveNetBufferLists 调用 NdisFReturnNetBufferLists
  4. 用户连接到暴露的设备,使数据包出列并再次注入。
  5. 设备调用 NdisFIndicateReceiveNetBufferLists RX 路径正常,网络正常。

我在 TX 路径中做了同样的事情:

  1. Filter 获取 FilterSendNetBufferLists 中的 NBL
  2. FilterSendNetBufferLists 创建 NBL 克隆并排队进行进一步处理
  3. FilterSendNetBufferLists 调用 NdisFSendNetBufferListsComplete。
  4. 用户连接到暴露的设备,使数据包出列并再次注入。
  5. 设备调用 NdisFSendNetBufferLists

TX 路径无效。 我正在通过发送 ICMP 数据包(只是 ping DNS 服务器 IP)来测试它。 我在路由器和我的测试机之间安装了 wireshark。 Wireshark 捕获由 TX 路径发出的传出 ICMP 数据包(第 5 步),但是没有响应数据包。

当我在 FilterSendNetBufferLists 中调用 NdisFSendNetBufferListsComplete 时到底发生了什么? TCP/IP 驱动程序是否收到数据包已无误传输的信息?

Off-the-cuff,我猜你没有在 TX 路径中调用 NdisCopySendNetBufferListInfo,这意味着校验和卸载元数据正在丢失。

如果 NIC 声称支持校验和卸载(即 NIC 硬件可以插入 IPv4、TCPv4、and/or TCPv6 校验和),则 TCPIP driver 不会尝试将IPv4/TCP header 中的有效校验和。 (实际上,它明确地将部分校验和放在那里,这在软件中很容易计算,在硬件中计算起来有点困难。)然后 TCPIP driver 将在 NBL 的信息字段中设置一些标志,指示硬件究竟如何将校验和插入数据包有效负载。

当您克隆 NBL 时,默认情况下,克隆不会继承任何元数据。因此,克隆的 NBL 在数据包有效负载中具有不完整的校验和,但缺少对 NIC 硬件插入校验和的指令。

修复很简单:NdisCopySendNetBufferListInfo 复制与 TX 路径相关的所有数据包元数据。 (对于 RX 路径有一个类似的 NdisCopyRecieveNetBufferListInfo`,您还应该考虑从 driver 调用它。)每当您克隆 NBL 时,您都应该调用这些例程之一,并且克隆最终将属于同一个数据包“流”作为原始 NBL。

为什么调用 NdisAllocateCloneNetBufferList 时 NDIS 不自动复制元数据?表面问题是 NDIS 不知道我们是在做 TX 还是 RX 路径。但更深层次的问题是 NDIS 不知道你计划破坏数据包的程度。例如,如果您的 driver 在 RX 数据包上重写了 TCP header,那么天真地复制 NIC 的 TCP 校验和计算和 RSS 散列可能是不合适的。

所以有效地调用 NdisCopySendNetBufferListInfo 意味着您声称您没有过多地破坏数据包,以至于它们看起来与任何硬件卸载不同。例如,您没有插入协议 headers,更改 TCP 端口号等(如果您 正在 做这些事情,那么您要么必须另外编写一些代码来平滑卸载,或完全禁用它们。)

顺便说一句,这是一个有趣而微妙的问题,每个人的直觉都会出错:

Does TCP/IP driver get an information that packet has been transmitted without any errors?

Ndis[F|M]SendNetBufferListsComplete not 表示数据包已无误传输。这只意味着一件事:数据包有效负载、MDL(s)、NB(s) 和 NBL 不再使用,协议 driver 可以重新调整它们的用途。

当传输到典型的 PCIe 硬件时,这意味着到 NIC 板载 RAM 的 DMA 已完成,并且 NIC 承诺不再接触数据包有效负载缓冲区。

这是一个简单的答案,但它立即引发了一个 follow-up 问题:如果 SendComplete 并不意味着数据包已成功传输,那么 协议如何判断包是否发送成功?

答案是协议不关心数据包是否被传输到下一跳。他们真正关心的是远程端点是否收到了数据包。找出答案的唯一方法是某种 ACK 系统。所以没有人真的费心去建立一个信号,表明 NIC 硬件实际上已经将 NBL 传输到下一跳,因为协议无论如何都不能对这些信息做太多事情。

(数据包时间戳 (IEEE15888/PTP/NTP) 是上述讨论的一个例外。但即使在那种情况下,我们 实际上 也不想知道数据包何时离开本地主机。我们实际上想知道数据包何时到达远程端点。但物理定律就是这样,后者是不可知的,所以我们必须满足于知道 TX 数据包何时离开本地主机。)

请注意,如果您确定数据包没有传输,那么您可以在 NET_BUFFER_LIST::Status 中写下一个错误代码,一些协议(例如 UDP + winsock)会将错误发送到应用程序。但在那种情况下,您只是在优化一条更快的错误路径——应用程序本质上仍然有义务构建一个 network-level 反馈机制(例如 ACK)以了解数据包是否一路到达目的地。