Как работает захват пакетов (NPF; WinPcap)?

Я читал о том, как WinPcap фильтрует пакеты здесь и изучил проект ndis/filter с GitHub (образцы драйверов для Microsoft/Windows). Я привожу основные цифры со страницы WinPcap ниже, потому что они связаны с вопросом.

введите здесь описание изображения


введите здесь описание изображения

Мой главный вопрос: если NPF отбрасывает пакет (пакеты), это означает, что пакеты не будут перехвачены или что пакеты не будут отправлены/получены? Например (как я вижу):

  1. dumpcap начинает прослушивать пакеты на eth0.
  2. Chrome отправляет DNS-запрос.
  3. Драйвер NDIS обрабатывает этот пакет. (точнее - NetBufferList; через SendNetBufferListsHandler и функции SendNetBufferListsCompleteHandler)
  4. Анализ NBL: анализ каждого буфера и проверка характеристик пакета;
  5. Если мы хотим отбросить пакет, нам нужно собрать новый NBL (без нежелательных пакетов) и вызвать SendNetBufferListsCompleteHandler с новым NBList;
  6. Если мы хотим вырезать этот пакет из захвата, нам нужно собрать новый NBL (без нежелательных пакетов) и вызвать NdisFIndicateReceiveNetBufferLists с новым NBList;

Тот же случай с получением пакетов (через функции ReceiveNetBufferListsHandler и ReturnNetBufferListsHandler).

Правильно ли я понимаю, что есть возможность отбросить пакет по NPF, отправить/получить его в/из сети и удалить его из "списка захвата пакетов"?

Если да, то как правильно реализовать отбрасывание пакетов?

Я не нашел примеров кода для отбрасывания пакетов с помощью функций SendNetBufferListsHandler/SendNetBufferListsCompleteHandler и ReceiveNetBufferListsHandler/ReturnNetBufferListsHandler.


person slinkin    schedule 09.04.2019    source источник


Ответы (1)


если NPF отбрасывает пакет (пакеты) это значит, что пакеты не будут перехвачены или что пакеты не будут отправлены/получены?

Пакет не будет перехвачен, но он все равно будет доставлен в остальную часть сетевого стека. Это, я полагаю, по двум причинам:

  1. Инструменты захвата пакетов, как правило, используются для диагностики, поэтому их философия не делает хуже. Все инструменты захвата пакетов, которые я знаю, предпочтут позволить пакетам продолжать проходить мимо них, даже если они не в состоянии идти в ногу.

  2. В частности, NPF (также известный как winpcap/wireshark) разработан таким образом, что предотвращает блокировку/отбрасывание трафика. Даже если вы захотите внести некоторые изменения в NPF, вы не сможете этого сделать. Причина в том, что NPF реализован как драйвер протокола. Как драйвер протокола, он является аналогом TCPIP и не может напрямую вмешиваться в то, что делает TCPIP. (Это маленькое чудо, что NPF даже может видеть, что передает TCPIP - это делается с помощью магии loopback уровня 2. [Не связано с loopback уровня 3, например :: 1 и 127.0.0.1 и т. д.])

В проекте nmap есть ответвление NPF, которое реализует его как драйвер фильтра NDIS. Такой драйвер способен блокировать, задерживать, перезаписывать или вводить трафик. Поэтому, если вы заинтересованы в изменении философии № 1 выше, вам следует начать с форка nmap, а не официального winpcap.

(И вообще, лично я бы порекомендовал форк nmap, даже если вам не нужно отбрасывать трафик. Драйверы-фильтры будут намного быстрее, чем драйвер протокола, переводящий сетевой адаптер в режим loopback уровня 2.)

После того, как вы просмотрите nmap-npf, вы сможете найти обратные вызовы из примера драйвера фильтра NDIS, например FilterReceiveNetBufferLists.

Отбрасывать пакеты на самом деле довольно просто ;) Однако есть некоторые подводные камни, так что давайте рассмотрим несколько примеров.

На пути передачи у нас есть связанный список NBL, и мы хотим разделить его на два списка, один для удаления и один для продолжения отправки. Один NBL может содержать несколько пакетов, но каждый пакет гарантированно относится к одному и тому же потоку (например, сокету TCP). Поэтому обычно вы можете сделать упрощающее предположение, что каждый пакет в NBL всегда обрабатывается одинаково: если вы хотите отбросить один, вы хотите отбросить их все.

Если это предположение не верно, т. е. если вы хотите выборочно отбрасывать некоторые пакеты из сокета TCP, но не все пакеты, то вам нужно сделать что-то более сложное. Вы не можете напрямую удалить один NET_BUFFER из NET_BUFFER_LIST; вместо этого вы должны клонировать 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 на NET_BUFFER_LIST. (Сетевая карта не может полностью знать, какие пакеты являются частью одного и того же потока, поэтому группировка еще не выполнена.) Итак, эта маленькая хитрость исчезла, но есть новая: вы должны проверить Флаг NDIS_RECEIVE_FLAGS_RESOURCES. Отсутствие проверки этого флага является причиной № 1 потери времени на поиск ошибок в драйверах фильтров, поэтому я должен придать этому большое значение.

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.

Обновление из будущего: https://github.com/microsoft/ndis-driver-library имеет NdisClassifyNblChain2

person Jeffrey Tippet    schedule 13.04.2019
comment
Джеффри, спасибо за ответ. Я ждал вас, так как я упомянул, что вы являетесь экспертом в драйверах NDIS). ">ответ о стеке сетевых драйверов. Теперь для меня более понятно, где находится драйвер НПФ. Я переосмыслил свой главный вопрос, и теперь он звучит так: Можно ли фильтровать пакеты среди драйверов протоколов через драйвер фильтра NDIS. Например. если я хочу передать пакет драйверу протокола TCP/IP, но не хочу передавать этот пакет NPF. - person slinkin; 14.04.2019
comment
Очень интересная и полезная информация, однако ваш код немного (сильно) отличается от официальных примеров. Например, функция FilterReceiveNetBufferLists имеет больше параметров и отличается. Я заинтересован в том, чтобы отбрасывать пакеты наилучшим образом и со смешанной информацией... это будет ужасно для новичка вроде меня в NDIS. Не могли бы вы опубликовать некоторую информацию, используя последний фильтр NDIS? Было бы очень признательно! - person Norbert Boros; 06.11.2020
comment
Чтобы быть более конкретным, этот фильтр NDIS: github.com /Microsoft/Windows-driver-samples/tree/master/network/ . И ради примера я хотел бы посмотреть, как можно, например, отбрасывать целые (или частичные) пакеты на порт 5000 TCP. Опять же, очень признателен! - person Norbert Boros; 06.11.2020
comment
› например, функция FilterReceiveNetBufferLists имеет больше параметров. Мой код — псевдокод; Я упустил детали, которые не важны для темы. - person Jeffrey Tippet; 08.11.2020
comment
› И ради примера, я хотел бы посмотреть, как можно отбрасывать целые (или частичные) пакеты, например, на порт 5000 TCP. Это больше, чем вписывается в ответ stackoverflow. Однако обратите внимание, что если ваш реальный проект использует TCP или UDP, вы можете обнаружить, что вызов WFP (или даже просто правило брандмауэра) лучше подходит, чем LWF NDIS. NDIS — это уровень 2, WFP — это уровень 3 и уровень 4. - person Jeffrey Tippet; 08.11.2020