Advanced Routing и QoS

Автор: Andy Gorev www.linux.by
Оригинал: www.atmsk.ru

Это предварительный вариант статьи для обсуждения и дополнений (скоро в DocBook CVS). Окончательный вариант войдет в очередной дистрибутив AltLinux.
1. Введение.

Алексей Кузнецов и NET core team уже давно и небезуспешно разрабатывают сетевые функции ядра Линукс. Например вот что мы можем делать, используя только ядро:

Все эти вещи обычно не нужны для рабочей станции, однако если вы управляете маршрутизатором (router), то это поможет в работе. Во многом возможности ядра Линукс на уровне, или даже опережают возможности Cisco IOS, но к сожалению отстают в этой области по документированности, в силу большой занятости его разработчика. Однако я подскажу, где искать дальнейшую информацию.

Половина из вышеперечисленного реализуется с использованием iptables, но в данном обзоре все-же рассматриваются особенности ip/tc. Однако я буду иногда ссылаться на iptables. Итак, вы должны хотя-бы в общих чертах представлять себе как работает стек TCP/IP, фильтрация на базе ipchains/iptables, и прочитали например Network Administration Guide (см. пакет nag).

2. Iproute2.

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

3. IP.

Даже если вы только установили пакет iproute2 и ничего не настраивали, вы уже можете просматривать некоторую новую информацию о своей системе. Например:По умолчанию у ядра имеется три таблицы маршрутов - local, main и default. Старая утилита route показывает содержимое только таблицы main. Таблицы local и default - новые. Мы сами можем создавать свои таблицы маршрутов, и с помощью правил (ip rules) указывать, какой трафик маршрутизировать согласно какой таблице. Классический механизм маршрутизации основан на destination addres пакета. Мы же можем, кроме этого, управлять маршрутизацией, используя поле TOS пакета, или согласно fwmark, который установил на пакет iptables. В этом и заключается концепция Advanced Routing. Детальную семантику команды ip, и настройку NAT на ее основе, можно найти в ip-cref Алексея Кузнецова. Этот документ есть в составе пакета iproute2.

4. Advanced Routing

Теперь я приведу несколько примеров, иллюстрирующих возможности table-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 (чаще всего так и делают), но заворачивать отмеченные маркой пакеты в другую таблицу, и следовательно интерфейс, все-равно придется приблизительно вышеприведенным правилом. Отмечу мало-известный факт, что ipfw-марка существует только в пределах интерфейса. Поэтому она не передается за ваш router, и даже не пытайтесь увидеть ее tcpdump-ом. Маркируются-ли пакеты, мы можем узнать, вызывая iptables с ключем -v. Возвращаясь к теме напомню, что и в этом случае таблицу 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 устройство, но это из другой темы. Напомню, что существует "Ip Command Reference" в составе пакета iproute2. Кроме всего прочего, "ip" корректно сосуществует и дополняет пакеты динамической маршрутизации (zebra, gated).

5. Управление трафиком.

Для управления трафиком в составе iproute2 находится утилита "tc". Там-же можно найти кое-какие man-pages для нее. Вообще говоря, механизм управления трафиком с помощью классов и tc - довольно сложная тема, поэтому если вы не хотите вникать в теорию, пролистывайте сразу до реализации.

Итак, управление трафиком позволяет делать следующее:
SHAPING. Шейпинг - техника ограничения трафика в ту или иную сторону. Может применяться не только для "нарезания" канала, но и для сглаживания бросков при пиковых нагрузках.
SCHEDULING. Упорядочивание типов трафика в канале, позволяет избегать задержек для критичных типов трафика. Например ICMP ping. Другими словами, это приоритезация (QoS).
POLICING. Политика входящего трафика позволяет определить, когда трафик пропускать, а когда уничтожать (drop). Например, частично поможет от DDoS.
Процесс управления трафиком осуществляется с помощью трех взаимосвязанных частей - дисциплин (Queue Disciplines -> qdisks), классов (classes) и фильтров (filters). Это все в терминологии "tc". На самом деле, дисциплина и очередь, а так-же фильтр и классификатор - это _практически_ синонимы.

Небольшое объяснение дальнейшей терминологии поможет вникнуть в процесс классификации трафика:Важно понимать, что реально ограничивать мы можем только исходящий трафик. И в самом деле, как можно заставить кого-то слать нам пакеты медленнее? Однако есть несколько механизмов, позволяющих делать это.
1)Бесклассовый qdisk Ingress поможет вам, если вы хотите просто убивать трафик, превышающий установленную величину, и предназначенный только этому вашему единственному хосту. Это полезно для вынесения буфера из модема в ядро, для того чтобы повысить интерактив;
2)Использование внутреннего для ядра устройства IMQ, тоже позволит решить эту задачу, и даже будет возможно строить классы, и ставить входящие пакеты в очередь! Только для этого надо патчить ядро. Остальную информацию можно найти по адресу: http://luxik.cdi.cz/~patrick/imq/
3)Изменение параметра TCP WIN может позволить управлять входящим трафиком "удаленно", но для этого снова надо патчить ядро. Кроме того, у этого решения нет возможности sheduling. Есть еще недостатки, связанные с не совсем верной реализацией стека TCP/IP на некоторых платформах. Поэтому "удаленное" управление работает не всегда. Хотя это самый корректный, с точки зрения RFC метод. Патчи редки, и не всегда грамотны. Официальных вариантов нет.
4)Самый распространенный и реальный в реализации вариант - рассматривать два интерфейса, через которые проходит транзитный трафик, как исходящие. Предположим, есть клиент, подключенный по ppp к хосту. Для ограничения входящего для клиента трафика вешаем qdisk на ppp, а для ограничения исходящего от клиента - на eth из хоста во внутреннюю сеть. С точки зрения хоста - оба интерфейса исходящие.

