В предыдущей статье я рассказывал про фильтр 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.