...
...
...

Advanced Routing и QoS для Linux

редакционное вступление

Недавно в редакционном вступлении к одной из статей я позволила себе замечание о том, что «Сетевые Решения» уникальны тем, что за годы своего существования практически не повторялись в темах статей. Вот и сглазила :) Два замечательных автора — Иван Песин и Андрей Горев — практически одновременно (с разбежкой в месяц, статья Ивана вышла в предыдущем номере «СР») предлагают материалы на тему управления трафиком под Linux.
Но даже если вы прочитали статью Песина, не спешите пролистнуть этот материал. Во-первых, здесь вы найдете информацию по расширенной маршрутизации (Advanced Routing), чего вовсе не было в предыдущей статье. Во-вторых, сдесь есть два больших раздела по HTB со сравнительной характеристикой этого алгоритма (сравниваем с CBQ) и отличным примером. В третьих, у каждого автора свой стиль и свои взгляды на то, что важно, а что не очень, каким деталям следует уделить больше внимания, а какие можно упомянуть вскользь. Возможно, вы чего-то недопоняли из предыдущего материала, и этот момент вам покажется более внятно изложенным здесь.
Да и вообще, тема модная и актуальная. Так что можно и повториться :)

введение

Алексей Кузнецов и NET core team уже давно и небезуспешно разрабатывают сетевые функции ядра Линукс. Например вот что мы можем делать, используя только ядро:
— управлять полосой пропускания для разных компьютеров;
— успешно разделять полосу пропускания между разными типами трафика;
— приоритизировать трафик в полосе, согласно нашим нуждам (Quality of Service);
— защищаться от DoS-атак;
— фильтровать трафик (firewall);
— использовать один реальный адрес, для работы всей локалки в Интернет (SNAT);
— объединять несколько каналов в один и балансировать нагрузку;
— ограничивать доступ к сервисам;
— перебрасывать порты с одной машины на другую (DNAT);
— ограничивать полосу пропускания как входящего, так и исходящего трафика;
— маршрутизировать по портам, адресам, MAC-адресу, TOS, времени дня, и даже по имени пользователя;
— туннелирование, multicast routing, IPSec, и многое другое.
Все эти вещи обычно не нужны для рабочей станции, однако если вы управляете маршрутизатором (router), то это поможет в работе. Во многом возможности ядра Линукс на уровне, или даже опережают возможности Cisco IOS, но, к сожалению, отстают в этой области по документированности всилу большой занятости разработчика. Однако ниже будут приведены ссылки, где искать дальнейшую информацию.
Половина из вышеперечисленного реализуется использованием iptables, но в данном обзоре все-же рассматриваются особенности ip/tc. Тем не менее, ссылаться на iptables иногда придется.
Итак, вы должны хотя-бы в общих чертах представлять себе, как работает стек TCP/IP, фильтрация на базе ipchains/iptables, и прочитали, например, Network Administration Guide или любое другое руководство по базовому функционированию сетей в Линукс.

Iproute2

Для вас может оказаться сюрпризом, что вышеперечисленные свойства ядра уже по умолчанию включены в него и работают! Просто работают они все по умолчанию, то есть, по сути, ничего особенного не делают. Более того, старинные команды типа route, arp, ifconfig просто ничего не покажут вам из тех возможностей, которые уже давно, начиная с ядер 2.2, имеются на вооружении. Для того, чтобы задействовать, настроить, или хотя-бы увидеть дополнительные возможности ядра, необходимо установить пакет iproute2. Автор утилит, вошедших в пакет — опять-же Алексей Кузнецов. Центральная утилита пакета — “ip” — заменяет собой все вышеперечисленные “старинные” команды, помогает реализовать возможности многотабличной маршрутизации (advanced routing), туннелирования и multicast routing. Кроме того, Линукс имеет гибкую систему управления трафиком, называемую Traffic Control. Эта система поддерживает множество методов для классификации, приоритизации, разделения и ограничения (shaping) обоих типов трафика — входящего и исходящего. Управлять всем этим позволяет вторая утилита пакета — “tc”. Подчеркну, что обе утилиты являются как-бы интерпретаторами команд, а весь функционал выполняет ядро.

утилита ip

