Хабрахабр

Генерация трафика в юзерспейсе

Генерация трафика посредством MoonGen + DPDK + Lua в представлении художника

Сетевое оборудование и ПО должно быть протестировано в искусственных условиях близких к реальным — с интенсивными потоками трафика, имитирующего атаки. Нейтрализация DDoS-атак в реальных условиях требует предварительных тестирования и проверки различных техник. Без таких экспериментов крайне затруднительно получить достоверную информацию о специфических особенностях и ограничениях, имеющихся у любого сложного инструмента.

В данном материале мы раскроем некоторые методы генерации трафика, используемые в Qrator Labs.

ПРЕДУПРЕЖДЕНИЕ

Организация DoS-атак преследуется по закону и может вести к суровому наказанию. Мы настойчиво рекомендуем читателю не пытаться использовать упомянутые инструменты для атак на объекты реальной инфраструктуры. Qrator Labs проводит все тесты в изолированном лабораторном окружении.

Современный технический уровень

Показательной задачей в нашей области является насыщение 10G Ethernet-интерфейса небольшими пакетами, что подразумевает обработку 14.88 Mpps (миллионов пакетов в секунду). Здесь и далее мы рассматриваем сетевые пакеты Ethernet наименьшего размера — 64 байта, — поскольку нашим основным интересом является максимизация количества переданных пакетов в единицу времени. Простой подсчет показывает, что у нас есть всего около 67 наносекунд для обработки одного такого пакета.

Все становится еще сложнее, когда мы начинаем работать с 40G и 100G Ethernet-интерфейсами и пытаемся полностью насытить их вплоть до line rate (максимально возможной заявленной производительности сетевого устройства). Просто для сравнения — это время близко к тому, что требуется современному процессору для получения кусочка данных из памяти в случае промаха в кэш.

Примером подобного решения является ядерный модуль pktgen [2]. Так как в обычном случае поток данных проходит через приложение в пользовательском пространстве (userspace), далее через ядро, попадая, наконец, в сетевой контроллер (NIC), первой и наиболее прямолинейной идеей является попытка настроить генерацию пакетов прямо в ядре. Данный способ позволяет заметно улучшить производительность, но недостаточно гибок, так как малейшее изменение исходного кода в ядре ведет к длительному циклу сборки, перезагрузке модулей ядра или даже всей системы и, собственно, тестирования, что снижает общую продуктивность (то есть требует от программиста больше времени и усилий).

Этот путь более сложен, однако сто́ит усилий в целях достижения более высокой производительности. Другим возможным подходом является получение прямого доступа из userspace к буферам памяти сетевого контроллера. Примерами такого подхода являются технологии netmap, PF_RING и DPDK [4]. Недостатки включают в себя высокую сложность и низкую гибкость.

Пример: Ixia. Еще одним эффективным, хотя и весьма затратным способом добиться высокой производительности является использование не универсального, а специализированного оборудования.

Ниже мы опишем собственный опыт с одним из таких инструментов — MoonGen. Также существуют решения основанные на DPDK с использованием скриптов, что повышает гибкость в управлении параметрами генератора, а также позволяет варьировать тип генерируемых пакетов в процессе запуска.

Архитектура MoonGen

Отличительными особенностями MoonGen являются:

  1. Обработка данных DPDK в userspace, это основная причина прироста производительности;
  2. Стек Lua [5] с простыми скриптами на верхнем уровне и привязками к библиотеке DPDK, написанной на языке C, на нижнем;
  3. Благодаря технологии JIT (just in time) Lua-скрипты работают достаточно быстро, что несколько противоречит общепринятым представлениям об эффективности скриптовых языков.

MoonGen может восприниматься как Lua-обертка вокруг библиотеки DPDK. По меньшей мере следующие операции DPDK видны на уровне пользовательского интерфейса Lua:

  • Конфигурирование сетевых контроллеров;
  • Аллокация и прямой доступ к пулам и буферам памяти, которые, в целях оптимизации, должны выделяться непрерывными выровненными областями;
  • Прямой доступ к RSS-очередям сетевых контроллеров;
  • API для управления вычислительными потоками, учитывающие неоднородность доступа к памяти (NUMA и CPU affinity) [12].

Архитектура MoonGen, схема из материала [1].

MoonGen

MoonGen — это скриптовый высокоскоростной генератор пакетов, основанный на библиотеке DPDK. Скрипты Lua контролируют полностью весь процесс: созданный пользователем скрипт занимается созданием, модификацией и отправкой пакетов. Благодаря очень быстрому LuaJIT и библиотеке обработки пакетов DPDK, такая архитектура позволяет насытить 10-гигабитный Ethernet-интерфейс 64-байтными пакетами, используя только одно ядро центрального процессора. MoonGen позволяет достичь такой скорости даже в случае, когда Lua-скрипт модифицирует каждый пакет. При этом не используются трюки вроде переиспользования одного и того же буфера сетевого контроллера.

