如何使用 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 的大小,则需要更新它们。