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

时间:2019-04-09 21:03:03

标签: c windows driver packet-capture ndis

我从GitHub( Microsoft / Windows-driver-samples )了解了WinPcap如何过滤数据包here并检查了ndis/filter project的知识。我在下面的 WinPcap 页面中提供了主要数据,因为它们与问题有关。

enter image description here


enter image description here

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

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

与接收数据包(通过ReceiveNetBufferListsHandler and ReturnNetBufferListsHandler函数)相同。

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

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

我没有找到通过 SendNetBufferListsHandler / SendNetBufferListsCompleteHandler ReceiveNetBufferListsHandler / ReturnNetBufferListsHandler 丢弃数据包的代码示例功能。

1 个答案:

答案 0 :(得分:2)

  

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

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

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

  2. NPF(又名winpcap / wireshark)特别设计为防止阻止/丢弃流量。即使您愿意对NPF进行一些修改,也无法做到。原因是NPF被实现为协议驱动程序。作为协议驱动程序,它是TCPIP的一个 peer ,并且不能直接干扰TCPIP的功能。 (这是NPF甚至可以看到TCPIP传输的一个小奇迹-这是通过第2层回送的魔力实现的。[与第3层回送无关,例如:: 1和127.0.0.1等。)

nmap项目有一个NPF分支,可将其实现为NDIS筛选器驱动程序。 那种驱动程序能够阻止,延迟,重写或注入流量。因此,如果您对更改上面的#1哲学感兴趣,则应该从nmap的分支开始,而不是“官方的” winpcap。

(而且,总的来说,即使您不需要丢弃流量,我个人也会推荐nmap的fork。筛选器驱动程序比协议驱动程序要快得多,后者使网络适配器进入第2层环回模式。 )

一旦您查看了nmap-npf,就可以从NDIS示例过滤器驱动程序中找到回调,例如FilterReceiveNetBufferLists。

丢弃数据包实际上很容易;)不过,有些陷阱,让我们来看一些示例。

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

如果此假设为 not true,即,如果您确实要选择性地丢弃TCP套接字内的某些数据包,而不是所有数据包,那么您需要做一些更复杂的事情。您不能直接从NET_BUFFER_LIST中删除单个N​​ET_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);
}

在接收路径上,可以确保每个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);
    }
}

请注意,我使用了一个名为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;
}

最后,如果您从未来几年开始阅读此书,建议您从Microsoft寻找一个名为nblutil.h的示例头文件。 (我们尚未发布它,但是我正在研究它。)它有一个非常不错的例程ndisClassifyNblChain,可以为您完成几乎所有的工作。它具有较高的可扩展性,并使用了一些技巧来提高性能,而您所发现的结果已经超出了已经太长的StackOverflow答案。