Распределение интернет-канала на Linux с HTB

Осуществлять шейпирование трафика будем посредством утилиты tc из пакета iproute2.
Знания без которых нельзя осознать всю полноту управления трафиком в Linux:
Шейпировать можно только исходящий из интерфейса трафик. (в связи с чем возникает проблема с nat, о ней мы поговорим позже). Предположим, что имеется роутер между «интернетом» и абонентом. В роутер воткнуто две сетевые карты: eth0 смотрит на абонента, а eth1 в мир. Для ограничения «скорости скачивания» правила шейпирования описываем на eth0, для ограничения «отдачи» — на eth1.
Необходимо понимать разницу между rate и ceil. Rate — гарантированная полоса пропуская, Ceil — максимальная полоса которую может получить данный класс, rate не может быть больше ceil
Параметры rate и ceil для корневого класса должны совпадать. Таким образом мы определяем общую полосу пропускания.
Сумма Rate’ов классов-потомков, не должна превышать Rate родительского класса. Конечно можно не выполнять этот пункт, но тогда могут возникнуть проблемы с предоставлением «гарантированной полосы пропускания».
Идентификаторы классов указаны в шестнадцатеричной системе, и должны находиться в пределах от 2 до 2^16
Для промежуточных классов необязательно создавать дисциплины и фильтры.
Идентификаторы классов в пределах интерфейса должны быть уникальны.

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

В принципе все просто, но в реальной жизни придется потратить много нервов, что бы добиться желаемого результата.
Один из способов добиться результата не тратя много нервов — использовать скрипт htbinit (взять его можно сsourceforge.net/projects/htbinit/). Синтаксис конфигов простой, создание классов тоже просто.
Для построения конфигураций средней сложность вполне достаточно.
Пример.
Имеется локальная сеть, средних размеров. Вам необходимо ограничивать скорость абонентов согласно прейскуранту, трансляция адресов (NAT) происходит на другом сервере.

Получение htbinit
debian:~# wget downloads.sourceforge.net/project/htbinit/HTB.init/0.8.5/htb.init-v0.8.5?use_mirror=surfnet
debian:~# mv htb.init-v0.8.5 /usr/local/sbin/
debian:~# cd /usr/local/sbin/
debian:/usr/local/sbin# mv htb.init-v0.8.5 htb.init
debian:/usr/local/sbin# chmod +x htb.init

По умолчанию htbinit хранит конфиги в папке /etc/sysconfig/htb.
debian:~# mkdir -p /etc/sysconfig/htb
Создаем коневую дисциплину для интерфейса eth0 (интерфейс смотрит в локальную сеть, «на абонента»)
debian:~# touch /etc/sysconfig/htb/eth0

Указываем в какой класс будет попадать трафик не попавший ни в один из фильтров
debian:~# mcedit /etc/sysconfig/htb/eth0
DEFAULT=42

Создаем корневой класс
debian:~# touch /etc/sysconfig/htb/eth0-2.root
debian:~# mcedit /etc/sysconfig/htb/eth0-2.root
RATE=100Mbit

Можно не указывать CEIL, тогда он автоматом будет равен RATE.

Создаем класс для не классифицированного трафика
debian:~# touch /etc/sysconfig/htb/eth0-2:42.root
debian:~# mcedit /etc/sysconfig/htb/eth0-2:42.root
RATE=4Kbit

Создаем общий клиентский класс. Пускай его ID будет равно «D» и всего для абонентов мы выделим 10 Мбит.
debian:~# touch /etc/sysconfig/htb/eth0-2:D.sumamry_cilents_class
debian:~# mcedit /etc/sysconfig/htb/eth0-2:D.sumamry_cilents_class
RATE=10Mbit

