Не так давно мы выпустили новую версию open source xFlow-коллектора и анализатора xenoeye. Это неплохой повод попиариться. тем более что xFlow-коллекторами/анализаторами часто пользуются для анализа, мониторинга и борьбы с DoS/DDoS атаками, это сейчас очень актуально.

Если совсем коротко - анализатор собирает xFlow (Netflow и некоторые родственные протоколы типа Jflow, IPFIX, sFlow), распределяет их по объектам мониторинга, экспортирует информацию в СУБД (в текущей версии PostgreSQL), и может быстро реагировать на всплески трафика выше порогов для детекции DoS/DDoS атак с помощью скользящих средних.

Информацию из СУБД можно визуализировать разными способами - генерировать статические картинки и отчеты или показывать красивое в Grafana.
Реагировать на всплески можно тоже по-разному - отправлять сообщения в мессенджер, писать данные об аномалиях в БД, анонсировать BGP Flowspec для подавления атак.

Конечно, существует много как коммерческих, так и open source анализаторов, но они не всегда устраивают пользователей, причем по разным причинам. И альтернативный софт, который решает некоторые задачи по-другому - это всегда хорошо.

В статье буду стараться почти не дублировать документацию, которая есть на гитхабе. Документация немого занудная, здесь попытаюсь посравниваться с другими анализаторами, написать более просто как что устроено, и почему мы сделали именно так а не иначе.

Производительность, масштабирование на несколько ядер и устойчивость

Как ни странно, при общении с сетевыми инженерами почти всегда первый вопрос, который они задают - про производительность. Вопрос вроде бы простой, а ответить на него не очень просто.

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

Даже в средних по размеру сетях используют семплирование xFlow - учитываются не все пакеты, а только каждый N-й, проходящий через оборудование.
На самом деле учитывают, конечно, не строго каждый N-й, а случайный пакет из N, но для простоты не будем вдаваться в детали.
Вот это число N, которое называют sampling rate, в крупных сетях может достигать тысяч и даже десятков тысяч.
То есть роутер или коммутатор посылает коллектору информацию только о каждом, допустим, 10000-м пакете.

Семплирование снижает нагрузку на оборудование и на софт коллекторов/анализаторов, но при этом теряется много информации о трафике. Мы можем видеть только очень грубо что происходит в сети.
Чем больше sampling rate, тем проще работать софту, и тем меньше деталей трафика можно увидеть. Некоторые производители анализирующего софта прямо в рекомендациях пишут - если софт захлебывается, то поднимайте sampling rate на роутерах.

Чтобы хоть как-то понимать, с какой производительностью работают анализаторы, используют метрику FPS - flows per second.
Если вендор указывает FPS рядом с терабитами - это уже очень неплохо!

Однако, даже с FPS не все просто. Протоколы Netflow разных версий, IPFIX и sFlow - не одинаковые.
Netflow v5 и sFlow - протоколы с фиксированными полями. Можно взять произвольный пакет и спокойно его распарсить.
Netflow v9 и IPFIX - template-based протоколы. Роутер время от времени посылает template/option template пакеты. Обычно роутеры настраивают так, чтобы они посылали эти пакеты раз в единицы-десятки минут. Анализатор должен сопоставить data-пакет с соответствующим template-пакетом, и только после этого он может распарсить и получить данные.
Производительность разбора пакетов с фиксированными полями и пакетов с шаблонами, конечно, различается. И вендоры обычно не указывают, на каких именно протоколах они получили свои рекламные FPS.

Второй момент - масштабирование на несколько ядер.
Некоторые анализаторы этого вообще не делают, некоторые делают достаточно своеобразно, некоторые очень заморачиваются и делают балансировщики внутри анализатора.

Третий момент - это собственно полезная работа, которую производит анализатор.
Кроме парсинга xFlow пакетов, нужно сделать с этими данными что-то дальше.
Средние и крупные сети, как правило, это набор более мелких пользователей, которые владеют небольшими подсетями.
Обычно эти пользователи задаются так называемыми "объектами мониторинга" (МО). Где-то МО пользователей задаются подсетями, где-то номерами автономных систем. Этих МО может быть достаточно много - сотни и тысячи на крупную сеть.
И, конечно, количество МО влияет на производительность. Одно дело, когда мы анализируем трафик одного пользователя, пусть и с большим набором подсетей, и совсем другое - когда анализируем трафик отдельных сотен пользователей.

Перейдем к цифрам

На какие значения FPS можно вообще ориентироваться, пусть даже приблизительно?

  • Один из самых популярных open source продуктов (и самый популярный из всех goflow*-based): Akvorado is performant enough to handle 100 000 flows per second with 64 GB of RAM and 24 vCPU.

  • FastNetMon: Scalability tested up to 5 Tbits and 3 millions of flow/s for a single server.

  • Flowmon: 80CPU; 256 GB RAM; Peak 400,000; Moderate user experience 200,000; Best user experience 100,000 (FPS)

  • Servicepipe (Сервиспайп): FlowCollector обрабатывает NetFlow до 250 000 fps на 1 платформу.

  • Гарда Anti-DDoS: возможности масштабирования: 1 млн. флоу/с на анализ и 10 Тбит/с на фильтрацию трафика

(все данные взяты с сайтов вендоров)

Мне кажется, разработчики (или, скорее, менеджеры) дают цифры не в очень удобном формате: непонятно, как считать производительность, если мы хотим анализировать xFlow в виртуалках? Сколько давать виртуалке процессоров, сколько памяти?
Поэтому мы считали пиковую производительность для xenoeye по ядрам.
На почти холостом ходу (2 объекта мониторинга) он может обрабатывать IPFIX со скоростью до 700K fps per core. Это лучше считать потолком, чем больше будет МО, тем будет медленнее.
При ~2000 объектах мониторинга в виртуалке 12 ядер/32G RAM система может обрабатывать ~200K fps sFlow.

Интересный вопрос, который почти никогда не задают

а как ведут себя анализаторы, когда роутеры присылают им слишком много фловов в секунду?

Обычно такое происходит в самый неподходящий момент - под атакой. Роутеры могут начать экспортировать в разы больше фловов и софт может начать захлебываться.
Разработчики анализаторов стараются сделать софт по красоте - например, автоматически распределяют нагрузку по нескольким ядрам. А это не всегда хорошо работает в corner-кейсах.
Крупные и средние сети - это сети с несколькими роутерами. В то же время трафик атаки, как правило, идет только через 1-2 роутера.
При хорошей атаке и равномерной балансировке все рабочие ядра уходят в полку и анализатор начинает искажать данные по всем роутерам, а не только по тем, которые под атакой.
Иногда это выглядит вообще ужасно - весь анализатор целиком встает колом, на графиках появляются дыры или пилы.
Мне хотелось избежать такого поведения, поэтому в xenoeye нагрузка распределяется по ядрам почти вручную. Для небольших сетей морочиться с этим не нужно, можно обрабатывать трафик роутера одним рабочим потоком. С большой вероятностью производительности будет достаточно (700K fps это много).
Для сетей побольше в конфиге задаются секции, которые отвечают за рабочие потоки.
Идея такая - если фловов навалило слишком много, захлебываться будут только те рабочие потоки, которые обрабатывают трафик роутера под атакой. Остальные потоки будут работать штатно, то есть по остальным роутерам система будет показывать данные без искажений.
Проверить, насколько исказились данные xFlow по роутеру, можно сравнивая SNMP и flow.
В целом такой подход не всем нравится. Я общался с коллегами-программистами, идея им показалась не очень. Но я не настоящий программист, поэтому без особых угрызений совести сделал так.

Объекты мониторинга

У некоторых xFlow коллекторов/анализаторов, особенно у простых опенсорсных, вообще нет явной концепции объектов мониторинга.
У сложных коммерческих наоборот, эта концепция есть и ей активно пользуются.
В xenoeye это тоже есть, и это ключевая штука - к объектам мониторинга привязаны данные отчетов и скользящие средние.

Объект мониторинга - это фактически фильтр, по которому отбирается трафик. Это может быть трафик на какие-то сети, из каких-то сетей, трафик автономной системы, трафик с определенных портов роутера и т.д.
Кроме таких традиционных МО, можно задавать экзотические - например, гео-трафик, идущий из какой-то страны (или в какую-то страну), выделять трафик "приложений" - по протоколам и TCP/UDP портам, номерам VLANов роутера или TCP-флагам.
В конфигурации МО разрешаются почти любые поля, которые есть в xFlow, скобки и логические операции "И", "ИЛИ", "НЕ".
В анализаторах часто "обогащают" фловы гео-информацией и информацией об автономных системах. Эти данные берутся из внешних баз данных. Для IP адреса ищется гео-объект или автономная система и добавляется к флову. Некоторые роутеры могут экспортировать номера AS внутри flow, но даже если этой информации нет, ее тоже можно взять из внешних баз.
В xenoeye для "обогащения" используются функции. Например, чтобы отобрать весь TCP-трафик, который идет в Россию на 443 порт, можно сделать МО с таким фильтром:

"proto 6 and dst port 443 and country_code(dst host) 'ru'"

Это, конечно, немного надуманный пример, он просто для иллюстрации.

Мы используем открытые базы данных автономных систем и GeoIP и с недавнего времени базу РКН (это MaxMind-подобная база, РКН раздает ее операторам бесплатно).