Если вы продолжаете понимать, что здесь написано, то рассмотрим непосредственно алгоритмы управления трафиком.

6.Queque Disciplines.

Даже если вы не установили пакет iproute2, ядро уже использует по умолчанию classless qdisc! Он называется pfifo_fast, и представляет собой трех-полосный буфер, приоритезация в котором может производиться с помощью поля Type Of Service (TOS), т.е. с помощью iptables. Однако это работает не очень хорошо, особенно в условиях высокой загрузки канала. Кроме того, бесклассовая архитектура этой дисциплины работает только для всего интерфейса в целом. Подробнее про управление с помощью TOS можно прочитать в man iptables.
Наиболее часто используется Token Bucket Filter qdisk. Это типичный шейпер, который вы должны использовать, если вам нужно просто ограничить полосу пропускания по какому-нибудь критерию. В кратце, механизм действия TBF такой: существует корзина (bucket), в которую собираются жетоны (tokens). Как только корзина наполняется, qdisk отдает трафик. Если скорость трафика превышает размер корзины (скорости ее наполнения), то трафик задерживается, или даже теряется.
Другой, часто используемый бесклассовый qdisk - SFQ (Stochastic Fairness Queueing). Принцип его действия таков, что поступающий в qdisk трафик разбивается на множество FIFO очередей, каждой из которых в случайном порядке отдается приоритет. Таким образом, каждая TCP сессия находится в равных условиях, по сравнению с остальными, и не может "забить" канал. Подводя итог, надо заметить, что:До сих пор, я не отмечал преимуществ классового подхода для управления трафиком. Все просто - бесклассовый qdisk, установленный сразу на устройство, действует вообще на все это устройство. Бесклассовые дисциплины обычно используются как листья в дереве классовой дисциплины. Это позволяет реализовывать наследование и приоритезацию сразу по нескольким классам. Классовость дает гибкость в построении нашего дерева правил, в которых в качестве классификаторов может быть множество параметров. Например адреса или типы трафика по портам. Таким образом, в классовой структуре, классы расположены в root qdisk, который в свою очередь привязан к интерфейсу; и классы тоже могут содержать подклассы и qdisks в качестве листьев.
Существует два широко используемых Classfull Qdisks. Первый, весьма сложный в своей семантике, но поэтому гибкий - CBQ (Class Based Queue). Эта дисциплина была разработана одной из первых, и на ее основе строятся все наиболее популярные схемы распределения трафика. Она позволяет выполнять все возможные манипуляции по построению классов, приоритезации трафика и его ограничения. При разделении полосы с помощью шейперов, например TBF или SFQ, возможно наследование свободного канала как вниз по дереву классов, так и вверх. CBQ позволяет приоритезировать трафик наподобие PRIO qdisk. При разделении канала на полосы, дисциплина работает в целом относительно точно, так как использует его временные характеристики. Но не всегда точно в рамках отдельного класса. Основной рекомендацией при использовании CBQ является то, что надо следить, чтобы сумма полос нижележащих классов была меньше или равна каналу родительского класса. Из этого правила могут быть исключения, например если ваш канал никогда полностью не загружен по всем классам (когда трафик минимален). Но в любом случае описание структуры CBQ начинается с указания точной максимальной полосы пропускания вашего канала. Что не всегда известно. Сложность при использовании CBQ заключается в его не простом синтаксисе, а так-же довольно не интуитивной системе построения классов в целом.
Второй Classfull Qdisk - HTB (Hierachical Token Bucket). Эта дисциплина появилась в официальном ядре начиная с версии 2.4.20. Алгоритм ее работы во многом схож с TBF и CBQ одновременно. HTB отличает такая-же гибкость, как у CBQ, но в отличие от последнего, HTB гораздо более интуитивно понятен, имеет упрощенную конфигурацию, более точен в механизмах шейпинга. Кроме того, для HTB не нужно описывать пропускную способность всего канала. Подробнее о HTB я изложил в отдельном материале ниже.

7. Классификаторы.

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

# 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, и полные примеров.

8.Реализация.

