QoS в Linux: фильтр U32

Так повелось, что фильтр U32 в подсистеме управления трафиком ядра Linux считается простым и понятным, а потому в подробном документировании не нуждается. Например, в LARTC (Linux Advanced Routing and Traffic Control) про него лишь несколько абзацев. Но на самом деле U32 устроен гораздо сложнее и интереснее, но и в использовании он не так прост, как может показаться. Под катом статья по этому фильтру с примерами использования и подробными пояснениями.


Сопоставление


И так, основная функция фильтра U32 заключается в том, что берётся некоторый блок данных из пакета, и сравнивается с заданным значением. Если значения совпадают, то выполняются некоторые действия над пакетом. Блок данных в пакете задаётся следующими параметрами:

  • Размер блока данных. Определяется параметрами u32/u16/u8. Интуитивно понятно, что цифры — это длина блока в битах. Ядро оперирует 32хбитными блоками. В tc же можно задавать длину блока в 8, 16 и 32 бита.
  • Битовая маска. Нужна для того, если нужно проверить не весь блок данных, а только отдельные его биты. Естественно, длина маски должна совпадать с длиной блока. На блок накладывается маска с помощью операции побитового И, и результат этой операции уже сравнивается с заданным значением.
  • Смещение от начала пакета. Все смещения выравниваются по 32хбитной границе. Тут не всё так очевидно, как кажется, и позже мы об этом поговорим, но пока этого упрощения достаточно. Смещение, равное нулю, в большинстве случаев, соответствует началу заголовка сетевого уровня, т.е. началу IPv4/IPv6 пакета.


Например, возьмём следующий пример команды tc:

tc filter add                         \
dev eth0                              \
parent 1:                             \
pref 10                               \
protocol ip                           \
u32                                   \
match u32 0xc0a80100 0xffffff00 at 12 \
classid 1:8


Мы добавляем фильтр типа u32 с приоритетом 10 к дисциплине 1:0 устройства eth0. И если сопоставление удачно, то пакет попадёт в класс 1:8. Седьмая строка нам особенно интересна, рассмотрим её подробно:

  • match u32 — задаёт начало условия сопоставления и размер блока данных, который будет взят из пакета.
  • 0xc0a80100 — это значение, с которым мы будем сравнивать биты из пакета.
  • 0xffffff00 — битовая маска, которая будет накладываться на данные из пакета, и результат уже будет сравниваться с тем, что мы задали. Если наше значение и маскированный блок данных равны, то выполняются определённые действия. Чаще всего этим действием будет классифицирование пакета.
  • at 12 — смещение от начала, по которому находится начало блока данных. Если этот параметр не указан, то смещение считается равным нулю. В самом начале работы фильтра нулевая граница считается началом заголовка сетевого уровня, например IPv4. Возможны и отрицательные смещения.


Этими действиями мы проверяем адрес источника (в этом можно убедиться, если взглянуть на формат заголовка IPv4), и если он принадлежит подсети 192.168.1.0/24, то отправляем пакет в класс 1:8. Естественно, постоянно копаться в RFC слишком утомительно, да и все смещения держать в голове сложно, поэтому tc предоставляет синтаксический сахар для часто употребляемых случаев. Например, наш пример становится гораздо понятнее, если записать его так:

tc filter add               \
dev eth0                    \
parent 1:                   \
pref 10                     \
protocol ip                 \
u32                         \
match ip src 192.168.1.0/24 \
classid 1:8


В одной команде можно указать несколько параметров «match», причём сопоставление будет успешно только в том случае, если все условия будут выполняться. Отправим все пакеты с адресом источника 192.168.1.0/24 и значением ToS равным 0x10 (интерактивный трафик) в класс 1:1:

tc filter add               \
dev eth0                    \
parent 1:                   \
pref 10                     \
protocol ip                 \
u32                         \
match ip src 192.168.1.0/24 \
match ip tos 0x10 0x1e      \
classid 1:1


