Анализ гигабита на commodity hardware: что есть уже сейчас
Галопом по доступным сегодня средствам сбора и (или) анализа трафика на типовом оборудовании на гигабитных скоростях, в основном, для ОС Linux. Рассматриваются: mmap-pcap + NAPI и PF_RING, а также Gulp как пример того, чего можно достичь в юзерспейсе, слегка изменив настройки планировщика.
Как было показано в предыдущей заметке, опережающее развитие технологий передачи данных по Гилдеру приводит к тому, что на типовом "железе" становится всё труднее обрабатывать типовой канал передачи данных при полной его утилизации. Этот тренд накладывается на несовершенство некоторых аспектов реализации современных ОС общего назначения, что ведёт к большим накладным расходам. Результат можно наблюдать на примере поведения библиотеки libpcap в ОС Linux при использовании настроек по-умолчанию.
Справедливости ради нужно отметить, что во многом такое положение дел с libpcap продиктовано требованиями универсальности и независимости интерфейса библиотеки от используемой аппаратной платформы и операционной системы. В разных ОС libpcap использует различные нативные механизмы ОС. Например, во FreeBSD она работает через BPF, а в Linux - через raw sockets.
В ОС Linux существует несколько способов повышения производительности libpcap. Первый способ - использование mmap() для передачи пакетов с уровня ядра в юзерспейс. Он реализован Филом Вудом (Phil Wood) в модифицированной версии библиотеки. Для установки надо взять исходники, собрать библиотеку, и пересобрать весь использующий её софт. Второй способ - использование активного поллинга, который реализован в Linux NAPI и поддерживается большинством драйверов сетевых карт. Поллинг работает примерно так: при получении прерывания от устройства ядро маскирует все последующие прерывания и начинает периодически опрашивать устройство для его обслуживания. Как только устройство обслужено, ядро снова демаскирует прерывания от него. Вместе два этих способа дают приличный прирост производительности libpcap в Linux, но не решают проблему для гигабитных скоростей.
Один из авторов ntop.org, Люка Дери (Luca Deri), ещё в 2004 году опубликовал результаты экспериментов с libpcap на различных платформах и различном оборудовании. В тестах использовались ядра Linux версий 2.4 и 2.6, FreeBSD 4.8, Windows 2000. За 4 года результаты, конечно, значительно устарели, но судя по отдельным более современным исследованиям, с 2004 года ситуация с libpcap мало изменилась. Худшие результаты были продемонстрированы при использовании libpcap на Linux 2.4 на 100-мегабитном канале (80Kpps) и на довольно слабенькой машине - VIA C3 533Mhz CPU и остальное в том же духе - потери составили 99,8% пакетов. При этом версия с mmap улучшила результат в 5 раз - потери снизились до 99%. На том же оборудовании libpcap на FreeBSD потеряла "всего" 66% пакетов, а результат WinPCAP составил 32% потерь.
В следующем эксперимента он использовал поллинг в ОС Linux (2.6.1, NAPI) и FreeBSD. В качестве аппаратной платформы использовался Pentium III 550Mhz с гигабитной сетевой картой Intel. Тест проводился с разным размером кадров - 64 байта, 512 байт и 1500 байт. Результаты приведены в таблице 1 (указаны потери):
| Размер кадра |
Linux 2.6.1, NAPI обычная libpcap |
Linux 2.6.1, NAPI libpcap-mmap |
FreeBSD с включённым поллингом |
|---|---|---|---|
| 64 | 97,5% | 85,1% | 2,7% |
| 512 | 98,9% | 88,3% | 52,7% |
| 1500 | 65,7% | 6,5% | 43,9% |
Видно, что с мелкими кадрами ситуация совсем плохая. Хотя на больших кадрах линуксовая версия libcap-mmap показывает себя вполне неплохо. В статье есть ещё несколько тестов на более быстрых процессорах, в том числе с использованием авторского патча на ядро. В целом же можно сделать вывод, что использование версии libpcap с mmap() и поллинг сетевой карты существенно улучшают производительность при выводе трафика в юзерспейс, но недостаточно, чтобы можно было говорить о wire speed.
Отсюда плавно переходим к PF_RING - это патч на ядро Linux, автором которого является тот же Люка Дери. PF_RING предоставляет приложениям в юзерспейсе сокеты специального вида, где с каждым сокетом ассоциирован кольцевой буфер, который отображается в юзерспейс с помощью того же mmap(). Кадр из буфера сетевой карты через DMA попадает сразу в кольцевой буфер, и становится доступен приложению (см. рисунок 1). Цикл работы PF_RING выглядит следующим образом:
- создаётся сокет типа PF_RING;
- ядро выделяет память под кольцевой буфер, а когда приложение закрывает сокет - освобождает;
- когда драйвер получает кадр с сетевой карты (через DMA) и копирует его в кольцевой буфер. Если буфер полон, кадр сбрасывается;
- на тех интерфейсах, для которых есть сокеты PF_RING, кадр по-умолчанию не передаётся стеку операционной системы, что ускоряет обработку кадров; при желании, эту опцию можно включить;
- кольцевой буфер экспортируется в юзерспейс с помощью mmap();
- для доступа приложения к буферу оно должно открыть сокет и вызвать mmap(), получив таким образом указатель чтения на позицию в кольцевом буфере;
- ядро при записи в буфер продвигает указатель записи, а указатель чтения сдвигается по мере получения пакетов приложением;
- новые кадры перезаписывают уже считанные приложением;
- для управления потоком кадров используется "текущее ведро" (leaky bucket).

