Использование iproute 2 при настройке Linux маршрутизатора
Наверное, любой из вас хотя бы отдаленно знает, что такое маршрутизация. Итак, маршрутизация - это, как бы это банально не звучало, есть выбор маршрута. В данной статье под этим термином я буду понимать выбор маршрута следования сетевого IP пакета. Дело в том, что современные программные маршрутизаторы (а рассказывать я буду как раз об одном из представителей данного класса устройств) умеют полноценно работать только с протоколом ip.
Почему же я решил описать построение маршрутизатора именно на основе ОС GNU/Linux? Тут две основные причины:
- ядро GNU/Linux способно уместиться на дискете, что может позволить создать весьма функциональный маршрутизатор вне зависимости от конкретной машины, кроме этого можно "оживить" старые машины и заставить их работать на пользу людям;
- ядро Linux(2.4, 2.2) поддерживает очень полезные функции маршрутизации, и может быть специально заточено под использование в качестве маршрутизатора, кроме этого стандартный брандмауэр Linux 2.4 – iptables может "метить" (не подумайте ничего плохого) определённые пакеты, а ядро может выполнять выбор маршрута согласно этим меткам.
Это открывает широкие возможности при создании сетей со сложной структурой. Ещё очень важной особенностью является универсальность GNU/Linux - по-моему, эта ОС поддерживает в той или иной степени все распространённые сетевые протоколы. Ещё одной немаловажной особенностью является бесплатность всей системы маршрутизации. С точки зрения многих администраторов маршрутизатор - это просто черный ящик, принимающий и передающий пакеты, однако грамотная настройка маршрутизации – залог эффективности и зачастую безопасности всей сети.
Очень интересно использовать маршрутизацию для распределения нагрузки, передачи определённого трафика на определённый хост(для анализа), и уменьшения опасности DoS-атак. Маршрутизация способна ограничивать сетевые "штормы" и существенно увеличить пропускную способность сети. Я решил построить эту статья в виде конкретных примеров настройки маршрутизации (в дальнейшем я иногда буду употреблять слово роутинг).
Маршрутизация бывает статической и динамической. Отличие в том, что при статической маршрутизации все правила передачи пакетов прописываются статически и могут быть изменены только вручную, динамическая маршрутизация применяется, когда в сети существет несколько маршрутизаторов, и нахождение пути до удаленного хоста становится нетривиальной задачей. Динамическая маршрутизация больше подходит для часто меняющихся сетей со сложной структурой.
Хотя GNU/Linux поддерживает оба типа маршрутизации, но в рамках данной статьи я буду рассказывать о статической маршрутизации при помощи пакета iproute2, кстати написанного нашим программистом Алексеем Кузнецовым. Для начала работы необходимо настроить соответствующим образом ядро и установить пакет iproute. Остановлюсь на настройке ядра. В ядре необходимо включить ряд опций маршрутизации (думаю нет нужды объяснять, как настраивать и компилировать ядро).Я предполагаю, что вы настраиваете ядро командой make menuconfig.
На странице Networking Options необходимо включить следующие элементы:
• IP: advanced router - включение расширенных возможностей маршрутизации
• IP: policy routing - маршрутизация по некоторым внутренним полям пакетов(обычно применяется совместно с брандмауэром), а также для расширенных возможностей маршрутизации, например, маршрутизация согласно адресу-источнику пакета (source-based routing)
• IP: use netfilter MARK value as routing key - включение возможности маршрутизации согласно маркировке пакета брандмауэром
• IP: use TOS value as routing key - маршрутизация пакетов на основе заголовка тип сервиса(TOS), помогает увеличить пропускную способность сети при наличии нескольких путей прохождения пакетов
• IP: large routing tables - включение больших (>64 правил) таблиц маршрутизации ядра.
Можно также включить поддержку туннелей, но я не буду на этом задерживаться. После настройки ядра необходимо установить iproute2 в большинстве дистрибутивов GNU/Linux эта программа входит в дистрибутив, например, для Debian GNU/Linux команда будет выглядеть так:
apt-get install iproute
исходные коды могут быть получены по адресу ftp://ftp.inr.ac.ru/iprouting/ iproute2-xxx.tar.gz компиляция стандартная, но цели install в Makefile нет - необходимо скопировать бинарные файлы из каталога ip(cp ifcfg ip routef routel rtacct rtmon rtpr /sbin) и из каталога tc(cp tc /sbin) в /sbin, а ./etc/iproute2/ - в /etc/iproute2/
Не поленитесь также скачать Linux Advanced Routing and Traffic Control HOWTO,
которое может быть найдено на узле www.lartc.org. На самом деле это руководство просто необходимо для настройки сложной статической маршрутизации на основе Linux. Я сам настраивал маршрутизацию в сети на основе этого руководства, поэтому если эта статья не решила вашей проблемы лучше обратиться к данному документу.
Пакет iproute состоит фактически из двух утилит управления трафиком:
• ip - управление собственно маршрутизацией
• tc - управление очередями маршрутизации
Для начала расскажу об общих принципах команды ip, синтаксис команды таков: ip [опции] {объект маршрутизации} {команда или HELP}
Из опций наиболее полезным является выбор семейства IP -
--4 - IPv4
--6 - IPv6
Объекты маршрутизации представлены следующим списком:
• link - сетевое устройство(реальное физическое или виртуальное, например vlan или туннель)
• address - ip адресс устройства
• neighbour - кеш ARP
• route - таблицы маршрутизации
• rule - правила маршрутизации
• maddress - широковещательный адрес
• mroute - широковещательные таблицы маршрутизации
• tunnel - IP туннель
Команды для разных объектов разные, но для всех объектов существует стандартный набор команд add(добавить), delete(удалить) и show(показать можно также приенять list или ls). Синтаксис различных команд для разных объектов может быть совершенно разным, поэтому я не буду описывать здесь все команды каждого объекта. Я буду придерживаться стиля Linux Adv. Routing HOWTO и приведу полезные примеры употребления команды ip. Для начала просмотрим сетевые устройства, присутствующие на нашей тестовой машине (пусть у неё будут ip адреса 192.168.1.1 и 192.168.2.1):
# ip link list
1: lo: <LOOPBACK,UP> mtu 16436 qdisc noqueue
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
2: dummy0: <BROADCAST,NOARP> mtu 1500 qdisc noop
link/ether 00:00:00:00:00:00 brd ff:ff:ff:ff:ff:ff
3: eth0: <BROADCAST, MULTICAST, UP> mtu 1500 qdisc pfifo_fast qlen 10
link/ether 48:54:e8:01:ef:56 brd ff:ff:ff:ff:ff:ff
4: eth1: <BROADCAST, MULTICAST, UP> mtu 1500 qdisc pfifo_fast qlen 10
link/ether 00:e0:4c:39:ef:56 brd ff:ff:ff:ff:ff:ff
Теперь настало время перейти к рассмотрению простейшего случая организации маршрутизации. Допустим, в локальной сети крупных размеров есть три компьютера, которым положено иметь доступ к глобальной сети. При этом имеется два соединения с провайдером - быстрое ADSL и медленное модемное. Желательно один компьютер (c адресом 192.168.1.10) направить в глобальную сеть через модем, а два других (с адресами 192.168.1.20 и 192.168.21) через ADSL. Трафик, направленный во внешний мир с других компьютеров желательно перенаправлять на сниффер, расположенный по адресу 192.168.1.100, причем сниффер может располагаться и на данном компьютере (tcpdump -i ethX). Схема подключения примерно такова:
Просмотрим сетевые карты на сервере, список будет примерно таким:
# ip link list
1: lo: <LOOPBACK,UP> mtu 16436 qdisc noqueue
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
2: eth0: <BROADCAST,MULTICAST,UP> mtu 1500 qdisc pfifo_fast qlen 10
link/ether 48:54:e8:01:ef:56 brd ff:ff:ff:ff:ff:ff
inet 192.168.1.1/24 brd 192.168.1.255 scope global eth0
3764: ppp0: <POINTTOPOINT,MULTICAST,NOARP,UP> mtu 1492 qdisc pfifo_fast qlen
link/ppp
inet 213.59.72.1 peer 213.59.72.48/24 scope global ppp0
3765: ppp1: <POINTTOPOINT,MULTICAST,NOARP,UP> mtu 1492 qdisc pfifo_fast qlen
link/ppp
inet 213.59.72.2 peer 213.59.72.10/24 scope global ppp1
Очевидно, что ppp0 соотвествует модемному соединению, а ppp1 – ADSL соединению. Просмотрим таблицы маршрутизации (здесь идет отображение всех таблиц маршрутизации ядра, ядро принимает решение о применениии той или иной таблицы на основании адреса источника пакета, утилита route способна опреировать только с таблицой main и local, iproute2 дает возможность создавать собственные таблицы, что будет описано несколько позже):
# ip rule list
0: from all lookup local
32766: from all lookup main
32767: from all lookup default
Как видно, пока наши таблицы применимы ко всем пакетам. Добавим новую таблицу для машин, связанных с Интернетом через ADSL:
# echo 200 inet_adsl >> /etc/iproute2/rt_tables
Эта команда требует некоторого пояснения: номер 200 выбран произвольно, главное, чтобы он не совпадал с другими номерами таблиц маршрутизации, имя inet_adsl также дается произвольно, но потом этой таблицей можно управлять по имени, так что в ваших же интересах дать понятное имя таблице, дабы облегчить себе процесс дальнейшей настройки.
Добавим в таблицу правила приема пакетов:
# ip rule add from 192.168.1.20 table inet_adsl
# ip rule add from 192.168.1.21 table inet_adsl
Эти команды, думаю, являются понятными, поэтому сразу просмотрим наши таблицы маршрутизации:
# ip rule list
0: from all lookup local
32764: from 192.168.1.20 lookup inet_adsl
32765: from 192.168.1.21 lookup inet_adsl
32766: from all lookup main
32767: from all lookup default
Теперь необходимо добавить маршртизатор по умолчанию для таблицы inet_adsl - тогда все пакеты от необходимых машин будут направляться к заданному шлюзу:
# ip route add default via 213.79.52.10 dev ppp1 table inet_adsl
После этого необходимо сбросить кеш маршрутизатора:
# ip route flush cache
Теперь очередь настроить модемное соединение. Думаю, следующие команды не должны вызвать сложности:
# echo 201 inet_modem >> /etc/iproute2/rt_tables
# ip rule add from 192.168.1.10 table inet_modem
# ip route add default via 213.79.52.48 dev ppp0 table inet_modem
# ip route flush cache
Для просмотра таблиц маршрутизации можно использовать команду
# ip route list [table table_name]
Теперь необходимо включить сниффер для отслеживания пакетов, которые пришли из локальной сети. Добавим виртуальную сетевую карту:
# ifconfig eth0:1 192.168.1.100 up
И настроим правила маршрутизации так, чтобы пакеты с локальной сети, направленные во внешнюю сеть направлялись на адрес 192.168.1.100, т.е. чтобы администратор мог наблюдать за попытками выхода во внешнюю сеть. Эта проблема не так тривиальна, как предыдущая, но решение все-таки существует. Задача решается интеграцией возможностей netfilter(iptables) и iproute2.
Внутри ядра существует возможность установки на пакетах меток (метки устанавливает iptables, но учтите, что эти метки существуют только в пределах ядра, и не выходят за границы данного компьютера).Подробное описание системы netfilter выходит за рамки данной статьи, поэтому я ограничусь описанием процесса установки метки на конкретном примере:
# iptables -A PREROUTING -i eth0 -s 192.168.1.0/24 -d ! 192.168.1.0/24 -t mangle -j MARK --set-mark 2
Некоторые комментарии: обратите внимание на флаг -j MARK и --set-mark: последний флаг может устанавливать метку от 1 до 255.
После установки правила iptables неоходимо вновь вернуться к настройке iproute2. Учтите, что сейчас все необходимые нам пакеты помечены меткой 2, осталось только направить все такие пакеты на сниффер, расположенный по адресу 192.168.1.100:
# echo 202 sniffing >> /etc/iproute2/rt_tables
# ip rule add fwmark 2 table sniffing
Заметьте, эта строка выполняет выборку пакетов согласно их метке.
# ip route add default via 192.168.1.100 dev eth0:1 table sniffing
# ip route flush cache
Запускаем собственно сниффер (в фоновом режиме)
# tcpdump -i eth0:1 > /var/log/tcpdump.log &
При этом необходимо позаботиться о правильной установке прав доступа к файлу дампа, установите правильную umask или установите атрибуты вручную:
# touch /var/log/tcpdump.log
# chattr 600 /var/log/tcpdump.log
Для повышения безопасности можно также запустить сниффер через chroot (chroot /var/log tcpdump -i eth0:1), но обычно это делается в инициализационном скрипте.
Есть ещё несколько ньюансов в данном примере, а именно установки сетевых опций ядра. Опции ядра обычно устанавливаются посредством файловой системы /proc занесением необходимых значений в определенные файлы. Для нас необходимо отключить icmp redirect ответы, чтобы наш маршрутизатор не сообщал клиентам о выборе необходимого маршрута непосредственно (это лишит нас возможности установки меток на пакеты, а кроме того понимается далеко не всеми клиентами по умолчанию). Для этого делаем следующее:
# echo 0 > /proc/sys/net/ipv4/conf/all/send_redirects
# echo 0 > /proc/sys/net/ipv4/conf/default/send_redirects
# echo 0 > /proc/sys/net/ipv4/conf/eth0/send_redirects
Не забывайте также о правильной настройке брандмауэра, а также, если имеется несколько подсетей, желательно убедиться, что выключена прямая передача пакетов из подсети в подсеть (т.е. если пакет направлен в другую подсеть, он не должен передаваться на другой сетевой интерфейс без обработки):
# echo 0 > /proc/sys/net/ipv4/ip_forward
Единственный серьезный минус приведенной схемы - возможность подмены ip-адреса. К сожалению этот недостаток исправить невозможно, но можно дополнительно отслеживать обращения ко внешней сети. На этом я завершу описание этой "простенькой" задачки для администратора и перейду к описанию установки IP-туннелей.
Вообще любой сетевой туннель выполняет инкапсуляцию пакетов (фактически к каждому пакету добавляется необходимый заголовок). Туннели позволяют организовать связь нескольких подсетей одним соединением. В ядро Linux интегрирована поддержка нескольких типов IP-туннелей. Управление туннелями осуществляется посредством команды ip tunnel. Но для начала необходимо включить поддержку туннелей в ядре. На странице Networking options отмечаем следующие опции:
IP: tunneling - поддержка туннелей ядром
IP: GRE tunnels over ip - поддержка GRE туннелей, которые обладают
возможностью инкапсулировать ipv6 трафик, кроме этого GRE является стандартом де-факто в маршрутизаторах Cisco, поэтому для организации туннеля между Linux машиной и маршрутизатором Cisco применяйте GRE туннели.
Представим себе организацию туннеля между двумя компьютерами и соединяющем две подсети.
Для добавления GRE туннеля можно воспользоваться следующими командами: на сервере 192.168.1.1:
# ip tunnel add tuna mode gre remote 192.168.2.1 local 192.168.1.1 ttl 255
Эта команда задает GRE туннель от машины 192.168.1.1 до машины 192.168.2.1, для создания чисто IPV6 туннеля используется тип sit (mode sit), при этом необходимо вручную добавлять IPV6 адресс туннелю (ip --6 addr add ipv6_addr dev tunsit). Учтите, что вы можете добавлять туннель с любым именем, состоящим из букв и цифр. Поле ttl является необязательным, но каждому пакету, проходящему через тннель будет присваиваться заданный ttl. Вторым этапом настройки туннеля является настройка маршрутизации через этот туннель: включаем виртуальный сетевой интерфейс, созданный предыдущей командой
# ip link set tuna up
теперь необходимо назначить созданному туннелю ip адрес
# ip addr add 192.168.1.101 dev tuna
добавляем маршрут к сети 192.168.2.0/24 через созданный туннель
# ip route add 192.168.2.0/24 dev tuna
Последнее действие можно выполнить и с помощью старой утилиты route route add -net 192.168.2.0 netmask 255.255.255.0 dev tuna но синтаксис iproute IMHO несколько проще. На другом конце туннеля (192.168.2.1) проделываем схожие действия:
# ip tunnel add tunb mode gre remote 192.168.1.1 local 192.168.2.1 ttl 255
# ip link set tunb up
# ip addr add 192.168.2.101 dev tunb
# ip route add 192.168.1.0/24 dev tunb
После этого туннель начинает функционировать. Учтите также, что к данным, проходящим по туннелю дописывается дополнительный заголовок 20 байт длиной, таким образом MTU для туннеля составляет не 1500, а 1480 байт. Для решения этой команды несколько модифицируем команду добавления маршрута, указав mtu:
# ip route add 192.168.2.0/24 dev tuna mtu 1480
Явное указание mtu очень полезная вещь во многих случаях, например, при организации VLAN (IEE802.1q) также необходимо уменьшать значение MTU интерфейса.
Если планируется организовать туннель с маршрутизатором CISCO, то его конфигурация может выглядеть следующим образом:
interface Tunnel1
description IP tunnel
no ip address
no ip directed-broadcast
ip address 192.168.2.101/24
tunnel source Serial0
tunnel destination 192.168.1.101
tunnel mode ipip
ip route 192.168.1.0/24 Tunnel1
Итак, подведём итог этой небольшой статьи. Для выполнения статической маршрутизации лучше всего подходит iproute2 для GNU/Linux. Маршрутизация позволяет выполнять достаточно сложные операции по передаче пакетов, при этом возможно грамотно установить политику доступа к определённым подсетям и узлам сети. Одним из наиболее полезных в практическом плане инструментом оптимизации сетевых операций является управление очередями устройств, но это предмет моей следующей статьи, которую вы сможете скорее всего найти в следующем номере журнала. Для установки маршрутизатора не требуется мощного компьютера, в некоторых случаях достаточно floppy-дистрибутива Linux. Одним из таких дистрибутивов является ориентированный на маршрутизацию дистрибутив linuxrouter (www.linuxrouter.org). Он построен на базе ядра 2.2 и 2.0, что является приемлемым вариантом для построения маршрутизатора (включает iproute2, но, к сожалению, я не нашел в составе дистрибутива утилиты tc).
Если же в вашей сети несколько маршрутизаторов или структура сети является непостоянной, то лучшим выбором является устаноыка динамического маршрутизатора, имеющего возможность автоматического обновления маршрутных таблиц. Для любителей маршрутизаторов Cisco могу посоветовать роутер Zebra, эмулирующий синтаксис Cisco IOS. Ну вот и все, разговор о пакете iproute будет продолжен в следующем номере. Приведу список полезных ссылок:
http://www.lartc.org - Linux Advanced Routing and Traffic Control HOWTO -
обязательный документ, помогающий грамотно настроить маршрутизатор
http://www.linuxrouter.org - floppy-дисрибутив GNU/Linux, ориентированный
на маршрутизацию
http://www.opennet.ru - большая подборка документации о маршрутизации
Книга: "Маршрутизация в Linux" Брокмайер, Лебланк, Маккарти Издательский
дом "Вильямс", 2002. ISBN 5-8459-0271-1. Книга в основном рассказывает об
общих вопросах маршрутизации и настройки демона динамической маршрутизации
gated.
________________________________________
Часть 2.
Сейчас ведутся активные споры по наименованию Linux. Так как эта операционная система базируется на ПО GNU, то было предложено называть её GNU/Linux, поэтому далее я буду называть операционную систему GNU/Linux, а ядро - Linux, что соответствует истине.
Данная статья является продолжением описания пакета маршрутизации GNU/Linux
iproute2 (см. журнал "Системный администратор" 5 2003). Далее будут описаны принципы управления сетевым трафиком посредством очередей. Эта тема зачастую не упоминается в руководствах, но на самом деле с помощью очередей сетевых пакетов можно выполнять широкий круг исключительно полезных задач.
Приведу несколько примеров, которые часто встречаются на практике. Итак, очереди способны контролировать скорость передачи пакетов, ограничивая нежелательный сетевой трафик по скорости (позволяет избежать выход из строя сервера или отдельных демонов в результате DoS и даже DDoS атак). Позволяет осуществлять распределение нагрузки между несколькими сетевыми интерфейсами. С помощью очередей также можно добиться существенного увеличения производительности сети в целом при помощи разделения различных видов трафика (например, интерактивные данные должны обрабатываться быстрее) на основе поля ToS (type of service - тип услуг). Iproute2 также дает возможность ограничения SYN-flood и ICMP-dDoS атак. Кроме этого можно устанавливать свой предел скорости на основе различных фильтров.
Начну я с некоторых пояснений терминологии. Что есть очередь в данном контексте? Очередь - это механизм, позволяющий управлять приемом и передачей пакетов. Очередь является физическим объектом в памяти и содержит пакеты, поступившие на сетевой интерфейс. Чтобы понять принцип работы очередей (а именно возможности ограничения скорости) рассмотрим такой пример: пакет поступает в пустую очередь и передается на обработку, следующий пакет поступает в очередь и ждет обработки первого пакета (здесь под словом обработка подразумевается доставка пакета ядром на сокет-приемник), если в очередь поступает много пакетов, то они не успевают обрабатываться, и очередь растет.
Если размер очереди ограничен (а он ограничен всегда), то следующие пакеты не попадают в очередь а просто отбрасываются. Таким образом, регулируя длину очереди и зная время обработки одного пакета можно вычислить наибольшую скорость передачи пакетов. Замечу, что утилита tc пакета iproute2, выполняющая контроль над очередями, осуществляет преобразование скорость-->длина очереди автоматически, и вам необходимо указывать не непосредственно длину очереди, а лимит скорости в формате:
mbit - мегабиты в секунду;
kbit - килобиты в секунду;
mbps - мегабайты в секунду;
kbps - килобиты в секунду.
На деле реализация очередей несколько отличается от вышеприведенной, т.к. отбрасывание пакетов из конца очереди приводит к неприятным последствиям для TCP-протокола: например, когда сообщение потеряно, приложение-отправитель может рассматривать это как сигнал о том, что оно посылает пакеты слишком быстро. TCP реагирует на такой сигнал замедлением отправки сообщений. Но когда очередь полна, то часто несколько сообщений отбрасываются друг за другом - в результате целый ряд приложений решает замедлить передачу.
После этого приложения зондируют сеть для определения ее загруженности и буквально через несколько секунд возобновляют передачу с прежним темпом, что опять приводит к перегрузке. Поэтому обычно очередь просто отбрасывает лишние пакеты от различных источников (обычно используется алгоритм генерации случайных чисел), если за определенный промежуток времени её размер превзошёл некий лимит. Такой механизм называется (случайное раннее обнаружение - Random Early Detection, RED).
Вторым краеугольным камнем системы управления трафиком iproute2 является многополосность очередей. Представим себе, что у сетевого интерфейса 2 независимые очереди, имеющие различный приоритет. То есть пакеты, поступившие в очередь 2 будут обрабатываться, только если в очереди 1 пакетов, ждущих обработки, нет.
Что же дает такая схема? С помощью механизма полос можно выделять трафик, требующий быстрой обработки, и помещать его в 1-ю полосу, в то время как остальной трафик, не требующий особенно высокой скорости (например ftp, smtp, pop) помещается в полосу с меньшим приоритетом (большим порядковым номером). Нечто подобное предлагают ATM или Frame-Relay сети, но iproute2 позволяет организовать подобное поведение на любом интерфейсе. Необходимость в такой системе возникает при передаче интерактивных данных, например, видео или голосовых (ip-телефония).
Существует следующий подход к выделению интерактивного трафика: поле ToS - метка, присваиваемая пакету с помощью netfilter (--tos) или присваиваемая непосредственно сетевой службой (ftp, smtp...)
Поле ToS является ранней попыткой реализации сервиса QoS, обеспечивающей достаточно небольшой набор значений, приведу таблицу наиболее популярных протоколов и значения ToS для них:
• минимальная задержка - telnet, ftp(команды), ssh, smtp(команды), DNS(udp)
• максимальная полоса пропускания - ftp(данные), smtp(данные)
• максимальная надежность
• минимальная стоимость в настоящее время основном используются значения
• "минимальная задержка" и "максимальная полоса пропускания" (хотя я не исключаю других вариантов).
Очереди, имеющие возможность фильтрации трафика по полосам, называют CBQ, (Class-Based Queuing - очередь, классифицирующая пакеты) хотя это и не совсем правильно, т.к. существует отдельный тип очереди cbq, поэтому очереди такого типа я в дальнейшем буду обозначать как classfull. Команда tc имеет возможность управления множеством типов очередей, поэтому я бы хотел несколько подробнее остановиться на описании синтаксиса этой команды (тем более, что зачастую длинные цепочки команд tc зачастую выглядят весьма устрашающе).
Как и ip команда tc может управлять несколькими сетевыми объектами. Их
собственно три:
• qdisc - собственно управление очередями
• class - управление определёнными частями очереди, например, полосами
• filters - управление фильтрами - фильтр определяет в какую полосу очереди попадет тот или иной пакет (на основании определенных параметров пакета, например, протокола, порта, метки, и.т.д.).
Синтаксис команд следующий (нагло содрано мною из мануала :)):
tc qdisc [ add | change | replace | del | link ] dev DEV [ parent qdisc-id | root ] [ handle qdisc-id ] queue-type [ qdisc specific parameters ]
tc class [ add | change | replace | del ] dev DEV parent qdisc-id [ classid class-id ] queue-type [ qdisc specific parameters]
tc filter [ add | change | replace | del ] dev DEV [ parent qdisc-id | root ] protocol protocol prio priority filter-type [ filtertype specific parameters ] flowid flow-id
tc qdisc show [ dev DEV ] - показ очередей интерфейса
tc class show dev DEV - показ классов сетевого устройства
tc filter show dev DEV - показ фильтров интерфейса DEV
В основном отметьте последние 3 команды, т.к. назначение первых будет подробно описано далее на конкретных примерах.
Параметров у различных объектов очень много, чтобы описать их все, поэтому сейчас я бы хотел пояснить значение параметров parent qdisc-id и handle(classid) qdisc-id. На самом деле при добавлении новой очереди к очереди-контейнеру (classfull-очереди, понятие будет рассмотрено далее) производится построение дерева объектов очередей (на самом деле дерево содержится не в ядре, которое ничего не подозревает о наличии подочередей, а выбор подочереди для извлечения из неё пакета осуществляет очередь-контейнер).
Например:
tc qdisc add dev eth0 root handle 1: classful-queue [parameters]
дерево выглядит так:
root 1:
добавляем далее
tc qdisc add dev eth0 parent 1: handle 10: queue-type [parameters] root 1: | 10:
для примера добавлю ещё одну очередь:
tc qdisc add dev eth0 parent 1: handle 20: queue-type [parameters] root 1:1 | \ 10: 20:
Как происходит извлечение пакета из очереди ядром? Ядро запрашивает очередь самого верхнего уровня(root) о необходимости извлечения пакета. Очередь верхнего уровня проверяет очереди нижнего уровня и выбирает ту, которая подходит по определенным параметрам (это зависит от типа очереди-контейнера и её настроек), после осуществления выбора очередь верхнего уровня извлекает пакет и передает его выше (очереди, находящуюся выше по иерархии, или ядру, если это очередь root). Таким образом сама иерархия построена по принципу минимальных знаний о нижних уровнях. Каждый уровень знает о существовании только одного нижнего уровня и осуществляет выбор только в рамках одного уровня. Так примерно можно представить схему уровней очередей:
kernel
|
root
|
level 1
/ | \
level 2 level 2 level 2
|
level 3
По моему мнению, построение дерева очередей - предмет большинства ошибок в скриптах, т.к. очень легко перепутать идентификаторы очередей. Для тех, кто собрался продумывать систему контроля трафика необходимо заранее спланировать (например, нарисовать схему на бумаге) будущее дерево, дабы избежать досадных промахов. Особенно важным становится этот аспект при построении сложных очередей, состоящих из нескольких простых очередей.
Замена (удаление) очередей производится командой tc qdisc change(del) с
указанием значений parent и handle:
# tc qdisc del dev eth0 root handle 1: queue-type
После некоторого чисто теоретического вступления настало время поговорить о
типах очередей и о применении их на практике. Таковых типов существует 6.
Среди них 3 типа являются classfull (возможность разделять трафик по полосам): prio, cbq, htb, а 3 являются обычными (classless): tbf, pfifo, sfq. Classless очереди являются более простыми объектами, чем classfull и способны лишь устанавливать определённые ограничения на передачу трафика.
Основное отличие 2 семейств очередей в том, что classfull очереди содержат внутри себя classless "подочереди", отличающиеся приоритетом. Таким образом, classfull очереди являются контейнерными объектами и позволяют выполнять разделение трафика по другим очередям (в терминологии traffic control - классами).
Для начала я рассмотрю classless очереди, а потом перейду к более сложным (но, несомненно, более интересным) classfull очередям.
TBF(token bucket filter) - простой тип очереди, не выполняющий разделения пакетов, который удерживает скорость передачи пакетов на примерно постоянном уровне (меньшем, чем реальная скорость интерфейса).
При этом, если скорость передачи меньше заданного значения, то пакеты кладутся в очередь, пока не будет достигнут определённый размер очереди, который затем передается на обработку (но происходит некоторая задержка данных, т.к. происходит ожидание необходимого количества пакетов для поддержания постоянной скорости передачи). Если же скорость превышает заданную, то "лишние" пакеты просто отбрасываются. Такой тип очереди нельзя порекомендовать для сетей, где очень резко меняется загруженность, т.к. могут возникнуть неоправданные задержки при слабой загрузке сети и снижение пропускной способности интерфейса при большой загрузке.
Основные параметры tbf:
• limit или latency - число байт, которые могут быть помещены в очередь для ожидания, фактически - максимальное время, которое может провести пакет в очереди (чем больше limit - тем сильнее увеличивается задержка данных при низкой загрузке интерфейса)
• burst(buffer или maxburst) - длина буфера очереди в байтах, чем больше заданная скорость передачи, тем больше должен быть буфер данных, например, для скорости 10 mbit необходим буфер не менее 10 кб, а для скорости 256 kbit - не менее 1 кб. В общем, рассчитывается как rate_in_bytes/100. Для 100 мбит - 104857600/800 = 1310172 байт. При указании этого параметра учтите, что все указанные значения - минимальные, ничто не мешает вам увеличить этот параметр для гарантии того, что пакеты не будут отбрасываться очередью по причине переполнения последней. Фактически, размер буфера ограничен только размером физической памяти системы (т.к. буфер очереди обязан находиться в памяти).
• mpu - минимальный размер пакета для помещения в очередь, пакеты меньшей длины отбрасываются, для сетей ethernet каждый пакет должен быть больше 64 байт
• rate - заданый уровень скорости
• peakrate - максимально возможная скорость передачи пакетов из очереди в интерфейс, по умолчанию 1 мегабит в секунду.
С одной стороны tbf снижает в общем пропускную способность интерфейса, но существуют ситуации, когда это оказывается полезным (хотя, честно говоря, я с такими ситуациями не встречался, но, может быть, кому-то это окажется полезным). Например, имеется ADSL модем, который имеет очень большую очередь на upload. Поэтому при попытке передачи данных в сеть, скорость загрузки данных сильно падает, т.к. на модеме заполняется длинная очередь, контролировать которую не представляется возможным. Одним из вариантов решения проблемы является размещение tbf очереди на Linux маршрутизаторе и установке максимального времени ожидания пакета в очереди. Пример конфигурации:
# tc qdisc add dev ppp0 root tbf rate 220kbit latency 50ms burst 1500
Скорость rate выбрана исходя из максимальной скорости интерфейса (в данном примере 256 кбит/с) минус несколько процентов. Размер очереди burst выбран также соответственно скорости. Особо отметьте параметр latency - чем меньше этот параметр, тем меньше пакеты будут задерживаться в очереди – меньшая задержка увеличивает интерактивность данных. Заметьте, TBF не спасет вас от флуда - см. замечание ниже в описании sfq очередей.
Следующий тип classless очереди - sfq(stochastic fairness queueing - очередь равномерного случайного распределения пакетов). Алгоритм работы этого типа таков: данные, поступающие в очередь разделяются на достаточно большое количество "виртуальных" подочередей (виртуальных, т.к. в памяти существует одна очередь, но она представляется совокупностью многих подочередей), из подочередей данные извлекаются по очереди. Т.е. это напоминает Token Ring сети с передачей маркера по кольцу. Подочередь, получившая маркер передает один пакет данных, а маркер переходит к следующей подочереди. Случайность передачи обеспечивается тем, что размер подочередей обычно не фиксируется жестко и данные могут попадать в различные подочереди.
Такая очередь весьма полезна при сильной загрузке сетевого интерфейса многими клиентами. SFQ не позволяет захватить одному клиенту все ресурсы обработки пакетов, поэтому обеспечивается примерно одинаковая скорость поступления данных от различных клиентов. Учтите, это не спасет от DoS или банального флуда, т.к. любая очередь управляет скоростью обработки данных, т.е. фактически скоростью передачи ответных пакетов, а при наводнении пакетами интерфейса, последний не сможет принимать другие пакеты, и очередь в этом случае не помогает, т.к. нарушается работа самого сетевого устройства.
Таким образом, sfq очереди эффективны для регулирования скорости _передачи_ пакетов различным клиентам. Никогда не забывайте об этом факте (хотя далее будет приведен пример, позволяющий "спасти" систему от DoS-атак при помощи очередей, но при этом, если наводняется сам интерфейс, то работать он все равно не будет). Параметры sfq очередей:
pertrub - число секунд, после которого происходит перерасчет длины подочередей, по умолчанию - 0, т.е. переконфигурирования не происходит, рекоммендуется устанавливать этот параметр равный примерно 10 секундам; quantum - число байт, которые может передать подочередь, получившая маркер, значение по умолчанию - MTU интерфейса - подходит для большинства случаев, т.к. это гарантирует, что очередь сможет передать хотя бы один пакет. SFQ-очереди удобно применять для балансирования нагрузки сервера:
# tc qdisc add dev eth0 root sfq pertrub 10
Наконец, самый простой тип classless очереди - pfifo (лимит пакетов) и bfifo (лимит байт). Эти очереди являются простыми очередями определённой длины, не выполняющими никаких действий над поступающими в них пакетами.
Единственный параметр таких очередей - limit, означающий размер очереди в пакетах(pfifo) и в байтах(bfifo).
# tc qdisc add dev eth0 root pfifo limit 500
Создает очередь длиной в 500 пакетов.
Прежде чем приступать к чтению следующего раздела, полезно вспомнить о принципах построения дерева объектов очередей, т.к. далее будет рассказываться о classfull очередях, при создании которых необходимо добавлять очереди (или классы) в очереди-контейнеры.
Итак, classfull очереди. Как работает такая очередь? Гм... строго говоря, classfull очередь - это обычно некий контейнер, содержащий другие очереди. Решение, куда отправить тот или иной пакет, принимается на основании фильтров (о них будет рассказано далее) или на основании приоритета очереди-потомка. Решение, откуда отправлять пакет на обработку верхнему уровню, принимается на основании приоритета и настроек очереди-контейнера. Очереди же внутри контейнера не знают о существовании родителя непосредственно, т.е. они продолжают вести себя обычным образом, но все операции осуществляются не с ядром, а с очередью верхнего уровня. Итак, далее я бы хотел рассказать о некоторых типах classfull очередей, которые являются наиболее полезными на практике.
Первый тип - PRIO очереди. Очередь такого типа может разделять трафик между тремя полосами, которые являются очередями любого типа. Разделение осуществляется на основании фильтров (см. далее). Каждая подочередь, входящая в PRIO имеет свой приоритет, определяемый значением handle. При этом больший приоритет имеет та подочередь, которая относится к классу 1:. Т.е. при извлечении пакета из очереди вначале исследуется подочередь с большим приоритетом, если в последней нет пакетов для обработки, то выбирается очередь с более низким приоритетом и.т.д. Каким же образом трафик разделяется между полосами? Есть два пути: использование фильтров и использование поля TOS. На основании поля TOS различный вид трафика помещается в подочереди с разным приоритетом (отметьте, что очереди с меньшим номером обладают большим приоритетом):
TOS полосы
Максимальная надежность - 1
Минимальная цена - 2
Максимальная пропускная способность - 2
Минимльная задержка (интерактивный) - 1
Среди параметров prio-очереди можно отметить только 2: bands - число полос, по умолчанию 3, и priomap - карта распределения трафика в зависимости от поля TOS. Эти параметры редко приходится менять, т.к. для сложных ситуаций лучше применять более сложные очереди :).
Очередь prio очень полезна, если необходимо повысить общую пропускную способность интерфейса. Разделение на основе TOS особенно выгодно в сетях Unix, т.к. популярные сервера и приложения умеют грамотно устанавливать флаг TOS (например SSH устанавливает TOS "Минимальная задержка", а scp - "Максимальная пропускная способность"). К сожалению в сетях, где имеются клиенты M$, ситуация усложняется, т.к. по неизвестной причине M$ ставит свои ОС в привеллигированные условия, устанавливая TOS "Интерактивный" по умолчанию. PRIO еще полезна тем, что может содержать очереди различного типа, например, tbf или sfq. Это её основное отличие от очереди pfifo_fast, которая создает три полосы pfifo по умолчанию, и не позволяет менять тип подочередей. Поэтому тип pfifo_fast относится к classless, хотя осуществляет разделение трафика, но подочереди являются неотделимой частью самой pfifo_fast. Приведу пример создания prio очереди, содержащей 2 подочереди sfq и одну tbf:
Псевдо-дерево будет выглядеть так:
root prio
1:1 1:2 1:3
| | |
10: 20: 30:
(0)sfq (1)tbf (2)sfq
# tc qdisc add dev eth0 root handle 1: prio
Заметьте, что 1: добавляет автоматически три полосы - 1:1 1:2 1:3, т.к. по умолчанию количество полос равно 3-м.
# tc qdisc add dev eth0 parent 1:1 handle 10: sfq pertrub 10
# tc qdisc add dev eth0 parent 1:2 handle 20: tbf rate 1mbit buffer 15000 latency 10ms
# tc qdisc add dev eth0 parent 1:3 handle 30: sfq pertrub 10
Если вы попробуете передавать данные различного типа, например ssh и ftp, то заметите, что интерактивный тарфик поступает в 1-ю и 2-ю полосы, а неинтерактивный - в 3-ю полосу. Если prio является достаточно простым типом очереди, то следующие типы не обладают этим свойством. Прежде всего, это тип cbq(classfull based queueing). Этот тип позволяет добавлять множество классов и осуществлять весьма нетривиальную обработку трафика.
Распределение трафика по классам осуществляется посредством фильторов. Но cbq очереди слишком сложны и не всегда делают то, что от них требуется. По этой причине я буду рассматривать не cbq очереди, а их ближайших родственников - htb(hierarchial token bucket), которые имеют те же возможность, что и cbq-очереди, но лишены недостатков последних – излишней громоздкости синтаксиса и непрозрачности (единственным недостатком является то, что для их использования необходимо патчить 2.2 ядро, но 2.4.20 ядро имеет встроенную поддержку этого типа очередей). Итак, позвольте представить, htb-очереди. Для начала убедитесь, что вы имеете ядро версии 2.4.20 и выше. Если такового не имеется необходимо сходить на сайт проекта htb - http://luxik.cdi.cz/~devik/qos/htb/ и скачать пакет htb3, содержащий патчи для ядер 2.2 и 2.4, а также исправленную версию tc. Находится этот пакетпо адресу http://luxik.cdi.cz/~devik/qos/htb/v3/htb3.6-020525.tgz. Что же позволяют делать htb очереди? Они позволяют регулировать полосу пропускания трафика и фактически создавать на базе одного интерфейса несколько более медленных, и разделять трафик по полосам, отличающимся по скорости передачи. На самом деле htb-очередь - очень полезная вещь.
Допустим, нам необходимо повысить полосу пропускания www и smtp трафика, но
понизить скорость ftp трафика. С помощью очереди prio можно лишь менять приоритет разного рода трафика, но если передается только ftp-данные то каким бы ни был приоритет ftp, все равно этот трафик будет передаваться с максимальной скоростью. Единственной возможностью решения такой ситуации для prio-очереди является использование в качестве полос tbf очередей. В то же время, если необходимо организовать весьма нетривиальное распределение трафика и ограничение полос пропускания, то лучше всего использовать htb очередь. Для упрощения понимания принципов создания htb-очередей приведу простой пример. Допустим, к серверу подключено 2-е клиентов (А и Б). Эти два клиента пользуются только www и smtp сервисами (такое упрощение сделано для наглядности, далее будут приведены примеры реальных скриптов). Имеется 10-и мегабитный интерфейс, полосы пропускания будут выглядеть таким образом:
Kлиент Сервис Полоса пропускания
A smtp 2mbit
A www 3mbit
B all 1mbit
other all 2mbit
Команды, реализующие эту схему, будут выглядеть так:
# tc qdisc add dev eth0 root handle 1: htb default 13
Думаю, эта команда не вызывает трудностей в понимании. Параметр default 13 означает, что по умолчанию трафик будет направляться на полосу с идентификатором 13. Трафик "по умолчанию" означает тот трафик, который не был распределён при помощи фильтров.
Теперь необходимо добавить полосы - классы. Учтите, что классы – это объекты, которые являются составными частями classfull очередей, но сами по себе не являются очередями. Таким образом схема составления htb очереди выглядит примерно так: добавление самой очереди -> добавление классов -> добавление фильтров, распределяющих трафик по классам -> добавление очередей(обычно classless, по умолчанию pfifo), реализующих классы.
Команды, делающие это, представлены ниже.
Добавляем классы:
tc class add dev eth0 parent 1: classid 1:1 htb rate 9mbit ceil 9mbit burst 12500
tc class add dev eth0 parent 1:1 classid 1:10 htb rate 2mbit ceil 9mbit burst 12500
tc class add dev eth0 parent 1:1 classid 1:11 htb rate 3mbit ceil 9mbit burst 12500
tc class add dev eth0 parent 1:1 classid 1:12 htb rate 1mbit ceil 9mbit burst 12500
tc class add dev eth0 parent 1:1 classid 1:13 htb rate 3mbit ceil 9mbit burst 12500
Псевдодерево очередей будет выглядеть так:
1:0 root
|
1:1
/ | | \
1:10 1:11 1:12 1:13
Сделаю некоторые пояснения относительно параметров классов htb. Параметр rate означает полосу пропускания, ceil означает максимальную скорость обмена класса с родительской очередью (или классом). Также есть возможность указания приоритета каждого класса аналогично prio-очереди при помощи параметра prio(аналогично меньшее значение параметра означает больший приоритет). Существуют 2 параметра burst и cburst, регулирующие параметры буфера очереди:
burst - размер в байтах буфера, для Intel машин вычисляется по формуле (speed_in_bits)/(100*8); для нашего случая минимальное значение - 12500 байт
cburst - означает минимальный размер в байтах данных передаваемых родительской очереди; обычно не меньше mtu интерфейса.
После добавления классов логичным является добавление средств, распределяющих трафик по этим классам. Таким средством являются фильтры.
Вернемся к нашему простому примеру. Для него фильтры будут добавляться так:
# tc filter add dev eth0 protocol ip parent 1:0 prio 1 u32 \
match ip src A_ip match ip dport 80 0xffff flowid 1:10
# tc filter add dev eth0 protocol ip parent 1:0 prio 1 u32 \
match ip src A_ip match ip dport 25 0xfff flowid 1:11
# tc filter add dev eth0 protocol ip parent 1:0 prio 1 u32 \
match ip src B_ip flowid 1:12
Поясню все по порядку. Директива protocol означает протокол для фильтрации,
parent - очередь, касательно которой работает фильтр (в данном случае корневая htb), prio - приоритет данного фильтра (чем меньше значение, тем больше приоритет), u32 значит, что ведется поиск совпадений в любой части пакета, match ... - правила совпадения определенных полей пакета с заданными, если этой директивы нет, то под этот фильтр подходят любые неотфильторованные ранее пакеты (хотя необходимо установить более низкий приоритет для этого фильтра, иначе он будет обрабатывать весь трафик, что не есть хорошо):
# tc filter add dev eth0 protocol ip parent 1:0 prio 2 flowid some_band
директива flowid обозначает идентификатор класса, куда фильтр будет отправлять трафик. Как правильно строить правила совпадения? При построении учтите несколько общих правил:
• если в одной команде tc filter add встретилось несколько директив match, то они объединяются операцией "И"
• если необходимо создать несколько правил, объединенных "ИЛИ", то необходимо создавать эти правила в различных командах tc filter add
Составлять правила фильтров совсем несложно:
match ip src ip_addr - поиск совпадения ip-адреса отправителя
match ip dst ip_addr - поиск совпадения ip-адреса назначения(полезно при маршрутизации)
match ip sport|dport port_num 0xffff - поиск совпадения порта-источника или порта назначения, назначение символов 0xffff - установка маски совпадения
Существует также возможность фильтрации на основе меток netfilter. Метки ставятся с помощью iptables следующим образом:
# iptables -A FORWARD -t mangle -i eth0 -j MARK --set-mark 6
где вместо 6-ки может быть любое число до 255.
Установка фильтра выглядит несколько необычно:
# tc filter add dev eth0 ptrotocol ip parent 1:0 prio 1 handle 6 fw classid 1:1
обратите внимание на отсутствие директивы u32, директивы handle, fw и classid(вместо flowid).
Далее будет показано на примере, как выбирать пакеты с установленными флагами SYN, ASK, выбирать протокол(TCP, UDP, ICMP), что позволяет предотвращать некоторые виды DoS-атак.
Для понимания фильтров, которые будут рассмотрены чуть позже, разъясню назначение некоторых дополнительных параметров фильтров:
а) поиск "сырых" байт - match [u32|u16|u8] 0xXX 0xMASK at where, где маска - 16-иричное значение маски соответствующего формата, например u32 - 32 бита, u16 - 16 бит, u8 8 бит соответственно. Параметр where - обычное число, означающее количество соответствующих элементов(u32, u16, u8), отсчитанных от начала пакета. Например, фильтр match u8 0x10 0чаа at 33 означает выбор поля ToS "Минимальная задержка"
б) поиск протокола - match ip protocol 6 0xff - поиск пакетов TCP протокола
в) непосредственный поиск поля ToS - match ip tos 0x10 0xff
г) поиск совпадения области роутинга:
# ip route add some_network via some_gate dev eth0 realm 2
область обозначается директивой realm, и фильтр для пакетов, попадающих в эту область(т.е. тех, что направлены в сеть some_network)
# tc filter add dev eth0 parent 1:0 protocol ip prio 1 route to 2
Если нужно выбирать пакеты, приходящие из области маршрутизации, то нужно просто заменить to на from
д) отбор трафика, превосходящего определенный лимит скорости (очень полезно): в команде tc filter можно применять те же директивы, что и в tbf-очереди
при указании особого параметра - police:
• buffer
• mtu
• mpu
• rate
Если трафик превышает установленный лимит то _сам_ фильтр может выполнять определённые действия:
• drop - отбрасывание трафика, превысившего лимит
• pass - пропускание такого трафика
е) существует также возможность создания хешей фильтров(т.е. групп фильтров, которые применяются при прохождении определённых других фильтров), но это применяется лишь в случаях тысяч правил, когда обработка всех фильтров занимает значительное процессорное время. Объем этой статьи не позволяет мне рассказать и об этой возможности, поэтому при возникновении этой проблемы нужно почитать HOWTO - http://www.lartc.org
После настройки фильтрации необходимо добавить очереди, реализующие классы.
Это делается вполне стандартным образом(очереди могут быть любого типа, что позволяет выполнять самые разнообразные задачи). Для нашего примера это будет выглядеть так:
# tc qdisc add dev eth0 parent 1:10 handle 20: pfifo limit 500
# tc qdisc add dev eth0 parent 1:11 handle 30: pfifo limit 500
# tc qdisc add dev eth0 parent 1:12 handle 40: pfifo limit 500
# tc qdisc add dev eth0 parent 1:13 handle 50: sfq perturb 10
Обратите внимание, что родителями этим очередям являются классы, таким образом псевдодерево будет выглядеть таким образом:
1:0 root
|
1:1
/ | | \
1:10 1:11 1:12 1:13
| | | |
20: 30: 40: 50:
Еще хотел бы остановить ваше внимание на классе 1:1. На самом деле может быть несколько классов верхнего уровня, что позволяет строить весьма сложную иерархию классов.
На этом я, пожалуй, закончу теоретическое рассмотрение очередей и приведу реальный пример использования очередей для управления трафиком. В этом примере я покажу основные возможности очередей: разделение трафика и создание полос пропускания, фильтрацию на основе поля ToS, защита сервера от SYN и ICMP флуда, разделение трафика между несколькими сетевыми интерфейсами(туннели высокой пропускной способности, аналог port link в Cisco Catalyst).
Итак, конфигурация сети:
В нашем случае имеется сервер, соединяющий две локальные сети (Server A). Он соединен через две сетевые карты с 2-м сервером (Server B) для повышения эффективности обмена данными между двумя серверами (например, резервные копии одного сервера на другом). Я не буду подробно останавливаться на механизмах маршрутизации и разграничения доступа, а для начала просто поясню, как при помощи очередей реализовать распределение трафика по двум интерфейсам. Для этого существует специальный тип очереди - teql. В первую очередь, необходимо добавить такую очередь к двум интерфейсам (в нашем случае - eth1 и eth2):
# tc qdisc add dev eth1 root teql0
# tc qdisc add dev eth2 root teql0
После этого появляется виртуальный интерфейс - teql0. Работа с ним не отличается от работы с любым другим интрефейсом:
# ip link set dev teql0 up - включает интерфейс.
Учтите, что подобную операцию нужно прделать на обеих машинах(Server A и
Server B), а после этого необходимо настроить первоначально маршрутизацию:
Server A
# ip addr add dev eth1 10.0.0.1/31
# ip addr add dev eth2 10.0.0.3/31
# ip addr add dev teql0 10.0.0.5/31
Server B
# ip addr add dev eth1 10.0.0.2/31
# ip addr add dev eth2 10.0.0.4/31
# ip addr add dev teql0 10.0.0.6/31
При этом серверы будут общаться через виртуальную подсеть 10.0.0.0/31 и будут видеть друг друга через адреса 10.0.0.5 и 10.0.0.6 соответственно.
Необходимо также отключить "отброс" пакетов серверами:
# echo 0 > /proc/sys/net/ipv4/conf/eth1/rp_filter
# echo 0 > /proc/sys/net/ipv4/conf/eth2/rp_filter
т.к. это может вызвать нежелательные циклы пакетов между серверами (каждый сервер будет отбрасывать не предназначенные ему пакеты в петлю, и эти пакеты будут гулять, пока не истечет ttl).
На этом будем считать настройку интерфейсов eth1 и eth2 законченной (если Server B настроен правильно, то причин для беспокойства нет, иначе есть смысл настроить firewall и максимальную скорость связи). Главной "оптимизации" подвергнется интерфейс eth0, ведущий в локальную сеть. Eth0 - стамегабитная сетевая карта. В нашей сети имеется 2 "особых" клиента - 192.168.2.2 и 192.168.2.3. Им необходимо выделить отдельные полосы пропускания для ftp и smb трафика (по 10 мбит каждому). Кроме этого, сеть состоит из рабочих станций под управлением Unix/Linux и очень много работают с сервером по ssh (10 мбит дополнительно). Помимо всего прочего в сети необходимо учитывать ToS-флаги и ограничивать icmp трафик (защита от любителей делать ping -f). Задачка не из легких, не так ли? ;) Особенно если учесть, что мы будем устанавливать для различных полос различный приоритет...
Для начала создаем htb очередь для выделения полос пропускания:
# tc qdisc add dev eth0 root handle 1: default 14
и сами полосы:
# tc class add dev eth0 parent 1: handle 1:1 rate 10mbit burst 150000 \
ceil 100mbit
# tc class add dev eth0 parent 1:1 handle 1:11 rate 10mbit burst 15000 \
ceil 100mbit prio 2
# tc class add dev eth0 parent 1:1 handle 1:12 rate 10mbit burst 15000 \
ceil 100mbit prio 2
# tc class add dev eth0 parent 1:1 handle 1:13 rate 10mbit burst 15000 \
ceil 100mbit prio 2
# tc class add dev eth0 parent 1:1 handle 1:14 rate 69mbit burst 100000 \
ceil 100mbit prio 3
Одна полоса для ICMP-трафика
# tc class add dev eth0 parent 1:1 handle 1:15 rate 100kbit burst 1000 \
ceil 100mbit prio 4
Ещё одна полоса для TCP-AСK пакетов (эти пакеты должны иметь максимальныйприоритет, т.к. это существенно повышает скорость загрузки данных с сервера к клиенту)
# tc class add dev eth0 parent 1:1 handle 1:16 rate 900kbit burst 2500 \
ceil 100mbit prio 1
создаем фильтры:
а) создаем фильтры для особых клиентов:
Для клиента А
# tc filter add dev eth0 protocol ip parent 1:0 prio 1 u32 \
match ip src 192.168.2.2 match ip dport 110 0xffff flowid 1:11
# tc filter add dev eth0 protocol ip parent 1:0 prio 1 u32 \
match ip src 192.168.2.2 match ip dport 138 0xfff flowid 1:11
Через 138-й порт в протоколе NetBios передаются данные
Для клиента Б
# tc filter add dev eth0 protocol ip parent 1:0 prio 1 u32 \
match ip src 192.168.2.3 match ip dport 110 0xffff flowid 1:12
# tc filter add dev eth0 protocol ip parent 1:0 prio 1 u32 \
match ip src 192.168.2.3 match ip dport 138 0xfff flowid 1:12
б) создаем фильтрацию ssh трафика:
# tc filter add dev eth0 protocol ip parent 1:0 prio 1 u32 \
match ip dport 22 0xffff flowid 1:13
в) задаем предел для ICMP и SYN-TCP трафика:
SYN:
Для начала установим правила отбора SYN пакетов при помощи iptables (это
намного проще, чем использовать сырой разбор пакетов и маскирование битов):
# iptables -A PREROUTING -i eth0 -t mangle -p tcp --syn \
-j MARK --set-mark 1
После этого необходимо добавить особый тип очереди - ingress, которая
пропускает трафик не выше заданной скорости, при этом заметьте, что очередь
не имеет размещения (всегда root), что позволяет добавлять к интерфейсу
очередь ingress не мешая htb (или любой другой очереди), подключенной к
этому же интерфесу:
# tc qdisc add dev eth0 handle ffff: ingress
# tc filter add dev eth0 parent ffff: protocol ip prio 50 handle 1 fw \
police rate 100kbit burst 1500 mtu 9k drop flowid :1
Мы установили лимит SYN пакетов до 320 пакетов в секунду (пакет с SYN
флагом занимает 320 бит, но 320 пакетов в секунду несколько многовато, хотя
точно должно обеспечить обработку всех запросов на соединение).
ICMP:
Тут мы имеем дело с обычным выделением полосы пропускания и фильтрацией
протокола:
# tc filter add dev eth0 parent 10:0 protocol ip prio 100 u32 match ip
protocol 1 0xff flowid 1:15
значение protocol 1 соответствует ICMP.
г) добавляем фильтр TCP-AСK пакетов:
# tc filter add dev eth0 parent 1: protocol ip prio 10 u32 \
match ip protocol 6 0xff \
match u8 0x05 0x0f at 0 \
match u16 0x0000 0xffc0 at 2 \
match u8 0x10 0xff at 33 \
flowid 1:15
Эта строчка выглядит несколько сложно, но здесь используется сырой разбор заголовков пакетов, поэтому всеьма сложно понять что к чему.
На этом настройки фильтрации заканчиваются. Приступим к добавлению очередей, реализующих классы:
а) для особых клиентов подойдут обычные pfifo-очереди:
# tc qdisc add dev eth0 parent 1:11 handle 20: pfifo limit 500
# tc qdisc add dev eth0 parent 1:12 handle 30: pfifo limit 500
б) для ssh полосы лучше всего использовать sfq для равномерности
распределения клиентов:
# tc qdisc add dev eth0 parent 1:13 handle 40: sfq pertrub 10
в) для остальной части подсети лучше всего использовать pfifo_fast очередь, т.к. необходимо выделять приоритетный трафик. pfifo_fast было выбрано вместо prio только по причине простоты использования (думаю, скрипт, реализующий установку всех очередей, и так способен смутить любого):
# tc qdisc add dev eth0 parent 1:14 handle 50: pfifo_fast
г) для AСK пакетов подойдет также pfifo очередь
# tc qdisc add dev eth0 parent 1:15 handle 60: pfifo limit 15
Вот и все, я завершаю свой пример и собственнно рассказ об traffic control в
GNU/Linux. Надеюсь, что моя статья окажется кому-нибудь полезной. На этом я и прощаюсь с вами.
Приведу ряд полезных ссылок:
http://www.lartc.org - Linux Advanced Routing and Traffic Control HOWTO http://luxik.cdi.cz/~devik/qos/htb/manual/userg.htm - руководство по работе с htb очередями (приведены графики производительности и описаны все основные параметры очередей этого типа).
RFC795 - документ, посвященный использованию поля ToS.
Операции с документом