Даже если вы только установили пакет iproute2 и ничего не настраивали, вы уже можете просмотреть некоторую новую информацию о своей системе. Например (в скобках сокращенная нотация):
ip link list (ip l l) —покажет конфигурацию интерфейсов;
ip address show (ip a sh) —покажет IP-адреса на интерфейсах;
ip route show (ip ro sh) —покажет таблицу маршрутов main (сравните с route -n);
ip neigh show (ip n sh) —покажет таблицу ARP;
ip rule list (ip ru l) —покажет список правил, согласно которым принимается решение о маршрутизации.
По умолчанию у ядра имеется три таблицы маршрутов — local, main и default. Старая утилита route показывает содержимое только таблицы main. Таблицы local и default — новые. Мы сами можем создавать свои таблицы маршрутов и с помощью правил (ip rules) указывать, какой трафик маршрутизировать согласно какой таблице. Классический механизм маршрутизации основан на поле destination address пакета. Мы же можем, кроме этого, управлять маршрутизацией, используя поле TOS пакета, или согласно fwmark, который установил на пакет iptables. В этом и заключается концепция Advanced Routing. Детальную семантику команды ip, и настройку NAT на ее основе, можно найти в ip-cref Алексея Кузнецова. Этот документ есть в составе пакета iproute2.

Advanced Routing

Теперь приведем несколько примеров, иллюстрирующих возможности табличной маршрутизации.Для начала рассмотрим вариант простейшей маршрутизации по адресу src. Предположим у нас есть высокоскоростной и дорогой линк на провайдера (xDSL) и медленный, но дешевый линк по коммутируемому доступу (dial-up). Маршрут по умолчанию в основной таблице установлен на xDSL, но мы хотим одну из машин внутренней сети направить в наш медленный линк и освободить таким образом основной канал. Для этого мы создадим для этой машины отдельную таблицу маршрутов, которую назовем Manager:

# echo 100 Manager >> /etc/iproute2/rt_tables

Теперь создаем правило-селектор по адресу нашей выделенной машины, чтобы маршрутизация для нее переходила в новую таблицу:

# ip rule add from 191.216.121.1 table Manager

Осталось добавить маршрут по умолчанию в таблицу Manager (там пока пусто) и сбросить кэш маршрутов:

# ip route add default via 191.216.121.14 dev ppp2 table Manager
# ip route flush cache


Все готово. Здесь ppp2 — наш дешевый линк. Это можно было, конечно, сделать и не создавая отдельную таблицу маршрутов, это просто демонстрация создания таблицы.
Далее предположим, что нам надо направить в dial-up весь SMTP-трафик, идущий с внутреннего адреса. Используя iptables помечаем пакеты SMTP:

# iptables -t mangle -I PREROUTING -p tcp -s 10.0.0.1 --sport 25 -j MARK --set-mark 0x10


Маскарадим отмеченные пакеты, и отправляем их в таблицу Manager, содержащую маршрут в ppp2:

# ip rule add fwmark 10 preference 200 nat 191.216.121.2 table Manager

NAT-ить можно и на уровне iptables, но заворачивать отмеченные маркой пакеты в другую таблицу, и следовательно интерфейс, все-равно надо приблизительно вышеприведенным правилом. Опять-же, как и в предыдущем случае, здесь таблицу Manager можно было не создавать. Приблизительно по такому-же принципу строится маршрутизация на основе TOS.
Сейчас рассмотрим ситуацию с разделением каналов. Предположим, у нас есть два линка на разных провайдеров. Создаем для каждого свою таблицу маршрутов и правила, привязывающие соответственные сети провайдеров к нужным интерфейсам. Это гарантирует нам возврат пакета, отправленного через одного из провайдеров от него-же. Однако если маршрут по умолчанию будет указывать только на один линк, мы получим разбалансированную нагрузку по каналам. Для решения этой проблемы маршрут по умолчанию укажем на два устройства сразу:

# ip route add default scope global nexthop via $ip_prov1 dev ppp0 weight 1 nexthop via $ip_prov2 dev ppp1 weight 1

Это сбалансирует маршруты через обоих провайдеров. Параметр weight может быть настроен для перевеса нагрузки в ту или иную сторону. Однако надо отметить, что вы не сможете угадать, по какому каналу уходит тот или иной пакет (не входящий, а исходящий), не прописывая статические маршруты. Еще можно сконфигурировать EQL-устройство, использовать match limit iptables (об этом я писал в СР №11), или применить хитроумную балансировку каналами на основе Nano-HOWTO, но это все выходит за рамки данного обзора. Напомню, что существует “Ip Command Reference” в составе пакета iproute2. Кроме всего прочего, ip корректно сосуществует и дополняет пакеты динамической маршрутизации (zebra, gated).

