Шейпирование трафика в Linux

Опубликовано admin в Пнд, 26/07/2010 - 11:49
Под катом описано как шейпировать трафик в Linux и что для этого нужно знать.
Осуществлять шейпирование трафика будем посредством утилиты 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/ (_http://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 (_http://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 фильтров такой вариант подойдет. Для более крупных уже нужно использовать быстрые хэш фильтры, о ни и о приоретизации трафика я расскажу в следующей статье.

Статья взята с http://habrahabr.ru (_http://habrahabr.ru/blogs/sysadm/88624/)