Содержание
Advanced Routing и QoS
Автор: Andy Gorev www.linux.by
Оригинал: www.atmsk.ru
Это предварительный вариант статьи для обсуждения и дополнений (скоро в DocBook CVS). Окончательный вариант войдет в очередной дистрибутив AltLinux.
Введение
Алексей Кузнецов и 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 (см. пакет nag).
Iproute2
Для вас может оказаться сюрпризом, что вышеперечисленные свойства ядра уже по умолчанию включены в него, и работают! Просто работают они все по умолчанию, то есть, по сути ничего особенного не делают. Более того, старинные команды типа route, arp, ifconfig просто ничего не покажут вам из тех возможностей, которые уже давно, начиная с ядер 2.2, имеются на вооружении. Для того, чтобы задействовать, настроить, или хотя-бы увидеть дополнительные возможности ядра, необходимо установить пакет iproute2. Автор утилит, вошедших в пакет, опять-же Алексей Кузнецов. Центральная утилита пакета - "ip" заменяет собой все вышеперечисленные "старинные" команды, помогает реализовать возможности многотабличной маршрутизации (advanced routing), туннелирования и multicast routing. Кроме того, Линукс имеет гибкую систему управления трафиком, называемую Traffic Control. Эта система поддерживает множество методов для классификации, приоритезации, разделения, и ограничения (shaping) обоих типов трафика - входящего и исходящего. Управлять всем этим позволяет вторая утилита пакета - "tc". Подчеркну, что обе утилиты являются как-бы интерпретаторами команд, а все функции выполняет ядро.
IP
Даже если вы только установили пакет iproute2 и ничего не настраивали, вы уже можете просматривать некоторую новую информацию о своей системе. Например:
- ip link list - покажет конфигурацию интерфейсов;
- ip address show - покажет IP адреса на интерфейсах;
- ip route show - покажет таблицу маршрутов main (сравните с route -n);
- ip neigh show - покажет таблицу ARP;
- ip rule list - покажет список правил, согласно которым принимается решение о маршрутизации.
По умолчанию у ядра имеется три таблицы маршрутов - local, main и default. Старая утилита route показывает содержимое только таблицы main. Таблицы local и default - новые. Мы сами можем создавать свои таблицы маршрутов, и с помощью правил (ip rules) указывать, какой трафик маршрутизировать согласно какой таблице. Классический механизм маршрутизации основан на destination addres пакета. Мы же можем, кроме этого, управлять маршрутизацией, используя поле TOS пакета, или согласно fwmark, который установил на пакет iptables. В этом и заключается концепция Advanced Routing. Детальную семантику команды ip, и настройку NAT на ее основе, можно найти в ip-cref Алексея Кузнецова. Этот документ есть в составе пакета iproute2.
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).
Управление трафиком
Для управления трафиком в составе iproute2 находится утилита "tc". Там-же можно найти кое-какие man-pages для нее. Вообще говоря, механизм управления трафиком с помощью классов и tc - довольно сложная тема, поэтому если вы не хотите вникать в теорию, пролистывайте сразу до реализации.
Итак, управление трафиком позволяет делать следующее:
SHAPING. Шейпинг - техника ограничения трафика в ту или иную сторону. Может применяться не только для "нарезания" канала, но и для сглаживания бросков при пиковых нагрузках.
SCHEDULING. Упорядочивание типов трафика в канале, позволяет избегать задержек для критичных типов трафика. Например ICMP ping. Другими словами, это приоритезация (QoS).
POLICING. Политика входящего трафика позволяет определить, когда трафик пропускать, а когда уничтожать (drop). Например, частично поможет от DDoS.
Процесс управления трафиком осуществляется с помощью трех взаимосвязанных частей - дисциплин (Queue Disciplines → qdisks), классов (classes) и фильтров (filters). Это все в терминологии "tc". На самом деле, дисциплина и очередь, а так-же фильтр и классификатор - это _практически_ синонимы.
Небольшое объяснение дальнейшей терминологии поможет вникнуть в процесс классификации трафика:
- Qdisk - алгоритм, который управляет очередью пакетов на интерфейсе, входящей (ingress), или исходящей (egress). В qdisk "сидят" пакеты, если их например нужно задержать.
- Classless Qdisc - qdisk, который не может иметь подклассов, и, следовательно, разветвленную систему обработки пакетов.
- Classfull Qdisk - дисциплина, которая может содержать подклассы, которые, в свою очередь, могут содержать вложенные дисциплины и т.д. Обычно HTB или CBQ.
- Classes - логические контейнеры. Интерфейс с развитой системой классов, организует их в виде дерева. В нем конечные классы становятся листьями этой структуры (leaves), и всегда содержат qdisk.
- Classifier - каждая, полная классов дисциплина, определяет с помощью классификации в какой класс отсылать пакет.
- Filter - классификация происходит с использованием фильтров. Если значения фильтра совпали (например IP адрес), пакет следует согласно этому фильтру в нужный класс.
- Sheduling - как уже было указано выше, это механизм, позволяющий определить, какому пакету в какой последовательности покидать qdisk.
- Shaping - процесс задержки пакетов в qdisk или их отбрасывания. Shaping с постановкой в очередь (т.е. с задержкой) может выполняться только в egress qdisks.
Важно понимать, что реально ограничивать мы можем только исходящий трафик. И в самом деле, как можно заставить кого-то слать нам пакеты медленнее? Однако есть несколько механизмов, позволяющих делать это.
- Бесклассовый qdisk Ingress поможет вам, если вы хотите просто убивать трафик, превышающий установленную величину, и предназначенный только этому вашему единственному хосту. Это полезно для вынесения буфера из модема в ядро, для того чтобы повысить интерактив;
- Использование внутреннего для ядра устройства IMQ, тоже позволит решить эту задачу, и даже будет возможно строить классы, и ставить входящие пакеты в очередь! Только для этого надо патчить ядро. Остальную информацию можно найти по адресу: http://luxik.cdi.cz/~patrick/imq/
- Изменение параметра TCP WIN может позволить управлять входящим трафиком "удаленно", но для этого снова надо патчить ядро. Кроме того, у этого решения нет возможности sheduling. Есть еще недостатки, связанные с не совсем верной реализацией стека TCP/IP на некоторых платформах. Поэтому "удаленное" управление работает не всегда. Хотя это самый корректный, с точки зрения RFC метод. Патчи редки, и не всегда грамотны. Официальных вариантов нет.
- Самый распространенный и реальный в реализации вариант - рассматривать два интерфейса, через которые проходит транзитный трафик, как исходящие. Предположим, есть клиент, подключенный по ppp к хосту. Для ограничения входящего для клиента трафика вешаем qdisk на ppp, а для ограничения исходящего от клиента - на eth из хоста во внутреннюю сеть. С точки зрения хоста - оба интерфейса исходящие.
Если вы продолжаете понимать, что здесь написано, то рассмотрим непосредственно алгоритмы управления трафиком.
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 сессия находится в равных условиях, по сравнению с остальными, и не может "забить" канал. Подводя итог, надо заметить, что:
- Для простого ограничения трафика лучше использовать TBF;
- Если ваш канал реально загружен, лучшим решением будет SFQ;
- Если ваш канал очень большой, обратите внимание на RED (man 8 tc-red);
- Если вам нужна только приоритезация без шейпинга, почитайте про PRIO qdisk. Он очень похож на pfifo_fast, только условно классовый (man 8 tc-prio);
- Для ограничения входящего трафика, который не будет форвардиться дальше, используйте Ingress qdisk. Только не забывайте, что он либо только пропускает пакеты, либо убивает(drop) их.
До сих пор, я не отмечал преимуществ классового подхода для управления трафиком. Все просто - бесклассовый 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 я изложил в отдельном материале ниже.
Классификаторы
Для того, чтобы определить в какой класс направить пакет, используются фильтры-классификаторы. Они описываются так-же как классы и дисциплины, с помощью утилиты "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, и полные примеров.
Реализация
Практическое использование всей мощи CBQ уже реализовано для наиболее распространенного варианта. Вам даже не придется разбираться с семантикой каждого отдельного qdisks и классификаторов. В пакет iproute2 включен скрипт, выполненный как сервис, который достаточно просто позволяет создать разветвленную систему классов. Скрипт называется /etc/rc.d/init.d/cbq, и представляет собой наполовину комментарий, с инструкцией по его использованию. При запуске, он анализирует содержимое каталога /etc/sysconfig/cbq, в котором, кстати, уже лежит заготовленный пример класса. Далее он многократно запускает утилиту tc (на каждый новый класс), и создает таким образом древовидную систему. В корне этой системы - СBQ qdisk (с описанием вашего максимума пропускной способности в канале), от которого будут ответвляться TBF (по умолчанию) листья ваших классов. О формате файлов классов, и значение опций смотрите в содержимое самого скрипта. Статистику по классам и дисциплинам можно просмотреть, используя команду:
# service cbq list # service cbq stats
С помощью второй команды можно просмотреть значения счетчиков байт в дисциплинах, и убедиться, что ваше дерево работает. Серьезный недостаток подобного подхода - скрипт не понимает ppp интерфейсов. А в остальном, если не вдаваться в продвинутые способы использования tc, очень даже полезен.
Дополнительная информация
- http://www.docum.org/ - Хорошая страничка с описаниями qdisks и примерами.
- http://lartc.org/ - "Штаб-квартира" Linux Advanced Routing & Traffic Control. Огромный HOWTO, и все возможности для управления трафиком.
- 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 можно ознакомится в исходниках ядра.
—-
Обсуждение