Разделение, ограничение и управление трафиком — актуальная и сложная задача,
которую обычно возлагают на дорогостоящее специальное сетевое оборудование. Но
решить ее можно и с помощью подсистемы Linux-ядра Traffic Control, не
уступающей по возможностям Cisco IOS.

Допустим, существует офис некой компании X, и в нем числится около ста
сотрудников, каждый из которых может выходить в интернет через шлюз. Скорость
внешнего канала составляет 100 Мбит. Системный администратор справился с
настройкой шлюза в силу своих способностей – что и посчитал достаточным для
правильного функционирования сети. К чему это привело? К увольнению
недальновидного (или ленивого) админа.

Со временем большинство сотрудников начали жаловаться на "тормоза" интернета,
а другие, наоборот, заметили, что могут качать торренты в рабочее время на очень
внушительных скоростях. Говоря админским языком, в сети образовались заторы,
вызванные теми, кто в тот момент активно ее использовал. Стомегабитный канал
распределялся неравномерно между пользователями, и каждый мог занять его весь.
Остальным приходилось ждать.

 

Краткий сценарий

Решение проблемы: разделение канала между сотрудниками с ограничением
скорости! Сеть будет функционировать на "5+", если каждый сотрудник получит в
распоряжение отдельный канал, скорость которого будет составлять 1 Мбит. Тогда
отдельно взятый интернет-пользователь не сможет занять больше причитающейся ему
доли и отобрать часть канала у других. С точки зрения компании, это еще и
отличный способ экономии (после разделения канала оказывается, что его суммарная
пропускная способность даже излишне высока) и ведения статистики по трафику для
отдельно взятого сотрудника.

Обычно для разделения канала с ограничением скорости используются возможности
операционной системы IOS, на которой функционирует сетевое оборудование Cisco
(дешевые решения от других производителей, таких, как Dlink, Trendnet и Netgear,
вообще не обладают такой возможностью). Однако особой необходимости тратить
баснословные суммы на аппаратные шлюзы от Cisco нет. Ядро Linux уже более пяти
лет как содержит в себе код сложной и весьма функциональной подсистемы
управления трафиком Traffic Control, которая по некоторым параметрам даже
обходит IOS.

 

Подсистема Traffic Control

Подсистема Traffic Control поддерживает множество методов
классификации, приоритезации, разделения и ограничения трафика (как исходящего,
так и входящего). Она очень гибка в настройке, но сложна в понимании. Поэтому мы
уделим значительную часть статьи теоретическому материалу и лишь затем приступим
к решению задачи с помощью HTB – одной из наиболее гибких и популярных дисциплин
Traffic Control.

Подсистема управления трафиком Linux позволяет делать следующее:

  • Shaping. Шейпинг — ограничение трафика, задержка пакетов с
    целью создания желаемой скорости передачи. Может использоваться не только для
    "сужения" исходящего канала, но и для сглаживания бросков во время пиковых
    нагрузок.
  • Scheduling. Планирование – упорядочивание типов трафика в
    канале. Позволяет избегать задержек для критичных типов трафика (QoS).
  • Policing. Политика входящего трафика. Позволяет ограничить
    входящий трафик путем уничтожения превысивших лимит пакетов. Помогает бороться
    с DDoS.

Отметим, что ограничение без потерь возможно только в отношении исходящего
трафика. Стек протоколов TCP/IP не предусматривает возможности заставить
удаленную сторону слать пакеты медленнее (и это правильно).

В обработке трафика участвуют три ключевых сущности: дисциплины обработки
пакетов, классы и фильтры. Настройка эффективной системы ограничения трафика
невозможна без понимания механизмов их работы, роли и связи друг с другом. Мы в
подробностях рассмотрим каждую из них:

  • Дисциплина обработки пакетов (qdisc) — очередь пакетов и
    закрепленный за ней алгоритм обработки.
  • Класс (class) — логический контейнер, который может
    содержать несколько подклассов или дисциплину.
  • Фильтр (filter) — механизм классификации трафика.