Так как прием пакетов управляется исключительно пользовательским Lua скриптом, он может быть использован и для создания более сложных тестовых скриптов. MoonGen может также принимать пакеты, то есть проверять, какие пакеты были отброшены тестируемой системой. Подобная конфигурация может использоваться, в частности, для тестирования так называемых мидлбоксов (оборудования между точкой отправки и приема трафика), например файрволлов. Например, возможно использование двух экземпляров MoonGen для установки соединения друг с другом. MoonGen фокусируется на четырех основных направлениях:

  • Высокая производительность и многоядерное масштабирование: более 20 миллионов пакетов в секунду на одном ядре CPU;
  • Гибкость: каждый пакет генерируется в реальном времени на основе созданного пользователем скрипта Lua;
  • Точные отметки времени: на обычном (commodity) железе временна́я разметка производится с миллисекундной точностью;
  • Точный контроль интервалов между отправляемыми пакетами: надежная генерация требуемых паттернов и типов трафика на обычном железе.

DPDK

DPDK расшифровывается как Data Plane Development Kit и состоит из библиотек, основными функциями которых является повышение производительности генерации сетевых пакетов на широком разнообразии архитектур центральных процессоров.

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

Передача данных в рамках userspace — это то, что мы наблюдаем не так часто, в основном потому, что большинство приложений коммуницирует с сетевым оборудованием через операционную систему и стек ядра, что является противоположностью модели DPDK. DPDK — это легковесный и удобный способ построения тестов и скриптов.

Lua

Основной целью существования Lua является предоставление простых и гибких выразительных средств, расширяемых под конкретные текущие задачи, вместо набора примитивов, применимого лишь в одной парадигме программирования. В результате, базовый язык очень легок — весь интерпретатор занимает лишь 180 кБ в скомпилированном виде и легко адаптируется к широкому спектру возможных реализаций.

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

Lua задействует компиляцию JIT (just in time), поэтому, будучи скриптовым языком, показывает производительность сравнимую с компилируемыми языками, такими как C [10].

Почему MoonGen

Являясь компанией, специализирующейся на нейтрализации DDoS-атак, Qrator Labs нуждается в надежном способе создавать, модернизировать и тестировать собственные решения по безопасности. Именно для последнего — тестирования, необходимы различные способы генерации трафика, имитирующие реальные атаки. Тем не менее, не так просто сымитировать опасную, при этом прямолинейную, атаку флудом на 2-3 уровнях модели OSI, в первую очередь из-за трудностей с достижением высокой производительности в генерации пакетов.

Другими словами, для компании, занимающейся непрерывной доступностью и нейтрализацией DDoS, симуляция различных DoS-атак в изолированном лабораторном окружении — это способ понять, как в реальности поведет себя различное оборудование, входящее в состав аппаратных комплексов компании.

Передача данных в рамках userspace значительно поднимает производительность рассматриваемого стека (MoonGen + DPDK), по сравнению со многими другими вариантами генерации высоких значений трафика. MoonGen — это хороший способ генерировать близкие к предельным для сетевого контроллера значения трафика на минимуме ядер центрального процессора. Мы также поддерживаем клон [7] оригинального репозитория MoonGen с целью расширения функциональности и имплементации собственных тестов. Использование чистого DPDK требует значительно бо́льших усилий, поэтому не нужно удивляться нашему стремлению к оптимизации работы.

В случае относительно простой обработки пакетов это решение работает достаточно быстро для насыщения 10G-интерфейса на одном ядре CPU. С целью достижения максимальной гибкости, логика генерации пакетов задается пользователем с помощью скрипта Lua, что является одной из основных особенностей работы MoonGen. Типичный способ модификации входящих пакетов и создания новых — это работа с пакетами одного типа, в которых меняются лишь некоторые из полей.

Отметим, что любая модификация пакета может быть произведена в том же самом буфере, где оказался сгенерированный или полученный на предыдущем этапе пакет. В качестве примера может служить тест l3-tcp-syn-ack-flood, описанный ниже. Действительно, такого рода преобразования пакетов выполняются очень быстро, так как не задействуют дорогие операции, вроде системных вызовов, доступа к потенциально не закэшированным участкам памяти и тому подобных.

Тесты на оборудовании Qrator Labs

Qrator Labs проводит все тесты в лаборатории на различном оборудовании. В данном случае нами были задействованы следующие контроллеры сетевых интерфейсов:

  • Intel 82599ES 10G
  • Mellanox ConnectX-4 40G
  • Mellanox ConnectX-5 100G

