Времена узких интернет-каналов постепенно уходят в прошлое, но иногда еще бывает нужно шейпить сетевой трафик. В Linux для этого есть соответствующие механизмы ядра и утилиты для управления механизмами. Все это хозяйство довольно сложно устроено, обычно постижение шейпинга занимает не один день. Хотя, в простых случаях можно накопипастить заклинания tc из статей или найти скрипт, который эти заклинания генерирует.
Как человеку любознательному, всегда было интересно, можно ли сделать процесс настройки шейпинга для небольших сетей проще? Можно ли хотя бы грубо детектировать важный трафик и отделять его от неважного без DPI и сигнатурного анализа? Можно ли шейпить трафик в любых направлениях без создания псевдо-интерфейсов или добавления модулей в ядро? И вот, после некоторых размышлений и гуглежа, решил написать простой шейпер в userspace. Чтоб попробовать ответить на вопросы экспериментом.
В результате эксперимента получилась вот такая штука github.com/vmxdev/damper
Работает штука приблизительно так:
При старте создаются два программных потока. В первом NFQUEUE захватывает пакеты, они анализируются, каждому пакету назначается «вес» (или приоритет) и он сохраняется в очереди с приоритетами. Когда очередь заполнена, пакеты с низким приоритетом затираются высокоприоритетными. Другой поток выбирает пакеты с наибольшим весом и помечает их к отправке. Отправка происходит с ограниченной скоростью, из-за этого собственно и получается шейпирование.
Этот механизм позволяет копировать сетевые пакеты в пользовательское пространство для обработки. Приложение, которое слушает соответствующую очередь, должно вынести вердикт относительно пакета (пропустить или отбросить). Кроме этого допускается изменение пакета. Этим механизмом пользуются IDS/IPS типа Snort или Suricata. Пакеты помечаются для обработки в iptables, цель NFQUEUE. То есть мы можем выбрать любое направление (входящий трафик, исходящий, транзитный, или, скажем, UDP с порта 666 на порт 13) и направить его в шейпер. Там пакеты будут анализироваться, возможно изменять свой порядок, а при превышении лимита самые низкоприоритетные будут отбрасываться.
Первые рабочие версии шейпера захватывали пакеты, помещали их в очередь и потом ре-инъектировали в сырой (raw) сокет. На StackOverflow и некоторых других статьях пишут что это единственный способ задерживать пакеты перед перепосылкой. Конструкция работала, но товарищ Vel разъяснил, где можно задерживать пакет прямо в NFQUEUE, настройка шейпера упростилась.
Так как шейпер экспериментальный, я сделал его не монолитным, а «модульным». В каждом модуле вычисляется вес пакета по разным критериям. Из коробки есть 4 модуля, но можно легко написать еще какой-нибудь. Модули можно использовать вместе, можно по отдельности.
Модули:
Веса пакетов, измеренные в каждом модуле, умножаются на коэффициент(каждому модулю можно задать свой) и складываются. Получается результирующий вес.
Шейпер умеет вести статистику и рисовать графики. Вживую это выглядит так: 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, код местами сумбурный и практически не причесывался.
Если у кого-то есть соображения, пожелания и предложения по поводу, было бы интересно почитать.
Как человеку любознательному, всегда было интересно, можно ли сделать процесс настройки шейпинга для небольших сетей проще? Можно ли хотя бы грубо детектировать важный трафик и отделять его от неважного без 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, код местами сумбурный и практически не причесывался.
Если у кого-то есть соображения, пожелания и предложения по поводу, было бы интересно почитать.
Поделиться с друзьями
Комментарии (4)
legioner
19.09.2016 10:14Тесты производительности производились? Не съест ли все бонусы удобства работы высокая нагрузка на процессор?
vmx
19.09.2016 20:57Для себя делал простые тесты (и немного профилирование). Синтетические (флуд-трафиком) и так, на живых людях. Но у всех во-первых разное железо, во-вторых разное использование интернета, в-третьих разный Linux. Гарантированно такие же замеры у других людей дадут другие результаты.
Ну, скажем, на синтетическом UDP-трафике в 200 мегабит, который шейпится до 100м на моей железке процесс damper в топе ест где-то 15-17% CPU. Больше мне не нужно, не тестировал, там скорее всего начнутся какие-то неожиданные штуки.
Ядерные шейперы быстрее, конечно. И отлаженнее, это тоже немаловажно
ZigFisher
Достаточно интересное решение. Спасибо!
На ваш взгляд, будет-ли работать данный код на роутере (а почему-бы и нет?) и насколько сильно будет грузить процессор.
Хочу сделать пакет под OpenWRT. Статистику хранить не планирую, использовать буду без флешки, реалтайм + 1-2 дня аптайма.
В связи с эти пару вопросов-просьб:
1. Можете-ли в конфиге предусмотреть какой-либо ограничитель по сохраняемым данным? Например: 1d, 3d, 1w, 1m и т.д.
2. Можете-ли сделать вариант html страницы, в которой все или часть JS подтягивается с внешних источников. Размер пакета хоть и небольшой получается, но в роутере без флешки ценен каждый десяток килобайт.
Использовать конструкцию планирую на объектах в поле, различных экспедициях, где канал лимитирован, а оборудование работает автономно от солнечных батарей. Вставить такую штуку в WiFi роутер и будет мне счастье ;)
vmx
> будет-ли работать данный код на роутере (а почему-бы и нет?) и насколько сильно будет грузить процессор.
С производительностью непонятно, надо пробовать. На ARM'ах Scaleway топ показывает максимум единицы процентов. Но там и трафика никакого нет. Показ статистики ощутимо сильнее нагружает процессор. Для графиков на каждый запрос генерируется PNG-картинка с максимальной компрессией. Несколько человек одновременно смотрящих статистику едят несколько десятков процентов этих армовских CPU (на современных x86/64 все работает конечно бодрее). Ну, для внутрисетевого сервиса должно быть нормально
> Можете-ли в конфиге предусмотреть какой-либо ограничитель по сохраняемым данным? Например: 1d, 3d, 1w, 1m и т.д.
Звучит разумно. Все равно вечно хранить статистику никто не планирует. Можно сделать дефолтный период в год, например, и дать пользователю возможность его изменять. Надо подумать
> Можете-ли сделать вариант html страницы, в которой все или часть JS подтягивается с внешних источников.
Да, хорошо.
Не могу обещать что сделаю быстро, но постараюсь