Так повелось, что фильтр 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 вернёт наверх не классифицированный пакет.
Кроме классифицирования, действием может быть переход к другому списку фильтров для проверки пакета. Делается это с помощью параметра «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. — автор, к сожалению, мне неизвестен, но всё указывает на Рассела Стюарта.