Vadim Goncharov (nuclight) wrote,
Vadim Goncharov
nuclight

Category:

Программирование ng_bpf(4) и L7 filtering на FreeBSD

Тут коллега 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
Tags: freebsd, l7, netgraph, сети
Subscribe

  • Post a new comment

    Error

    Anonymous comments are disabled in this journal

    default userpic

    Your reply will be screened

    Your IP address will be recorded 

  • 1 comment