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 <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.
    Поделиться публикацией
    Похожие публикации
    Ой, у вас баннер убежал!

    Ну. И что?
    Реклама
    Комментарии 10
    • +3
      насколько влияет длина/сложность правил на производительность?
      • +1
        В общем случае, чем больше правил и они сложнее, тем нагрузка больше на цпу. Но в случае применения фильтров на основе хеш-таблиц даже с большим количеством фильтров нагрузка относительно невелика. Как показывает практика, тот же netfilter съедает на порядок больше ресурсов, чем фильтры.
      • 0
        А если например ограничивать от 100 мегабит и для tap интерфейсов?
        Я заметил, что tc не дружит с чем-то больше 10 мбит/с.
        • 0
          проблема с ограничением в 10 мбит/с может быть вызвана неоптимальностью классов и фильтров tc или же низкопроизводительными сетевыми картами
          • +1
            На больших скоростях лучше использовать полисеры. Разницы между шейпером и полисером уже не ощущается. Так же лучше использоваться дисциплину HFSC вместо HTB, так как она с распараллеливается лучше и сама по себе не так сильно нагружает железо.
          • +1
            большое спасибо за статью!
            • +1
              Сложные очень команды!
              • 0
                Ну тут уж ничего не поделаешь :) Можно использовать скрипты-обёртки, коих на просторах интернета великое множество.
                • +2
                  Они сложные ровно до того момента, пока в них не разберешься.
                • +1
                  Давно в пору админства я делал шейпер используя IMQ для управления входящим трафиком, уже много воды утекло :) но тема эта до сих пор меня увлекает.

                  Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

                  Самое читаемое