Новичкам про управление шириной канала в Linux

Некоторое время назад меня попросили настроить в удаленном филиале простейшую балансировку трафика. Работают они, бедолаги, через ADSL, и отправка электронных писем большого объема (сканы документов) забивает им весь обратный канал, что приводит к проблемам в работе с офисными онлайн-программами через VPN.
В качестве шлюза у них используется Linux (Fedora). До этого я пару раз видел, как подобная балансировка настраивается через ipfw на FreeBSD, а так как знаю механизм iptables достаточно хорошо, не ожидал особых проблем. Но поискав в Интернете, я был неприятно удивлен тем, что iptables мне тут совсем не помощник. И знания о порядке прохождения пакетов через его таблицы и правила мне почти не пригодятся. Нужно изучать tc из пакета iproute2.

Неожиданно для себя, я потратил два дня, для того чтобы более-менее разобраться в балансировке трафика средствами iproute2. Сначала попалась не самая лучшая для новичка статья про HTB(здесь). Различные примеры из Интернет тоже порой вводили в ступор, так как в них часто не было описания конкретных опций или смысла их применения. Поэтому я и попытался собрать полученные мною знания в одну статью, а главное описать все на доступном для новичков уровне.

Сразу оговорюсь, “резать” будем только исходящий с сетевого интерфейса трафик. Входящий тоже можно регулировать, но это требует дополнительных хитростей.

Бесклассовые дисциплины


Итак, в Linux для управления трафиком каждому сетевому интерфейсу назначается дисциплина (qdisc). Именно из дисциплин и строится вся система управления трафиком. Но пугаться не стоит, на самом деле, дисциплина — это просто алгоритм обработки очереди сетевых пакетов.
Дисциплин на одном интерфейсе может быть задействовано несколько, а непосредственно к интерфейсу крепится так называемая корневая дисциплина (root qdisc). При этом каждый интерфейс имеет свою собственную корневую дисциплину.

prio_fast

По-умолчанию после загрузки системы, root qdisc задает алгоритм обработки пакетов типа pfifo_fast.

image

Проверяем:

# tc qdisc

qdisc pfifo_fast 0: dev eth0 root bands 3 priomap 1 2 2 2 1 2 0 0 1 1 1 1 1 1 1 1


pfifo_fast — это обычный алгоритм “First Input — First Output”, но с некоторой приоритизацией трафика. Дисциплина такого типа содержит внутри себя три очереди FIFO с разным приоритетом обработки пакетов. Пакеты раскладываются по ним на основе флага ToS (Type of Service) в каждом IP-пакете. Пакет попавший в FIFO0 имеет наивысший приоритет к обработке, в FIFO2 — наименьший. Сам ToS требует отдельного разговора, поэтому предлагаю ограничиться тем фактом, что операционная система сама знает какой ToS назначить отправленному IP-пакету. Например, в пакетах telnet и ping ToS будет иметь разные значения.

0: — дескриптор корневой дисциплины.
Дескрипторы должны иметь вид старший_номер: младший_номер, но у дисциплин младший номер всегда должен быть 0, и поэтому его можно опускать.

Параметр priomap 1 2 2 2 1 2 0 0 1 1 1 1 1 1 1 1, как раз задает побитовое соответствие поля ТоS каждой внутренней очереди (band). Например, при ToS=4, пакет обрабатывается в очереди 1, при ToS=7 в очереди 0.

В ряде источников указано, что параметры дисциплины pfifo_fast нельзя изменить, поверим.

TBF

Теперь рассмотрим как можно ограничить скорость общего исходящего трафика. Для этого назначим корневой дисциплиной интерфейса дисциплину типа TBF (Token Bucket Filter).

# tc qdisc add dev eth0 root tbf rate 180kbit latency 20ms buffer 1540

rate 180kbit — устанавливает порог скорости передачи на интерфейсе.

latency 20ms — задает максимальное время нахождения пакета данных в ожидании токена.

buffer 1540 — задаем размер буфера токенов в байтах. В примерах пишут, что для ограничения в 10Мbit/s достаточно буфера на 10Kbytes. Главное не сделать его слишком малого размера, больше можно. Примерная формула расчета: rate_in_Bytes/100.

image

Дисциплина ТBF для своей работы использует механизм токенов. Токены генерируются системой с постоянной скоростью и помещаются в буфер(bucket). За каждый токен, вышедший из буфера с интерфейса уходит IP-пакет.
Если скорости передачи пакетов и генерации токенов совпадает, процесс передачи данных идет без задержки.
Если скорость передачи пакетов меньше чем скорость токенов, последние начинают накапливаться в буфере и затем могут использоваться для кратковременной передачи данных на скорости выше пороговой.
Если скорость передачи пакетов выше — токенов начинает не хватать. Пакеты данных ожидают новых токенов некоторое время, а затем начинают отбрасываться.

Описанные две дисциплины относятся к так называемым бесклассовым (classless) дисциплинам. Они имеют ряд функциональных ограничений: подключаются только к интерфейсу (либо краевому классу), плюс для них нельзя применять фильтры пакетов. И соответственно мою задачу по балансировке почтового трафика с их помощью решить не удастся.
Кстати, полный набор бесклассовых дисциплин несколько шире: pfifo, bfifo, sqf (обеспечивает одинаковую скорость пакетов поступивших из разных потоков), esqf и т.д.

Классовые дисциплины


Если дисциплины можно представить как отрезки водопроводных труб, то классы — это соединители (фитинги). Это может быть простой фитинг-переходник, а может хитрый фитинг-разветвитель с десятком отводов.

В качестве родителя класса может выступать либо дисциплина, либо другой класс.
К классу могут присоединяться дочерние классы (состыковка нескольких фитингов).

Класс, не имеющий дочерних классов, называется краевым (leaf class). Здесь пакеты данных, пробежав по нашему “водопроводу” покидают систему управления трафиком и отправляются сетевым интерфейсом. По-умолчанию, любой краевой класс имеет присоединенную дисциплину типа fifo, и именно она определяет порядок передачи пакетов для этого класса. Но вся прелесть в том, что мы можем поменять эту дисциплину на любую другую.
В случае добавления дочернего класса данная дисциплина удаляется.

prio

Вернемся к задаче по балансировке почтового трафика и рассмотрим классовую дисциплину prio.
Она очень похожа на уже описанную pfifo_fast. Но данная дисциплина особенная тем, что при ее назначении автоматически создается три класса (количество можно менять параметром bands).

Заменим корневую дисциплину интерфейса на prio.

# tc qdisc add dev eth0 root handle 1: prio

handle 1: — задаем дескриптор данной root qdisc. В классовых дисциплинах его затем указывают при подключении классов.

Проверяем дисциплину:

# tc qdisc

qdisc prio 1: dev eth0 bands 3 priomap 1 2 2 2 1 2 0 0 1 1 1 1 1 1 1 1


Проверяем классы:

# tc -d -s class show dev eth0

class prio 1:1 parent 1:
Sent 734914 bytes 7875 pkt (dropped 0, overlimits 0 requeues 0)
backlog 0b 0p requeues 0
class prio 1:2 parent 1:
Sent 1555058583 bytes 8280199 pkt (dropped 124919, overlimits 26443 requeues 0)
backlog 0b 0p requeues 0
class prio 1:3 parent 1:
Sent 57934378 bytes 802213 pkt (dropped 70976, overlimits 284608 requeues 0)
backlog 0b 0p requeues 0


Видим три класса с идентификаторами 1:1, 1:2 и 1:3, подключенные к родительской диcциплине 1: типа prio (классы обязаны иметь общий старший_номер идентификатора со своим родителем).
Т.е на “трубу” root qdisc, которая разделяет потоки данных также как это делает pfifo_fast мы насадили тройник. Руководствуясь ToS, в класс 1:1 попадает высокоприоритетный трафик, в класс 1:2 обычный трафик, в класс 1:3 совсем уж «мусор».

Допустим обратный канал ADSL выдает скорость 90Кбайт/c. Разделим его на 20Kбайт/c под почту и 70Kбайт/c на все остальное.

