发送在我的 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 路径:
- Filter 获取 FilterReceiveNetBufferLists 中的 NBL
- FilterReceiveNetBufferLists 创建 NBL 克隆并入队以进行进一步处理
- FilterReceiveNetBufferLists 调用 NdisFReturnNetBufferLists
- 用户连接到暴露的设备,使数据包出列并再次注入。
- 设备调用 NdisFIndicateReceiveNetBufferLists
RX 路径正常,网络正常。
我在 TX 路径中做了同样的事情:
- Filter 获取 FilterSendNetBufferLists 中的 NBL
- FilterSendNetBufferLists 创建 NBL 克隆并排队进行进一步处理
- FilterSendNetBufferLists 调用 NdisFSendNetBufferListsComplete。
- 用户连接到暴露的设备,使数据包出列并再次注入。
- 设备调用 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)以了解数据包是否一路到达目的地。
我正在尝试使用 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 路径:
- Filter 获取 FilterReceiveNetBufferLists 中的 NBL
- FilterReceiveNetBufferLists 创建 NBL 克隆并入队以进行进一步处理
- FilterReceiveNetBufferLists 调用 NdisFReturnNetBufferLists
- 用户连接到暴露的设备,使数据包出列并再次注入。
- 设备调用 NdisFIndicateReceiveNetBufferLists RX 路径正常,网络正常。
我在 TX 路径中做了同样的事情:
- Filter 获取 FilterSendNetBufferLists 中的 NBL
- FilterSendNetBufferLists 创建 NBL 克隆并排队进行进一步处理
- FilterSendNetBufferLists 调用 NdisFSendNetBufferListsComplete。
- 用户连接到暴露的设备,使数据包出列并再次注入。
- 设备调用 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)以了解数据包是否一路到达目的地。