+7(495)587-42-74 (МСК)
+7(800)100-37-24 (Бесплатный)
DDoSExpert
профессиональная защита от атак
16+

DDoS под прицелом: NetMap, PF RING ZC и Intel DPDK (Часть вторая. NetMap.)

2015-06-29 07:20:00

Перейдём к более пристальному изучению технологий и в этой статье рассмотрим технологию NetMap, автором которой является профессор Пизанского университета Luigi Rizzo. Ниже мы изучим архитектуру и api NetMap, а также продемонстрируем небольшой пример кода. Забегая вперёд отметим, что именно данную технологию DDoSExpert использует в своей сети фильтрации DDoS атак.

 

Данная схема демонстрирует упрощенную версию типичного приложения с использованием NetMap. Обмен данными между приложением и NIC осуществляется через структуры данных, называемые NetMap rings и NIC rings. Входящий и исходящий трафик использует отдельные кольца; в каждом направлении могут использоваться как одно так и несколько колец. Во время выполнения приложения с помощью NetMap сетевой стек полностью отключается. Данное состояние назовём режимом NetMap.

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

Стоит учитывать, что в случае высокоскоростной передачи пакетов будет создаваться большое количество прерываний, что может привести к коллапсу системы и невозможности выполнять полезные операции. Для решения данной проблемы в операционных системах используется альтернативный механизм опроса готовности устройства - polling. Также ряд производителей сетевых адаптеров используют в своих адаптерах множетсвенные очереди для приёма и передачи пакетов, что позволят привязать каждую очередь NIC к ядру процессора, либо использовать карту как отдельные устройства в отдельной виртуальной системе. каждый из этих подходов позволяет снизить нагрузку на CPU и, как следствие, получить возможность захватить большее количество пакетов при DDoS-атаках.

Для перехода в режим NetMap должен быть видоизменён драйвер, что ограничивает возможность использования данной технологии. На данный момент гарантированно поддерживаются гигабитные адаптеры Nvidia, Realtek, Intel с помощью драйверов ForceDeth, r8169 и E1000, а также 10-гигабитные Intel ixgbe. С каждой новой версией NetMap количество необходимых изменений драйвера сокращается, что упрощает реализацию поддержки технологии в новых драйверах.

Рассмотрим основные структуры данных NetMap. Все они были разработан с целью уменьшение overhead’ов при обработки пакетов, поддержки множественных очередей сетевых адаптеров, и обеспечения эффективной передачи пакета как между NIC и сетевым стеком, так и между интерфейсами.

Объекты NetMap размещаются в одной области разделяемой памяти, выделяемой ядром и доступной процессам из пространства пользователя. Такое размещение в выделенном сегменте памяти позволяет использовать механизм zero copy для передачи пакетов между стеком и интерфейсами. При этом  разделение интерфейсов организовано так, чтобы области памяти доступ к которым осуществляется различными процессами были изолированы друг от друга, а так как процессы пользователей работают в разных адресах все ссылки передаваемые в структурах NetMap являются смещениями.

Передаваемые в пространство пользователя структуры содержат следующие объекты:

  • packet buffers / пакетные буферы
  • NetMap rings / кольцевые очереди
  • netmap_if / интерфейсный дескриптор

Все пакетные буферы выделяются в тот момент, когда сетевая карта переводится в режим NetMap. Они имеют фиксированный размер 2K и одновременно доступны как процессам пользователя так и сетевым адаптерам, при  этом каждый буфер имеет уникальный индекс, который может быть вычислен процессом пользователя.

Каждый пакетный буфер привязан к очереди сетевой карты (hardware ring) и кольцевой очереди (NetMap rings). В свою очередь каждая кольцевая очередь имеет ряд параметров: массив слотов slots[], состоящий из ring_size данных содержащих индекс пакетного буфера, размер пакета и флаги, текущий слот cur, количество доступных слотов avail и смещение buf_ofs.

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