Программно объект мониторинга устроен как очень простая стековая виртуальная машина. Фильтр каждого МО компилируется в байт-код и потом исполняется для каждого приходящего от роутера флова. Так определяется принадлежность флова объекту.
Опять же, настоящие программисты, наверное, сделали бы это правильно, с помощью LLVM, но мне не нужно было заморачиваться. Судя по отчетам perf, это и так работает достаточно быстро, на реальных системах анализатор проводит основное время в матчах IP-адресов и парсинге template-based *flow.

После того, как мы начали ставить анализатор на разных сетях, выяснилось, что объекты мониторинга лучше делать "иерархическими", вложенными. Обычно разным клиентам (владельцам разных подсетей) нужны более-менее одинаковые отчеты. В top level мы определяем сети клиента. Уровнем ниже - более "глубокие" МО для этого клиента.
Как правило, это объекты с потенциальным DoS/DDoS-трафиком: отдельные МО для фрагментированных пакетов, NTP, DNS, остальных UDP-based протоколов, которые могут амплифицироваться, TCP c SYN-флагом и т.п.
Как только объем трафика по этим МО пробивает порог, мы сразу понимаем тип атаки, на какой адрес идет атака (или она на всю подсеть), можем сразу запускать скрипт для этого типа атаки и формировать BGP Flowspec.

Объекты мониторинга в xenoeye хранятся в виде текстовых файлов, JSON c разрешенными комментариями. Обычные текстовые файлы были выбраны специально. Для небольших сетей их можно написать руками, для больших мы генерируем всю пачку МО скриптами.

Иерархичность сделана с помощью файловой системы - вложенные каталоги, в которых есть файл mo.conf, считаются вложенными объектами.

Временны́е окна фиксированного размера и скользящие средние

После того, как мы определили какому МО принадлежит flow, с ним уже можно производить разные действия - добавлять к статистике для этого МО или считать объем трафика в скользящем окне.
Для отчетов удобно не хранить все фловы, а пред-агрегировать их за какие-то разумные периоды, временны́е окна фиксированного размера. Обычно выбирают окна размером в десятки секунд или единицы минут. В некоторых анализаторах размеры этих окон захардкожены, их нельзя менять, у нас можно выставлять произвольные.
Данные накапливаются N секунд, потом анализатор формирует текстовый файл (SQL-скрипт) и пишет его на диск. Это тоже сделано специально. Анализатор не очень привязан к конкретной СУБД, он просто генерирует текстовые файлы. Сейчас это SQL-скрипты для PostgreSQL, но ничего не мешает нам добавить генерацию файлов для ClickHouse или еще какого-нибудь модного хранилища.

Перед тем, как экспортироваться в СУБД, данные могут быть обрезаны. Очень часто, особенно в крупных сетях, хранить все IP-адреса, порты или еще какие-то сущности бессмысленно и очень ресурсозатратно - для этого нужны огромные быстрые хранилища.
Xenoeye умеет сортировать данные перед экспортом в СУБД в порядке убывания PPS/BPS, выбирать top-X (X задается пользователем), а остальное все схлопнуть в одну запись "Others".
Это важная фича - можно подобрать размер временных окон и top-X для экспорта так, чтобы в СУБД писалось не очень много данных. Тогда их проще обрабатывать и можно использовать дешевые медленные хранилища.

Большинство анализаторов для детекции всплесков используют те же окна фиксированного размера. Смотрят на соседние окна, считают дельту и понимают, произошел всплеск или нет.
Однако, это не самый точный и быстрый способ. Для того, чтобы таким способом отдетектировать всплеск, нужно дождаться окончания периода. Быстрые всплески могут раствориться в фиксированном окне.
Точнее и быстрее аномалии можно детектировать с помощью скользящих средних. Скорость трафика в скользящем окне пересчитывается после получения каждого флова, так что можно реагировать очень быстро.
Наверное, самый известный из анализаторов, который использует скользящие средние - это FastNetMon.
Мы тоже можем использовать скользящие средние. Этот механизм работает почти независимо от механизма, который экспортирует данные в СУБД.
Информация о скользящих средних никуда не экспортируется, для работы механизма не нужна СУБД.
Однако, при пробитии порога система может включить сбор расширенной информации. Эта информация будет экспортироваться в СУБД до окончания аномалии. Это может пригодиться для postmortem анализа.

При пробитии порога система просто запускает внешний скрипт и передает ему разные параметры: назввние объекта мониторинга, обьем трафика который вызвал пробой порога и дополнительную информацию, которую задает пользователь. Это может быть IP адрес, IP протокол, TCP/UDP порты и т.п.

В этих скриптах мы и реагируем на DoS/DDoS атаки.

DoS/DDoS атаки, уведомления в Telegram, BGP и BGP Flowspec

