Pull to refresh

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

Reading time 7 min
Views 47K
В предыдущей статье я рассказывал про фильтр 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                                      \
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.
Tags:
Hubs:
+36
Comments 10
Comments Comments 10

Articles