Теперь можно заняться абонентами.
Что бы ограничить скорость скачивания в 512 КБит абоненту Василию нужно:
Создать класс для абонента Василия. Пускай ID клиентов будут начинаться с 255. Весьма полезно, если потребуется создать какой либо «системный» класс.
debian:~# touch /etc/sysconfig/htb/eth0-2:D:100.client_login
Интерфейс, идентификатор класса и родительские классы описываются в названии конфига.
Запись eth0-2:D:100.client_login означает что
eth0- Данный класс принадлежит к интерфейсу eth0
2 Корневой класс
D Родительский класс (В нашем случае общий клиентский класс)
100 Идентификатор класса клента
client_login Мнемоническая составляющая названия файла, несет смысловую нагрузку, в генерации классов не используется

Название файла генерируется таким образом:
1. Интерфейс к которому принадлежит класс.
2. Разделитель «-«(минус)
3. ID корневого класса
4. Разделитель «:»(двоеточие)
5. ID родительского класса
6. Разделитель «:»(двоеточие)
7. ID клиентского класса
8. Разделитель «.»(точка)
9. Мнемоническая составляющая названия файла
Пункты 5 и 6 могут отсутствовать, в зависимость от выбранной Вами стратегии шейпирования.

debian:~# mcedit /etc/sysconfig/htb/eth0-2:D:100.client_login
RATE=512Kbit
LEAF=sfq
RULE=0.0.0.0/0,client_ip/32

По минимуму в конфиге должно быть 3 строки.
RATE — полоса предоставляемая абоненту.
LEAF — указатель на то, что класс конечный и для него необходимо создать создать дисциплину, в нашем случае sfq («псевдочестное» распределение полосы)
RULE — определяем что попадает в класс.Фильтровать можно по ип адресам, подсетям ип адресов, портам. Количество записей RULE может быть более одной.

Если нам нужно шейпировать трафик и натить на том же руотере, то вместо RULE нужно использовать MARK. Единственное придется маркировать пакеты.
Пример скрипта генерирующего конфиги для htbinit в конфигурации с NATом.
#!/bin/bash
#определяем переменные
declare -a rules
inif="eth0"
outif="eth1"
vlan="10"
workdir="/home/netup"
vardir="${workdir}/var"
htb_dir="/etc/sysconfig/htb"

mysql="/usr/bin/mysql -h host -u user -ppassword -D base"
ipt="/usr/local/sbin/iptables"
IPT_UNLIM="UNLIM_"${vlan}
utm_rules="${vardir}/htb_rules_htb"
htb_rules="${vardir}/htb_rules_utm"

#подготавливаем конфиги при первом запуске
[ -e ${htb_rules} ] || touch ${htb_rules}
#полная регенерация конфигов
if [ "${1}" = "zopa" ];then
echo ""> ${htb_rules};
fi

#получаем данные (id в шестнадцатиричной системе)
echo "select id,ip,mask,speed from view_shaper order by id;"|$mysql|sed '1d' > ${utm_rules};
#если достучались к базе и есть разница с текущим конфигом
exp="${utm_rules} ${htb_rules}"
[ -s ${utm_rules} ] && if [ -n "`diff ${exp}`" ]
then

#удаляем конфиги надобность в которых отпала
for i in `diff ${exp}|awk '{if (NF==5)print $2}'|sort -n|uniq`
do
inshaper=${htb_dir}/${inif}-2:${i}.rule_${i};
[ -e ${inshaper} ] && rm ${inshaper};
outshaper=${htb_dir}/${outif}-2:${i}.rule_${i};
[ -e ${outshaper} ] && rm ${outshaper};
done;

#формируем новые конфиги
rules=(`diff ${exp}|grep "<"|awk '{if (NF==5)print $2,$3,$4,$5}'`)
for i in `seq 1 4 ${#rules[@]}`;
do
id=${rules[$i-1]};
ip=${rules[$i]};
mask=${rules[$i+1]};
rate=${rules[$i+2]};

#Формируем названия конфигов
inshaper=${htb_dir}"/"${inif}-2:${id}.rule_${id};
outshaper=${htb_dir}/${outif}-2:${id}.rule_${id};