управление трафиком

Для управления трафиком в составе iproute2 находится утилита “tc”. Там-же можно найти кое-какие man-pages для нее. Вообще говоря, механизм управления трафиком с помощью классов и tc — довольно сложная тема, поэтому, если вы не хотите вникать в теорию, пролистывайте сразу до реализации.
Итак, управление трафиком позволяет делать следующее:
Shaping.Шейпинг — техника ограничения трафика в ту или иную сторону. Может применяться не только для “нарезания” канала, но и для сглаживания бросков при пиковых нагрузках.
Scheduling.Упорядочивание типов трафика в канале позволяет избегать задержек для критичных типов трафика. Например ICMP ping. Другими словами, это приоритизация.
Policing.Политика входящего трафика позволяет определить, когда трафик пропускать, а когда уничтожать (drop). Например, частично поможет от DDoS.
Процесс управления трафиком осуществляется с помощью трех взаимосвязанных частей — дисциплин (Queue Disciplines => qdiscs); классов (classes) и фильтров (filters). Это все в терминологии "tc". На самом деле, дисциплина и очередь, а так-же фильтр и классификатор — это практически синонимы.Небольшое объяснение дальнейшей терминологии поможет вникнуть в процесс классификации трафика:
Qdisc— алгоритм, который управляет очередью пакетов на интерфейсе, входящей (ingress), или исходящей (egress). В qdisc “сидят” пакеты, если их нужно задержать, например.
Classless Qdisc— qdisc, который не может иметь подклассов, и, следовательно, разветвленную систему обработки пакетов.
Classfull Qdisc— дисциплина, которая может содержать подклассы, которые, в свою очередь, могут содержать вложенные дисциплины и т.д. Обычно HTB или CBQ.
Classes— логические контейнеры. Интерфейс с развитой системой классов, организует их в виде дерева. В нем конечные классы становятся листьями этой структуры (leaves), и всегда содержат qdisc.
Classifier— каждая, полная классов дисциплина, определяет с помощью классификации в какой класс отсылать пакет.
Filter— классификация происходит с использованием фильтров. Если значения фильтра совпали (например IP-адрес), пакет следует согласно этому фильтру в указанный класс.
Sheduling— как уже было указано выше, это механизм, позволяющий определить, какому пакету в какой последовательности покидать qdisc.
Shaping— процесс задержки пакетов в qdisc или их отбрасывания. Shaping с постановкой в очередь (т.е. с задержкой) может выполняться только в egress qdiscs.Важно понимать, что реально ограничивать мы можем только исходящий трафик. И в самом деле, как можно заставить кого-то слать нам пакеты медленнее? Однако есть несколько механизмов, позволяющих делать это.
Бесклассовый qdisc Ingress поможет вам, если вы хотите просто убивать трафик, превышающий установленную величину и предназначенный только этому вашему единственному хосту. Это полезно для вынесения буфера из модема в ядро, для того чтобы повысить интерактив.
Использование внутреннего для ядра устройства IMQ тоже позволит решить эту задачу, и даже будет возможно строить классы и ставить входящие пакеты в очередь! Только для этого надо патчить ядро. Однако эти патчи более официально не развиваются разработчиком. Остальную информацию можно найти по адресу http://luxik.cdi.cz/~patrick/imq/ или в фундаментальной статье И.Песина в предыдущем номере СР.
Изменение параметра TCP WIN теоретически может позволить управлять входящим трафиком “удаленно”, но для этого снова надо патчить ядро. Кроме того, у этого решения нет возможности sheduling. Есть еще недостатки, связанные с не совсем верной реализацией стека TCP/IP на некоторых платформах. Поэтому "удаленное" управление работает не всегда. Хотя это самый корректный, с точки зрения RFC метод. Патчи редки, и не всегда грамотны. Официальных вариантов нет.
Самый распространенный и реальный в реализации вариант — рассматривать два интерфейса, через которые проходит транзитный трафик, как исходящие. Предположим, есть клиент, подключенный по ppp к хосту. Для ограничения входящего для клиента трафика устанавливаем qdisc на ppp, а для ограничения исходящего от клиента — на eth из хоста во внутреннюю сеть. С точки зрения хоста — оба интерфейса исходящие.Если вы продолжаете понимать, что здесь написано, то рассмотрим непосредственно алгоритмы управления трафиком :-)