Рисунок 1. Схема работы PF_RING.
Приложения могут использовать как родной интерфейс библиотеки, так и модифицированную автором PF_RING версию libpcap. Но ядро так или иначе придётся патчить - PF_RING всё ещё не вошёл в основную ветку kernel.org, несмотря на то, что успешно развивается и поддерживается уже пятый или шестой год. По тестам из той же статьи 2004 года libpcap + PF_RING + NAPI на 1500-байтных кадрах показывает такую же производительность, как libpcap-mmap + NAPI, а на средних и мелких кадрах намного превосходит. Более свежие тесты (2008 год) с использованием двухъядерного Xeon 3.2ГГц и генератора трафика IXIA 400 демонстрируют какие-то потери кадров лишь после 1.8 Mpps (на двух гигабитных сетевых интерфейсах).
| Размер пакета |
Скорость в Kpps | Скорость в Mbps | Доля свободного времени CPU |
|---|---|---|---|
| 250 | 259.23 | 518 | >90% |
| 250 | 462.9 | 925.9 | 88% |
| 128 | 355.1 | 363.6 | 86% |
| 128 | 844.6 | 864.8 | 82% |
Таблица 2. Производительность PF_RING на одноядерном Celeron 3.2ГГц
На сегодняшний день кроме быстрого вывода трафика в юзерспейс PF_RING умеет следующее:
- Bloom filtering на уровне драйвера - эффективный по памяти способ организации фильтров
- несколько фильтров на каждый сокет
- архитектурная независимость - работает для x86, MIPS и т.д., есть реализация для OpenWRT
- фильтры уровня PF_RING с поддержкой string matching (реализация Aho-Corasick)
- готовая реализация NetFlow, плюс можно добавлять свои плагины ядерного уровня
В последнем для данной заметки примере решается конкретная
прикладная задача - перехват и запись на файловую систему гигабитного
трафика с канальной скоростью. Для решения данной задачи Corey Satten
из университета Вашингтона разработал и реализовал простое
многопоточное средство Gulp,
которое примечательно тем, что использует всё тот же кольцевой буфер
без блокировок в качестве интерфейса передачи трафика между двумя
потоками. Первый поток получает пакеты из libpcap, а второй пишет их на
диск. Простой привязкой этих двух потоков к разным ядрам процессора и
повышением приоритета читающего потока автору удалось обеспечить запись
гигабитного трафика на диск без потерь. К сожалению, в описанном тесте
средняя скорость составляла всего 96 kpps, то есть в канале
преимущественно были кадры большого размера. На мелких кадрах, надо
полагать, ситуация была бы не столь радужна. Примечательна фраза
наверху домашней странички автора Gulp: "Due to lack of funds, the Security Solutions Team at the University of
Washington was abruptly eliminated on May 20, 2008 and I was laid off
(along with 65 others throughout "UW Technology")." Удачи тебе, Corey!
В заключение отмечу, что сегодня уже доступны системы с 64 и 256 логическими ядрами на сановском UltraSPARC T2 (Niagara 2), и скоро выйдет Rock. Вот-вот обещают 16-ядерные процессоры интеловской архитектуры. А так как задачи анализа сетевого трафика, как правило, хорошо параллелятся, то у нас есть надежда и на успешный разбор 10Гбитного трафика одним-двумя юнитами и четырьмя сотнями ватт потребления, без потерь и всё такое на wire speed. Надо только сделать что-нибудь подобное PF_RING с конвейерами из процессорных ядер в кернелспейсе.


^_^