Шейпер для Linux в пользовательском пространстве (NFQUEUE-based)

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

Как человеку любознательному, всегда было интересно, можно ли сделать процесс настройки шейпинга для небольших сетей проще? Можно ли хотя бы грубо детектировать важный трафик и отделять его от неважного без DPI и сигнатурного анализа? Можно ли шейпить трафик в любых направлениях без создания псевдо-интерфейсов или добавления модулей в ядро? И вот, после некоторых размышлений и гуглежа, решил написать простой шейпер в userspace. Чтоб попробовать ответить на вопросы экспериментом.

В результате эксперимента получилась вот такая штука github.com/vmxdev/damper

Работает штука приблизительно так:

При старте создаются два программных потока. В первом NFQUEUE захватывает пакеты, они анализируются, каждому пакету назначается «вес» (или приоритет) и он сохраняется в очереди с приоритетами. Когда очередь заполнена, пакеты с низким приоритетом затираются высокоприоритетными. Другой поток выбирает пакеты с наибольшим весом и помечает их к отправке. Отправка происходит с ограниченной скоростью, из-за этого собственно и получается шейпирование.

Небольшое отступление о механизме NFQUEUE


Этот механизм позволяет копировать сетевые пакеты в пользовательское пространство для обработки. Приложение, которое слушает соответствующую очередь, должно вынести вердикт относительно пакета (пропустить или отбросить). Кроме этого допускается изменение пакета. Этим механизмом пользуются IDS/IPS типа Snort или Suricata. Пакеты помечаются для обработки в iptables, цель NFQUEUE. То есть мы можем выбрать любое направление (входящий трафик, исходящий, транзитный, или, скажем, UDP с порта 666 на порт 13) и направить его в шейпер. Там пакеты будут анализироваться, возможно изменять свой порядок, а при превышении лимита самые низкоприоритетные будут отбрасываться.

Первые рабочие версии шейпера захватывали пакеты, помещали их в очередь и потом ре-инъектировали в сырой (raw) сокет. На StackOverflow и некоторых других статьях пишут что это единственный способ задерживать пакеты перед перепосылкой. Конструкция работала, но товарищ Vel разъяснил, где можно задерживать пакет прямо в NFQUEUE, настройка шейпера упростилась.

Модули


Так как шейпер экспериментальный, я сделал его не монолитным, а «модульным». В каждом модуле вычисляется вес пакета по разным критериям. Из коробки есть 4 модуля, но можно легко написать еще какой-нибудь. Модули можно использовать вместе, можно по отдельности.

Модули:

  • inhibit_big_flows — подавляет большие потоки. Чем больше передано байт между двумя IP-адресами, тем меньшим становится «вес» пакета. То есть повышается вероятность того что пакет из большой тяжелой сессии будет отброшен. Информация хранится в кольцевом буфере, так что время от времени наступает амнистия (количество отслеживаемых сессий задается в конфиге), потоки замещаются более свежими
  • bymark — вес пакетов задается по марке. iptables-ом выставляется марка по каким-то критериям, для этих пакетов будет применяться коэффициент, указанный в конфиге. Можно вручную поднять или понизить приоритет какого-то отмаркированного трафика
  • entropy — вес считается в зависимости от энтропии (точнее, мере энтропии по Шеннону) потока. Поток идентифицируется номером протокола и адресами участников. Для TCP/UDP учитывается еще и порт источника и назначения. Чем выше энтропия, тем меньше вес. Т.е. мультимедиа, шифрованный, сжатый трафик отбрасывается с большей вероятностью чем остальной.
  • И очень примитивный модуль random — просто добавляет случайности в процесс отброса и переупорядочивания пакетов (если использовать только этот модуль, получится классический RED).

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

Статистика и графики


Шейпер умеет вести статистику и рисовать графики. Вживую это выглядит так: damper.xenoeye.com. Зеленый — сколько пропущено байт/пакетов, красный — сколько отброшено. График можно позумить/поскроллить.

Второй график (включается директивой «wchart yes» в конфиге) — средние веса пакетов за секунду, отнормированные, с разбивкой по модулям.

Демо работает на не очень быстрых ARMах (Scaleway bare metal, 32-битные ARMv7), иногда может слегка залипать.

Отладка


У модулей inhibit_big_flows и entropy есть отладочный режим, включается в конфиге. В этом режиме модули каждые N секунд делают дамп текущих потоков с весами.

Кроме этого есть режим без ограничения скорости («limit no» в конфиге). В этом режиме все пакеты пропускаются (без анализа в модулях), но можно вести статистику прошедних пакетов/байт, медитировать на график загрузки канала, например.

Результаты


Шейпер получился достаточно простой (ну, на мой, замыленный, взгляд). Для использования нужно выбрать направление по которому шейпить, добавить правило iptables, выставить в конфиге нужную скорость и запустить.

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

На больших скоростях и большом количестве пользователей я его не тестировал, но на десятках мегабит и с несколькими пользователями субъективно получается лучше чем без шейпера. Хотя он грузит CPU, но это не только от использования NFQUEUE, а еще и от общей корявости кода (и немного от особенностей clock_nanosleep ()), можно отпимизировать и оптимизировать.

Это, конечно, только proof of concepts, код местами сумбурный и практически не причесывался.

Если у кого-то есть соображения, пожелания и предложения по поводу, было бы интересно почитать.

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

© Habrahabr.ru