Системное администрирование / QoS в Linux: издеваемся над трафиком

в 21:33, , рубрики: linux, qos, сети, метки: , ,

В предыдущей статье я рассказывал про фильтр U32. В этой статье речь пойдёт о так называемых tc actions — действиях, которые можно производить над трафиком. Например, можно построить файерволл без использования iptables/netfilter, или изменять отдельные байты в пакетах, перенаправлять/зеркалировать трафик на другие интерфейсы. Осваивать это будем на примерах. Продолжение под катом.

Что же это за 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                                       mathc ip protocol 6                       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.

Автор: EvilMan


* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js