Queque Disciplines

Даже если вы не установили пакет iproute2, ядро уже использует по умолчанию classless qdisc! Он называется pfifo_fast, и представляет собой трехполосный буфер, приоритизация в котором может производиться с помощью поля Type Of Service (TOS), то есть с помощью iptables. Однако это работает не очень хорошо, особенно в условиях высокой загрузки канала. Кроме того, бесклассовая архитектура этой дисциплины работает только для всего интерфейса в целом. Подробнее про управление с помощью TOS можно прочитать в man iptables.
Наиболее часто используется Token Bucket Filter qdisc. Это типичный шейпер, который вы должны использовать, если вам нужно просто ограничить полосу пропускания по какому-нибудь критерию. Вкратце, механизм действия TBF такой: существует корзина (bucket), в которую собираются жетоны (tokens). Как только корзина наполняется, qdisc отдает трафик. Если скорость трафика превышает размер корзины (скорости ее наполнения), то трафик задерживается, или даже теряется.
Другой, часто используемый бесклассовый qdisc — SFQ (Stochastic Fairness Queueing). Принцип его действия таков, что поступающий в qdisc трафик разбивается на множество FIFO очередей, каждой из который в случайном порядке отдается приоритет. Таким образом, каждая TCP-сессия находится в равных условиях, по сравнению с остальными, и не может “забить” канал.
В целом надо заметить, что:
— для простого ограничения трафика лучше использовать TBF;
— если ваш канал реально загружен, лучшим решением будет SFQ;
— если ваш канал очень большой, обратите внимание на RED (man 8 tc-red). Очень подробно RED рассмотрен в вышеупомянутой статье И.Песина. От себя добавлю, что применение RED оправданно лишь на каналах свыше 10Мбит/c;
— если вам нужна только приоритизация без шейпинга, почитайте про PRIO qdisc. Он очень похож на pfifo_fast, но условно классовый (man 8 tc-prio);
— для ограничения входящего трафика, который не будет форвардиться дальше, используйте Ingress qdisc. Только не забывайте, что он либо только пропускает пакеты, либо убивает(drop) их. Например для 115кбит канала на ppp0:

# tc qdisc del dev ppp0 ingress 2>/dev/null; tc qdisc add dev ppp0 handle ffff: ingress
# tc filter add dev ppp0 parent ffff: protocol ip prio 10 u32 match ip src 0.0.0.0/0 police rate 80kbit burst 4k drop flowid :1


