Шейпер для больших сетей
Шейпер для больших сетей (IPv4/IPv6) средствами iptables, ipset, tc
Особенности работы с большими сетями
В случае, если необходимо назначать индивидуальную скорость для сотен и тысяч адресов в сети, встроенный в etcnet инструментарий eqos становится мало пригодным. По той причине, что для отработки одного правила, в shell-скриптах производится десятки вызовов внешних программ и других скриптов. В результате останов и запуск сайтов с настройками для 3 тыс. классов может растянуться до получаса. В eqos из etcnet фильтры tc создаются линейного типа. В этом случае пакет будет обходить все правила фильтрации, пока не попадет на подходящий, либо не будет отправлен в фильтр по-умолчанию. При количестве правил фильтрации в несколько тысяч, и трафике в десятки тысяч пакетов в секунду, растет загрузка CPU системы и параллельно растут задержки прохождения транзитных пакетов. Это, в свою очередь, отрицательно влияет на комфортность работы с сетью. Аналогичные последствия и при злоупотреблении линейными правилами фильтрации в iptables/ip6tables.
Поэтому не используем eqos, сводим до минимума количество правил в iptables/ip6tables и в tc отказываемся от линейных фильтров.
В случае сети, в которой используется только IPv4, фильтры tc можно построить на основе хэш-таблиц
Предварительные требования к системе
В системе должен быть установлены пакеты
- iproute2-3.8.0-alt1 и новее;
- ipset-6.23-alt1 и новее;
- kernel-modules-ipset-*-6.24-alt1 и новее;
- iptables-1.4.21-alt1.M70P.1 и новее;
Создание дисциплин, классов и очередей
Так как управление трафиком для сотен и тысяч адресов в ручном режиме никто в здравом уме делать не будет, все приведенные здесь конфигурационные файлы есть результат выполнения сторонних программ и скриптов, если это особо не указано. Все параметры для tc и ipset загружаются в batch-режиме.
Итак, в качестве корневой дисциплины была выбрана hfsc, как более экономная к процессорному времени, и ее реализация в ядре linux не имеет global-locks, в отличии от htb, в результате чего нагрузка эффективно распределяется по всем процессорным ядрам в системе.
Пример batch-файла для tc:
/tmp/shape-tc.hfsc: # bondEXT - интерфейс в сторону мира # bondINT - интерфейс в сторону клиентов # # Чистим корневую дисциплину от всех предыдущих настроек qdisc del dev bondEXT root qdisc del dev bondINT root # назначаем корневую дисциплину, и определяем класс по-умолчанию для трафика, не попавшего ни в один определеный класс qdisc add dev bondEXT root handle 1: est 1sec 8sec hfsc default ffff qdisc add dev bondINT root handle 1: est 1sec 8sec hfsc default ffff # создаем корневой класс и определяем его параметры class add dev bondEXT parent 1: classid 1:1 est 1sec 8sec hfsc sc rate 2Gbit ul rate 2Gbit class add dev bondINT parent 1: classid 1:1 est 1sec 8sec hfsc sc rate 2Gbit ul rate 2Gbit # создаем клдасс по-умолчанию для трафика, не попавшего в определенный класс # !!!! ВАЖНО!!!! этот класс, как и фильтр по-умолчанию должен быть создан. filter add dev bondEXT parent 1: protocol ip prio 1000 u32 match u32 0 0 classid 1:ffff filter add dev bondINT parent 1: protocol ip prio 1000 u32 match u32 0 0 classid 1:ffff class add dev bondEXT parent 1: classid 1:ffff est 1sec 8sec hfsc sc umax 1500 dmax 150ms rate 1000kbit ul rate 100Mbit class add dev bondINT parent 1: classid 1:ffff est 1sec 8sec hfsc sc umax 1500 dmax 150ms rate 1000kbit ul rate 100Mbit qdisc add dev bondEXT parent 1:ffff handle ffff: pfifo limit 50000 qdisc add dev bondINT parent 1:ffff handle ffff: pfifo limit 50000
Пример правил для непосредственно управляемых адресов:
class add dev bondINT parent 1: classid 1:b est 1sec 8sec hfsc sc umax 1500b dmax 10ms rate 92160kbit ul rate 92160kbit qdisc add dev bondINT parent 1:b handle b: pfifo limit 200 class add dev bondEXT parent 1: classid 1:b est 1sec 8sec hfsc sc umax 1500b dmax 10ms rate 92160kbit ul rate 92160kbit qdisc add dev bondEXT parent 1:b handle b: pfifo limit 200 # class add dev bondINT parent 1: classid 1:c est 1sec 8sec hfsc sc umax 1500b dmax 5ms rate 2048kbit ul rate 2048kbit qdisc add dev bondINT parent 1:c handle c: pfifo limit 200 class add dev bondEXT parent 1: classid 1:c est 1sec 8sec hfsc sc umax 1500b dmax 5ms rate 2048kbit ul rate 2048kbit qdisc add dev bondEXT parent 1:c handle c: pfifo limit 200 # class add dev bondINT parent 1: classid 1:d est 1sec 8sec hfsc sc umax 1500b dmax 5ms rate 20480kbit ul rate 20480kbit qdisc add dev bondINT parent 1:d handle d: pfifo limit 200 class add dev bondEXT parent 1: classid 1:d est 1sec 8sec hfsc sc umax 1500b dmax 5ms rate 20480kbit ul rate 20480kbit qdisc add dev bondEXT parent 1:d handle d: pfifo limit 200 ... class add dev bondINT parent 1: classid 1:d44 est 1sec 8sec hfsc sc umax 1500b dmax 10ms rate 30720kbit ul rate 30720kbit qdisc add dev bondINT parent 1:d44 handle d44: pfifo limit 200 class add dev bondEXT parent 1: classid 1:d44 est 1sec 8sec hfsc sc umax 1500b dmax 10ms rate 30720kbit ul rate 30720kbit qdisc add dev bondEXT parent 1:d44 handle d44: pfifo limit 200
Классом 1:b назначена скорость 90Mbit, 1:c - 2Mbit, 1:d - 20Mbit и так далее вплоть до 1:d44 - 30Mbit. Обратите внимание, что в tc определения для классов, очередей и фильтров задаются в шестнадцатиричной системе в диапазоне 1-ffff.
Классификация трафика
Классифицировать трафик можно несколькими способами. Методы u32 match ip dst|src и handle MARK fw flowid нам не подходят, особенно второй метод, когда пакет анализируется два раза - один раз в iptables, когда выставляется MARK, и второй раз, когда помаркорованный пакет вторично анализируется в фильтре tc. Более производителей метод с использованием хэш-таблиц, более подробно можно почитать тут - [1]. Но этот метод подходит только когда в сети используется исключительно IPv4. Отдельно для IPv6 хэштаблицы не реализованы, а обходные методы не обеспечивают должной гибкости и простоты понимания для человека.
Поэтому был использован третий способ.
В ipset, начиная с версии 6.22 была добавлена очень полезный инструмент - оперированием структурой skb на основании правил iptables/ip6tables. Теперь логику классификации можно вынести в правила iptables и ipset.
Для удобства, здесь используется функционал etcnet.
- Создаем сеты, в которые будем вносить адреса:
IPv4 /etc/net/ifaces/default/fw/ipset/nethash/shaper4: family inet skbinfo IPv6 /etc/net/ifaces/default/fw/ipset/nethash/shaper6: family inet6 skbinfo
- Создаем правила iptables, которые будут производить классификацию трафика:
IPv4 /etc/net/ifaces/default/fw/iptables/mangle/POSTROUTING: -o bondEXT -j SET --map-set shaper4 src --map-prio -o bondINT -j SET --map-set shaper4 dst --map-prio IPv6 /etc/net/ifaces/default/fw/ip6tables/mangle/POSTROUTING: -o bondEXT -j SET --map-set shaper6 src --map-prio -o bondINT -j SET --map-set shaper6 dst --map-prio
- С помощью стороннего скрипта генерируем batch-файл для ipset такого вида:
/tmp/shape-set.txt: flush shaper4 flush shaper6 # add shaper4 172.16.17.196/32 skbprio 1:b # add shaper4 172.16.17.220/32 skbprio 1:c # add shaper4 172.16.1.2/32 skbprio 1:d add shaper4 172.16.1.5/32 skbprio 1:d add shaper4 172.16.1.10/32 skbprio 1:d add shaper4 172.16.1.12/32 skbprio 1:d add shaper4 172.16.1.14/32 skbprio 1:d add shaper4 172.16.1.31/32 skbprio 1:d add shaper4 172.16.1.103/32 skbprio 1:d add shaper4 172.16.1.128/32 skbprio 1:d add shaper4 172.16.1.184/32 skbprio 1:d add shaper4 172.16.2.115/32 skbprio 1:d add shaper4 172.16.5.198/32 skbprio 1:d add shaper4 172.16.6.38/32 skbprio 1:d add shaper4 172.16.6.58/32 skbprio 1:d add shaper4 172.16.6.69/32 skbprio 1:d add shaper4 172.16.9.228/32 skbprio 1:d add shaper4 172.16.10.208/32 skbprio 1:d add shaper4 172.16.11.67/32 skbprio 1:d add shaper4 172.16.11.68/32 skbprio 1:d add shaper4 172.16.11.147/32 skbprio 1:d add shaper4 172.16.15.213/32 skbprio 1:d add shaper4 172.16.15.214/32 skbprio 1:d add shaper4 172.16.17.75/32 skbprio 1:d ... add shaper4 172.16.17.231/32 skbprio 1:d44 add shaper6 XXXX:4680:26:0:0:0:0:202/124 skbprio 1:d44
обратите внимание, что адреса IPv4 заносятся в сет shaper4, адреса для IPv6 - в shaper6. В этих сетах и происходит присвоение трафику определенные классы. Соответствие идентификаторов классов в batch-файлах tc и ipset возлагается на внешний скрипт генерации.
Загрузка параметров в систему
Загрузка правил производится по крону, запуском такого скрипта:
/usr/local/sbin/shaper.sh: #!/bin/sh ВЫЗОВ_СКРИПТА_ГЕНЕРИРУЮЩЕГО_shape-tc.hfsc_И_shape-set.txt if [ ! -f /tmp/shape-tc.old ]; then touch /tmp/shape-tc.old fi if [ ! -f /tmp/shape-set.old ]; then touch /tmp/shape-set.old fi # данные меняются не так часто, поэтому реальная я перезагрузка # производится только когда что-то поменялось T=`diff /tmp/shape-tc.old /tmp/shape-tc.hfsc` if [ "$T" != "" ] then /sbin/tc -force -batch /tmp/shape-tc.hfsc > /dev/null 2>&1 /sbin/ipset -! restore < /tmp/shape-set.txt > /dev/null 2>&1 mv -f /tmp/shape-tc.hfsc /tmp/shape-tc.old mv -f /tmp/shape-set.txt /tmp/shape-set.old else T=`diff /tmp/shape-set.old /tmp/shape-set.txt` if [ "$T" != "" ] then /sbin/tc -force -batch /tmp/shape-tc.hfsc > /dev/null 2>&1 /sbin/ipset -! restore < /tmp/shape-set.txt > /dev/null 2>&1 mv -f /tmp/shape-tc.hfsc /tmp/shape-tc.old mv -f /tmp/shape-set.txt /tmp/shape-set.old fi fi
Полисинг для больших сетей (IPv4/IPv6) средствами nftables
Отличия полсинга от шейпера
Важным отличием полисера от шейпера в том, что полисер при своей работе безусловно отбрасывает те передаваемые пакеты, которые создают превышение по заданной полосе пропускания, в то время как шейпер за счет применения алгоритмов буферизации сглаживает пики превышения. Шейпер оптимально работает в случаях, когда требуется достаточно точно задавать полосы пропускания до десятков мегабит в секунду, при более высоких скоростях происходит большой перерасход памяти на буферизацию задершиваемых данных, высокой нагрузке CPU и лавинообразному падению суммарной производительности системы. Полисер лишен этих недостатков, с его помощью можно задавать полосы пропускания от десятков мегабит до десятков гигабит в секунду.
Реализация средствами nftables
Используя подсистему nftables все правила классификации трафика и присвоения полосы реализуются одним инструментов в одном месте. И за счет того, что правила фильтрации предварительно компилируются в пространстве пользователя и потом загружаются и исполняются в BPF виртуальной машине, значительно повышается производительность системы.
Предварительные требования к системе
В системе должен быть установлены пакеты
- kernel-image-std-def-5.15.23-alt1 и новее
- libmnl-1.0.4-alt2
- libnftnl-1.2.1-alt1
- nftables-1.0.1-alt1
Предварительная конфигурация
Здесь мы создаем таблицу mangle с типом inet, при котором обрабатывается и ipv4 и ipv6 трафик, задаем какие подсети у нас являются локальными, создаем verdict-сеты для ipv4 и ipv6 адресов клиентов на входящий и исходящий трафик, задаем полосу пропускания для локального трафика (обратите внимание, скорость задается в байтах, килобайтах, мегабайтах в секунду) и в цепочках POSTROUTING и PREROUTING указываем правила классификации трафика через механизм vmap
Создаем файл mangle.nft:
table inet mangle { set localnet4 { type ipv4_addr flags interval elements = { 5.xxx.xxx.0/22, 193.xxx.xxx.0/23, 100.64.0.0/10, 172.16.0.0/12, 10.0.0.0/16, 10.1.1.0/24 } } set localnet6 { type ipv6_addr flags interval elements = { fe80::/10, 2a0e:xxxx::/29, fd00::/8 } } map poly_u_4 { type ipv4_addr : verdict flags interval counter } map poly_d_4 { type ipv4_addr : verdict flags interval counter } map poly_u_6 { type ipv6_addr : verdict flags interval counter } map poly_d_6 { type ipv6_addr : verdict flags interval counter } chain POSTROUTING { type filter hook postrouting priority mangle; policy accept; ip daddr @localnet4 ip saddr @localnet4 goto inet_down ip6 daddr @localnet6 ip6 saddr @localnet6 goto inet_down ip daddr vmap @poly_d_4 ip6 daddr vmap @poly_d_6 } chain PREROUTING { type filter hook prerouting priority mangle; policy accept; ip daddr @localnet4 ip saddr @localnet4 goto inet_down ip6 daddr @localnet6 ip6 saddr @localnet6 goto inet_down ip saddr vmap @poly_u_4 ip6 saddr vmap @poly_u_6 } chain inet_down { # If from localnet - accept limit rate over 10000000 kbytes/second counter drop } }
Загружаем его через nft -o -f mangle.nft
Правила для задания полосы пропускания
Применение nft add chain / nft add element на массивах в тысячи едениц крайне непроищводительно, то по данным из биллинга скриптами генерим файл конфигурации такого вида:
table inet mangle { chain policer_dl_10372 { limit rate over 640 kbytes/second burst 128 kbytes counter drop } chain policer_ul_10372 { limit rate over 640 kbytes/second burst 128 kbytes counter drop } chain policer_dl_10612 { limit rate over 192 kbytes/second burst 38 kbytes counter drop } chain policer_ul_10612 { limit rate over 192 kbytes/second burst 38 kbytes counter drop } map poly_d_4 { type ipv4_addr : verdict flags interval elements = { 100.64.2.102/32 : goto policer_dl_10372, 100.64.2.73/32 : goto policer_dl_10612 } } map poly_u_4 { type ipv4_addr : verdict flags interval elements = { 100.64.2.102/32 : goto policer_ul_10372, 100.64.2.73/32 : goto policer_ul_10612 } } map poly_d_6 { type ipv6_addr : verdict flags interval elements = { 2a0e:xxxx:xxxx:e83:0:0:0:0/64 : goto policer_dl_10612 } } map poly_u_6 { type ipv6_addr : verdict flags interval elements = { 2a0e:xxxx:xxxx:e83:0:0:0:0/64 : goto policer_ul_10612 } } }
И загружаем его в один прием через
nft -o -f filename.nft
В результате новые цепочки и элементы vmap будут добавлены к существующему содержимому таблицы mangle.
Итоговый результат можно посмотреть через:
nft list table inet mangle