В предыдущей статье я рассказывал про фильтр U32. В этой статье речь пойдёт о так называемых tc actions — действиях, которые можно производить над трафиком. Например, можно построить файерволл без использования iptables/netfilter, или изменять отдельные байты в пакетах, перенаправлять/зеркалировать трафик на другие интерфейсы. Осваивать это будем на примерах. Продолжение под катом.
Traffic Control Action (далее просто «действия») — это расширение фильтров в подсистеме управления трафиком. Расширения эти нужны для самых разнообразных нужд — от простейшего отбрасывания пакетов до изменений самого трафика. Действие прикрепляется к отдельному фильтру, и таким образом манипуляции производятся только над выбранным трафиком, что добавляет гибкости. Кроме того, можно строить целые цепочки действий через пайпы (подобно конвейерной обработке данных в консоли), комбинируя их. Манипуляции могут производиться как над входящим трафиком, так и над исходящим.
Прежде всего нам необходимо добавить классовую или бесклассовую дисциплину к интерфейсу, а к ней уже будут добавляться фильтры с действиями. Если мы хотим издеваться над входящим трафиком, то надо добавлять ingress дисциплину. Её отличительной особенностью является то, что её хэндл всегда равен «ffff:» и она всегда является бесклассовой.
Естественно, в ядро должны быть включены соответствующие модули. Находятся они в ветке Networking support — Networking options — QoS and/or fair queueing. Вам необходимы включенные опции Actions и модули с действиями, которые будете использовать. В дистрибутивных ядрах, обычно, всё уже включено.
Для упрощения построения фильтров, мы будем отбирать трафик для манипуляций с помощью меток. Этот способ подходит лишь для исходящего трафика. Почему так? Давайте посмотрим на эту картинку, на которой изображён путь пакета по сетевому стеку Linux. Как можно заметить, дисциплина и классификация входящих пакетов выполняется гораздо раньше, чем любые хуки netfiter, и поэтому нам просто негде пометить пакет раньше. В этом случае, для классификации имеет смысл строить фильтры по другим критериям, например, используя U32. Другой способ обойти данную проблему — перенаправлять трафик на другой интерфейс.
Давайте рассмотрим простейший пример применения действий. Наверняка, многие с ним уже сталкивались. Речь пойдёт о ограничениях полосы пропускания для отдельных типов трафика с помощью так называемого полисера. Полисер работает по алгоритму текущего ведра (почитать об этом алгоритме можно на википедии или у Таненбаума).
Допустим, мы хотим ограничить скорость входящего трафика протокола tcp c ip-адреса 192.168.10.3 на адрес 192.168.10.5. Можно сделать это следующим образом:
Самый большой интерес для нас представляют две последние строки (если вам непонятны и другие строки, то прочтите LARTC и про фильтр U32).
Запустим, например iperf на обоих машинах и измерим скорость. Если всё правильно сделано, то скорость от 192.168.10.3 до 192.168.10.5 должна быть в районе двух мегабит (это в случае, если кроме тестовых данных между узлами ничего не передаётся). В статистике можно увидеть, сколько данных прошло через фильтр, сколько раз он сработал, сколько пакетов было пропущено и отброшено и т.п.
Подобным образом используются и другие действия. Для каждого действия можно вызвать небольшую справку по параметрам. Например, для того же полисера это можно сделать командой:
Для того, чтобы узнать подсказку к другим действиям, просто укажите их название вместо «police».
На текущий момент в ядро включены следующие действия:
Действия могут применяться как по одиночке, так и совместно, образуя цепочки. Всё это похоже на конвейерную обработку данных в консоли, когда вывод одной программы подаётся на ввод другой. С действиями точно так же. Например, попробуем изменить какое-нибудь поле в заголовке пакета. После этого нам необходимо будет пересчитать и обновить контрольную сумму. Для этого действия pedit и csum будут объединены в цепочку. Для наглядности, отзеркалируем результирующие пакеты на интерфейс ifb0 и посмотрим их tcpdump-ом.
Команда выглядит довольно устрашающе. Начало нам знакомо — добавляем фильтр для того, чтобы отобрать нужные нам пакеты по адресам источника и назначения, протоколу и номеру порта (protocol tcp, ip src 10.10.20.119, ip dst 10.10.20.254, tcp dport 10500). Но вместо классифицирования мы меняем содержимое пакета (параметр «action pedit») — одинарное слово по смещению 22 байта от начала ip-пакета. Если поглядеть на формат заголовков, то это поле соответствует номеру порта получателя в tcp. Мы перезаписываем его, устанавливая равным 11500 («munge offset 22 u16 set 11500»). Но после того, как мы поменяли поле, контрольная сумма заголовка изменится. Чтобы её пересчитать, пакеты перенаправляются действию csum с помощью параметра «pipe». Csum пересчитывает контрольную сумму заголовка tcp и направляет пакеты действию «mirred» так же с помощью параметра «pipe». В результате работы действия «mirred» на интерфейс ifb0 приходят копии пакетов, которые были отправлены.
Проверим, как всё работает с помощью анализа статистики, а так же запустив tcpdump на интерфейсе ifb0:
Вот в принципе и всё, что я хотел рассказать по поводу применения действий.
LARTC — Linux Advanced Routing and Traffic Control.
Пример использования ifb и действия mirred.
Что же это за tc actions такие?
Traffic Control Action (далее просто «действия») — это расширение фильтров в подсистеме управления трафиком. Расширения эти нужны для самых разнообразных нужд — от простейшего отбрасывания пакетов до изменений самого трафика. Действие прикрепляется к отдельному фильтру, и таким образом манипуляции производятся только над выбранным трафиком, что добавляет гибкости. Кроме того, можно строить целые цепочки действий через пайпы (подобно конвейерной обработке данных в консоли), комбинируя их. Манипуляции могут производиться как над входящим трафиком, так и над исходящим.
Прежде всего нам необходимо добавить классовую или бесклассовую дисциплину к интерфейсу, а к ней уже будут добавляться фильтры с действиями. Если мы хотим издеваться над входящим трафиком, то надо добавлять ingress дисциплину. Её отличительной особенностью является то, что её хэндл всегда равен «ffff:» и она всегда является бесклассовой.
Естественно, в ядро должны быть включены соответствующие модули. Находятся они в ветке Networking support — Networking options — QoS and/or fair queueing. Вам необходимы включенные опции Actions и модули с действиями, которые будете использовать. В дистрибутивных ядрах, обычно, всё уже включено.
Простейший пример использования действий
Для упрощения построения фильтров, мы будем отбирать трафик для манипуляций с помощью меток. Этот способ подходит лишь для исходящего трафика. Почему так? Давайте посмотрим на эту картинку, на которой изображён путь пакета по сетевому стеку Linux. Как можно заметить, дисциплина и классификация входящих пакетов выполняется гораздо раньше, чем любые хуки netfiter, и поэтому нам просто негде пометить пакет раньше. В этом случае, для классификации имеет смысл строить фильтры по другим критериям, например, используя U32. Другой способ обойти данную проблему — перенаправлять трафик на другой интерфейс.
Давайте рассмотрим простейший пример применения действий. Наверняка, многие с ним уже сталкивались. Речь пойдёт о ограничениях полосы пропускания для отдельных типов трафика с помощью так называемого полисера. Полисер работает по алгоритму текущего ведра (почитать об этом алгоритме можно на википедии или у Таненбаума).
Допустим, мы хотим ограничить скорость входящего трафика протокола tcp c ip-адреса 192.168.10.3 на адрес 192.168.10.5. Можно сделать это следующим образом:
#добавляем дисциплину для
#входящего трафика
tc qdisc add \
dev eth0 \
ingress
#добавляем фильтр с полисером
#протокол tcp
#адрес источника 192.168.10.3/32
#адрес назначения 192.168.10.5/32
tc filter add \
dev eth0 \
parent ffff: \
pref 10 \
protocol ip \
handle ::1 \
u32 \
match ip protocol 6 0xff \
match ip src 192.168.10.3/32 \
match ip dst 192.168.10.5/32 \
action police \
rate 2Mbit burst 200K exceed-conform drop
Самый большой интерес для нас представляют две последние строки (если вам непонятны и другие строки, то прочтите LARTC и про фильтр U32).
- action police — указывает на то, что подпадающий под фильтр трафик будет обрабатываться полисером. Далее идут параметры полисера.
- rate 2Mbit burst 200K — задаём полосу пропускания в 2 мегабита в секунду. «burst 200K» — это один из параметров, нужный для правильной работы полисера. Есть и другие параметры, но мы их не будем рассматривать.
- exceed-conform drop — определяет действие над пакетами, которые «переливаются через край ведра», в данном случае они отбрасываются. Пакеты же, которые влезают в полосу 2 мегабита пропускаются.
Запустим, например iperf на обоих машинах и измерим скорость. Если всё правильно сделано, то скорость от 192.168.10.3 до 192.168.10.5 должна быть в районе двух мегабит (это в случае, если кроме тестовых данных между узлами ничего не передаётся). В статистике можно увидеть, сколько данных прошло через фильтр, сколько раз он сработал, сколько пакетов было пропущено и отброшено и т.п.
~$ iperf -s -p 10500
------------------------------------------------------------
Server listening on TCP port 10500
TCP window size: 85.3 KByte (default)
------------------------------------------------------------
[ 4] local 192.168.10.5 port 10500 connected \
with 192.168.10.3 port 59154
[ ID] Interval Transfer Bandwidth
[ 4] 0.0-11.2 sec 2.73 MBytes 2.04 Mbits/sec
~$ tc -s -p f ls dev eth0 parent ffff:
filter protocol ip pref 10 u32
filter protocol ip pref 10 u32 fh 800: ht divisor 1
filter protocol ip pref 10 u32 fh 800::1 \
order 1 key ht 800 bkt 0 terminal flowid ??? \
(rule hit 2251145 success 4589)
match IP src 91.193.236.62/32 (success 5843 )
match IP dst 91.193.236.44/32 (success 4608 )
match IP protocol 6 (success 4589 )
action order 1:
police 0x1e rate 2000Kbit burst 200Kb mtu 2Kb \
action drop overhead 0b ref 1 bind 1
Action statistics:
Sent 6870220 bytes 4589 pkt
(dropped 761, overlimits 761 requeues 0)
backlog 0b 0p requeues 0
Подобным образом используются и другие действия. Для каждого действия можно вызвать небольшую справку по параметрам. Например, для того же полисера это можно сделать командой:
tc filter add \
dev eth0 \
parent ffff: \
u32 \
match u32 0 0 \
action police \
help
Usage: ... police rate BPS burst BYTES[/BYTES]
[ mtu BYTES[/BYTES] ] [ peakrate BPS ]
[ avrate BPS ] [ overhead BYTES ]
[ linklayer TYPE ] [ ACTIONTERM ]
Old Syntax
ACTIONTERM := action <EXCEEDACT>[/NOTEXCEEDACT]
New Syntax
ACTIONTERM := conform-exceed <EXCEEDACT>[/NOTEXCEEDACT]
Where:
*EXCEEDACT := pipe | ok | reclassify | drop | continue
Where: pipe is only valid for new syntax
Для того, чтобы узнать подсказку к другим действиям, просто укажите их название вместо «police».
Краткий перечень действий
На текущий момент в ядро включены следующие действия:
- police — как и говорилось ранее, реализует функции полисера для ограничения скоростей.
- gact — generic action — позволяет пропускать, отбрасывать, переклассифицировать пакеты и т.п. С помощью этого действия можно реализовать подобие файерволла.
- mirred — с помощью этого расширения можно зеркалировать или перенаправлять пакеты на другие сетевые интерфейсы. Широкое применение получило совместно с IFB-интерфейсами для сглаживания (шейпинга) входящего трафика.
- ipt — iptables target — даёт применять к пакетам действия iptables, например, маркирование. В этом случае, если фильтр прикреплён к ingress-дисциплине, то это примерно соответствует действиям в цепочке mangle-prerouting.
- nat — stateless nat — реализует преобразование сетевых адресов без учёта состояний. Т.е. просто меняет в заголовке один ip-адрес на другой.
- pedit — packet edit — с его помощью можно изменять в пакетах отдельные биты и байты. Пример его применения будет позже.
- skbedit — позволяет изменять поля структуры sk_buf, в которой хранится пакет. Применяется для изменения приоритета, в основном.
- csum — check sum update — пересчитывает контрольные суммы и обновляет их значения в заголовках пакетов. Обычно используется совместно с pedit.
Объединение действий в цепочку
Действия могут применяться как по одиночке, так и совместно, образуя цепочки. Всё это похоже на конвейерную обработку данных в консоли, когда вывод одной программы подаётся на ввод другой. С действиями точно так же. Например, попробуем изменить какое-нибудь поле в заголовке пакета. После этого нам необходимо будет пересчитать и обновить контрольную сумму. Для этого действия pedit и csum будут объединены в цепочку. Для наглядности, отзеркалируем результирующие пакеты на интерфейс ifb0 и посмотрим их tcpdump-ом.
tc filter add \
dev eth0 \
parent 1: \
pref 10 \
protocol ip \
handle ::1 \
u32 \
match ip protocol 6 0xff \
match ip src 10.10.20.119/32 \
match ip dst 10.10.20.254/32 \
match u16 10500 0xffff at 22 \
action pedit \
munge offset 22 u16 set 11500 \
pipe \
action csum \
tcp \
pipe \
action mirred \
egress mirror dev ifb0
Команда выглядит довольно устрашающе. Начало нам знакомо — добавляем фильтр для того, чтобы отобрать нужные нам пакеты по адресам источника и назначения, протоколу и номеру порта (protocol tcp, ip src 10.10.20.119, ip dst 10.10.20.254, tcp dport 10500). Но вместо классифицирования мы меняем содержимое пакета (параметр «action pedit») — одинарное слово по смещению 22 байта от начала ip-пакета. Если поглядеть на формат заголовков, то это поле соответствует номеру порта получателя в tcp. Мы перезаписываем его, устанавливая равным 11500 («munge offset 22 u16 set 11500»). Но после того, как мы поменяли поле, контрольная сумма заголовка изменится. Чтобы её пересчитать, пакеты перенаправляются действию csum с помощью параметра «pipe». Csum пересчитывает контрольную сумму заголовка tcp и направляет пакеты действию «mirred» так же с помощью параметра «pipe». В результате работы действия «mirred» на интерфейс ifb0 приходят копии пакетов, которые были отправлены.
Проверим, как всё работает с помощью анализа статистики, а так же запустив tcpdump на интерфейсе ifb0:
#выводим статистику работы фильтров и действий
~$ tc -s -p f ls dev eth0
filter parent 1: protocol ip pref 10 u32
filter parent 1: protocol ip pref 10 u32 fh 800: ht divisor 1
filter parent 1: protocol ip pref 10 u32 fh 800::1 order 1 key ht 800 bkt 0
terminal flowid ??? (rule hit 102554 success 0)
match IP protocol 6 (success 102517 )
match IP src 10.10.20.119/32 (success 0 )
match IP dst 10.10.20.254/32 (success 0 )
match dport 10500 (success 0 )
action order 1: pedit action pipe keys 1
index 66 ref 1 bind 1 installed 132 sec used 132 sec
key #0 at 20: val 00002cec mask ffff0000
Action statistics:
Sent 0 bytes 0 pkt (dropped 0, overlimits 0 requeues 0)
backlog 0b 0p requeues 0
action order 2: csum (tсp) action pipe
index 29 ref 1 bind 1 installed 132 sec used 132 sec
Action statistics:
Sent 0 bytes 0 pkt (dropped 0, overlimits 0 requeues 0)
backlog 0b 0p requeues 0
action order 3: mirred (Egress Mirror to device ifb0) pipe
index 79 ref 1 bind 1 installed 132 sec used 132 sec
Action statistics:
Sent 0 bytes 0 pkt (dropped 0, overlimits 0 requeues 0)
backlog 0b 0p requeues 0
#отсылаем пакеты tcp на 10.10.20.254:10500
~$ telnet 10.10.20.254 10500
#параллельно в другой консоли смотрим, что у нас
#сыпется на интерфейс ifb0
~$ tcpdump -nvvi ifb0
tcpdump: WARNING: ifb0: no IPv4 address assigned
tcpdump: listening on ifb0, link-type EN10MB (Ethernet),
capture size 65535 bytes
...
00:46:11.080234
IP (tos 0x10, ttl 64, id 46378, offset 0,
flags [DF], proto TCP (6), length 60)
10.10.20.119.36342 > 10.10.20.254.11500:
Flags [S], cksum 0x2001 (correct),
seq 1542179969, win 14600, options
[mss 1460,sackOK,TS val 1417050539 ecr 0,nop,wscale 4],
length 0
...
#ещё раз смотрим статистику
~$ tc -s -p f ls dev eth0
filter parent 1: protocol ip pref 10 u32
filter parent 1: protocol ip pref 10 u32 fh 800: ht divisor 1
filter parent 1: protocol ip pref 10 u32 fh 800::1 order 1 key ht 800 bkt 0
terminal flowid ??? (rule hit 580151 success 12)
match IP protocol 6 (success 579716 )
match IP src 10.10.20.119/32 (success 12 )
match IP dst 10.10.20.254/32 (success 12 )
match dport 10500 (success 12 )
action order 1: pedit action pipe keys 1
index 66 ref 1 bind 1 installed 747 sec used 454 sec
key #0 at 20: val 00002cec mask ffff0000
Action statistics:
Sent 888 bytes 12 pkt (dropped 0, overlimits 0 requeues 0)
backlog 0b 0p requeues 0
action order 2: csum (tdp) action pipe
index 29 ref 1 bind 1 installed 747 sec used 454 sec
Action statistics:
Sent 888 bytes 12 pkt (dropped 0, overlimits 0 requeues 0)
backlog 0b 0p requeues 0
action order 3: mirred (Egress Mirror to device ifb0) pipe
index 79 ref 1 bind 1 installed 747 sec used 454 sec
Action statistics:
Sent 888 bytes 12 pkt (dropped 0, overlimits 0 requeues 0)
backlog 0b 0p requeues 0
Вот в принципе и всё, что я хотел рассказать по поводу применения действий.
Полезные ссылки
LARTC — Linux Advanced Routing and Traffic Control.
Пример использования ifb и действия mirred.