В простейшем варианте путь пакета от приложения до удаленного конца
соединения выглядит так. Приложение генерирует (пользуясь услугами библиотек и
ядра) сетевой пакет и отдает его ядру, которое помещает пакет в очередь FIFO
(первым пришел, первым ушел). Драйвер сетевой карты время от времени обращается
к специальному алгоритму ядра, который извлекает из очереди пакет и отдает его
драйверу. Далее пакет уходит к адресату. Все просто.

Linux действует таким же образом. Но формат представления очереди и алгоритм
ее обработки, в совокупности называемые дисциплиной обработки пакетов, в нем
заменяемы! По умолчанию используется дисциплина pfifo_fast, реализующая очередь
FIFO. Пользуясь утилитой tc, администратор может заменить ее на другую
дисциплину, которая будет переупорядочивать пакеты (планирование), задерживать
их на определенное время (шейпинг) или выполнять другие действия.

 

Дисциплины классов

Traffic Control не был бы столь гибким, если бы не позволял разбивать
трафик на классы с помощью классовой дисциплины и набора ее подклассов.
Схематически классовая дисциплина очень похожа на файловую систему, c тем лишь
исключением, что ее корень или классы (каталоги) могут содержать либо дисциплину
(файл), либо подклассы (подкаталоги). Одно из двух. Классовые дисциплины и
классы предназначены для построения дерева выбора. Сначала весь трафик
разбивается на несколько общих классов (например, трафик до Отдела-1, трафик до
специализированных внутренних серверов и т.д.), а затем каждый из них
разбивается на несколько подклассов (например, трафик до DNS-сервера Отдела-1),
за которыми уже могут быть закреплены дисциплины.

Чтобы управлять тем, дисциплиной какого класса будет обработан определенный
тип трафика, классовые дисциплины позволяют подключать к себе фильтры. Это дает
возможность "завернуть" определенный трафик в один из ее подклассов. Фильтры
используют классификаторы для идентификации пакетов нужного типа и как бы
говорят ядру: "Этот вид трафика должен обрабатываться с помощью дисциплины вот
этого класса". Существует несколько разных классификаторов. Самыми популярными
являются u32 и fw. Первый позволяет выделять пакеты по исходящим адресам и
адресам назначения, портам, парам "хост:порт", типам протокола и типу сервиса.
Второй классифицирует пакеты путем чтения маркировок, записанных брандмауэром
iptables/netfilter (цель MARK).

За каждым сетевым интерфейсом должны быть закреплены две особые дисциплины:
корневая дисциплина (root qdisc) и входящая дисциплина (ingress qdisc). В первую
помещается весь исходящий трафик (по умолчанию используется дисциплина
pfifo_fast). Во вторую — входящий.

Для идентификации дисциплин и классов используются дескрипторы. Они состоят
из старшего и младшего номеров. Первый — это произвольное число, однако все
классы, имеющие общего родителя, должны иметь одинаковый старший номер. Младший
номер используется либо для произвольной идентификации классов, либо для
указания на то, что объект является дисциплиной (номер 0). Специальный
дескриптор ffff:0 зарезервирован для входящей дисциплины.

 

Утилита tc

Для конфигурирования подсистемы управления трафиком предназначена утилита tc
из пакета iproute2. Она принимает набор команд в качестве аргументов, с помощью
которых можно создавать классы, привязывать к ним дисциплины и добавлять
фильтры. Синтаксис ее похож на синтаксис команды ipfw из операционной системы
FreeBSD, так что знакомые с ним быстро освоятся.

Для примера рассмотрим простейший вариант использования:

# tc qdisc add dev eth0 root tbf rate 256kbit \
latency 50ms burst 1540

Эта команда устанавливает ограничение для всего исходящего трафика в 256
Кбит/с. Разберем подробнее все аргументы tc:

  • qdisc add — добавляем новую дисциплину (для удаления используй del).
  • dev eth0 — указываем устройство, к которому будет привязана дисциплина.
  • root — наша дисциплина корневая (будет обрабатываться весь трафик).
  • tbf — имя дисциплины.
  • rate 256kbit latency 50ms burst 1540 — параметры, специфичные для данной
    дисциплины: rate — ограничение скорости, latency — максимальный "возраст"
    пакета в очереди, burst — размер буфера.

Проще говоря, команда подключает дисциплину tbf в качестве корневой на
интерфейсе eth0 и задает ей несколько параметров. Token Bucket Filter (TBF) —
это бесклассовая дисциплина, которая передает поступающие пакеты с заданной
скоростью.

Способ указания скоростей и других величин в утилите tc несколько отличается
от общепринятого, поэтому следующую табличку придется запомнить:

 

Формат указания скорости в утилите tc

mbps = 1024 kbps = 1024 * 1024 bps => Байт/с
mbit = 1024 kbit => Кбит/с
mb = 1024 kb = 1024 * 1024 b => Байт

Заменить стандартную корневую дисциплину на любую бесклассовую совсем
несложно, но на таком коне далеко не уедешь. Для создания разветвленной системы
управления трафиком нужны классовые дисциплины, классы, фильтры и целое дерево
дисциплин. Чтобы настроить все это, может понадобиться не один десяток команд.
Мы рассмотрим несколько вводных примеров, перед тем как перейти к обсуждению
дисциплины HTB.

 

Пример дерева дисциплин

Классовая дисциплина prio предназначена для классификации трафика с помощью
фильтров или приоритезации. По умолчанию prio содержит три класса, в каждом из
которых находится обычная дисциплина FIFO. Когда сетевая карта обращается за
очередным пакетом, проверяется класс :1. Если он не содержит пакетов,
проверяется класс :2 и только в последнюю очередь — :3. Получается, что пакеты
класса :1 получают наивысший приоритет, а :3 — наименьший. Решение о том, в
какой класс направить трафик, дисциплина prio принимает на основе поля TOS
сетевого пакета.

Подключим дисциплину prio в качестве корневой и назначим ей имя (дескриптор)
"1:0":

# tc qdisc add dev eth0 root handle 1:0 prio

Результат этой команды: дисциплина prio, подключенная в качестве корня, и три
класса (1:1, 1:2 и 1:3) внутри нее, к каждому из которых подключена дисциплина
FIFO.

Мы вольны заменить любую из дисциплин, подключенных к классам, чем и
воспользуемся для подключения дисциплины sfq с дескриптором "10:0" к классу
"1:1":

# tc qdisc add dev eth0 parent 1:1 handle 10:0 sfq

Это обеспечит справедливое разделение канала между интерактивными
приложениями (они имеют наивысший приоритет). Чтобы остальные приложения, такие
как менеджеры закачек и torrent-клиенты (которые обычно шлют пакеты с меньшим
приоритетом в поле TOS), не мешали интерактивным, ограничим для них скорость:

# tc qdisc add dev eth0 parent 1:2 handle 20:0 tbf \
rate 512kbit buffer 3200 limit 3000
# tc qdisc add dev eth0 parent 1:3 handle 30:0 tbf \
rate 256kbit buffer 6400 limit 3000

Такая схема будет плохо работать в реальной жизни, но для примера вполне
годится.

Теперь сделаем так, чтобы весь SSH-трафик имел наивысший приоритет. Для этого
закрепим за корневой дисциплиной prio фильтр, который будет перенаправлять
пакеты с портом назначения 22 в дисциплину класса "1:1".

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

Рассмотрим подробнее механизм подключения фильтров.

  • filter add — Добавляем фильтр.
  • dev eth0 — Указываем устройство.
  • parent 1:0 — Дескриптор родителя.
  • protocol ip — Протокол, с которым будет работать фильтр.
  • prio 1 — Присваиваем классифицированному трафику приоритет 1 (наивысший).
  • u32 — Используемый классификатор.
  • match ip dport 22 0xffff — Параметры классификатора. В данном случае
    указание отбирать пакеты с портом назначения 22.
  • flowid 1:1 — Отфильтрованные пакеты должны иметь класс "1:1" и
    обрабатываться с помощью его дисциплины.

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

 

Классовая дисциплина HTB

Еще в первый релиз системы Traffic Control была включена классовая
дисциплина CBQ (Class-Based Queue), предназначенная для реализации сложных
систем управления и ограничения трафика. CBQ завоевала большую популярность
благодаря своей гибкости, но была очень сложна, запутана и обладала рядом
ограничений (тут и необходимость заранее указывать максимальную пропускную
способность канала, и неэффективный алгоритм шейпинга).