#Рассчитываем марки для пакетов
inmark="0x"`echo "obase=16; $((0x${id}*2))"|bc`
outmark="0x"`echo "obase=16; $((0x${id}*2+1))"|bc`

#Удаляем «старые» правила маркировки
${ipt} -t mangle -S ${IPT_UNLIM}|grep -w ${ip}|awk -v ipt=${ipt} '{$1="-D";system(ipt" -t mangle "$0)}';
#Создаем конфиг и правила на внутреннем интерфейсе
if [ -e ${inshaper} ]; then
#echo "RULE="0.0.0.0/0,"${ip}"/"${mask}">> ${inshaper}
${ipt} -t mangle -A ${IPT_UNLIM} -s ${ip} -j MARK --set-mark ${inmark}
else
echo "RATE=4096bit" > ${inshaper}
echo "CEIL="$((${rate}*1024))"bit" >>${inshaper}
echo "LEAF=sfq" >> ${inshaper}
#echo "RULE=0.0.0.0/0,"${ip}"/"${mask}>> ${inshaper};
echo "MARK="${inmark}>> ${inshaper};
${ipt} -t mangle -A ${IPT_UNLIM} -d ${ip}/${mask} -j MARK --set-mark ${inmark}
fi

#Создаем конфиг и правила на внутреннем интерфейсе
if [ -e $outshaper ]; then
#echo "RULE="${ip}"/"${mask}",0.0.0.0/0" >> ${outshaper}
${ipt} -t mangle -A ${IPT_UNLIM} -s ${ip}/${mask} -j MARK --set-mark ${outmark}
else
echo "RATE=4096bit" > ${outshaper}
echo "CEIL="$((${rate}*1024))"bit" >> ${outshaper}
echo "LEAF=sfq" >> ${outshaper}
#echo "RULE="${ip}"/"${mask}",0.0.0.0/0" >> ${outshaper}
echo "MARK="${outmark}>> $outshaper;
${ipt} -t mangle -A ${IPT_UNLIM} -s ${ip}/${mask} -j MARK --set-mark ${outmark}
fi
echo $ip
done;
cp ${exp}
[ -e /var/cache/htb.init ] && rm /var/cache/htb.init
/usr/local/sbin/htb.init restart
fi

Для конфигураций до 1000-1500 фильтров такой вариант подойдет. Для более крупных уже нужно использовать быстрые хэш фильтры, о ни и о приоретизации трафика я расскажу в следующей статье.

www.opennet.ru/docs/RUS/LARTC/x1075.html#HIERARCHICALTOKENBUCKET

Динамический шейпер на Linux с HTB

Сформулируем, что же мы хотим получить в результате:
1. Чтобы канал поровну делился между пользователями.
2. Чтобы канал зря не простаивал.
3. Чтобы онлайн-игры, ssh и telnet не «лагали» даже при полной загрузке канала, например торрентами.

Если интернетом будут одновременно пользоваться 10 пользователей — каждый получит в свое распоряжение 1/10 часть канала, если в данный момент активен только один пользователь — он будет использовать весь канал сам.
Добиться этого можно используя планировщик пакетов HTB, который входит в ядро linux начиная с версии 2.4.20.
Можно конфигурировать шейпер с помощью команды tc, но для более удобной и наглядной настройки я рекомендую скачать скрипт htb.init. Они использует для конфигурации htb набор конфигурационных файлов, именуемых так, что при сортировке по алфавиту их имена позволяют визуально представить себе дерево классов шейпера и удобно его редактировать.
Предположим, что у нас на сервере есть интерфейс eth0, через который мы подключены к интернет, и eth1, который «смотрит» в локальную сеть.

Управлять можно только исходящим из интерфейса трафиком, поэтому для eth0 будут правила для upload трафика пользователей, а для — eth1 — download трафика.

По умолчанию конфигурационные файлы htb.init находятся в /etc/htb/. Для начала напишем правила шейпинга для upload трафика, они у нас будут простые.
Создаем файл с именем eth0 (интерейс «смотрящий» в интернет), в него напищем следующие строки:
DEFAULT=20
R2Q=1