Проверим, правильно ли всё работает:
#смотрим наш фильтр
~$ tc -s 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::800 order 2048 key ht 800 bkt 0 flowid 1:1  (rule hit 1911 success 0)

  match c0a80100/ffffff00 at 12 (success 0 )
  match 00100000/001e0000 at 0 (success 0 )

#если задать ключ -p, то tc будет некоторые смещения преобразовывать
#в более удобочитаемый вид, а другие - скрывать
~$ 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::800 order 2048 key ht 800 bkt 0 flowid 1:1  (rule hit 3413 success 0)

  match IP src 192.168.1.0/24 (success 0 )  (success 0 )

#пингуем что-нибудь с заданным адресом источника и значением ToS
$ ping -f -I 192.168.1.1 -Q 0x10 www.ya.ru
PING ya.ru (93.158.134.3) from 192.168.1.1 : 56(84) bytes of data.
 
--- ya.ru ping statistics ---
107 packets transmitted, 107 received, 0% packet loss, time 619ms
rtt min/avg/max/mdev = 4.492/5.240/7.560/0.536 ms, ipg/ewma 5.842/5.403 ms

#смотрим, отрабатывает ли фильтр как положено
~$ tc -s 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::800 order 2048 key ht 800 bkt 0 flowid 1:1  (rule hit 354903 success 107)

  match c0a80100/ffffff00 at 12 (success 107 ) 
  match 00100000/001e0000 at 0 (success 107 )


Как видите, всё работает как и должно — пакеты попадают в нужный класс. Но если внимательно посмотреть на вывод tc, то некоторые моменты нам ещё неизвестны. Например, эти загадочные значения fh 800::800. Это так называемые хэндлы (калька с английского «handle») — идентификаторы фильтров внутри U32.

Каждый хэндл отдельного фильтра состоит из трёх шестнадцатеричных чисел и уникален в пределах пространства фильтра U32 для интерфейса. В нашем случае это хэндл 800::800. Нас сейчас интересует лишь последняя цифра — номер добавляемого фильтра. Если его не указывать, то система сама назначает его — следующий за самым старшим, начиная с 0x800. Номер фильтра находится в диапазоне от 0x001 до 0xfff. Можно вручную указать номер фильтра соответствующим параметром:

tc filter add               \
dev eth0                    \
parent 1:                   \
pref 10                     \
protocol ip                 \
handle ::1                  \
u32                         \
match ip src 192.168.1.0/24 \
match ip tos 0x10 0x1e      \
classid 1:1


Фильтры выполняются в порядке следования их номеров.

Связывание


Пусть мы добавляем два фильтра:

tc filter add               \
dev eth0                    \
parent 1:                   \
pref 10                     \
protocol ip                 \
handle ::1                  \
u32                         \
match ip src 192.168.1.0/24 \
match ip tos 0x10 0x1e      \
classid 1:1

tc filter add               \
dev eth0                    \
parent 1:                   \
pref 10                     \
protocol ip                 \
handle ::2                  \
u32                         \
match ip src 192.168.1.0/24 \
match ip tos 0x08 0x1e      \
classid 1:2


Оба фильтра будут иметь одинаковый префикс — первые два числа хэндла (800:0). Если представить их записанными друг за другом в порядке следования номера — последнего номера хэндла, то получится некоторый список фильтров:

список 800:0:
::1 - ip src 192.168.1.0/24 tos 0x10 -> classid 1:1
::2 - ip src 192.168.1.0/24 tos 0x08 -> classid 1:2


Так как в фильтрах одного списка хэндлы различаются лишь последними числами, то сам список можно идентифицировать первыми двумя числами хэндлов — их префиксом (800:0). Как ранее говорилось, фильтры выполняются в порядке их следования. Если в каком-либо из фильтров сопоставление было успешным, то выполняется некоторое действие и U32 завершает работу. Если же мы дошли до конца списка, и так и не получили ни одного успешного сопоставления, то фильтр U32 вернёт наверх не классифицированный пакет.

