QoS в Linux

В предыдущей статье я рассказывал про фильтр 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 [/NOTEXCEEDACT]
New Syntax
  ACTIONTERM := conform-exceed [/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.

Добавить комментарий

Войти с помощью: