Шейпер для Linux в пользовательском пространстве (NFQUEUE-based)
Как человеку любознательному, всегда было интересно, можно ли сделать процесс настройки шейпинга для небольших сетей проще? Можно ли хотя бы грубо детектировать важный трафик и отделять его от неважного без 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, код местами сумбурный и практически не причесывался.
Если у кого-то есть соображения, пожелания и предложения по поводу, было бы интересно почитать.