u32 simple flow chart

Кроме классифицирования, действием может быть переход к другому списку фильтров для проверки пакета. Делается это с помощью параметра «link» вместо «classid» (даже если параметр «classid» указан, то он всё равно игнорируется), например, вот так:

tc filter add               \
dev eth0                    \
parent 1:                   \
prio 10                     \
protocol ip                 \
handle ::2                  \
u32                         \
match ip src 192.168.1.0/24 \
link 1:


Если пакет имеет адрес источника из подсети 192.168.1.0/24, то начинает выполняться проверка по списку фильтров с хэндлом 1:0. Если в нём не окажется успешных сопоставлений, то мы вернёмся к прежнему списку 800:0 и продолжим проверку по нему. В свою очередь, фильтры из списка 1:0 могут осуществлять переходы на другие списки, а те на третьи и т.д. И так до семи переходов (кому этого мало, то можете изменить в исходных текстах ядра макроподстановку TC_U32_MAXDEPTH).



Так же следует учесть, что само по себе разбиение большого количества фильтров на множество списков и организация подобных переходов в среднем будет не особо быстрее, чем проверка по одному большому списку. Но, как правило, связывание используется совместно с другим механизмом U32 — хешированием.

Хеширование


До этого мы рассматривали списки фильтров. Но на самом деле они являются лишь частью большей структуры, называемой хэш-таблицей. Хэш-таблица, в данном случае, представляет собой одномерный массив ячеек (англ. array of buckets), в каждой из которых хранится по одному списку фильтров.

В хэндлах, как раз, первое число — это номер хэш-таблицы, а второе — это номер ячейки. Номера хэш-таблиц находятся в диапазоне от 0x000 до 0xfff, а ячеек — от 0x00 до 0xff. Количество ячеек может быть от 1 до 256, причём оно должно быть степенью двойки (другое значение не получится задать — tc выдаст сообщение об ошибке). Хэш-таблица с номером 0x800 называется корневой и состоит из одной ячейки, она создаётся автоматически. Проверка пакета всегда начинается с просмотра списка в ячейке 800:0.

Создать дополнительные хеш-таблицы можно примерно так:

tc filter add     \
dev eth0          \
parent 1: pref 10 \
protocol ip       \
handle 1:         \
u32 divisor 1


Где «handle 1:» задаёт номер хэш-таблицы, а «divisor 1» — количество ячеек в ней (в данном случае хэш-таблица с номером 1 имеет лишь одну ячейку, в которой будет находиться список фильтров 1:0).

Расширим наш пример с классифицированием по адресу источника и полю ToS с помощью переходов по спискам:

#добавляем классовую дисциплину prio с восемью дочерними классами
~$ tc q add \
dev eth0 \
root \
est 0.1s 10s \
handle 1: \
prio bands 8

#добавляем хэш-таблицу 1: с одной ячейкой
~$ tc f add \
dev eth0 \
parent 1: \
pref 10 \
protocol ip \
handle 1: \
u32 \
divisor 1

#добавляем сопоставление по айпи-адресу источника
~$ tc f add \
dev eth0 \
parent 1: \
pref 10 \
protocol ip \
handle ::1 \
u32 \
match ip src 192.168.1.0/24 \
link 1:

#добавляем сопоставление по полю ToS в хэш-таблицу 1:
~$ tc f add \
dev eth0 \
parent 1: \
pref 10 \
protocol ip \
handle ::1 \
u32 \
ht 1: \
match ip tos 0x08 0x1e \
classid 1:3

~$ tc f add \
dev eth0 \
parent 1: \
pref 10 \
protocol ip \
handle ::2 \
u32 \
ht 1: \
match ip tos 0x10 0x1e \
classid 1:1

