Шейпирование трафика в Linux. Часть 2

Вторая часть статьи об управлении трафиком в Linux. В статье приведены примеры приоретизации трафика (QoS) и рассказано об использовании hash таблиц при фильтрации трафика (fast hash tables), использование которых позволяет существенно увеличить производительность.
В первой части мы остановились на генерации конфигов средней сложность для htbinit. Сегодня мы поговорим о приоретизации трафика и использовании хэш таблиц в фильтрах. Я подразумеваю, что Вы прочитали первую часть статьи .
Предположим, что нам надоели замечания пользователей о высоком пинге, надоели постоянные детские вопли на форуме о том, что в counter-strike играть невозможно: «лаги страшные», или «у меня странички медленно грузятся, вконтакте открывался 10 минут», а писать что все замеры нужно производить при отключенном «торренте» уже нету сил.
Посему тто бы наша совесть была чиста, и мы могли не кривя душой сказать: «проблема на стороне провайдера предоставляющего данный сервис»(обозначить направление движения в сторону южного полюса) — озаботимся приоретизацией трафика.

Приоритеты выставим в таком порядке:
1. icmp
2. udp
3. tcp port 80
4. bulk traffic

Рассмотрим ограничение полосы на внутреннем интерфейсе, управлять будем «скоростью скачивания».
Напомню.
В простом варианте изложения алгоритм нарезки трафика выглядит так:
1. Создаем корневую дисциплину для интерфейса и указываем класс куда будет попадать не классифицированный трафик.
2. Создаем корневой класс и определяем ширину канала.
3. Создаем дочерний класс для шейпирования абонента.
4. Создаем дисциплину шейпирования для класса абонента.
5. Создаем фильтра позволяющие классифицировать трафик абонента.

Пример
#!/bin/bash

#"Очищаем" интерфейс eht1
/sbin/tc qdisc del dev eth1 root handle 1: htb default 15
#Создаем заново дисциплину и указываем дефолтный класс
/sbin/tc qdisc add dev eth1 root handle 1: htb default 15
#Создаем общий для клиентов класс
/sbin/tc class add dev eth1 parent 1: classid 1:1 htb rate 10Mbit ceil 10Mbit

#Создаем корневой класс клиента
/sbin/tc class add dev eth1 parent 1:1 classid 1:10 htb rate 512kbit ceil 512kbit

#Создаем 4 подкласса для каждого из видов трафика
/sbin/tc class add dev eth1 parent 1:10 classid 1:11 htb rate 1kbit ceil 512kbit prio 1
/sbin/tc class add dev eth1 parent 1:10 classid 1:12 htb rate 1kbit ceil 512kbit prio 2
/sbin/tc class add dev eth1 parent 1:10 classid 1:13 htb rate 1kbit ceil 512kbit prio 3
/sbin/tc class add dev eth1 parent 1:10 classid 1:14 htb rate 1kbit ceil 512kbit prio 4

#Создаем дисциплины шейпирования для конечных классов
/sbin/tc qdisc add dev eth1 parent 1:11 handle 11: sfq perturb 10
/sbin/tc qdisc add dev eth1 parent 1:12 handle 12: sfq perturb 10
/sbin/tc qdisc add dev eth1 parent 1:13 handle 13: sfq perturb 10
/sbin/tc qdisc add dev eth1 parent 1:14 handle 14: sfq perturb 10

Создаем 4 фильтра, для каждого из видов трафика
/sbin/tc filter add dev eth1 parent 1:0 protocol ip prio 1 u32 match ip dst 192.168.2.2/32 match ip protocol 1 0xff flowid 1:11
/sbin/tc filter add dev eth1 parent 1:0 protocol ip prio 2 u32 match ip dst 192.168.2.2/32 match ip protocol 17 0xff flowid 1:12
/sbin/tc filter add dev eth1 parent 1:0 protocol ip prio 3 u32 match ip dst 192.168.2.2/32 match ip protocol 6 0xff match ip sport 80 0xffff flowid 1:13
/sbin/tc filter add dev eth1 parent 1:0 protocol ip prio 4 u32 match ip dst 192.168.2.2/32 flowid 1:14