До сих пор я не отмечал преимуществ классового подхода, для управления трафиком. Все просто — бесклассовый qdisc, установленный сразу на устройство, действует глобально на это устройство. Бесклассовые дисциплины обычно используются как листья в дереве классовой дисциплины. Это позволяет реализовывать наследование и приоритизацию сразу по нескольким классам. Представим, что класс — это один адрес из внутренней сети, и у нас есть роутер, который реализует маршрутизацию и управление трафиком для многих адресов (подклассов). Классовость дает гибкость в построении нашего дерева правил, в которых в качестве классификаторов может быть множество параметров. Например адреса или типы трафика по портам. Таким образом, в классовой структуре, классы расположены в root qdisc, который в свою очередь привязан к интерфейсу; и классы тоже могут содержать подклассы и qdiscs в качестве листьев.
Существует два широко используемых Classfull Qdiscs. Первый, весьма сложный в своей семантике, но оттого гибкий — CBQ (Class Based Queue). Эта дисциплина была разработана одной из первых, и на ее основе строятся все наиболее популярные схемы распределения трафика. Она позволяет выполнять все возможные манипуляции по построению классов, приоритизации трафика и его ограничения. При разделении полосы с помощью шейперов, например TBF или SFQ, возможно наследование свободного канала как вниз по дереву классов, так и вверх. CBQ позволяет приоритизировать трафик наподобие PRIO qdisc. Дисциплина работает в целом относительно точно, при разделении канала на полосы, так как использует временнЫе характеристики канала. Но не всегда точно в рамках отдельного класса. Основной рекомендацией при использовании CBQ является то, что надо следить, чтобы сумма полос нижележащих классов была меньше или равна каналу родительского класса. Из этого правила могут быть исключения, например если ваш канал никогда полностью не загружен по всем классам (когда трафик минимален). Но в любом случае описание структуры CBQ начинается с указания точной максимальной полосы пропускания вашего канала. Что не всегда известно. Сложность при использовании CBQ заключается в его непростом синтаксисе, а также довольно не интуитивной системе построения классов в целом.
Второй Classfull Qdisc — HTB (Hierachical Token Bucket). Алгоритм работы НТВ во многом схож с TBF и CBQ одновременно. Его отличает такая же гибкость, как у CBQ, но, в отличие от последней, HTB имеет множество преимуществ и более популярен. Вкратце перечислим, что дает нам HTB и каковы преимущества этой дисциплины.
1. Последняя реализация так-же быстра, как и CBQ, но гораздо точнее ее.
2. HTB более понятен и легче конфигурируется, чем CBQ.
3. HTB работает похоже на CBQ, но не расчитывает на временнЫе параметры в канале в момент простоев (idle time) для разграничения полос. Вместо этого, он работает как полноклассовый Token Bucket Filter. У НТВ небольшое число параметров, которые неплохо документированы.
5. Вытекает из предыдущего — классы уже работают как TBF-шэйперы и мы можем выбирать qdisc, который будет не резать, а распределять трафик. У CBQ.init в листьях был только TBF (в свежих версиях и SFQ).
Решение на основе НТВ более расширяемо. Нужно мало действий, чтобы создать минимальную конфигурацию. Про CBQ так не скажешь.
6. HTB может наследовать неиспользуемую полосу пропускания как по горизонтали, так и по вертикали в дереве классов.
7. Наследование происходит на основе приоритетов классов и фильтров, а так-же установленного rate.
8. Вводится понятие CIR(знакомое по frame-relay), т.е. минимально-допустимой или гарантируемой полосы пропускания.
9. В HTB работают все настройки, в отличие от параметров bounded совместно с isolated в CBQ.
10. Разработка HTB продолжается, тогда как про CBQ Кузнецов почему-то забыл. Приходится даже патчить его утилиту tc на предмет совместимости с HTB. Повторю, что в ядре HTB начиная с 2.4.20.
11. Наконец, существует довольно оживленный список рассылки по вопросам конфигурирования и функционирования HTB.
12. И последнее — при описании дерева классов не обязательно точно описывать родительский RATE.
Подводя итог, приведем краткие правила использования HTB для построения классов:
1. Напоминаем, что управлять можно только исходящим трафиком.
2. kbps = kilobyte/s; kb = kilobyte; kbit = kilobit/s или kilobit — это нужно для понимания вывода статистики по сконфигурированным классам и дальнейших рассуждений.
3. Не нужно добавлять htb/cbq/tbf qdiscs в классы — это вносит задержки и жрет CPU. Мы будем использовать sfq и pbfifo.
4. Чем больше prio в классе, тем ниже приоритет при наследовании полосы. Допустимый диапазон — 0-7.
5. rate <= ceil в классе.
6. ∑ (rate of childs) <= parent rate & ceil; в идеальном случае ∑ (rate of childs) = parent rate. Если правило не соблюдается, то parent параметры HTB игнорирует (см. п.12 выше). Это допустимо, но не желательно. Реальный пример — имеем около 80 классов, которые имеют rate=32kbit. При их суммировании, имеем почти в три раза большее значение, чем имеющийся у нас мегабит исходящего канала, который описан как rate родительского класса. Таким образом HTB сохраняет свою целостность (в отличие от CBQ, у которого съезжает крыша, и он начинает ошибаться в статистике). В случае, если предположить, что все классы будут забиты трафиком (почти невероятно), то никто не получит свой CIR, но будет очень близок к нему тот класс, у которого выше prio и больше quantum.
7. Если ceil не указан, то он = rate.
8. Если класс промежуточный (не leaf и не root), в него не нужно вводить qdisc и фильтры (rules) соответственно. Приоритеты в таких классах также не работают, они нужны только для leafs.
9. Приоритет класса описывает то, какие классы в каком порядке наследуют полосу. Приоритет фильтра описывает порядок просмотра списка фильтров при классификации.
10. HTB обычно подразумевается как класс, но также существует и как qdisc, который присоединяется только непосредственно к интерфейсу (до root-class).
11. Численное значение любого класса лежит в пределах 2-fffe и должно быть уникально в пределах интерфейса.