Трафик из класса 1:1 специально ограничивать не будем. Его пакеты всегда смогут занять хоть всю ширину канала из-за высокого приоритета ToS, но объем трафика в этом классе у нас будет ничтожно мал по сравнению с остальными двумя классами. Поэтому отдельную полосу под него не резервируем.

Стандартный трафик, как правило, попадает в класс 1:2. Подключаем ко второму выводу класса-тройника трубу-дисциплину на 70Kбайт/c:

# tc qdisc add dev eth0 parent 1:2 handle 10: tfb rate 70kbps buffer 1500 latency 50ms

На третий вывод тройника подключим трубу-дисциплину на 20Kбайт/c:

# tc qdisc add dev eth0 parent 1:3 handle 20: tfb rate 20kbps buffer 1500 latency 50ms

Все три этих класса краевые.

И теперь осталось только направить почтовый трафик не в класс 1:2, как происходило раньше, а в класс 1:3. Это делается с помощью фильтров классовых дисциплин.

# tc filter add dev eth0 parent 1: protocol ip prio 1 u32 match ip dport 25 0xffff flowid 1:3

parent 1: — фильтр может крепиться только к дисциплине и вызывается из нее же. На основе срабатывания фильтра дисциплина решает в каком классе продолжится обработка пакета.

protocol ip — определяем тип сетевого протокола

prio 1 — параметр долго вводил меня в замешательство, так как он применяется в классах и фильтрах, плюс это название дисциплины. Здесь prio задает приоритет срабатывания фильтров, первыми задействуются фильтры с меньшим prio.

u32 — так называемый классификатор трафика, который может выполнять отбор пакетов по любому его признаку: по ip-адресу отправителя/получателя, по порту источника/приeмника, по типу протокола. Эти условия, собственно, и указаны далее.

match ip dport 25 0xffff — задает срабатывание фильтра при отправке пакетов на порт 25. 0xffff — это битовая маска для номера порта.

flowid 1:3 — указываем в какой класс передаются пакеты при срабатывании данного фильтра.

Сделано грубо, но задачу выполнит.

Смотрим статистику прохождения пакетов:

# tc -s -d qdisq show dev eth0
# tc -s -d class show dev eth0
# tc -s -d filter show dev eth0


Быстро удалить все классы, фильтры и вернуть root qdisc интерфейса в первоначальное состояние можно командой:

# tc qdisc del dev eth0 root

HTB

С другой стороны, у нас и так слишком тонкий обратный канал, чтобы резервировать 20Kбайт/c только под отправку электронной почты. Поэтому здесь лучше использовать классовую дисциплину HTB (Hierarchical Token Bucket). Она позволяет производить заимствование полосы пропускания дочерним классами у родительского.

image

# tc qdics add dev eth0 root handle 1: htb default 20

default 20 — задаем класс по-умолчанию. В нем будут обрабатываться пакеты, не попавшие в другие классы дисциплины htb. Если его не указать, то будет назначен “default 0” и весь неклассифицированный (непопавший под фильтры) трафик будет отправляться со скоростью интерфейса.

# tc class add dev eth0 parent 1: classid 1:1 htb rate 90kbps ceil 90kbps

прикрепляем к root qdisc класс с идентификатором 1:1. Тем самым ограничиваем скорость на интерфейсе до 90Кбайт/c.

classid 1:1 — идентификатор класса.

rate 90kbps — устанавливаем нижний порог пропускной способности для класса.

ceil 90kbps — устанавливаем верхний порог пропускной способности для класса.

# tc class add dev eth0 parent 1:1 classid 1:10 htb rate 20kbps ceil 70kbps

создаем класс 1:10, дочерний классу 1:1. Затем в него фильтром будет направляться исходящий почтовый трафик.

rate 20kbps — устанавливаем гарантированный нижний порог пропускной способности для класса.

ceil 70kbps — устанавливаем верхний порог пропускной способности для класса. В случае если у родительского класса будет свободна полоса пропускания (наличие “лишних” токенов), class 1:10 сможет временно поднять скорость передачи данных, вплоть до указанного предела в 70Кбайт/c.

# tc class add dev eth0 parent 1:1 classid 1:20 htb rate 70kbps ceil 90kbps