#для того, чтобы показать, что происходит возврат в прежний
#список, добавим ещё одно правило
~$ tc f add \
dev eth0 \
parent 1: \
pref 10 \
protocol ip \
handle ::2 \
u32 \
match ip src 192.168.1.0/24 \
classid 1:7

#просмотрим наши фильтры
~$ tc -s f ls dev eth0
filter parent 1: protocol ip pref 10 u32
filter parent 1: protocol ip pref 10 u32 fh 1: ht divisor 1
filter parent 1: protocol ip pref 10 u32 fh 1::1 order 1 key ht 1 bkt 0 flowid 1:3 (rule hit 0 success 0)

  match 00080000/001e0000 at 0 (success 0 )
filter parent 1: protocol ip pref 10 u32 fh 1::2 order 2 key ht 1 bkt 0 flowid 1:1 (rule hit 0 success 0)

  match 00100000/001e0000 at 0 (success 0 )
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 link 1: (rule hit 33900 success 0)

  match c0a80100/ffffff00 at 12 (success 0 )
filter parent 1: protocol ip pref 10 u32 fh 800::2 order 2 key ht 800 bkt 0 flowid 1:7 (rule hit 3583 success 0)

  match c0a80100/ffffff00 at 12 (success 0 )

#а теперь проверим работу, послав пакеты с различными значениями
#ToS из подсети 192.168.1.0/24, а затем проверив значения счётчиков
~$ ping -fc10 -I 192.168.1.1 -Q 0x08 8.8.8.8
~$ ping -fc15 -I 192.168.1.1 -Q 0x10 www.kernel.org
~$ ping -fc25 -I 192.168.1.1 -Q 0xaa www.habrahabr.ru

#смотрим ещё раз счётчики
~$ tc -s f ls dev eth0
filter parent 1: protocol ip pref 10 u32 
filter parent 1: protocol ip pref 10 u32 fh 1: ht divisor 1 
filter parent 1: protocol ip pref 10 u32 fh 1::1 order 1 key ht 1 bkt 0 flowid 1:3 (rule hit 50 success 10)

  match 00080000/001e0000 at 0 (success 10 ) 
filter parent 1: protocol ip pref 10 u32 fh 1::2 order 2 key ht 1 bkt 0 flowid 1:1 (rule hit 40 success 15)

  match 00100000/001e0000 at 0 (success 15 ) 
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 link 1: (rule hit 578192 success 0)

  match c0a80100/ffffff00 at 12 (success 50 ) 
filter parent 1: protocol ip pref 10 u32 fh 800::2 order 2 key ht 800 bkt 0 flowid 1:7 (rule hit 547850 success 25)

  match c0a80100/ffffff00 at 12 (success 25 ) 


Но с помощью параметра «link» можно переходить только к спискам фильтров, находящихся в ячейке с номером 0. Как же тогда проверять списки в других ячейках? Для этого используется параметр «hashkey» и механизм хеширования. Смысл хеширования в U32 в том, что номер ячейки, к списку в которой надо перейти, система получает на основе данных из пакета. Нужно это для сокращения количества проверок. В отличие от списка, время просмотра которого линейно зависит от количества фильтров в нём, хеширование выполняется за постоянное время (причём, весьма короткое). На больших количествах фильтров использование хеширования позволяет добиться на порядок большей производительности, чем с использованием одних только списков и переходов.



Перепишем наш предыдущий пример с использованием хеширования:

#добавляем хэш-таблицу с номером 1: на 32 ячейки
~$ tc f add \
dev eth0 \
parent 1: \
pref 10 \
protocol ip \
handle 1: \
u32 \
divisor 32

#номер ячейки в данном случае совпадает со значением ToS
~$ tc f add \
dev eth0 \
parent 1: \
pref 10 \
protocol ip \
handle 1:08:1 \
u32 \
ht 1:08 \
match u32 0 0 \
classid 1:3

~$ tc f add \
dev eth0 \
parent 1: \
pref 10 \
protocol ip \
handle 1:10:1 \
u32 \
ht 1:10 \
match u32 0 0 \
classid 1:1

#все пакеты с адресом источника 192.168.1.0/24 отправляем
#на проверку в хэш-таблицу 1: в ячейку равным значению полю ToS
~$ tc f add \
dev eth0 \
parent 1: \
pref 10 \
protocol ip \
handle ::1 \
u32 \
match ip src 192.168.1.0/24 \
link 1: \
hashkey mask 0x001f0000 at 0

#правило для проверки того, что неклассифицированные пакеты
#возвращаются обратно в корневую таблицу
~$ tc f add \
dev eth0 \
parent 1: \
pref 10 \
protocol ip \
handle ::2 \
u32 \
match ip src 192.168.1.0/24 \
classid 1:7

#посылаем пакеты с различным значением ToS
~$ ping -fc10 -I 192.168.1.1 -Q 0x08 8.8.8.8
~$ ping -fc30 -I 192.168.1.1 -Q 0x10 www.kernel.org
~$ ping -fc50 -I 192.168.1.1 -Q 0xaa www.habrahabr.ru

#и проверяем значения счётчиков фильтров
~$ tc -s f ls dev eth0
filter parent 1: protocol ip pref 10 u32
filter parent 1: protocol ip pref 10 u32 fh 1: ht divisor 32
filter parent 1: protocol ip pref 10 u32 fh 1:8:1 order 1 key ht 1 bkt 8 flowid 1:3 (rule hit 10 success 10)

  match 00000000/00000000 at 0 (success 10 )
filter parent 1: protocol ip pref 10 u32 fh 1:10:1 order 1 key ht 1 bkt 10 flowid 1:1 (rule hit 30 success 30)

  match 00000000/00000000 at 0 (success 30 )
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 link 1: (rule hit 30135 success 0)

  match c0a80100/ffffff00 at 12 (success 90 )
    hash mask 001f0000 at 0
filter parent 1: protocol ip pref 10 u32 fh 800::2 order 2 key ht 800 bkt 0 flowid 1:7  (rule hit 27250 success 50)

  match c0a80100/ffffff00 at 12 (success 50 )


Рассмотрим алгоритм хеширования на нашем примере. Данный алгоритм был внесён в версию ядра 2.6 и до сих пор не изменялся (в ядре версии 2.4 алгоритм другой).

  • Взять 32хбитное слово по смещению «at», указанному в параметре «hashkey». Так как смещение у нас нулевое, то берутся первые 32 бита IP-пакета.
  • Наложить на него битовую маску из того же параметра. Битовая маска у нас равна «0x001f0000», что соответствует положению поля ToS.
  • Результат сдвинуть в сторону младших разрядов на n-бит. Значение n вычисляется из маски и равно количеству младших нулевых бит в ней. В нашей маске количество младших нулевых бит равно 16, значит на эти 16 бит и будет сдвинуто маскированное двойное слово. После этого, значение ToS окажется в младших пяти разрядах.
  • На получившееся значение наложить маску 0xff. В данном случае это ничего не изменит. В других случаях это обнулит все октеты кроме младшего.
  • И в конце, если в целевой таблице меньше 256 ячеек, на результат накладывается битовая маска, равная числу k. Значение k на один меньше, чем ячеек в хэш-таблице, на которую будет переход. В нашей целевой таблице 32 ячейки, а значит значение k будет равно 31. После наложения этой маски наш результат гарантировано не будет больше количества ячеек.


К сожалению, tc не предоставляет никаких средств для упрощения написания смещений в это случае, так что не получится написать что-нибудь вроде «hashkey ip tos». Другая сложность — это определение, в какую именую ячейку помещать фильтры. Тут путь только один — вручную считать хеш (в tc есть параметр «sample», который позволяет автоматически высчитывать хеш для того, чтобы поместить фильтр в нужную ячейку, но алгоритм хеширования до сих пор от ядра 2.4 и для более свежих ядер не подходит).

Смещения