классификаторы


Для того, чтобы определить, в какой класс направить пакет, используются фильтры-классификаторы. Они описываются так-же как и классы и дисциплины, с помощью утилиты “tc”. Детальный синтаксис команды можно найти в “man 8 tc”. Наиболее часто используются два классификатора — u32 и fw. Первый более гибок и популярен. Он позволяет выделять пакеты по адресам src/dst, портам src/dst, парам “хост:порт”, типам протокола, типу сервиса. Второй классифицирует пакеты, отмеченные fwmark с помощью iptables.
Рассмотрим простейший пример приоритизации. Например, мы создали PRIO qdisc, называемый “10:”, который содержит три класса, и мы хотим назначить всему трафику ssh самую приоритетную полосу в qdisc. Тогда фильтры могут быть приблизительно такие:

# tc filter add dev eth0 protocol ip parent 10: prio 1 u32 match ip dport 22 0xffff flowid 10:1
# tc filter add dev eth0 protocol ip parent 10: prio 1 u32 match ip sport 80 0xffff flowid 10:1
# tc filter add dev eth0 protocol ip parent 10: prio 2 flowid 10:2


Мы сказали здесь: присоединить к eth0, узлу 10:, с приоритетом 1, u32 фильтр который совпадает точно по порту назначения ssh, и посылает в этом случае пакет в полосу 10:1. Во второй строке схожая ситуация происходит с HTTP-трафиком. Последнее правило понижает приоритет у всего остального.
Как видите, ничего сложного. Однако когда дело дойдет до построения классов с использованием CBQ, синтаксис заметно усложнится. В следующем разделе рассмотрим, как облегчить себе задачу в достаточно простом случае, если надо просто “нарезать” полосу, а в конце обзора можно будет найти ссылки на ресурсы, более подробно описывающие синтаксис ip/tc, и полные примеров.

реализация

дерево классов на основе CBQ
Для детального самостоятельного построения дерева классов на основе CBQ я рекомендую внимательно ознакомиться с материалом И.Песина из предыдущего номера СР, на который я уже не однократно ссылался, или изучить документацию по ссылкам из раздела дополнительной информации. Для наиболее распространенного же варианта использование всей мощи CBQ уже реализовано. Вам даже не придется разбираться с семантикой каждого отдельного qdiscs и классификаторов. По адресу http://sourceforge.net/projects/cbqinitможно найти скрипт, выполненный как сервис, который достаточно просто позволяет создать разветвленную систему классов. Скрипт называется cbq.init, и представляет собой наполовину комментарий с инструкцией по его использованию. При запуске, он анализирует содержимое каталога /etc/sysconfig/cbq, который содержит описание классов. Далее он многократно запускает утилиту tc (на каждый новый класс), и создает таким образом древовидную систему. В корне этой системы — СBQ qdisc (с описанием вашего максимума пропускной способности в канале), от которого будут ответвляться TBF (по умолчанию) листья ваших классов. О формате файлов классов и значении опций узнаете из содержимого самого скрипта. Статистику по классам и дисциплинам можно просмотреть, используя команды:

# service cbq list
# service cbq stats

С помощью второй команды можно просмотреть значения счетчиков байт в дисциплинах, и убедиться, что ваше дерево работает. Серьезный недостаток подобного подхода — скрипт не понимает ppp-интерфейсов. А в остальном, если не вдаваться в продвинутые способы использования tc, очень даже полезен.

дерево классов на основе HTB