Параметр DEFAULT задает номер класса, к которому будет относиться трафик «по умолчанию» — обычно это класс с минимальным приоритетом. Параметр R2Q влияет на работу алгоритма разделения канала и зависит от ширины канала. Я подбирал его значение эмпирическим путем, для моего исходящего канала в 2 Mbit.

Далее, создадим файл eth0-2.full2MBit, для класса включающего в себя весь доступный интернет-канал. Имя файла состоит из имени интерфейса и id класса, после точки идет смысловое имя класса, используется как комментарий и системой игнорируется.
RATE=2Mbit
CEIL=2Mbit

RATE — это наша гарантированная полоса, CEIL — максимальная полоса. Так как у меня канал с гарантированной максимальной полосой в 2 Mbit, то эти параметры у меня равны.

Теперь мы создадим по одному файлу для каждого класса трафика, который у нас будет. Я у себя создал отдельные классы для ssh трафика, а так же трафика игр World Of Warcraft и Counter Strike, хотя вы можете сделать для всего высокоприоритетного трафика один класс.

Пример для ssh — создаем файл eth0-2:10.ssh. В имени файла через двоеточие указан id родительского класса 2 и id текущего класса — 10. Идентификаторы для класса вы можете выбирать произвольно.
# class for outgoing ssh
RATE=128Kbit
CEIL=2Mbit
RULE=*:22
PRIO=1
BURST=100Kb

В параметре RATE указана гарантированная полоса для этого класса, в CEIL — максимальная. Мы выделяем для ssh 128 KBit (как минимум) и разрешаем ему загрузить весь канал (я закачивать файлы по sftp). PRIO задает приоритет класса трафика (1- максимальный, чем больше число — тем меньш приоритет). BURST задает максимальный объем трафика, который будет передан на максимальной скорости перед тем, как перейти к передаче данных из дургих классов. Установив этот параметр в достаточно высокое значение мы добиваемся того, что трафик ssh будет передан с минимальными задержками.
RULE задает правило, по которому будет отбираться трафик в этот класс.
Формат — RULE=[[saddr[/prefix]][:port[/mask]],][daddr[/prefix]][:port[/mask]]
Обратите внимание на запятую! RULE=*:22 обозначает трафик, у которого порт назначения 22, а RULE=*:22, обозначает трафик, у которого исходящий порт — 22.

Создадим так же классы для других видов трафика, и класс для трафика «по умолчанию» с id 20 (мы указали вначале что именно в класс номер 20 надо направлять трафик «по умолчанию»). В нем укажем используемую дисциплину разделения канала LEAF=sfq, для того чтобы upload поровну делился между TCP сессиями разных пользователей.

Для eth1 правила будут почти такие же, только с учетом что общая ширина канала — 100 Mbit, мы ведь хотим чтобы можно было обращаться к локальным ресурсам сервера на полной скорости, для интернет-трафика выделен отдельный класс на 2 MBit, у которого как потомки добавлены классы отдельных пользователей, разделение по классам я делал по IP адресам. Для каждого пользователя можно указать максимальную и гарантированную скорость, а так же приоритет.

После правки конфигурации перезапускаем htb.init:
/etc/init.d/htb.init restart
И правила шейпинга трафика сразу же вступают в силу.

В процессе состевления правил обычно возникает необходимость как-то визуализировать трафик, в целях отладки и мониторинга, поэтому решил написать плагин для системы мониторинга серверов munin, который бы визуализировал распределение по классам HTB трафика. Выводить решил загрузку только классов-листьев дерева, так как именно они обычно несут смысловую нагрузку.
Скачать плагин вы можете из официального репозитория плагинов munin, называется он qos_, просто скопируйте его в папку плагинов munin /usr/share/munin/plugins/ и в папке используемых плагинов /etc/munin/plugins сделайте на него символическую ссылку вида qos_eth1, где eth1 — имя интерфейса, на котором нужно мониторить загрузку.
В файле конфигурации плагинов можно добавить следущее:
[qos_eth1]
env.ignore_queue1_10 yes
env.label_name1_31 Viperet
env.label_name1_32 Cornet