Рассмотрим работу NetMap на примере фрагмента кода (с целью облегчения логики работы он упрощён, например, полностью удалена обработка исключений). Данный пример демонстрирует пересылку пакетов между интерфейсами eth0 и eth1:

  1. nm_desc ∗rx_if , nm_desc ∗tx_if ;
  2. rx_if = nm_open ( "netmap:eth0" , . . . ) ;
  3. tx_if = nm_open ( "netmap:eth1" , . . . ) ;
  4.  
  5. netmap_ring ∗rxring , netmap_ring ∗txring ;
  6. rxring = NETMAP_RXRING ( rx_if−>nifp , rx_if−>first_rx_ring ) ;
  7. txring = NETMAP_TXRING ( tx_if−>nifp , tx_if−>first_tx_ring ) ;
  8.  
  9. while ( 1 ) {
  10.  
  11. ioctl ( rx_if−>fd , NIOCRXSYNC , NULL ) ;
  12.  
  13. int pkts = MIN ( nm_ring_space ( rxring ) , nm_ring_space ( txring ) ) ;
  14.  
  15. if ( pkts ) {
  16.  
  17. int rx = rxring−>cur ;
  18. int tx = txring−>cur ;
  19.  
  20. while ( pkts ) {
  21.  
  22. netmap_slot ∗rs = &rxring−>slot [ rx ] ;
  23. netmap_slot ∗ts = &txring−>slot [ tx ] ;
  24.  
  25. /* copy the packet length. */
  26. ts−>len = rs−>len ;
  27.  
  28. /* switch buffers */
  29. uint32_t pkt = ts−>buf_idx ;
  30. ts−>buf_idx = rs−>buf_idx ;
  31. rs−>buf_idx = pkt ;
  32.  
  33. /* report the buffer change. */
  34. ts−>flags |= NS_BUF_CHANGED ;
  35. rs−>flags |= NS_BUF_CHANGED ;
  36.  
  37. rx = nm_ring_next ( rxring , rx ) ;
  38. tx = nm_ring_next ( txring , tx ) ;
  39.  
  40. pkts −= 1 ;
  41.  
  42. }
  43.  
  44. rxring−>cur = rx ;
  45. txring−>cur = tx ;
  46.  
  47. ioctl ( tx_if−>fd , NIOCTXSYNC , NULL ) ;
  48.  
  49. }
  50.  
  51. }
В строках 2,3 кода приложение получает доступ к сетевым адаптерам eth0 и eth1 вызовом функции nm_desc* nm_open(ifname, ...) . Данный вызов - это простой способ открыть и сконфигурировать NetMap устройство. Аргумент ifname задаёт имя устройства. Приставка "netmap:" открывает Ethernet устройство в режиме NetMap. Дополнительные(опущенные) аргументы могут быть использованы для настройки устройства.
При вызове nm_open создаётся дескриптор файла используя системный вызов open("/dev/netmap", O_RDWR). Затем конфигурируется сетевая карта и создаётся структура данных вызовом ioctl(fd, arg1, arg2), используя созданный ранее дескриптор. В стандартной конфигурации arg1 принимает значение NIOCREG привязывая все доступные NIC hardware rings данному дескриптору. Также можно осуществить привязку только выбранных пар колец. В качестве arg2 указывается имя адаптера.
В строках 5 и 6 создаются указатели для доступа к пакетам. Указатели на структуры данных хранятся только в виде смещений. Для расчёта смещений используются макросы NETMAP_RXRING() и NETMAP_TXRING().
После этапа инициализации приложение запускает бесконечный цикл(строка 9) в котором ожидаем получения пакетов.
ioctl(FD, NIOCTXSYNC) – синхронизируем netmap rings для отправки с соответствующими очередями сетевой карты, что эквивалентно отправке пакетов в сеть, синхронизация начинается с позиции cur.
ioctl (FD, NIOCRXSYNC) – аналогично синхронизируем очереди сетевой карты с соответствующими очередями netmap rings, для получения пакетов, поступивших из сети. При этом запись осуществляется начиная с позиции cur.

Приведённые выше системные вызовы являются не блокирующимися, они не выполняют излишнего копирования данных. Копирование осуществляется исключительно из сетевой карты в netmap rings и наоборот, при этом они работают как с одном, так и с многими пакетами за один системный вызов. Этот механизм играет ключевую роль и даёт кардинальное уменьшении overhead’ов при обработке пакетов. В процессе выполнения описанных системных вызовов ядро обработчика NetMap 1) проверяет cur/avail поля очереди и содержимое слотов задействованных обработчиком 2) синхронизирует содержимое задействованных пакетных слотов между netmap rings и hardware rings, а также  выдаёт команду сетевому адаптеру отправить пакеты, либо сообщает о наличии новых свободных буферов для приёма данных 3) обновляет поле avail в netmap rings. Таким образом ядро NetMap выполняет минимальный объём работы и в дополнение лишь осуществляет проверку введённых пользовательских данных для предотвращения краха системы. 

С помощью системных вызовов select() / poll() с файловым дескриптором /dev/netmap осуществляется поддержка блокируемого ввода/вывода. Результатом может являться досрочный возврат управления с параметром avail > 0. Перед возвратом управления из контекста ядра система выполнит действия аналогичным вызовам ioctl(…NIOC**SYNC). Данный подход позволяет приложению пользователя в цикле проверять состояние очередей, используя всего один системный вызов за проход не загружая CPU.

В продолжение цикла статей "DDoS под прицелом" рассмотрим следующего претендента на роль технологии для защиты от DDoS атак - PF_RING.