Как и в случае с cbq.init, существует скрипт htb.init, который был переписан с cbq.init и работает очень похоже. Найти его можно по адресу http://freshmeat.net/projects/htb.initДалее мы рассмотрим нюансы в примерной реализации дерева классов на его основе. А сейчас определим параметры НТВ, которые, кстати, вместе с примером описаны в этом скрипте. Все слова написанные ЗАГЛАВНЫМИ буквами распознаются htb.init как параметры. Однако, никто не запрещает писать plain-script с вызовами tc, и для него эти параметры такие-же.
  1. Для HTB qdisc устройства мы описываем DEFAULT класс по умолчанию, в который попадает трафик, не подпавший ни под одно rule, обычно это самый низкоприоритетный трафик. Если DEFAULT не указывать, то весь не склассифицированный трафик пойдет напрямую с наивысшим, нулевым приоритетом.
  2. Второй параметр R2Q используется для квантования трафика в классах. В случае малых скоростей рекомендуется значение = 1, что позволяет шейпить с точностью до 4kbit. Если он не указан, то =10, что для скоростей >120Kbit.
  3. В скриптах допустим параметр QUANTUM, но он используется только как параметр для sfq, а не htb. При использовании htb.init для htb-классов вычисляется автоматически, исходя из глобального параметра R2Q и rate класса. На что влияет quantum в HTB, можно попробовать понять из http://www.docum.org/stef.coene/qos/faq/cache/31.htmlНа что влияет QUANTUM (как параметр в скриптах) для sfq - читать наhttp://lartc.org/howto/lartc.qdisc.classless.html#LARTC.SFQ.
  4. CEIL используется для ограничения полосы в leaf (оконечном) классе, точно как RATE в cbq.init. RATE в HTB используется для управления тем, как полоса разбивается между дочерними классами, и гарантирует минимальную полосу, если есть у кого наследовать. Это и есть своего рода CIR.
  5. PRIO мы уже рассматривали. Обычно у классов хостов он где-то в середине, у интерактивного трафика — повыше, у остального (или по умолчанию) — пониже.
  6. Классификация трафика происходит так же, как и в cbq.init с помощью параметра RULE, которых допустимо несколько. Пакет, попавший под любое rule, будет отправлен в этот класс. Обращать внимание на запятые! rule=addres, — это source; а rule=addres — это destination. Допустимы звездочки, при описании портов. При введении rules, классификация происходит с использованием u32 классификатора, про который подробно можно прочитать по ссылке http://lartc.org/howto/lartc.adv- filter.html# LARTC.ADV-FILTER.U32.
  7. Классификация например в интерактивный класс может происходить также при помощи параметра MARK. Тут в дело вступает классификатор fw, который выбирает fwmarked пакеты. Отмечаем пакеты в файрволле. Приоритет при просмотре фильтров у fw в скрипте ниже, что IMHO не правильно. Для того, чтобы ловить сначала мелкие пакеты (ACKs), а только потом по адресам-портам, надо подправить приоритеты фильтрам прямо в htb.init. При генерации plain-script-а, htb.init назначит всем фильтрам один приоритет, который отличется численно для u32, fw и route классификаторов.
8. Параметры листьев: для sfq — QUANTUM и PERTURB — можно оставить по умолчанию. Для pfifo — LIMIT. Его смысл рассмотрим ниже. Других qdiscs, кроме как еще bfifo, для HTB не предусмотрено (RED не рассматриваем, да он и не поддерживается скриптом).
  9. BURST и CBURST можно не описывать, они вычисляются автоматически. Смысл — как и PEAK в CBQ (который вы тоже скорее всего не описывали).
И, наконец, примерная реализация.
В двух словах, htb.init парсит каталог /etc/sysconfig/htb, генерирует plain-script, состоящий из множества вызовов tc и запускает его. Точно также как и cbq.init. Одно из существенных изменений — именование файлов классов. Оно детально расписано внутри самого скрипта, так-что приведу только пример:

eth0-2:20:0101:A101.client_out

  1. Здесь до минуса — интерфейс, в его соответствующем файле описываем параметры htb qdisc (R2Q и DEFAULT).
  2. Далее id корнвого класса, в его файле только rate, для eth = 10/100Mbit.
3. Далее id=20. Это класс, объединяющий всех клиентов. Про него ниже.
  4. 0101 — parent класс клиента (только в случае запроса на prio, и соответственно разделение трафика по подклассам), обычно это просто листовой класс клиента. Чтобы разобраться, зачем нужно вводить подклассы, смотрите http://luxik.cdi.cz/~devik/qos/htb/manual/userg.htm#hsharing.
  5. A101 — leaf класс клиента, в котором описывается qdisc.
  6. Все, что после точки — смысловое описание класса.
