Tcpdump: Улучшение производительности захвата пакетов

Linux Logo Applications

Инструменты захвата пакетов регулярно испытывают проблемы с производительностью. Три задачи - чтение пакетов из сетевых интерфейсов, выполнение необходимой обработки и отправка результатов на диск/экран - могут показаться обычными, но их приходится выполнять с миллионами пакетов в секунду.

Измерение проблемы

Библиотека операционной системы, используемая для захвата пакетов (libpcap в Linux и Mac, winpcap/Npcap в Windows), использует общую область данных, в которую она записывает пакеты (и из которой программа захвата пакетов считывает их). Как только пакет получен программой захвата пакетов, пространство, использованное пакетом, освобождается для использования другим входящим пакетом. Если операционная система поставляет пакеты быстрее, чем программа захвата считывает их, это пространство заполняется. Когда это происходит и приходит новый пакет, самый старый пакет в этом пространстве отбрасывается, и библиотека запоминает это, увеличивая счетчик для каждого отброшенного пакета.

Когда инструмент захвата пакетов выключен, он может опросить libpcap для получения ряда статистических данных, таких как "общее количество полученных пакетов" и "сколько пакетов было отброшено". Если ваш инструмент захвата регулярно не успевает, потери могут составлять от 5% до 90+% от общего количества полученных пакетов. Если ваш инструмент сообщает об этом при выключении, посмотрите, составляет ли эта потеря менее 3% - вероятно, это приемлемо - или более 5% - время искать исправления производительности.

tcpdump сообщает эту статистику при выключении; вот вывод, когда я нажал ctrl-c в запущенном окне:

В этом прогоне на сетевой интерфейс поступило почти 93 миллиона пакетов. Из них около 8 миллионов были признаны интересными (остальные ~85 миллионов пакетов даже не были переданы tcpdump для обработки). Чуть более 10 тысяч были отброшены - ядро получило их и поместило туда, где их мог видеть tcpdump, но tcpdump не смог вовремя их получить, и поэтому они так и не были обработаны. В этом примере 0,12% интересных пакетов были потеряны из-за того, что tcpdump работал слишком медленно. Если бы у меня не было этого фильтра, tcpdump пришлось бы обрабатывать в 11 раз больше пакетов, и почти наверняка он не смог бы справиться с потоком.

Вход

Сначала мы рассмотрим данные, поступающие через сетевые интерфейсы. В этом обсуждении мы будем говорить только о захвате пакетов в реальном времени. Конечно, могут возникнуть проблемы с производительностью при чтении пакетов из pcap-файла (файл, содержащий пакеты, ранее захваченные с сетевого интерфейса), но когда это происходит, просто требуется больше времени для завершения анализа, в отличие от полной потери входящих данных.

Фильтрация

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

BPF

BPF, фильтр пакетов Беркли, - это способ описания пакетов, которые программа хочет видеть. Когда мы запускаем сниффер, мы предоставляем блок текста с этим описанием, а библиотека libpcap/winpcap/Npcap предоставляет снифферу только эти пакеты.

Например, программа dnstop хочет видеть только DNS-трафик, идущий на UDP-порт 53 или исходящий с него. По умолчанию она использует "udp port 53" в качестве фильтра. При первом запуске и настройке сниффинга она указывает ядру использовать этот фильтр. С этого момента ядро будет прослушивать входящие пакеты, но будет принимать только те пакеты, которые являются UDP и имеют порт 53 в качестве порта источника или назначения.

Это отбрасывает, вероятно, более 95% пакетов, появляющихся в сети, позволяя dnstop сосредоточиться только на тех, которые содержат запросы и ответы dns.

Построение фильтра "Включить"

Все эти фильтры представляют собой (в основном) читаемый текст, как, например, наш

udp port 53

Чтобы фильтровать по нескольким критериям, разделяйте их с помощью "и" или "или". Например, чтобы проверять только dns-трафик от основного dns-сервера Google, используйте

(host 8.8.8.8) and (port 53)

Обратите внимание, что я опустил "udp"; BPF позволяет использовать просто "port NN", чтобы включить TCP или UDP порт NN. Я также обвел отдельные проверки круглыми скобками; хотя в этом простом примере они не нужны, полезно выработать привычку их использовать. Некоторые из более сложных проверок очень трудно выполнить без них.

Построение фильтра "отбрасывания"