Всё было бы просто, если бы заголовки имели фиксированную длину. Но, к сожалению, это не так — заголовки могут иметь дополнительные необязательные элементы, что очень сильно затрудняет сопоставление по полям заголовков следующих уровней. К счастью, в U32 предусмотрено и это. Эта функция называется «header offsets» и предназначена для того, что бы узнать смещение следующего заголовка из самого пакета.

Но сначала ознакомимся с двумя новыми понятиями фильтра U32: постоянное смещение (для краткости будем обозначать его как «permoff») и временное смещение (обозначим его как «tempoff»). Значение permoff всегда прибавляется ко всем смещениям при следующем и дальнейших переходах, осуществляемых с помощью параметра «link», и используется для вычисления новых значений permoff и tempoff. Значение же tempoff прибавляется лишь к указываемым смещениям только при следующем переходе если в параметрах используется ключевое слово «nexthdr+», и на дальнейшие переходы не влияет. При возвращении обратно в предыдущий список значения «откатываются».

Рассмотрим небольшой гипотетический пример. Пусть permoff равно нулю при выполнении списка 1:0, и у нас появилось успешное сопоставление; а новое значение permoff стало равным 20, при этом мы перешли к выполнению списка 2:5. В этом случае, ко всем указанным смещениям в фильтрах списка 2:5 будет прибавлено значение 20. Если в списке 2:5 у нас будет ещё одно успешное сопоставление с переходом на список 3:12, и указано изменение постоянного смещения на 8, то новое значение permoff станет равным 28. И при выполнении списка 3:12 ко всем смещениям будет прибавляться уже значение 28. Если в списке 3:12 не будет успешных сопоставлений, то U32 вернётся назад к списку 2:5 и permoff снова станет равным 20. Если же и там не будет успешных сопоставлений, и мы вернёмся в список 1:0, то permoff станет снова равным нулю. По сути, мы просто смещаем нулевую отметку, относительно которой далее указываем смещения. Изначально значения permoff и tempoff равны нулю.

Заглянем в RFC и посмотрим на формат заголовка IPv4. К счастью для нас, в нём предусмотрено специальное поле, значение которого равно длине заголовка в двойных словах. И мы можем использовать эту информацию для того. чтобы вычислить положение заголовка следующего уровня.

Делается это так:
tc filter add                              \
dev eth0                                   \
parent 1:                                  \
pref 10                                    \
protocol ip                                \
u32                                        \
match ip src 192.168.1.0/24                \
match protocol 0x01 0xff                   \
link 1:                                    \
offset at 0 mask 0x0f00 shift 6 plus 0 eat 


Рассмотрим подробно последнюю строку и то, что при этом происходит:
  • offset — говорит о том, что мы изменяем значения permoff или tempoff при успешном сопоставлении.
  • Берётся одинарное слово по смещению, указанному в параметре «at». В нашем случае, он равен нулю, поэтому будут взяты первые 16 бит пакета. Значение параметра «at» приводится к кратному двум.
  • На полученное из пакета значение накладывается битовая маска. У нас она равна 0x0f00, что соответствует положению поля длины заголовка IPv4. Допустим, что у нас в этом поле находится значение 5 (в двоичном представлении 0101). В двоичном формате результат предыдущих операций будет равен «0000.0101.0000.0000».
  • Мы знаем, что значение поля длины заголовка IP равно длине заголовка в двойных словах. А значит, необходимо полученное значение каким-то образом преобразовать в байты. Дело осложняется тем, что в результате у нас нужные биты дополнительно смещены. Сместим значение так, чтобы оно оказалось в младших октетах — сдвинем в сторону младших разрядов на 8 бит и получим «0000.0000.0000.0101». Теперь умножаем это на 4, так как размер заголовка у нас указан в двойных словах. Умножение заменяем на сдвиг в сторону старших разрядов на 2 бита. После сдвига получим «0000.0000.0001.0100». Мы получили нужное значение. Объединим обе операции сдвига в одну. В итоге, чтобы получить длину заголовка в байтах, нам надо будет изначальное маскированное значение сдвинуть на шесть бит в сторону младших разрядов. Это и делается с помощью параметра «shift». После всех операций мы получили значение 20.
  • К полученному значению прибавляется значение параметра «plus». В нашем случае, это 0, поэтому ничего и не прибавляется. Можно было этот параметр даже и не указывать.
  • Параметр «eat» говорит, что вычисленное значение прибавляется к значению permoff. Иначе будет вычисляться значение tempoff.
  • В результате, при проверки фильтров из списка 1:0, нулевое смещение будет соответствовать началу следующего заголовка, и мы можем строить фильтры на основе сопоставления полей заголовка ICMP, например, проверяя значения типа сообщения. Если же вы изменяете значение tempoff, то в вызванном списке надо явно его указывать, используя для задания смещений ключевое слово «nexthdr+».