Отметим отдельно, что при работе с сетевыми контроллерами, работающими на стандартах выше 10G, проблема производительности встает все острее. На сегодняшний день не представляется возможным насыщение интерфейса 40G одним ядром, хотя небольшим количеством ядер это уже реально.

Это позволяет поднять производительность, а в некоторых особенных случаях — глубже изменить поведение NIC. В случае сетевых контроллеров производства Mellanox возможно изменение некоторых параметров и настроек устройства с помощью tuning guide [3], предоставляемого производителем. Даже если вы не можете найти такой документ в открытом доступе, всегда имеет смысл связаться с производителем напрямую. Другие производители могут иметь похожие документы для собственных высокопроизводительных устройств, предназначенных к профессиональному использованию. В нашем случае представители компании Mellanox были очень любезны и, помимо предоставления документации, быстро отвечали на возникающие у нас вопросы, благодаря чему удалось добиться утилизации полосы на 100%, что было для нас очень важно.

Тест TCP SYN flood

L3-tcp-syn-ack-flood — это пример имитации атаки типа SYN flood [6]. Это расширенная Qrator Labs версия теста l3-tcp-syn-flood из основного репозитория MoonGen, которая хранится в нашем клоне репозитория.

Наш тест может запускать три вида процессов:

  1. Генерировать с нуля поток пакетов TCP SYN, варьируя требуемые поля, такие как source IP address, source port number и др.;
  2. Создавать валидный ответ ACK на каждый полученный SYN-пакет согласно протоколу TCP;
  3. Создавать валидный ответ SYN-ACK на каждый полученный ACK-пакет согласно протоколу TCP.

Для примера, внутренний (соответственно, самый «горячий») цикл кода для создания ACK-ответов выглядит следующим образом:

local tx = 0
local rx = rxQ:recv(rxBufs)
for i = 1, rx do local buf = rxBufs[i] local pkt = buf:getTcpPacket(ipv4) if pkt.ip4:getProtocol() == ip4.PROTO_TCP and pkt.tcp:getSyn() and (pkt.tcp:getAck() or synack) then local seq = pkt.tcp:getSeqNumber() local ack = pkt.tcp:getAckNumber() pkt.tcp:unsetSyn() pkt.tcp:setAckNumber(seq+1) pkt.tcp:setSeqNumber(ack) local tmp = pkt.ip4.src:get() pkt.ip4.src:set(pkt.ip4.dst:get()) pkt.ip4.dst:set(tmp) … -- some more manipulations with packet fields tx = tx + 1 txBufs[tx] = buf end
end
if tx > 0 then txBufs:resize(tx) txBufs:offloadTcpChecksums(ipv4) -- offload checksums to NIC txQ:send(txBufs)
end

Общая идея создания ответного пакета заключается в следующем. Для начала, необходимо вынуть пакет из очереди RX, затем проверить, совпадает ли тип пакета с ожидаемым. В случае совпадения — подготовить ответ, модифицируя некоторые поля оригинального пакета. Наконец, поместить созданный пакет в очередь TX, используя тот же буфер. Для повышение производительности, вместо того чтобы по очереди брать и модифицировать пакеты один за одним, мы агрегируем их, извлекая из очереди RX все доступные пакеты, создаем соответствующие ответы и помещаем их все в очередь TX. Несмотря на достаточное большое количество манипуляций над одним пакетом, производительность остается высокой, в первую очередь благодаря тому, что Lua JIT компилирует все эти операции в небольшое количество процессорных инструкций. Множество других тестов, не только TCP SYN/ACK, работают по тому же принципу.

Этот NIC обладает двумя портами 40G с теоретическим потолком производительности в 59. Таблица ниже демонстрирует результаты теста SYN flood (генерация SYN без попыток ответа) с использованием Mellanox ConnectX-4. Конкретная реализация подключения NIC к PCIe несколько ограничивает пропускную способность (давая 2 * 50 вместо ожидаемых 2 * 59. 52 Mpps на одном порту и 2 * 50 Mpps для двух портов. 52).

cores per port

1 port, Mpps

2 ports, Mpps per each port

1

20

19

2

38

36

3

56.5

47

4

59.5

50

SYN flood test; NIC: Mellanox Technologies MT27700 Family (ConnectX-4), dual 40G port; CPU: Intel® Xeon® Silver 4114 CPU @ 2.20GHz

Следующая таблица показывает результаты того же самого теста SYN flood, проведенного на Mellanox ConnectX-5 с одним портом 100G.

cores

Mpps

1

35

2

