如何使用 NDIS 过滤器驱动程序读取接收到的数据包?
How can I read the received packets with a NDIS filter driver?
我目前正在试验 NDIS driver samples。
我正在尝试打印数据包内容(包括 MAC-地址、EtherType 和数据)。
我的第一个猜测是在函数 FilterReceiveNetBufferLists
中实现它。不幸的是,我不确定如何从 NetBufferLists
.
中提取数据包内容
这是正确的起点。考虑这段代码:
void FilterReceiveNetBufferLists(..., NET_BUFFER_LIST *nblChain, ...)
{
UCHAR buffer[14];
UCHAR *header;
for (NET_BUFFER_LIST *nbl = nblChain; nbl; nbl = nbl->Next) {
header = NdisGetDataBuffer(nbl->FirstNetBuffer, sizeof(buffer), buffer, 1, 1);
if (!header)
continue;
DbgPrint("MAC address: %02x-%02x-%02x-%02x-%02x-%02x\n",
header[0], header[1], header[2],
header[3], header[4], header[5]);
}
NdisFIndicateReceiveNetBufferLists(..., nblChain, ...);
}
这段代码有几点需要考虑。
NDIS 数据路径使用 NET_BUFFER_LIST (nbl) 作为其主要数据结构。 nbl 表示一组具有相同元数据的数据包。对于接收路径,没有人真正了解元数据,因此该集合中始终只有 1 个数据包。换句话说,nbl是一个长度为1的列表。对于接收路径,你可以指望它。
nbl 是一个或多个 NET_BUFFER (nb) 结构的列表。一个 nb 代表一个单一的网络框架(受制于 LSO 或 RSC)。所以 nb 最接近于你认为的数据包。它的元数据存储在包含它的 nbl 上。
在一个 nb 中,实际的数据包有效负载存储为一个或多个缓冲区,每个缓冲区表示为一个 MDL。在心理上,您应该假装 MDL 只是串联在一起。例如,网络 headers 可能在一个 MDL 中,而其余的有效负载可能在另一个 MDL 中。
最后,为了提高性能,NDIS 会为您的 LWF 提供尽可能多的 NBL。这意味着有一个或多个 NBL 的列表。
把它们放在一起,你有:
- 您的函数收到 NBL 列表。
- 每个 NBL 恰好包含 1 个 NB(在接收路径上)。
- 每个 NB 包含一个 MDL 列表。
- 每个 MDL 指向一个有效负载缓冲区。
所以在我们上面的示例代码中,for-loop 沿着第一个要点迭代:NBL 链。在循环中,我们只需要查看 nbl->FirstNetBuffer
,因为我们可以安全地假设除了第一个之外没有其他 nb。
必须直接 fiddle 使用所有这些 MDL 很不方便,因此我们使用辅助例程 NdisGetDataBuffer
。你告诉这个人你想看到多少字节的有效载荷,他会给你一个指向连续有效载荷范围的指针。
- 在好的情况下,您的缓冲区包含在单个 MDL 中,因此 NdisGetDataBuffer 只是给您一个指向该 MDL 缓冲区的指针。
- 在缓慢的情况下,您的缓冲区跨越多个 MDL,因此 NdisGetDataBuffer 小心地将有效负载的相关位复制到您提供的暂存缓冲区中。
如果您要检查的字节数超过几个字节,后一种情况可能会很棘手。如果你正在读取所有 1500 字节的数据包,你不能只在堆栈上分配 1500 字节(内核堆栈 space 是稀缺的,不像用户模式),所以你必须从池中分配它。一旦你弄明白了这一点,请注意,将所有 1500 字节的数据复制到每个数据包的暂存缓冲区中会减慢速度。减速太多了吗?这取决于您的需要。如果您只是偶尔检查数据包,或者如果您将 LWF 部署在 low-throughput NIC 上,则没有关系。如果你想超过 1Gbps,你不应该存储这么多数据。
另请注意,如果您最终想要修改 数据包,则需要警惕 NdisGetDataBuffer。它可以为您提供数据的 副本 (存储在您的本地暂存缓冲区中),因此如果您修改有效负载,这些更改实际上不会保留在数据包中。
如果您确实 需要扩展到高吞吐量或修改负载怎么办?然后你需要弄清楚如何操作 MDL 链。一开始这有点令人困惑,但请花一点时间阅读文档并自己画一些白板图。
我建议首先了解 MDL。从网络的角度来看,一个 MDL 只是一种保存 { char * buffer, size_t length } 以及 link 到下一个 MDL 的奇特方式。
接下来考虑NB的DataOffset和DataLength。这些在概念上将缓冲区边界从缓冲区的开头和结尾移入。他们并不真正关心 MDL 边界——例如,您可以通过递减 DataLength 来减少数据包有效负载的长度,如果这意味着一个或多个 MDL 不再为数据包提供任何缓冲区 space有效载荷,没什么大不了的,它们只是被忽略了。
最后,添加 CurrentMdl 和 CurrentMdlOffset。这些与上述所有内容都是多余的,但它们存在于(微基准)性能。如果您正在阅读 NB,您甚至不需要考虑它们,但如果您正在编辑 NB 的大小,则需要更新它们。
我目前正在试验 NDIS driver samples。 我正在尝试打印数据包内容(包括 MAC-地址、EtherType 和数据)。
我的第一个猜测是在函数 FilterReceiveNetBufferLists
中实现它。不幸的是,我不确定如何从 NetBufferLists
.
这是正确的起点。考虑这段代码:
void FilterReceiveNetBufferLists(..., NET_BUFFER_LIST *nblChain, ...)
{
UCHAR buffer[14];
UCHAR *header;
for (NET_BUFFER_LIST *nbl = nblChain; nbl; nbl = nbl->Next) {
header = NdisGetDataBuffer(nbl->FirstNetBuffer, sizeof(buffer), buffer, 1, 1);
if (!header)
continue;
DbgPrint("MAC address: %02x-%02x-%02x-%02x-%02x-%02x\n",
header[0], header[1], header[2],
header[3], header[4], header[5]);
}
NdisFIndicateReceiveNetBufferLists(..., nblChain, ...);
}
这段代码有几点需要考虑。
NDIS 数据路径使用 NET_BUFFER_LIST (nbl) 作为其主要数据结构。 nbl 表示一组具有相同元数据的数据包。对于接收路径,没有人真正了解元数据,因此该集合中始终只有 1 个数据包。换句话说,nbl是一个长度为1的列表。对于接收路径,你可以指望它。
nbl 是一个或多个 NET_BUFFER (nb) 结构的列表。一个 nb 代表一个单一的网络框架(受制于 LSO 或 RSC)。所以 nb 最接近于你认为的数据包。它的元数据存储在包含它的 nbl 上。
在一个 nb 中,实际的数据包有效负载存储为一个或多个缓冲区,每个缓冲区表示为一个 MDL。在心理上,您应该假装 MDL 只是串联在一起。例如,网络 headers 可能在一个 MDL 中,而其余的有效负载可能在另一个 MDL 中。
最后,为了提高性能,NDIS 会为您的 LWF 提供尽可能多的 NBL。这意味着有一个或多个 NBL 的列表。
把它们放在一起,你有:
- 您的函数收到 NBL 列表。
- 每个 NBL 恰好包含 1 个 NB(在接收路径上)。
- 每个 NB 包含一个 MDL 列表。
- 每个 MDL 指向一个有效负载缓冲区。
所以在我们上面的示例代码中,for-loop 沿着第一个要点迭代:NBL 链。在循环中,我们只需要查看 nbl->FirstNetBuffer
,因为我们可以安全地假设除了第一个之外没有其他 nb。
必须直接 fiddle 使用所有这些 MDL 很不方便,因此我们使用辅助例程 NdisGetDataBuffer
。你告诉这个人你想看到多少字节的有效载荷,他会给你一个指向连续有效载荷范围的指针。
- 在好的情况下,您的缓冲区包含在单个 MDL 中,因此 NdisGetDataBuffer 只是给您一个指向该 MDL 缓冲区的指针。
- 在缓慢的情况下,您的缓冲区跨越多个 MDL,因此 NdisGetDataBuffer 小心地将有效负载的相关位复制到您提供的暂存缓冲区中。
如果您要检查的字节数超过几个字节,后一种情况可能会很棘手。如果你正在读取所有 1500 字节的数据包,你不能只在堆栈上分配 1500 字节(内核堆栈 space 是稀缺的,不像用户模式),所以你必须从池中分配它。一旦你弄明白了这一点,请注意,将所有 1500 字节的数据复制到每个数据包的暂存缓冲区中会减慢速度。减速太多了吗?这取决于您的需要。如果您只是偶尔检查数据包,或者如果您将 LWF 部署在 low-throughput NIC 上,则没有关系。如果你想超过 1Gbps,你不应该存储这么多数据。
另请注意,如果您最终想要修改 数据包,则需要警惕 NdisGetDataBuffer。它可以为您提供数据的 副本 (存储在您的本地暂存缓冲区中),因此如果您修改有效负载,这些更改实际上不会保留在数据包中。
如果您确实 需要扩展到高吞吐量或修改负载怎么办?然后你需要弄清楚如何操作 MDL 链。一开始这有点令人困惑,但请花一点时间阅读文档并自己画一些白板图。
我建议首先了解 MDL。从网络的角度来看,一个 MDL 只是一种保存 { char * buffer, size_t length } 以及 link 到下一个 MDL 的奇特方式。
接下来考虑NB的DataOffset和DataLength。这些在概念上将缓冲区边界从缓冲区的开头和结尾移入。他们并不真正关心 MDL 边界——例如,您可以通过递减 DataLength 来减少数据包有效负载的长度,如果这意味着一个或多个 MDL 不再为数据包提供任何缓冲区 space有效载荷,没什么大不了的,它们只是被忽略了。
最后,添加 CurrentMdl 和 CurrentMdlOffset。这些与上述所有内容都是多余的,但它们存在于(微基准)性能。如果您正在阅读 NB,您甚至不需要考虑它们,但如果您正在编辑 NB 的大小,则需要更新它们。