Поэтому в скором времени появилась более эффективная и простая в
использовании альтернатива под названием HTB (Hierarchical Token
Bucket
). Классовая дисциплина HTB предназначена для разделения полосы
пропускания между различными видами трафика, каждому из которых может быть
предоставлена полоса гарантированной ширины. Она не обладает гибкостью CBQ, но
гораздо более проста в настройке и лишена ее недостатков. Именно на HTB сегодня
принято строить сложные и эффективные системы ограничения трафика.

Рассмотрим применение HTB на примере, представленном в начале статьи, но
более усложненном. Допустим, у нас есть шлюз на Linux, интерфейс eth1 которого
смотрит наружу, а eth0 — во внутреннюю сеть. Ширина канала — 100 Мбит. Задача:
разделить канал между сотрудниками компании так, чтобы директор и сотрудники
IT-отдела могли выходить в интернет без скоростных ограничений, маркетологи
получили ограничение в 2 Мбит/c каждый, менеджеры — 1 Мбит/c, секретари — 512
Кбит/c, а все остальные — 256 Кбит/c.

Есть два варианта решения. Первый: составить огромную таблицу IP-адресов и
создать специальные правила ограничений для каждого адреса (с точки зрения
системы HTB это будет выглядеть как огромный набор классов и фильтров, по одному
на каждый адрес). Второй: разбить всех потребителей канала на мета-группы,
каждую из которых выделить в отдельную подсеть (директор и IT-отдел —
172.16.1.0, маркетологи — 172.16.2.0, менеджеры — 172.16.3.0, секретари —
172.16.4.0, остальные — 172.16.5.0). Для каждой подсети назначить суммарное для
всех ее членов ограничение со справедливым разделением канала.

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

Для начала создадим работоспособную систему, основанную только на втором
варианте решения задачи. Подключим дисциплину HTB в качестве корневой:

# tc qdisc add dev eth0 root handle 1: htb default 15

Опция "default 15" говорит о том, что весь неклассифицированный фильтрами
трафик должен быть обработан с помощью дисциплин класса "1:15". Создадим
корневой класс, под который будет попадать весь трафик (это нужно для реализации
заимствования):

# tc class add dev eth0 parent 1: classid 1:1 htb \
rate 100mbps ceil 100mbps

Создадим в нем пять подклассов для пяти наших подсетей. Директору и IT-отделу
выделим 30-мегабитный канал с возможностью его расширения (заимствования) вплоть
до 100 Мбит в случаях, когда остальные каналы не заняты:

# tc class add dev eth0 parent 1:1 classid 1:11 \
htb rate 30mbps ceil 100mbps

Для маркетологов выделим 20-мегабитный канал:

# tc class add dev eth0 parent 1:1 classid 1:12 \
htb rate 20mbps

Менеджерам – 10 Мбит/с:

# tc class add dev eth0 parent 1:1 classid 1:13 htb rate 10mbps

Секретарям – 5 Мбит/с:

# tc class add dev eth0 parent 1:1 classid 1:14 htb rate 5mbps

И – 40 Мбит/с на всех остальных:

# tc class add dev eth0 parent 1:1 classid 1:15 htb rate 40mbps

По умолчанию к вновь созданным классам подключены дисциплины, реализующие
очередь FIFO. Это нам не подходит. Чтобы канал равномерно распределялся между
всеми участниками подсети, мы должны подключить к ним дисциплину sfq:

# tc qdisc add dev eth0 parent 1:11 handle 10:0 sfq perturb 10
# tc qdisc add dev eth0 parent 1:12 handle 20:0 sfq perturb 10
# tc qdisc add dev eth0 parent 1:13 handle 30:0 sfq perturb 10
# tc qdisc add dev eth0 parent 1:14 handle 40:0 sfq perturb 10
# tc qdisc add dev eth0 parent 1:15 handle 50:0 sfq perturb 10

Теперь подключим фильтры, которые будут классифицировать трафик:

# tc filter add dev eth0 protocol ip parent 1:0 prio 1 \
u32 match ip src 172.16.1.0/24 flowid 1:11
# tc filter add dev eth0 protocol ip parent 1:0 prio 1 \
u32 match ip src 172.16.2.0/24 flowid 1:12
# tc filter add dev eth0 protocol ip parent 1:0 prio 1 \
u32 match ip src 172.16.3.0/24 flowid 1:13
# tc filter add dev eth0 protocol ip parent 1:0 prio 1 \
u32 match ip src 172.16.4.0/24 flowid 1:14

Для "всех остальных" фильтр не нужен, потому как мы уже указали дефолтовый
класс неклассифицированного трафика.

Все, система будет работать, но не обеспечит жесткого ограничения для каждого
пользователя (если, например, в определенный момент времени интернетом будет
пользоваться только один менеджер, ему достанутся все 10 Мбит, отведенные для
всех менеджеров). Жесткое ограничение можно реализовать, если вместо дисциплин
подключить к классам другие классы HTB, по одному на каждого пользователя, и
создать соответствующие фильтры.

Для примера, установим ограничение в 256 Кбит/с для пользователя,
находящегося в подсети "все остальные". Сначала добавим к "классу-подсети" новый
"класс-пользователь":

# tc class add dev eth0 parent 1:15 classid 1:150 \
htb rate 256kbps

А затем фильтр:

# tc filter add dev eth0 protocol ip parent 1:15 prio 1 \
u32 match ip src 172.16.1.32 flowid 1:150

Подключать к классу дисциплину нет необходимости, так как по умолчанию к нему
уже подключена дисциплина FIFO. Подобные команды придется выполнить в отношении
каждого пользователя, не забывая давать им уникальные дескрипторы класса. При
желании все это нетрудно упаковать в простой скрипт, который будет проходить по
списку IP-адресов и выполнять связку команд для каждого адреса.

 

Наиболее используемые дисциплины

  • pfifo — Простейшая очередь FIFO (первым пришел, первым ушел).
    Размер буфера задается в пакетах.
  • bfifo — Аналог pfifo с буфером, размер которого задается в байтах.
  • pfifo_fast — Реализует простую очередь FIFO с тремя полосами.
    Используется по умолчанию в качестве корневой и не принимает аргументов.
  • tbf — Token Bucket Filter (TBF). Передает поступающие пакеты со
    скоростью, не превышающей заданный порог. Простая и точная реализация делает
    ее идеальным решением для ограничения полосы пропускания всего интерфейса.
  • sfq — Stochastic Fairness Queueing (SFQ). Реализация алгоритма
    справедливой очередизации. Поровну разделяет полосу пропускания между
    несколькими соединениями. Эффективно работает только на загруженном
    интерфейсе.
  • red — Random Early Detection (RED). Симуляция затора. Отбрасывает
    пакеты случайным образом при достижении заданной полосы пропускания. Хорошо
    подходит для ограничения прожорливых в плане трафика приложений.
  • prio — Разделяет трафик по приоритетам (поле TOS). По умолчанию
    создает три класса, в первый из которых попадают пакеты с большим приоритетом,
    а в третий — с наименьшим.
  • cbq — Class Based Queueing (CBQ). Классовая дисциплина,
    предназначенная для создания сложных систем управления трафиком. Поддерживает
    ограничения и приоритеты.
  • htb — Hierarchical Token Bucket (HTB). Предназначена для разделения
    полосы пропускания между различными видами трафика на полосы заданной ширины,
    с возможностью заимствования. Поддерживает приоритеты.

5 комментариев

  1. 08.10.2014 at 14:22

    Привет. все выше tc filter add dev eth0 protocol ip parent 1:15 prio 1

    u32 match ip src 172.16.1.32 flowid 1:150 нормально а он пишет такое, Unknown filter » u32″, hence option «match» is unparsable

  2. spilva

    21.10.2015 at 12:02

    Из статьи не ясно, откуда берутся значения handle и perturb при подключении дисциплины SFQ, и что они вообще означают.

    А в целом, хорошо и доступно для понимания.

Оставить мнение

Check Also

Скрытая сила пробела. Эксплуатируем критическую уязвимость в Apache Tomcat

В этой статье мы поговорим о баге в Apache Tomcat, популярнейшем веб-сервере для сайтов на…