Тут коллега
Citrin (
ospf_ripe)
поделился опытом о программировании netgraph-ноды
ng_bpf(4). Этот узел использует BPF (Berkeley Packet Filter) и позволяет ловить пакеты по определенным критериям, то есть может все то же самое, что и всем хорошо известный
tcpdump. То есть, если
ipfw(8) у нас умеет фильтровать только по заголовку пакета, то с помощью этой ноды можем фильтровать пакеты прямо в ядре как хотим, по содержимому, благо netgraph легко стыкуется с
ipfw при помощи
ng_ipfw(4). Citrin'у это понадобилось для фильтрации спамерских DNS-запросов на MX-записи (DoS'ящих DNS-сервер), что требует не такого уж и тривиального анализа пакета - нужно смотреть на байты, которые лежат по смещениям, вычисляемым по другим байтам в пакете. И возможности BPF эту задачу решить позволяют.
Да вот незадача - язык
bpf(4) представляет собой специфический ассемблер виртуальной BPF-машины. И если
tcpdump предоставляет удобный язык, самостоятельно компилируя заданное выражение в опкоды BPF (между прочим, в
libpcap встроен неплохой компилятор, даже с оптимизатором), то
ng_bpf требует программирования прямо в этих самых опкодах, которые надо откуда-то сначала получить. Конечно, можно изучить этот ассемблер и написать нужную программу на нем, он не очень сложен - но понятно, что это неудобно.
Man-страница ng_bpf(4) предлагает способ использования для этой цели отладочного вывода компилятора
libpcap, используемого в
tcpdump, с помощью вспомогательного awk-скрипта. Здесь, однако, возникает проблема с отсылкой пакетов в netgraph из
ipfw - они туда приходят без Ethernet-заголовка, занимающего 14 байт. А
tcpdump генерирует программу с его проверкой. Есть, конечно, вариант подключения
ng_bpf к
ng_ether(4), напрямую, без файрвола, тогда выражение будет соответствовать. Однако это означает, что необходимо будет самостоятельно здесь же в программе проверять адреса назначения и тому подобное, что удобнее делать в файрволе, отправляя лишь нужные пакеты, кроме того, поскольку проверяться будет каждый пакет, возрастет нагрузка (
ng_bpf может быть довольно прожорлив). Citrin решил это написанием небольшой программки на Си, которая компилирует выражение с типом
DLT_RAW - каковые пакеты и ходят через
ng_ipfw, что описал в подробностях на
http://citrin.ru/freebsd:ng_ipfw_ng_bpf вместе с остальными подробностями по фильтрации MX-запросов (живой пример всегда интересен).
Здесь надо заметить, что по ссылке описан случай, когда пакеты, удовлетворяющие условию, просто отбрасываются - часто же нужно что-то сделать с ними дальше в файрволе другое, после проверки по содержимому как части правил файрвола, так сказать. Это можно сделать спомощью появившихся во FreeBSD 6.2-RELEASE тегов
ipfw (ipfw tags) - в нетграф на пакет навешивается тег, потом в
ipfw он проверяется (возможен и обратный сценарий). Когда я
писал для этой цели
ng_tag(4) (эта нода тоже вошла в состав 6.2), я столкнулся с той же проблемой - мне надо было фильтровать p2p-пакеты, что делалось в
ng_bpf(4). И я, и Citrin попробовали опцию
tcpdump -y raw, но
tcpdump проверяет допустимый DLT для интерфейса, и не дает указать
DLT_RAW. В результате в
примере в man ng_tag даются выражения
tcpdump в "сырых" смещениях от начала пакета, вычисленных вручную по их формату (это все же проще, чем писать на ассемблере BPF, да); то же сделано и в проскакивавших в mail-листах FreeBSD подробных примерах фильтрации
ICQ (авторства
bird_of_Luck в IRC-сети RusNet) и
BitTorrent.
Стоит заметить, что не всякий L7 filtering может быть сделан с помощью
ng_bpf - поскольку в этом ассемблере запрещены условные переходы назад и таким образом циклы, нельзя организовать, например, поиск фиксированной подстроки по заранее неизвестному (и никак не вычисляемому из других данных) смещению в пакете (то, что делает
iptables -m string) - думается, в будущем будет написан netgraph-узел, который займется именно этим :)
...Ну а что касается любителей хитрых трюков, то способ использования
tcpdump в привычном виде без всяких программок все же был найден :) Для этого необходим файл, записанный ранее с помощью
tcpdump -w с data link type
DLT_RAW. Содержимое неважно - ведь используется отладочный вывод. То есть, необходимо дать команду вида
tcpdump -ddd -r file.raw expression (заменить в скриптах по вкусу).
Получить такой файл можно, например, с помощью утилиты
ipfwpcap(8). Это, кстати, довольно полезная вещь сама по себе - вешается на divert-сокет и записывает в файл, который потом можно прочитать с помощью
tcpdump -r, пришедшие в него пакеты. Она включена в состав 7-CURRENT, правда, я бы порекомендовал взять версию с
http://antigreen.org/vadim/freebsd/ipfwpcap/ - там добавлено несколько полезных фич вроде переоткрытия логов по SIGHUP и т.п. приближений по функционалу к
pflogd(8).
UPD: Более подробно тема программирования BPF раскрыта здесь:
http://nuclight.livejournal.com/124989.html