Мы используем для управления BGP классическую схему: локально, рядом с анализатором ставим gobgp, устанавливаем с него пиринг с роутерами и с помощью gobgp CLI посылаем анонсы.
Для разных типов атак мы посылаем разные анонсы.
В документации есть пример Telegram-робота, который шлет уведомления об аномалии.
Так как сети у всех разные, подходы к защите от атак тоже разные, скрипты ниже можно рассматривать только как пример.
Некоторые операторы просто посылают хосты под атакой в BGP Blackhole. Некоторые используют оборудование очистки, и при атаках перенаправляют туда весь трафик хоста.
Кто-то использует комбинированную защиту, BGP Flowspec + очистители, скрипты ниже как раз для этого случая.

Скрипты для подавления трех распростаненных атак: NTP-амплификации, DNS-амплификации и TCP SYN-flood атаки.

NTP-host-attack.sh

#!/bin/bash

msgs_dir=/var/lib/xenoeye/telemsg/

args=("$@")

MO=${args[0]}
IP=${args[4]}

filename_src=${args[3]}
filename_dst=${msgs_dir}${filename_src##*/}".n"
filename_dst_tmp=${filename_dst}".tmp"

dt=$(date '+%F %T.%3N');

# Посылаем уведомление в Telegram
echo -n "Overlimit detected at '<b>${dt}</b>', object '<b>${MO}</b>', IP <b>${IP}</b>, PPS <b>$((${args[5]}))</b>, limit $((${args[6]})) PPS" > ${filename_dst_tmp}
mv ${filename_dst_tmp} ${filename_dst}

# Aнонсируем flowspec, rate-limit NTP на хост 
gobgp global rib -a ipv4-flowspec add match destination ${IP} protocol udp source-port '==123' then rate-limit 200.0

DNS-host-attack.sh

#!/bin/bash

# посылаем уведомление в Telegram
# ...

# трафик на DST порты ниже 1024 дропаем
gobgp global rib -a ipv4-flowspec add match destination ${IP} protocol udp source-port '==53' destination-port '>=0&<=1024'  then discard
# трафик на DST порты выше 1024 отправляем на устройство очистки
gobgp global rib -a ipv4-flowspec add match destination ${IP} protocol udp source-port '==53' destination-port '>1024' then redirect 1234:100

TCP-SYN-host-attack.sh

#!/bin/bash

# посылаем уведомление в Telegram
# ...

# можно отправлять на очистку только трафик на определенный порт
#gobgp global rib -a ipv4-flowspec add match destination ${IP} protocol tcp destination-port '==443' then redirect 1234:100
# а можно отправлять весь TCP-трафик
gobgp global rib -a ipv4-flowspec add match destination ${IP} protocol tcp then redirect 1234:100

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

#!/bin/bash

msgs_dir=/var/lib/xenoeye/telemsg/

args=("$@")

filename_src=${args[3]}
filename_dst=${msgs_dir}${filename_src##*/}".s"
filename_dst_tmp=${filename_dst}".stmp"

dt=$(date '+%F %T.%3N');

# пишем в телеграм об окончании атаки
echo -n "Overlimit is over at '<b>${dt}</b>', object '<b>${args[0]}</b>', IP <b>${args[4]}</b>, PPS <b>$((${args[5]}))</b>, limit $((${args[6]})) PPS" > ${filename_dst_tmp}
mv ${filename_dst_tmp} ${filename_dst}

# снимаем анонс
gobgp global rib -a ipv4-flowspec del match destination ${args[4]} protocol udp source-port '==123' then rate-limit 200.0

Кроме атак извне, на операторские сети, иногда (к сожалению, довольно редко) операторы и хостеры детектируют атаки, спам и прочую бот-активность из своих сетей наружу. Это можно сделать теми же средствами - анализом трафика по портам, протоколам с сохранением в БД или скользящими средними, нужно только изменить в МО направление трафика.

Заключение

Надеюсь, статья будет интересной хотя бы для обсуждения.

Знаю, что Хабр читают хорошие специалисты. Если вы видите ошибку, неточность, если вы знаете другие подходы к решению этих задач - пишите комментарии, пишите issue на гитхабе.

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

Комментарии (3)


  1. Stillgray
    14.05.2025 00:47

    Костылил сборщик и анализатор на связке goflow+kafka+clickhouse+grafana ради любопытства.
    Трафик конторы на 300 пользователей переваривает не напрягаясь.
    Плюсы такого решения в том, что данные не нужно агрегировать, все флоу сохраняются в БД, также легко горизонтально масштабируется.


    1. AVikont
      14.05.2025 00:47

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


  1. igrblkv
    14.05.2025 00:47

    А можно совсем для тупых ссылку на быстрый старт, что-бы хоть что-то увидеть в веб-морде, а не No data? Надо на коммутаторах перенаправление на контейнер включать или он сам что-то собирать должен?
    Шаблон для проксмокса развернул.