* This source code was highlighted with Source Code Highlighter (_http://virtser.net/blog/post/source-code-highlighter.aspx).


Думаю из примера видно, что для реализации приоретизиции трафика мы создали для каждого отдельный класс. Далее создаем фильтры, причем чем меньше prio, тем больше у трафика приоритет. Вообще правила обрабатываются согласно тому как их добавляли, в случае с приоритетами — первыми обслуживаются правила, имеющие наивысший приоритет.
Так же вы можете каждому из классов обзначить RATE равный одной четвертой от RATE корневого класса клиента, что бы уже наверняка.
Хочу обратить внимание, на то что нам пришлось создать 4 фильтра, для оного ип, а если ип 3-4 тысячи, то получим огромный линейный список правил — в итоге производительность упадет ниже плинтуса.
Что бы этого избежать воспользуемся «быстрыми» хэш таблицами.
О том, что такое хэш таблицы Вы можете посмотреть на Википедии.
Создадим 4 таблицы по 256 ячеек, каждая из таблиц будет оценивать один из октетов ип адреса.
И в конечном итоге, вместо поиска по всему списку, как это было бы в классическом варианте, мы уменьшим количество проверок и значительно снизим нагрузку.
Пример.
#Создаем корневой фильтр
/sbin/tc filter add dev eth1 parent 1:0 protocol ip u32
#Создаем 4 хеш таблицы для каждого октета
/sbin/tc filter add dev eth1 parent 1:0 handle 10: protocol ip u32 divisor 256
/sbin/tc filter add dev eth1 parent 1:0 handle 11: protocol ip u32 divisor 256
/sbin/tc filter add dev eth1 parent 1:0 handle 12: protocol ip u32 divisor 256
/sbin/tc filter add dev eth1 parent 1:0 handle 13: protocol ip u32 divisor 256
#Создаем фильтр направлящий весь трафик в хеш таблицу с ID 10
/sbin/tc filter add dev eth1 parent 1:0 protocol ip u32 ht 801:: match ip dst 0.0.0.0/0 hashkey mask 0xff000000 at 16 link 10:
#Добавляем правило в 10 хеш таблицу, если первый октет равен 192, то оправляем пакет в 11 хеш таблицу
/sbin/tc filter add dev eth1 parent 1:0 protocol ip u32 ht 10:c0: match ip dst 192.0.0.0/8 hashkey mask 0xff0000 at 16 link 11:
#Добавляем правило в 11 хеш таблицу, если второй октет равен 168, то оправляем пакет в 12 хеш таблицу
/sbin/tc filter add dev eth1 parent 1:0 protocol ip u32 ht 11:a8: match ip dst 192.168.0.0/16 hashkey mask 0xff00 at 16 link 12:
#Добавляем правило в 12 хеш таблицу, если третий октет равен 2, то оправляем пакет в 13 хеш таблицу
/sbin/tc filter add dev eth1 parent 1:0 protocol ip u32 ht 12:2: match ip dst 192.168.2.0/24 hashkey mask 0xff at 16 link 13:

#Добавляем правила в 13 хеш таблицу, оцениваем 4 октет и направляем в необходимый класс, в зависимости от вида трафика
/sbin/tc filter add dev eth1 parent 1:0 protocol ip prio 1 u32 ht 13:2: match ip dst 192.168.2.2/32 match ip protocol 1 0xff flowid 1:11
/sbin/tc filter add dev eth1 parent 1:0 protocol ip prio 2 u32 ht 13:2: match ip dst 192.168.2.2/32 match ip protocol 17 0xff flowid 1:12
/sbin/tc filter add dev eth1 parent 1:0 protocol ip prio 3 u32 ht 13:2: match ip dst 192.168.2.2/32 match ip protocol 6 0xff match ip sport 80 0xffff flowid 1:13
/sbin/tc filter add dev eth1 parent 1:0 protocol ip prio 4 u32 ht 13:2: match ip dst 192.168.2.2/32 flowid 1:14


* This source code was highlighted with Source Code Highlighter (_http://virtser.net/blog/post/source-code-highlighter.aspx).

Ниже приведен листинг скрипка генерирующего правила для tc включающие в себя приоретизацию и хеш фильтры.
Скрип полностью рабочий, думаю не составит большого труда модифицировать его под себя.
При переходе с линейных фильтров на хеш таблицы нагрузка упала почти в 4 раза.
#!/bin/bash
mysql="mysql -ppass -u user -h host"
#создаем корневуй дсциплину, класс, корневой фильтр и тиблицы хешей для внешних интерфейсов
echo "select ext_iface from system.shaper_view group by ext_iface;"|$mysql|sed 1d|
awk '{
print "/sbin/tc qdisc del dev "$1" root handle 1: htb default 15";
print "/sbin/tc qdisc add dev "$1" root handle 1: htb default 15";
print "/sbin/tc class add dev "$1" parent 1: classid 1:1 htb rate 10Mbit ceil 10Mbit";
print "/sbin/tc filter add dev "$1" parent 1:1 prio 10 protocol ip u32";
print "/sbin/tc filter add dev "$1" parent 1:0 protocol ip u32";
print "/sbin/tc filter add dev "$1" parent 1:0 handle 10: protocol ip u32 divisor 256";
print "/sbin/tc filter add dev "$1" parent 1:0 handle 11: protocol ip u32 divisor 256";
print "/sbin/tc filter add dev "$1" parent 1:0 handle 12: protocol ip u32 divisor 256";
print "/sbin/tc filter add dev "$1" parent 1:0 handle 13: protocol ip u32 divisor 256";
print "/sbin/tc filter add dev "$1" parent 1:0 protocol ip u32 ht 801:: match ip dst 0.0.0.0/0 hashkey mask 0xff000000 at 16 link 10:";
}'

#создаем корневуй дсциплину, класс, корневой фильтр и тиблицы хешей для внутренних интерфейсов
echo "select int_iface from system.shaper_view group by int_iface;"|$mysql|sed 1d|
awk '{
print "/sbin/tc qdisc del dev "$1" root handle 1: htb default 15";
print "/sbin/tc qdisc add dev "$1" root handle 1: htb default 15";
print "/sbin/tc class add dev "$1" parent 1: classid 1:1 htb rate 10Mbit ceil 10Mbit";
print "/sbin/tc filter add dev "$1" parent 1:1 prio 10 protocol ip u32";
print "/sbin/tc filter add dev "$1" parent 1:0 protocol ip u32";
print "/sbin/tc filter add dev "$1" parent 1:0 handle 10: protocol ip u32 divisor 256";
print "/sbin/tc filter add dev "$1" parent 1:0 handle 11: protocol ip u32 divisor 256";
print "/sbin/tc filter add dev "$1" parent 1:0 handle 12: protocol ip u32 divisor 256";
print "/sbin/tc filter add dev "$1" parent 1:0 handle 13: protocol ip u32 divisor 256";
print "/sbin/tc filter add dev "$1" parent 1:0 protocol ip u32 ht 801:: match ip src 0.0.0.0/0 hashkey mask 0xff000000 at 12 link 10:";
}'


#создаем классы, фильтры,дисциплины и при необходимости заполняем таблицы хешей
echo "select user_id,ip,speed,int_iface,ext_iface from system.shaper_view order by user_id;"|$mysql|sed 1d|
awk '
BEGIN{
buf=0;
class_id=255;
tc_class="/sbin/tc class add dev";
tc_qdisc="/sbin/tc qdisc add dev";
tc_filter="/sbin/tc filter add dev";
}
{
if(buf!=$1)
{
printf "%s%x%s\n", "# ",class_id," "class_id" "$2;
client_speed=$3*128;
printf "%s%x%s\n",tc_class" "$4" parent 1:1 classid 1:",++class_id," htb rate "client_speed"bps ceil "client_speed"bps";
printf "%s%x%s\n",tc_class" "$5" parent 1:1 classid 1:",class_id," htb rate "client_speed"bps ceil "client_speed"bps";
client_class=class_id;
printf "%s%x%s%x%s\n",tc_class" "$4" parent 1:",client_class," classid 1:",++class_id," htb rate 1024bps ceil "client_speed"bps prio 1";
printf "%s%x%s%x%s\n",tc_class" "$5" parent 1:",client_class," classid 1:",class_id," htb rate 1024bps ceil "client_speed"bps prio 1";
printf "%s%x%s%x%s\n",tc_qdisc" "$4" parent 1:",class_id," handle ",class_id,": sfq perturb 10";
printf "%s%x%s%x%s\n",tc_qdisc" "$5" parent 1:",class_id," handle ",class_id,": sfq perturb 10";
printf "%s%x%s%x%s\n",tc_class" "$4" parent 1:",client_class," classid 1:",++class_id," htb rate 1024bps ceil "client_speed"bps prio 2";
printf "%s%x%s%x%s\n",tc_class" "$5" parent 1:",client_class," classid 1:",class_id," htb rate 1024bps ceil "client_speed"bps prio 2";
printf "%s%x%s%x%s\n",tc_qdisc" "$4" parent 1:",class_id," handle ",class_id,": sfq perturb 10";
printf "%s%x%s%x%s\n",tc_qdisc" "$5" parent 1:",class_id," handle ",class_id,": sfq perturb 10";
printf "%s%x%s%x%s\n",tc_class" "$4" parent 1:",client_class," classid 1:",++class_id," htb rate 1024bps ceil "client_speed"bps prio 3";
printf "%s%x%s%x%s\n",tc_class" "$5" parent 1:",client_class," classid 1:",class_id," htb rate 1024bps ceil "client_speed"bps prio 3";
printf "%s%x%s%x%s\n",tc_qdisc" "$4" parent 1:",class_id," handle ",class_id,": sfq perturb 10";
printf "%s%x%s%x%s\n",tc_qdisc" "$5" parent 1:",class_id," handle ",class_id,": sfq perturb 10";
printf "%s%x%s%x%s\n",tc_class" "$4" parent 1:",client_class," classid 1:",++class_id," htb rate 1024bps ceil "client_speed"bps prio 4";
printf "%s%x%s%x%s\n",tc_class" "$5" parent 1:",client_class," classid 1:",class_id," htb rate 1024bps ceil "client_speed"bps prio 4";
printf "%s%x%s%x%s\n",tc_qdisc" "$4" parent 1:",class_id," handle ",class_id,": sfq perturb 10";
printf "%s%x%s%x%s\n",tc_qdisc" "$5" parent 1:",class_id," handle ",class_id,": sfq perturb 10";
}
split($2,ip,".");
if (ht_1[ip[1]]!=1)
{
printf "%s%x%s\n",tc_filter" "$4" parent 1:0 protocol ip u32 ht 10:",ip[1],": match ip dst "ip[1]".0.0.0/8 hashkey mask 0xff0000 at 16 link 11:";
printf "%s%x%s\n",tc_filter" "$5" parent 1:0 protocol ip u32 ht 10:",ip[1],": match ip src "ip[1]".0.0.0/8 hashkey mask 0xff0000 at 12 link 11:";
ht_1[ip[1]]=1;
}
if (ht_2[ip[2]]!=1)
{
printf "%s%x%s\n",tc_filter" "$4" parent 1:0 protocol ip u32 ht 11:",ip[2],": match ip dst "ip[1]"."ip[2]".0.0/16 hashkey mask 0xff00 at 16 link 12:";
printf "%s%x%s\n",tc_filter" "$5" parent 1:0 protocol ip u32 ht 11:",ip[2],": match ip src "ip[1]"."ip[2]".0.0/16 hashkey mask 0xff00 at 12 link 12:";
ht_2[ip[2]]=1;
}
if (ht_3[ip[3]]!=1)
{
printf "%s%x%s\n",tc_filter" "$4" parent 1:0 protocol ip u32 ht 12:",ip[3],": match ip dst "ip[1]"."ip[2]"."ip[3]".0/24 hashkey mask 0xff at 16 link 13:";
printf "%s%x%s\n",tc_filter" "$5" parent 1:0 protocol ip u32 ht 12:",ip[3],": match ip src "ip[1]"."ip[2]"."ip[3]".0/24 hashkey mask 0xff at 12 link 13:";
ht_3[ip[3]]=1;
}
printf "%s%x%s%x\n",tc_filter" "$4" parent 1:0 protocol ip prio 1 u32 ht 13:",ip[4],": match ip dst "$2"/32 hashkey mask 0x0 at 16 match ip protocol 1 0xff flowid 1:",client_class
printf "%s%x%s%x\n",tc_filter" "$5" parent 1:0 protocol ip prio 1 u32 ht 13:",ip[4],": match ip src "$2"/32 hashkey mask 0x0 at 12 match ip protocol 1 0xff flowid 1:",client_class
printf "%s%x%s%x\n",tc_filter" "$4" parent 1:0 protocol ip prio 2 u32 ht 13:",ip[4],": match ip dst "$2"/32 hashkey mask 0x0 at 16 match ip protocol 17 0xff flowid 1:",++client_class
printf "%s%x%s%x\n",tc_filter" "$5" parent 1:0 protocol ip prio 2 u32 ht 13:",ip[4],": match ip src "$2"/32 hashkey mask 0x0 at 12 match ip protocol 17 0xff flowid 1:",client_class
printf "%s%x%s%x\n",tc_filter" "$4" parent 1:0 protocol ip prio 3 u32 ht 13:",ip[4],": match ip dst "$2"/32 hashkey mask 0x0 at 16 match ip protocol 6 0xff match ip sport 80 0xffff flowid 1:",++client_class
printf "%s%x%s%x\n",tc_filter" "$5" parent 1:0 protocol ip prio 3 u32 ht 13:",ip[4],": match ip src "$2"/32 hashkey mask 0x0 at 12 match ip protocol 6 0xff match ip dport 80 0xffff flowid 1:",client_class
printf "%s%x%s%x\n",tc_filter" "$4" parent 1:0 protocol ip prio 4 u32 ht 13:",ip[4],": match ip dst "$2"/32 hashkey mask 0x0 at 16 flowid 1:",++client_class
printf "%s%x%s%x\n",tc_filter" "$5" parent 1:0 protocol ip prio 4 u32 ht 13:",ip[4],": match ip src "$2"/32 hashkey mask 0x0 at 12 flowid 1:",client_class
buf=$1;
}'

* This source code was highlighted with Source Code Highlighter (_http://virtser.net/blog/post/source-code-highlighter.aspx).


Надеюсь данный опус помог Вам вникнуть в основы управления трафиком в Linux.

Источник: alexandr.sysoev.ru