В любой сети есть хороший, вредоносный и неизвестный трафик. Когда вы работаете со сниффером, который занимается сетевой безопасностью, невозможно использовать описанный выше подход для принятия решения о том, что отслеживать; мы не можем построить BPF, который будет включать только вредоносный и неизвестный трафик.

Вместо этого мы перевернем задачу и отбросим часть доброкачественного трафика. Это позволяет инструменту безопасности сосредоточиться на вредоносном и неизвестном трафике.

В качестве примера, скажем, что в данном конкретном офисе идет интенсивная передача файлов по ssh на 5 машин в колокационном центре и постоянная репликация баз данных между двумя серверами postgresql. Чтобы отбросить этот трафик, мы будем использовать:

not( (tcp port 22 and net 17.18.19) or (tcp port 5432 and host 10.0.0.2 and host 100.222.33.44) )

Этот фильтр просит ядро посылать вверх весь трафик, кроме

  1. ssh трафика к/от хостов, чьи IP начинаются с 17.18.19
  2. postgresql трафика туда и обратно между одним определенным внутренним IP адресом и одним определенным внешним адресом. Круглые скобки позволяют нам группировать элементы вместе, не беспокоясь о том, имеет ли приоритет "и", "или" или "не". Знак "не" в начале меняет логику; без него мы бы видели только трафик ssh и postgresql. С ним мы видим все остальное.

Фильтруя таким образом, мы можем значительно уменьшить количество трафика, который нужно обрабатывать, позволяя программе сниффера не отставать от всего остального трафика.

Следует отметить, что вам не обязательно характеризовать весь ваш доброкачественный трафик. Простое обеспечение фильтра, соответствующего некоторым из самых тяжелых видов трафика, может снизить нагрузку на сниффер настолько, что он сможет не отставать от остального трафика.

Чтобы включить такой фильтр, поместите его в конец командной строки tcpdump в одинарных кавычках, например:

Захват меньшей части пакета

Большинство снифферов по умолчанию захватывают весь пакет - все заголовки и все содержимое пакета. В некоторых случаях это не обязательно - можно захватить только первую часть пакета, а остальное отбросить. Это уменьшает объем необходимой памяти, объем необходимой обработки и объем дискового пространства, необходимого для сохранения пакетов на диске.

Для этого нам необходимы IP-адреса источника и получателя, протокол (TCP, UDP, другой) и общий размер пакета - все эти данные содержатся в IP-заголовке. Нам также нужны номера портов, используемых в пакетах TCP и UDP, которые находятся в заголовках TCP или UDP соответственно. Чтобы сохранить эти заголовки и отбросить большую часть полезной нагрузки пакета, мы должны захватить 96 байт в начале пакета (96 байт должно хватить на большинство случаев, и хотя они обычно включают часть полезной нагрузки, это все равно меньше, чем 1/15 часть пакета размером 1500 байт).

Общепринятое название для определения "сколько байт захватывать" - "snaplen".

В дополнение ко всем преимуществам производительности, уменьшение snaplen помогает сохранить конфиденциальность. Вы получаете все необходимое для общего анализа, не захватывая и не сохраняя полное содержимое этих разговоров (хотя иногда вы будете получать несколько байт пользовательской полезной нагрузки). Это может помочь, если у вас есть политики, касающиеся конфиденциальности коммуникаций в вашей сети.

Чтобы указать tcpdump захватывать только первые N байт пакета при обнюхивании, используйте параметр "-s":

Когда вы позже вернетесь к просмотру или обработке пакетов в этом файле, вы увидите, что заголовки IP, TCP и UDP присутствуют вместе с частью полезной нагрузки, но большая часть полезной нагрузки была отброшена.

Распространение потоков пакетов на несколько блоков захвата

Если вы настроили процесс захвата как можно лучше, но все равно не можете справиться с нагрузкой, может потребоваться

  1. разделить поток данных на N частей
  2. клонировать вашу систему захвата пакетов N раз, по одному для каждой части. Каждый клон получает 1/N-ю часть трафика, снижая нагрузку до управляемого уровня.

Инструментом для разделения данных на несколько потоков является аппаратный или программный балансировщик нагрузки. Это специализированные маршрутизаторы/приложения, которые не только разделяют нагрузку, но и делают это таким образом, что все пакеты, являющиеся частью определенного разговора, остаются вместе. К примеру Haproxy.

Avatar for Gnostis
Gnostis
Добавить комментарий