The OpenNET Project / Index page

[ новости /+++ | форум | wiki | теги | ]

Использование ng_ipfw + ng_bpf для фильтрации по телу пакета во FreeBSD (netgraph filter ip udp bpf tcpdump packet freebsd)


<< Предыдущая ИНДЕКС Исправить src / Печать Следующая >>
Ключевые слова: netgraph, filter, ip, udp, bpf, tcpdump, packet, freebsd,  (найти похожие документы)
From: Антон Южанинов <http://citrin.ru>; Date: Mon, 24 Oct 2007 14:31:37 +0000 (UTC) Subject: Использование ng_ipfw + ng_bpf для фильтрации по телу пакета во FreeBSD Оригинал: http://citrin.ru/freebsd:ng_ipfw_ng_bpf Использование ng_ipfw + ng_bpf для фильтрации по телу пакета ipfw это удобный и гибкий пакетный фильтр, но без внешних средств решение он может принимать только на основе данных в заголовке пакета (ip и udp/tcp). Иногда этого недостаточно и нужно учитывать содержимое пакета. Например у меня возникло желание зафильтровать DNS-запросы MX-записей, которые в большом количестве делают зараженные спамерскими троянами клиентские компьютеры, чем вызывают повышенную загрузку DNS-сервера. В случае если клиентские компьютеры за NAT, почтовых серверов на них быть не должно, и MX-записи им спрашивать не нужно. Правильно ли так делать это отдельный вопрос, здесь будет рассмотрен только технический аспект - как это сделать. Для решения данной задачи есть как минимум три варианта: * Из ipfw через divert-сокет отправлять пакеты процессу работающему в userspace, который будет слушать divert-сокет и фильтровать приходящие на него пакеты. Главный минус такого варианта - большие накладные расходы и как следствие низкая производительность. Другой минус - этот демон еще нужно написать. Хотя не исключено, что кто то уже написал демон слушающий divert и фильтрующий пакеты через bpf. * ng_bpf - узел Netgraph использующий bpf для фильтрации пакетов + ng_bpf можно подключить непосредственно к ng_ether. + пакеты в ng_bpf можно отправлять из ipfw через ng_ipfw, при выполнении определенного правила через. Я выбрал последний вариант (ng_ipfw+ng_bpf), несмотря на то, что он несколько сложнее (ниже напишу почему), чем ng_ether+ng_bpf. Во-первых связка ng_ipfw+ng_bpf теоретически должна работать немного быстрее (тесты не проводил), а во вторых из ipfw удобно рулить тем какие пакеты отправлять на дальнейшую обработку в bpf, а какие нет. bpf Для начала нужно определиться по какому bpf-выражению будем фильтровать пакеты. Исходная задача звучала так: DNS-запросы (бит QR установлен в 1). Тип запроса - MX. Со вторым сложнее - в одном пакете может быть запрос на несколько записей разных типов, а сами запросы имеют переменную длину, и начинаются со строки заканчивающейся на 0 длина которой не указана в полях. Парсить такой запрос полностью в bpf очень неудобно. Впрочем это и не нужно - запросы посылаемые спамботами содержать запрос только на одну запись типа MX и можно просто заглянуть в конец пакета - там будут поля Type (MX - 0x000f)и Class (IN - 0 *0001). Если это проверять, то под правило будут подпадать любые пакеты у которых в конце запрос на MX-запись. При составлении выражения удобно пользоваться описанием формата пакетов с сайта networksorcery.com (^1). udp[10] & 0 *80 = 0 - проверяем, что 1-й бит, байта со смещением 10 (^2) выставлен в 1, т. е. то, что это запрос, а не ответ. udp[udp[4:2]-4 : 4] = 0x000f0001 - последние 4 байта UDP пакета 0x000f0001. udp[4:2] это длинна UDP пакета в байтах (вместе с заголовком). В результате получается выражение: udp dst port 53 and udp[10] & 0x80 = 0 and udp[udp[4:2]-4 : 4] = 0x000f0001 Но для ng_bpf нужно не выражение, а bpf-программа - набор низкоуровневых инструкций, которые нужно выполнить для проверки пакета. Чем то напоминает упрощенный и усеченный ассемблер. В какие инструкции нужно преобразовать выражение зависит от того, что подается на вход bpf-фильтра - IP пакеты (начиная с заголовка IP-пакета без каких либо дополнительных заголовков), ethernet-фреймы (т. е. заголовок канального уровня и далее IP пакет) или пакет какого либо другого канального уровня. В случае если ng_bpf подключен к ng_ether на его вход подаются ethernet фреймы, и для получения bpf-кода можно воспользоваться способом описанным в man ng_bpf - tcpdump -ddd и последующее приведении в формат который нужен для netgraph с помощью awk. В случае подключения ng_bpf к ng_ipfw на вход подаются IP пакеты и такой способ не подойдет - в начале будет пара команд проверяющих поле в заголовке ethernet-фрейма которого в нашем случае нету, а в последующих командах будет использовано смещение на 14 байт больше чем нужно. Поэтому есть два способа - составить программу руками заглядывая в man bpf и /usr/include/net/bpf.h (за основу можно взять вывод tcpdump -ddd, но с нуля написать не сложнее чем вручную дизассемблировать вывод tcpdump -ddd, изменить и потом снова перевести это в bpf-код). Это процесс сильно напоминает программирование на ассемблере с последующим переводом этого в машинные коды. Или можно написать небольшую программку для компиляции выражения в bpf-код проверки IP пакетов (в libpcap это тип DLT_RAW, т. е. "сырой" IP пакет без каких либо дополнительных заголовков). Cначала я составил bpf-код вручную, потом решил, что такой способ хорошо только в учебных целях, поскольку отнимает много времени и для повседневной работы не годится. Потом написал маленькую программку которая это делает используя libpcap: /* * to compile type: * gcc -lpcap bpf_comp.c -o bpf_comp */ #include <err.h> #include <pcap.h> #include <stdio.h> #include <sysexits.h> int main(int argc, char *argv[]) { struct bpf_program bp; unsigned int i; if (argc != 2) errx(EX_USAGE, "Usage %s 'filter expression'", argv[0]); if(pcap_compile_nopcap(65535, DLT_RAW, &bp, argv[1], 1, 0)) { errx(EX_USAGE, "filter syntax error"); } [26]printf("bpf_prog_len=%d bpf_prog=[ ", bp.bf_len); for (i = 0; i < bp.bf_len; i ++) { [27]printf("{ code=%d jt=%d jf=%d k=%d } ", bp.bf_insns[i].code, bp.bf_insns[i].jt, bp.bf_insns[i].jf, bp.bf_insns[i].k); } [28]printf("]\n"); exit(0); } Для тестирования этой программы можно посмотреть bpf-программу для фильтрации udp пакетов :~> ./bpf_comp udp bpf_prog_len=5 bpf_prog=[ { code=0 jt=0 jf=0 k=0 } { code=48 jt=0 jf=0 k=9 } { code=21 jt=0 jf=1 k=17 } { code=6 jt=0 jf=0 k=65535 } { code=6 jt=0 jf=0 k=0 }] Если у Вас получится программа, не 5 команд (bpf_prog_len=5), а 7, значит libpcap собран с поддержкой IPv6 которая пока реализована не очень хорошо. В результате для DLT_RAW создается фильтрующий код, который кроме интересующих нас пакетов может поймать и лишние (это происходит в libpcap до версии 0.9.5 включительно, а в 0.9.6 это должно быть исправлено, но в любом случае если в сети используется только IPv4, то лучше собирать libpcap без поддержки IPv6, чтобы bpf-программа была короче и быстрее). Поэтому если у вас получилось 7 команд для udp, рекомендую пересобрать без поддержки IPv6: echo 'NO_INET6=true' >> /etc/make.conf cd /usr/src/lib/libpcap/ make clean make make install Для интересующего нас выражения получается такая bpf-программа: :~> ./bpf_comp 'udp dst port 53 and udp[10] & 0x80 = 0 and udp[udp[4:2]-4 : 4]= 0x000f0001' bpf_prog_len=18 bpf_prog=[ { code=0 jt=0 jf=0 k=0 } { code=48 jt=0 jf=0 k=9 } { code=21 jt=0 jf=14 k=17 } { code=40 jt=0 jf=0 k=6 } { code=69 jt=12 jf=0 k=8191 } { code=177 jt=0 jf=0 k=0 } { code=72 jt=0 jf=0 k=2 } { code=21 jt=0 jf=9 k=53 } { code=80 jt=0 jf=0 k=10 } { code=69 jt=7 jf=0 k=128 } { code=72 jt=0 jf=0 k=4 } { code=20 jt=0 jf=0 k=4 } { code=12 jt=0 jf=0 k=0 } { code=7 jt=0 jf=0 k=0 } { code=64 jt=0 jf=0 k=0 } { code=21 jt=0 jf=1 k=983041 } { code=6 jt=0 jf=0 k=65535 } { code=6 jt=0 jf=0 k=0 } ] (первая команда в данном случае лишняя и её можно выкинуть, но можно ли это делать зависит от того какая команда стоит за ней, в общем случае проще оставлять программу как есть). Netgraph Для использования узлов типа ng_bpf и ng_ipfw нужно загрузить соответствующие модули (если они не были включены в ядро): :~# kldload ng_ipfw :~# kldload ng_bpf Чтобы после перезагрузки они загружались автоматически нужно добавить в /boot/loader.conf ng_ipfw_load="YES" ng_bpf_load="YES" При загрузке модуля ng_ipfw автоматически создается одни узел с именем ipfw (дополнительные узлы типа ipfw создавать нельзя). Создаем узел типа bpf и подключаем его к ipfw: :~# ngctl mkpeer ipfw: bpf 1 main Задаем созданному узлу имя: :~# ngctl name ipfw:1 dns_mx_q_filter Конфигурируем его: :~# ngctl msg dns_mx_q_filter: setprogram { thisHook=\"main\" ifMatch=\"\" ifNotMatch=\"main\" bpf_prog_len=17 bpf_prog=[ { code=48 jt=0 jf=0 k=9 } { code=21 jt=0 jf=14 k=17 } { code=40 jt=0 jf=0 k=6 } { code=69 jt=12 jf=0 k=8191 } { code=177 jt=0 jf=0 k=0 } { code=72 jt=0 jf=0 k=2 } { code=21 jt=0 jf=9 k=53 } { code=80 jt=0 jf=0 k=10 } { code=69 jt=7 jf=0 k=128 } { code=72 jt=0 jf=0 k=4 } { code=20 jt=0 jf=0 k=4 } { code=12 jt=0 jf=0 k=0 } { code=7 jt=0 jf=0 k=0 } { code=64 jt=0 jf=0 k=0 } { code=21 jt=0 jf=1 k=983041 } { code=6 jt=0 jf=0 k=65535 } { code=6 jt=0 jf=0 k=0 } ] } thisHook - хук, входящие пакеты с которого будут фильтроваться заданной программой. ifMatch - хук куда будут отправляться пакеты для которых выполнено условия фильтра. Если задать пустым, то пакеты будут отбрасываться (что в данном случае и требовалось - фильтровать определенные пакеты). ifNotMatch - все остальные пакеты отправляем обратно в ipfw (^3). Можно посмотреть, что программа действительно задана: :~# ngctl msg dns_mx_q_filter: getprogram \"main\" Теперь осталось интересующие нас пакеты отправить из ipfw через хук с именем 1 в netgraph. Т. к. к хуку с именем 1 подключен узел типа ng_bpf, то пакеты попадут к нему: :~# ipfw add 123 netgraph 1 udp from 192.168.0.0/16 to me 53 Для просмотра статистики есть сообщение getstats :~# ngctl msg dns_mx_q_filter: getstats \"main\" Rec'd response "getstats" (3) from "[4fa]:": Args: { recvFrames=16333 recvOctets=977179 recvMatchFrames=9133 recvMatchOcte ts=512317 xmitFrames=7200 xmitOctets=464862 } Из 16333 пакетов отправленных правилом ipfw в ng_bpf, 9133 пакетов были запросами на mx-записи. Для конфигурации ng_ipfw+ng_bpf при загрузке можно положить скрипт в /usr/local/etc/rc.d/ (расширение .sh нужно только во FreeBSD 4, 5. Для 6-ки его лучше убрать). комментарии по поводу этой заметки можно писать в ЖЖ Ремарки: 1) первоисточник этой информации RFC, но в RFC эта информация представлена не так наглядно и удобно 2) 8 байт, заголовок UDP и 2 байта идентификатор DNS запроса 3) что с ними будет дальше зависит от значения sysctl net.inet.ip.fw.one_pass - либо будет разрешен, либо продолжится проверка следующих правил Скрипт для конфигурации ng_ipfw+ng_bpf при загрузке: #!/bin/sh # PROVIDE: ng_bpf # REQUIRE: LOGIN abi # BEFORE: securelevel . /etc/rc.subr name="ng_bpf" # see http://citrin.ru/freebsd:ng_ipfw_ng_bpf for more info # # udp[10] & 0x80 = 0 - Query bit = 1 # udp[udp[4:2]-4 : 4] = 0x000f0001 - type MX and class IN (at the end of the packet) # # udp dst port 53 and udp[10] & 0x80 = 0 and udp[udp[4:2]-4 : 4] = 0x000f0001 bpf_prog="bpf_prog_len=17 bpf_prog=[ { code=48 jt=0 jf=0 k=9 } { code=21 jt=0 jf=14 k=17 } { code=40 jt=0 jf=0 k=6 } { code=69 jt=12 jf=0 k=8191 } { code=177 jt=0 jf=0 k=0 } { code=72 jt=0 jf=0 k=2 } { code=21 jt=0 jf=9 k=53 } { code=80 jt=0 jf=0 k=10 } { code=69 jt=7 jf=0 k=128 } { code=72 jt=0 jf=0 k=4 } { code=20 jt=0 jf=0 k=4 } { code=12 jt=0 jf=0 k=0 } { code=7 jt=0 jf=0 k=0 } { code=64 jt=0 jf=0 k=0 } { code=21 jt=0 jf=1 k=983041 } { code=6 jt=0 jf=0 k=65535 } { code=6 jt=0 jf=0 k=0 } ]" ngctl="/usr/sbin/ngctl" start_cmd="start_cmd" # stop not implemented stop_cmd=":" extra_commands="stats" stats_cmd="getstats_cmd" start_cmd() { # modules must be already loaded (via /boot/loader.conf) debug "create ng_bpf node and connect to ipfw" $ngctl mkpeer ipfw: bpf 1 main $ngctl name ipfw:1 dns_mx_q_filter debug "set bpf program" $ngctl msg dns_mx_q_filter: setprogram { thisHook=\"main\" ifMatch=\"\" ifNotMatch=\"main\" $bpf_prog } } getstats_cmd() { $ngctl msg dns_mx_q_filter: getstats \"main\" } load_rc_config $name : ${ng_bpf_enable="YES"} run_rc_command "$1"

<< Предыдущая ИНДЕКС Исправить src / Печать Следующая >>

Обсуждение [ RSS ]
  • 1, adm.unix (??), 19:47, 29/09/2009 [ответить]  
  • +/
    QR  A one bit field that specifies whether this message is a query (0), or a response (1).

    http://tools.ietf.org/html/rfc1035

    В статье указано, что QR бит выставляется в 1.
    Ошибка!

     

     Добавить комментарий
    Имя:
    E-Mail:
    Заголовок:
    Текст:




    Спонсоры:
    Слёрм
    Inferno Solutions
    Hosting by Ihor
    Хостинг:

    Закладки на сайте
    Проследить за страницей
    Created 1996-2019 by Maxim Chirkov
    Добавить, Поддержать, Вебмастеру