Практическое использование всей мощи CBQ уже реализовано для наиболее распространенного варианта. Вам даже не придется разбираться с семантикой каждого отдельного qdisks и классификаторов. В пакет iproute2 включен скрипт, выполненный как сервис, который достаточно просто позволяет создать разветвленную систему классов. Скрипт называется /etc/rc.d/init.d/cbq, и представляет собой наполовину комментарий, с инструкцией по его использованию. При запуске, он анализирует содержимое каталога /etc/sysconfig/cbq, в котором, кстати, уже лежит заготовленный пример класса. Далее он многократно запускает утилиту tc (на каждый новый класс), и создает таким образом древовидную систему. В корне этой системы - СBQ qdisk (с описанием вашего максимума пропускной способности в канале), от которого будут ответвляться TBF (по умолчанию) листья ваших классов. О формате файлов классов, и значение опций смотрите в содержимое самого скрипта. Статистику по классам и дисциплинам можно просмотреть, используя команду:
Код:
# service cbq list
# service cbq stats

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

9.Дополнительная информация.

1) http://www.docum.org/ - Хорошая страничка с описаниями qdisks и примерами.
2) http://lartc.org/ - "Штаб-квартира" Linux Advanced Routing & Traffic Control. Огромный HOWTO, и все возможности для управления трафиком.
3) http://www.opennet.ru/docs/RUS/iptables/ - Наиболее полное руководство по iptables на русском языке.

Ммм, забыл еще добавить, что существует оч. хороший список рассылки. Правда там все говорят по английски, но весьма охотно отвечают. Автор docum.org тоже там. Архивы http://mailman.ds9a.nl/pipermail/lartc/. Подписка на www.lartc.org/#mailinglist

И еще, скрипт cbq.init переехал на http://sourceforge.net/projects/cbqinit/


После некоторого обсуждения статьи, стало ясно, что не хватает скриптов практической реализации использования CBQ. К сожалению, скрипт cbq.init пока не работает с HTB, однако существует простой скрипт для HTB, который вы можете найти ... у себя на винчестере! Вот он: [url]file:///usr/share/doc/HOWTO/HTML/en/ADSL-Bandwidth-Management-HOWTO/implementation.html.
Как видно из ссылки, нужен пакет howto-html-en. Скрипт сопровождает довольно краткое, но очень конкретное описание "как это работает". Он использует не только HTB для upstream, но и IMQ для downstream. Напомню, что IMQ требует патчения ядра. Другие примеры можно найти по ссылкам, которые я давал выше.

PS В последнюю сборку iproute2 для Сизифа я включил скрипты wondershaper от lartc.org Они работают и с HTB.

PPS На самом деле, есть htb.init. Про него читайте "Сагу о HTB" ниже.


После посещения местной Линуксовки, натолкнулся на статью моего коллеги, где будет больше конкретики. http://linuxadmin.chat.ru/pulsar/QoS.txt От себя добавлю лишь то, что не рассмотренный RED занимается "предварительным случайным дропанием" пакетов, "вылезающих" за рамки. Подробнее с RED можно ознакомится в исходниках ядра.


Сага по Hierarchical Token Bucket.



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

Начну с краткого описания HTB. Я думаю, есть хотя-бы приблизительное представление о том, как работает classfull шэйпинг, поэтому в описание терминологии вдаваться не нужно. Кроме того, все заинтересованные прочитали статью выше.

Итак, что дает нам HTB:

Несколько генеральных ссылок по HTB:
1) http://luxik.cdi.cz/~devik/qos/htb/manual/userg.htm
2) http://www.docum.org/stef.coene/qos/faq/
3) http://tldp.org/HOWTO/ADSL-Bandwidth-Management-HOWTO/index.html

Краткие правила при использовании HTB:

Теперь сообщу приятную новость. Есть-таки скрипт htb.init, который был переписан с cbq.init и работает очень похоже. Найти его можно по адресу http://freshmeat.net/projects/htb.init. Ниже я расскажу о нюансах в примерной реализации дерева классов на его основе. А сейчас рассмотрим параметры НТВ, которые, кстати, вместе с примером описаны в этом скрипте. Все слова написанные ЗАГЛАВНЫМИ буквами распознаются htb.init как параметры. Однако, никто не запрещает писать plain-script с вызовами tc, и для него эти параметры такие-же.

Наконец примерная реализация.
В двух словах, htb.init парсит каталог /etc/sysconfig/htb, генерит plain-script, состоящий из множества вызовов tc и запускает его. Точно также как и cbq.init. Одно из существенных изменений - именование файлов классов. Оно детально расписано внутри самого скрипта, так-что приведу только пример:

eth0-2:20:0101:A101.client_out

В нашей примерной реализации я создал три класса сразу под корневым - 10: 20: и 30: Их функции таковы:

Так-как рулеса в скриптах работают только с tcp и портами, udp и мелкие ACK-и придется маркировать в iptables. Для этого создаем соответствующую цепочку в mangle. Любопытных милости просим сходить по треттей ссылке из генеральных.

Напоследок замечу, что это действительно работает :-)

Спасибо за внимание, надеюсь на Ваше понимание. Советую перечитать сначала =)