После этого, мы можем добавлять в список 1:0 фильтры с сопоставлением по типу ICMP сообщения. Например, пакеты ICMP типа Echo-Request отправим в класс 1:8.

~$tc f add \
dev eth0 \
parent 1: \
pref 10 \
protocol ip \
handle 1::1 \
u32 \
ht 1: \
match u8 0x08 0xff \
classid 1:8

#посмотрим на счётчики фильтров
~$ sudo tc -s f ls dev eth0
filter parent 1: protocol ip pref 10 u32 
filter parent 1: protocol ip pref 10 u32 fh 1: ht divisor 1 
filter parent 1: protocol ip pref 10 u32 fh 1::1 order 1 key ht 1 bkt 0 flowid 1:8  (rule hit 0 success 0)
  
match 08000000/ff000000 at 0 (success 0 ) 
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 link 1:  (rule hit 48553 success 0)
  
  match c0a80100/ffffff00 at 12 (success 0 ) 
  match 00010000/00ff0000 at 8 (success 0 ) 
    offset 0f00>>6 at 0  eat

#отправим несколько эхо-реквестов
~$ ping -fc5 -I 192.168.1.1 www.ixbt.com

#и снова взглянем на значения счётчиков
~$ tc -s f ls dev eth0
filter parent 1: protocol ip pref 10 u32 
filter parent 1: protocol ip pref 10 u32 fh 1: ht divisor 1 
filter parent 1: protocol ip pref 10 u32 fh 1::1 order 1 key ht 1 bkt 0 flowid 1:8  (rule hit 5 success 5)
  
  match 08000000/ff000000 at 0 (success 5 ) 
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 link 1:  (rule hit 149972 success 0)

  match c0a80100/ffffff00 at 12 (success 5 ) 
  match 00010000/00ff0000 at 8 (success 5 ) 
    offset 0f00>>6 at 0  eat 


В общем-то и всё. В оригинале вы найдёте ещё немного информации — сухое руководство и список сопоставлений (тот самый синтаксический сахар).

Оригинал — The u32 filter. — автор, к сожалению, мне неизвестен, но всё указывает на Рассела Стюарта.
Поделиться публикацией
Похожие публикации
Ой, у вас баннер убежал!

