Документация по ОС FreeBSD Четверг, 25.04.2024, 05:31
Приветствую Вас Гость | RSS
Меню сайта

Категории каталога
IPFW [58]

Главная » Статьи » FireWall » IPFW

Использование ng_ipfw + ng_bpf для фильтрации по телу пакета во FreeBSD
Использование 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"


Источник: http://www.opennet.ru/base/net/ng_ipfw_ng_bpf.txt.html
Категория: IPFW | Добавил: oleg (11.01.2008) | Автор: Антон Южанинов
Просмотров: 1179 | Рейтинг: 0.0/0 |
Всего комментариев: 0
Добавлять комментарии могут только зарегистрированные пользователи.
[ Регистрация | Вход ]
Форма входа

Beastie

Друзья сайта

Статистика

Онлайн всего: 1
Гостей: 1
Пользователей: 0
links

Copyright MyCorp © 2024