69

3

104

4

127

5

120

6

131

7

132

8

144

SYN flood test; NIC: Mellanox Technologies MT27800 Family (ConnectX-5), single 100G port; CPU: Intel® Xeon® Silver 4114 CPU @ 2.20GHz

Отметьте, что во всех случаях мы достигаем более чем 96% от теоретического потолка производительности на небольшом количестве ядер процессора.

Захват входящего трафика и сохранение в PCAP-файлы

Другим примером теста является rx-to-pcap, который пытается захватить весь входящий трафик и сохранить в определенное количество PCAP-файлов [8]. Хотя конкретно этот тест и не касается генерации пакетов как таковой, он служит демонстрацией того факта, что самым слабым звеном в организации передачи данных через userspace является файловая система. Даже виртуальная файловая система tmpfs значительно замедляет поток. В данном случае 8 ядер центрального процессора необходимы для утилизации 14.88 Mpps, в то время как лишь одного ядра достаточно для получения (и сброса, либо перенаправления) того же объема трафика.

Следующая таблица демонстрирует количество трафика (в Mpps), который был получен и сохранен в PCAP-файлы, находящиеся в файловой системе ext2 на SSD (вторая колонка) или на файловой системе tmpfs (третья колонка).

cores

on SSD, Mpps

on tmpfs, Mpps

1

1.48

1.62

2

4

4.6

3

6.94

8.1

4

9.75

11.65

5

12.1

13.8

6

13.38

14.47

7

14.4

14.86

8

14.88

14.88

Rx-to-pcap test; NIC: Intel 82599ES 10-Gigabit; CPU: Intel® Xeon® CPU E5-2683 v4 @ 2.10GHz

Модификация MoonGen: менеджер заданий tman

Мы бы также хотели представить читателю собственное расширение функционала MoonGen, предоставляющее другой способ запустить группу задач для тестирования. Основная идея здесь заключается в разделении общей конфигурации и специфических для каждой задачи настроек, позволив запускать произвольное количество различных заданий (то есть скриптов Lua) одновременно. В нашем клоне репозитория MoonGen представлена имплементация MoonGen с менеджером заданий [9], здесь мы лишь кратко перечислим его основные функции.

Базовый сценарий выглядит следующим образом: Новый интерфейс командной строки позволяет запускать одновременно несколько заданий разного типа.

./build/tman [tman options...] [-- <task1-file> [task1 options...]] [-- <task2-file> [task2 options...]] [-- ...]

Кроме того, ./build/tman -h выдает подробную справку.

Файл задания tman должен четко определять следующие объекты: Однако существует некоторое ограничение — обычные файлы заданий Lua несовместимы с интерфейсом tman.

  • Функцию configure(parser), описывающую параметры задания;
  • Функцию task(taskNum, txInfo, rxInfo, args), описывающую собственно процесс-задание. Здесь txInfo и rxInfo являются массивами соответственно RX и TX очередей; args содержит параметры менеджера заданий и самого задания.
  • Примеры можно посмотреть в examples/tman.

Использование менеджера заданий дает большую гибкость в запуске гетерогенных тестов.

Выводы

Метод, который предлагает MoonGen, оказался хорошо подходящим нашим целям и удовлетворил сотрудников полученными результатами. Мы получили инструмент, обладающий высокой производительностью, при этом сохранив и тестовое окружение, и язык достаточно простыми. Высокая производительность данного сетапа достигается благодаря двум основным особенностям: прямым доступом к буферам контроллера сетевого интерфейса и технике компиляции Just-In-Time в Lua.

Как мы продемонстрировали, одного ядра может быть достаточно для насыщения порта 10G, в то время как с бо́льшим количеством ядер не представляет особой проблемы и полная загрузка порта 100G. Как правило, достижение теоретического потолка производительности контроллера сетевого интерфейса — вполне посильная задача.

Мы особенно благодарны команде Mellanox за помощь при работе с их оборудованием и команде MoonGen за их реакцию в исправлении ошибок.

Материалы

  1. MoonGen: A Scriptable High-Speed Packet Generator — Paul Emmerich et al., Internet Measurement Conference 2015 (IMC'15), 2015
  2. Pktgen
  3. Mellanox tuning guide
  4. Data Plane Development Kit
  5. Lua
  6. SYN flood
  7. Qrator Labs’ clone of MoonGen repository
  8. PCAP file format
  9. Task manager
  10. Lua performance
  11. Network Functions Virtualization Whitepaper
  12. NUMA, non-uniform memory access
Теги
Показать больше

Похожие статьи

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

Ваш e-mail не будет опубликован. Обязательные поля помечены *

Кнопка «Наверх»
Закрыть