В нашей примерной реализации для разделения клиентского, интерактивного и низкоприоритетного трафика создадим три класса сразу под корневым — 10: 20: и 30: Их функции таковы:
  10: — интерактивный трафик. Сюда попадает например ssh трафик, SMB в офисе, http на привилегированный хост — чтобы интранет работал без ограничений. С помощью марки в таблице mangle в этот же класс завернуты пакеты < 64 байт, в основном это ACK-и. Нужно для обеспечения клиентам upload-а во время download-а. А так-же завернуты UDP-запросы к DNS — они маленькие, а пользы прибавят. ICMP в этот класс не попадает, для возможности актуальной проверки канала на загрузку. Вы конечно можете делать это по своему. Подчеркну, что клиентский трафик, попавший в этот класс, не шейпится его классом (если не забыли сделать fw фильтру бОльший приоритет, чем u32 в htb.init, см. п.7 выше), и более приоритетен, чем его остальной трафик. Правда он достаточно мал, чтобы слишком выбиваться на картинках клиентского трафика. Служит для того, чтобы клиент не "убивал" одной закачкой весь свой канал. К классу прикреплена дисциплина sfq, так как в него попадают пакеты с разных адресов. Напомним, что SFQ работает по принципу русской рулетки и не дает одному виртуальному каналу TCP все время владеть полосой. Параметр QUANTUM оставлен по умолчанию, параметр PERTURB, так-же по умолчанию и равен 10 сек. Это время перестройки хэша "русской рулетки", то есть частота рандомизации хэша. Полоса класса например 10Мбит, ceil = 100Mbit.
  20: — клиентский трафик. Ниже лежат все классы-листья клиентов. Ничего особого — rate(который может быть overlimit) согласно нашему внешнему каналу, ceil = 100Mbit(как-раз для таких случаев). Особо остановимся на клиентских классах. В общем ничего мудреного, все согласно правилам выше. Про prio мы уже упоминали. В качестве qdisc в листьях используется pfifo (packet фифо), который еще проще, чем pfifo_fast, используемый в случае отсутствия какого-либо qdisc. Напомним, что pfifo_fast трехполосный, и распределение по полосам плохенько, но производилось с помощью TOS. TOS умеют выставлять некоторые "умные" приложения, типа openssh. Используемый pfifo имеет одну полосу, поэтому весь трафик в ней эквивалентен, однако в отличие от pfifo_fast имеет механизм статистики. Единственный параметр LIMIT указывает на размер очереди для pfifo в пакетах, для bfifo в байтах. Стандартная очередь pfifo{_fast} = 100. Чем больше очередь, тем беспрерывнее и "быстрее" работает одна TCP-сессия, например, закачка. Но и не дает никому другому воздуха, поэтому больше задержка в канале. При значениях по умолчанию qlen*MTU/sec=100*1500=150kbps, то есть, к примеру, на идеально забитом канале в 128kbit мы можем ожидать ответа например на пинг теоретически 150/16kbps больше 9 секунд! Исходя из этих соображений выбирайте значения поменьше, если хотите обеспечить интерактив.
  30: — класс по умолчанию, он же "опущенный". Сюда попадает весь не описанный фильтрами в классах трафик. Qdisc = SFQ, т.к. разномастный трафик от разных адресов. Так-как правила в скриптах работают только с TCP и портами, UDP и мелкие ACK-и придется маркировать в iptables. Для этого создаем соответствующую цепочку в mangle. Любопытных милости просим сходить по последней ссылке из дополнительной информации (ниже).
Напоследок заметим, что это действительно работает :-)

дополнительная информация

http://www.docum.org/ — Хорошая страничка с описаниями qdiscs и примерами.
http://lartc.org/ — “Штаб-квартира” Linux Advanced Routing & Traffic Control. Огромный HOWTO, и все возможности для управления трафиком.
http://gazette.linux.ru.net/rus/articles/index-iptables-tutorial.html — Наиболее полное руководство по iptables на русском языке.
http://lartc.org/manpages/ — Здесь находятся все man pages, на которые были ссылки в статье.
http://mailman.ds9a.nl/pipermail/lartc/ — архивы иhttp://www.lartc.org/#mailinglist- подписка на список рассылки, посвященный Linux Advanced Routing and Tcontrol. На английском языке.
http://luxik.cdi.cz/~devik/qos/htb/manual/userg.htm— руководство пользователя по НТВ от разработчика.
http://www.docum.org/stef.coene/qos/faq/cache/ — FAQ и примеры по HTB.
http://tldp.org/HOWTO/ADSL-Bandwidth-Management-HOWTO/ — пример реализации для ADSL и HTB.

Андрей Горев, вед. инженер, Авилинк ISP.
обсуждение статьи

© Сетевые решения