Параметр env.ignore_queue позволяет не отображать на графике состояние класса с указанным id, а параметр env.label_name — задать человекопонятную метку для класса на графике.

UPD: выложил пример конфига htb.init

Использование SC для упрощения настройки HTB шейпера

Речь пойдёт о замечательной утилите sc — предназначенной для упрощения управлением правилами шейпера в Linux.

На данный момент программа обладает следующими возможностями:

  • Шейпинг с помощью классификатора u32 с использованием хэширующих фильтров
  • Полисинг с помощью классификатора u32 с использованием хэширующих фильтров
  • Шейпинг с помощью классификатора flow + управление списками доступа с помошью ipset
  • Хранение данных о пользовательских скоростях в БД (MySQL, PostgreSQL, SQLite)
  • «Ночные скорости» реализованы в виде множителя который можно задавать по cron-у в нужные часы
  • Возможен гибридный режим работы когда на внутреннем интерфейсе осуществляется shaping + policing по обоим направлениям (полезно когда на том же сервере осуществляется NAT)
  • Возможна работа на сервере сетевые интерфейсы которого находятся в режиме Bridge
  • Простота установки и настройки

От себя добавлю, что скрипт действительно работает очень быстро с десятками тысяч правил.
Под CentOS/RHEL 5 не заработает по двум причинам:

1.Старое ядро 2.6.18 в котором нет классификатора flow

2.Старый iproute в котором утилита tc не знает параметра -p (данный параметр активно используется в утилите sc)

В RHEL/CentOS 6 скрипт работает на УРА во всех режимах.

Для примера приведу последовательность действий для настройки шейпинга на одном сервере с NAT.

Допустим:
eth0 — интерфейс смотрящий в сеть интернет с IP адресом 80.247.97.12
eth1 — интерфейс смотрящий в локальную сеть имеет IP адрес 10.0.0.1
Необходимо настроить shaping + policing на интерфейсе eth1

Устанавливаем репозиторий EPEL,CentALT для CentOS 6
Устанавливаем sc.


yum install sc

Поправим файл /etc/sc/sc.conf чтобы он принял следующий вид:

tc = /sbin/tc
out_if = eth0
in_if = eth1
filter_method = u32
limit_method = hybrid
debug = 0
verbose = 0
quiet = 0
colored = 1
joint = 0
network = 10.0.0.0/24
filter_network = 10.0.0.0/24
policer_burst_ratio = 0.3
quantum = 1500
rate_unit = kibit
rate_ratio = 1.0
leaf_qdisc = 'pfifo limit 50'
db_driver = SQLite
db_host = 127.0.0.1
db_name = /etc/sc/sc.db
db_user = username
db_pass = password
query_create = "CREATE TABLE rates (ip UNSIGNED INTEGER PRIMARY KEY, rate UNSIGNED INTEGER NOT NULL)"
query_load = "SELECT ip,rate FROM rates"
query_list = "SELECT ip,rate FROM rates WHERE ip=?"
query_add = "INSERT OR REPLACE INTO rates VALUES (?, ?)"
query_del = "DELETE FROM rates WHERE ip=?"
query_change = "REPLACE INTO rates VALUES (?, ?)"
syslog = 0

Инициализируем

sc init

Создадим файл БД

sc dbcreate

Добавим в него запись для IP нашего сервера

sc dbadd 10.0.0.1 100Mbit

Добавим запись для тестового клиента допустим 10.0.0.2

sc dbadd 10.0.0.1 1Mbit

Синхронизируем правила с БД

sc sync

Собственно на этом вся настройка заканчивается.

Добавление нового клиента заключается в добавлении записи в БД + синхронизации правил с помощью sc sync

Для того чтобы правила стартовали после перезагрузки сервера необходимо внести sc в автозапуск

chkconfig --add sc


chkconfig --level 3 sc on

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *