捕获数据包(NPF;WinPcap)如何工作?

sli*_*kin 3 c windows ndis driver packet-capture

在这里阅读了有关WinPcap如何过滤数据包的信息,并检查了GitHub ( Microsoft/Windows-driver-samples ) 中的ndis/filter 项目我在下面提供了WinPcap页面的主要数据,因为它们与问题相关。

在此输入图像描述


在此输入图像描述

我的主要问题:如果NPF丢弃一个数据包(数据包),这意味着数据包将不会被捕获或数据包将不会被发送/接收?例如(据我所知):

  1. dumpcap开始侦听eth0上的数据包。
  2. Chrome发送 DNS 请求。
  3. NDIS 驱动程序处理此数据包。(确切地说 - NetBufferList;通过SendNetBufferListsHandler 和 SendNetBufferListsCompleteHandler函数)
  4. 解析NBL:解析各个缓冲区并检查数据包特征;
  5. 如果我们想丢弃一个数据包,我们需要组装新的NBL(不含不需要的数据包)并使用新的NBList调用SendNetBufferListsCompleteHandler ;
  6. 如果我们想从捕获中删除此数据包,我们需要组装新的 NBL(不含不需要的数据包)并使用新的 NBList 调用NdisFIndicateReceiveNetBufferLists ;

接收数据包的情况相同(通过ReceiveNetBufferListsHandler 和 ReturnNetBufferListsHandler函数)。

我是否正确理解有机会通过 NPF 丢弃数据包,将发送到网络/从网络接收数据包,并将其从“捕获数据包列表”中删除?

如果是,如何实现正确丢包?

我没有找到通过SendNetBufferListsHandler / SendNetBufferListsCompleteHandlerReceiveNetBufferListsHandler / ReturnNetBufferListsHandler函数丢弃数据包的代码示例。

Jef*_*pet 7

如果 NPF 丢弃一个数据包(数据包),这意味着数据包将不会被捕获或数据包将不会被发送/接收?

数据包不会被捕获,但数据包仍将被传递到网络堆栈的其余部分。我想,这有两个原因:

  1. 数据包捕获工具往往用于诊断,因此它们往往有“不要让事情变得更糟”的理念。我所知道的所有数据包捕获工具都会选择让数据包继续流过它们,即使它们无法跟上。

  2. NPF(又名 winpcap/wireshark)的构建方式尤其可以防止其阻止/丢弃流量。即使你愿意对NPF做一些修改,你也做不到。原因是 NPF 是作为协议驱动程序实现的。作为协议驱动程序,它是TCPIP 的对等体,不能直接干扰 TCPIP 的工作。(NPF 甚至可以看到 TCPIP 传输的内容,这是一个小奇迹 - 这是通过 2 层环回的魔力完成的。[与 3 层环回无关,如 ::1 和 127.0.0.1 等])

nmap 项目有一个 NPF 的分支,将其实现为 NDIS 过滤器驱动程序。 这种驱动程序能够阻止、延迟、重写或注入流量。因此,如果您有兴趣改变上面的理念 #1,您应该从 nmap 的 fork 开始,而不是“官方”winpcap。

(而且,一般来说,我个人会推荐 nmap 的 fork,即使您不需要丢弃流量。过滤器驱动程序比将网络适配器置于第 2 层环回模式的协议驱动程序要快得多。)

查看 nmap-npf 后,您将能够找到来自 NDIS 示例过滤器驱动程序的回调,例如 FilterReceiveNetBufferLists。

丢弃数据包实际上非常容易;)不过,有一些问题,所以让我们看一些示例。

在传输路径上,我们有一个 NBL 链表,我们希望将其分成两个列表,一个用于丢弃,一个用于继续发送。单个NBL可以包含多个数据包,但保证每个数据包属于相同的“流”(例如,TCP套接字)。因此,通常您可以做出简化的假设,即 NBL 中的每个数据包总是以相同的方式处理:如果您想丢弃一个,则希望将它们全部丢弃。

如果这个假设不成立成立,即,如果您确实想有选择地丢弃 TCP 套接字内的某些数据包,但不是所有数据包,那么您需要做一些更复杂的事情。您不能直接从 NET_BUFFER_LIST 中删除单个 NET_BUFFER;相反,您必须克隆 NET_BUFFER_LIST 并复制要保留的 NET_BUFFER。

由于这是一个免费论坛,我只会给您举一个简单和常见情况的例子;)

void
FilterSendNetBufferLists(NET_BUFFER_LIST *nblChain, ULONG sendFlags)
{
    NET_BUFFER_LIST *drop = NULL;
    NET_BUFFER_LIST *keep = NULL;

    NET_BUFFER_LIST *next = NULL;
    NET_BUFFER_LIST *nbl = NULL;

    for (nbl = nblChain; nbl != NULL; nbl = next) {
        next = nbl->Next;

        // If the first NB in the NBL is drop-worthy, then all NBs are
        if (MyShouldDropPacket(nbl->FirstNetBuffer)) {
            nbl->Next = drop;
            drop = nbl;
            nbl->Status = NDIS_STATUS_FAILURE; // tell the protocol
        } else {
            nbl->Next = keep;
            keep = nbl;
        }
    }

    // Above would reverse the order of packets; let's undo that here.
    keep = ReverseNblChain(keep);

    . . . do something with the NBLs you want to keep. . .;

    // Send the keepers down the stack to be transmitted by the NIC.
    NdisFSendNetBufferLists(context, keep, portNumber, sendFlags);

    // Return the dropped packets back up to whoever tried to send them.
    NdisFSendCompleteNetBufferLists(context, drop, 0);
}
Run Code Online (Sandbox Code Playgroud)

在接收路径上,可以保证每个 NET_BUFFER_LIST 中只有一个 NET_BUFFER。(NIC 无法完全知道哪些数据包是同一流的一部分,因此尚未完成分组。)因此,这个小问题消失了,但有一个新的问题:您必须检查NDIS_RECEIVE_FLAGS_RESOURCES标志。不检查这个标志是导致过滤器驱动程序中的错误浪费时间的第一大原因,所以我必须对此大加重视。

void
FilterReceiveNetBufferLists(NET_BUFFER_LIST *nblChain, ULONG count, ULONG receiveFlags)
{
    NET_BUFFER_LIST *drop = NULL;
    NET_BUFFER_LIST *keep = NULL;

    NET_BUFFER_LIST *next = NULL;
    NET_BUFFER_LIST *nbl = NULL;

    for (nbl = nblChain; nbl != NULL; nbl = next) {
        next = nbl->Next;

        // There's only one packet in the NBL
        if (MyShouldDropPacket(nbl->FirstNetBuffer)) {
            nbl->Next = drop;
            drop = nbl;
            count -= 1; // decrement the NumberOfNetBufferLists
        } else {
            nbl->Next = keep;
            keep = nbl;
        }
    }

    keep = ReverseNblChain(keep);

    . . . do something with the NBLs you want to keep. . .;

    // Pass the keepers up the stack to be processed by protocols.
    NdisFIndicateReceiveNetBufferLists(context, keep, portNumber, count, receiveFlags);

    // Checking this flag is critical; never ever call
    // NdisFReturnNetBufferLists if the flag is set.
    if (0 == (NDIS_RECEIVE_FLAGS_RESOURCES & receiveFlags)) {
        NdisFReturnNetBufferLists(context, keep, 0);
    }
}
Run Code Online (Sandbox Code Playgroud)

请注意,我使用了一个名为 的辅助函数ReverseNblChain。颠倒数据包的顺序在技术上是合法的,但它会降低性能。TCPIP 只有当数据包通常按顺序到达时才能达到最佳性能。示例代码中的链表操作循环具有反转 NBL 列表的副作用,因此我们使用 来消除损坏ReverseNblChain。我们不需要反转丢弃链,因为没有人尝试重新组装丢弃的数据包;您可以按任何顺序保留它们。

NET_BUFFER_LIST * ReverseNblChain(NET_BUFFER_LIST *nblChain)
{
    NET_BUFFER_LIST *head = NULL;
    NET_BUFFER_LIST *next = NULL;
    NET_BUFFER_LIST *nbl = NULL;

    for (nbl = nblChain; nbl != NULL; nbl = next) {
        next = nbl->Next;
        nbl->Next = head;
        head = nbl;
    }

    return head;
}
Run Code Online (Sandbox Code Playgroud)

最后,如果您是在几年后阅读本文,我建议您查找来自 Microsoft 的名为nblutil.h. (我们还没有发布它,但我正在努力。)它有一个非常好的例程,名为 ,ndisClassifyNblChain它可以为您完成几乎所有的工作。它是为高可扩展性而设计的,并使用了一些技巧来获得比你在已经很长的 StackOverflow 答案中塞满的东西更好的性能。

未来更新: https: //github.com/microsoft/ndis-driver-libraryNdisClassifyNblChain2