Создаем класс по умолчанию. В него будет попадать весь остальной трафик. Точно также, параметрами rate и ceil, задаем расширение пропускной способности в случае отсутствия уже почтового трафика.

# tc filter add dev eth0 parent 1: protocol ip prio 1 u32 match ip dport 25 0xffff flowid 1:10

фильтр на базе u32, направляющий пакеты исходящие на 25й порт в класс 1:10.

Кстати, в документации указано, что по факту в HTB шейпинг трафика происходит только в краевых классах, в нашем случае 1:10 и 1:20. Указание параметров ограничения полосы пропускания в остальных классах HTB нужно лишь для функционирования системы заимствования между классами.

При добавлении класса также возможно указать параметр prio. Он задает приоритет класса (0 — макс.приоритет). Классы с меньшим приоритетом не обрабатываются пока есть данные в более приоритетных классах.

Источники:
Linux Advanced Routing & Traffic Control HOWTO
Traffic Control HOWTO
Повесть о Linux и управлении трафиком
Share post

Similar posts

AdBlock has stolen the banner, but banners are not teeth — they will be back

More
Ads

Comments 21

    +1
    Статья, конечно, не плохая, но с разжеванностью перебор…
    Хм, ни разу дальше первого источника (LARTC) не лазил. Сколько вопросов не возникало, обычно он отвечал на всё и примерами и теорией. Ну и да — man всегда с нами.
      +3
      Если у кого тяжело с английским, то на opennet'е перевод LARTC.
        0
        LARTC как-то не бросился мне в глаза в поисковой выдаче. Да и на свой опыт понадеялся. Поэтому пришлось поскакать по буеракам, прежде же выйти на прямую дорогу. Если бы зацепился за LARTC сразу — этой моей статьи бы не было.
        +2
        Статья хорошая, но я бы посоветовал переименовать ее в «управление шириной канала». Просто «полоса пропускания» у меня, электронщика, ассоциируется немного с другим, к линуксу не относящимся никаким боком.
          +1
          Кстати, на будущее, не стоит активно использовать u32 селектор, особенно на высоко нагруженных каналах — он медленно отрабатывает.
            +3
            С превеликим удовольствием, освоим вашу статью, почему, зачем и как нужно делать на нагруженных каналах.
              +1
              Собственно, там получается скорее набор советов, чем полноценная статья. Если хочется — напишу.
                +2
                Присоединяюсь к хотелкам статьи.
                  +1
                  Если соберётесь писать, могу несколько раз пробежаться по вашему черновику, дабы дополнить или уточнить что смогу, если пропустите. Сам не соберусь состряпать что-то вменяемое по объёму — дел до самой Ж.
                    +1
                    Да я уже с полгода собираюсь :). Как решусь — настряпаю черновик и буду его по знакомым таскать. Могу и Вам показать.
              0
              Спасибо.
                +3
                Статья хорошая. Спасибо. Могу дополнить то, что к Linux можно прикрутить dummynet и раскидывать трафик по очередям как в FreeBSD c помощью ipfw.
                  0
                  Да, да, да!
                    0
                    И получить все проблемы dummynet в Linux. Правильно использовать flow + ipset или u32 — это быстро и стабильно. Подробнее написано тут.
                      0
                      К винде, к стати тоже, но не пробовал.
                      0
                      Можете контролировать входящий трафик через интерфейс ifb, или на втором интерфейсе, для которого трафик будет исходящим. Решение не идеальное, но лучше, чем ничего.
                        +5
                        Новичкам????
                          0
                          Думаете, новичкам будет легче, начали бы они сразу с, как правильно отметил выше Alukardd, LARTC?
                          0
                          Как быть с удалением отдельного фильтра? удалять все и заполнять заново на больших конфигурациях неправильно.
                            0
                            Сбрасывать длинную конфигурацию построчно тоже тяжело, поэтому я и привел, как это сделать одной командой. Для удаления отдельных фильтров есть tc filter del… Для класса и дисциплины тоже del. В «man tc» есть описание всех доступных команд и аргументов.
                            0
                            Есть ещё скриптик под названием htbinit
                            Уж очень удобно позволяет всё настроить и управлять.

                            Only users with full accounts can post comments. Log in, please.