Ну. И что?
Реклама
Комментарии 11
    +5
    спасибо за отличную статью
      +5
      Рад стараться. Надеюсь, что статья окажется полезной. На первых парах tc у многих вызывает затруднения, а по фильтрам в нём информации не так уж и много.
      +4
      Давненько уже хороших дремучих статей с разъяснением на пальцах про linux не было, спасибо.
        +1
        Хабр торт. tc кстати работает в юзерспейсе или как?
          0
          Сама утилита tc работает в юзерспейсе и осуществляет управление ядерной частью через протокол netlink. Сама же обработка пакетов выполняется непосредственно ядром. Примерно так же и с утилитой iptables, которая лишь управляет подсистемой netfilter.
          0
          Спасибо за качественные материал.

          А возможно ли с помощью tc проверять заголовки L2?
            +1
            Да, можно. Например, можно построить вот такое правило, чтобы зеркалировать все арп-пакеты (значение «0x0806» в поле протокола заголовка ethernet) с мак-адресом источника «12:34:56:78:9a:bc»:

            tc filter add                         \
            dev eth0                              \
            parent 1:                             \
            protocol 802_3                        \
            pref 10                               \
            u32                                   \
            match u32 0x12345678 0xffffffff at -8 \
            match u32 0x9abc0000 0xffff0000 at -4 \
            match u16 0x0806 0xffff at -2         \
            action mirred egress mirror dev ifb0  
            


            Нулевое смещение, как я писал ранее, соответствует началу пакета сетевого уровня. Для фильтров с разным параметром «protocol» необходимо указывать разный приоритет (параметр «pref»). Поддерживаются следующие обозначения протоколов: loop, pup, ip, irda, control, x25, arp, bpq, mobitex, tr_802_2, dec, dna_dl, dna_rc, ppptalk, localtalk, dna_rt, lat, cust, wan, ppp, ddcm, sca, rarp, atalk, snap, 802_2, aarp, ipx, ipv6, all, ax25, 802_3. Но придётся покопаться в стандартах, чтобы посчитать смещения и длины полей.

            Для ethernet-фреймов есть немного синтаксического сахара для сопоставления по мак-адресам. Например, предыдущий пример, написанный в другой манере:

            tc filter add                         \
            dev eth0                              \
            parent 1:                             \
            protocol 802_3                        \
            pref 10                               \
            u32                                   \
            match ether src 12:34:56:78:9a:bc     \
            match u16 0x0806 0xffff at -2         \
            action mirred egress mirror dev ifb0  
            


            Ещё одно небольшое замечание по поводу проверки полей заголовков 802.1q (Vlan): по-умолчанию влан-интерфейсы создаются с флагом «reorder header», что приводит к тому, что теги во фреймах отсутствуют (информация о номере влана и приоритете хранится в структуре sk_buf, соответствующей пакету). Чтобы теги появились, надо флаг на влан-интерфейсе выключить (или при создании влана указывать этот флаг отключённым), например, с помощью утилиты ip:

            #меняем флаг reorder_hdr на интерфейсе eth0.134
            ip link set dev eth0.134 type vlan id 134 reorder_hdr off
            #создаём влан-интерфейс
            ip link add eth0 name eth0.135 type vlan id 135 reorder_hdr off
            


              +1
              Прошу прощения, пропущено ключевое слово в команде. Влан надо создавать так:

              ip link add link eth0 name eth0.135 type vlan id 135 reorder_hdr off
              


              Так же этот флаг можно задать/снять с помощью утилиты vconfig.
                0
                Ещё одно небольшое замечание по вланам. Некоторые сетевые карты имеют аппаратную поддержку 802.1q, то есть вставляют и удаляют теги сами. В этом случае вы так же не сможете фильтровать трафик по полям тега, вы можете даже не видеть теги в выводе tcpdump. К счастью, в последних версиях ядра при соответствующей поддержке со стороны драйверов можно аппаратную акселерацию вланов отключать. Делается это с помощью утилиты ethtool (для компактности часть вывода я опустил):

                ~$ ethtool -k eth3
                Offload parameters for eth3:
                ...
                rx-vlan-offload: on
                tx-vlan-offload: on
                
                ~$ ethtool -K eth3 rxvlan off txvlan off
                
                ~$ ethtool -k eth3
                Offload parameters for eth3:
                ...
                rx-vlan-offload: off
                tx-vlan-offload: off
                


                Если у вас этих опций в ethtool нет, поставьте версию посвежее.
              +1
              Полезная статья, разжёвано про хэширование. Очень хорошо.
              Добавлю в избранное, чтобы советовать новичкам для прочтения.
                +1
                Спасибо за статью, жаль не могу плюсануть.

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

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