Порядок прохождения пакетов по различным пакетным фильтрам в FreeBSD



http://cebka.pp.ru/blog/2007/10/-freebsd.html ( Vsevolod Stakhov )
Порядок загрузки модулей, осуществляющих фильтрацию пакетов, определяет, в каком порядке будут проходить пакеты через них (это определяется тем, когда модуль зарегистрировал хук у pfil'a).
Как происходит передача пакета хуку pfil'om:
for (pfh = pfil_hook_get(dir, ph); 
        pfh != NULL;
        pfh = TAILQ_NEXT(pfh, pfil_link)) {
            if (pfh->pfil_func != NULL) {
                rv = (*pfh->pfil_func)(pfh->pfil_arg, &m, ifp, dir, inp);
                if (rv != 0 || m == NULL)
                       break;
            }   
}
То есть, перебираются все хуки, начиная с TAILQ_FIRST и им передается пакет в качестве параметра.
Если пакет дропается, то дальше по цепочке он не передается (это надо помнить при записи данных о дропнутых пакетах в лог).
Регистрируются хуки таким образом:
    /*
     * insert the input list in reverse order of the output list
     * so that the same path is followed in or out of the kernel.
     */
    if (flags & PFIL_IN)
        TAILQ_INSERT_HEAD(list, pfh1, pfil_link);
    else
        TAILQ_INSERT_TAIL(list, pfh1, pfil_link);
То есть, для входящего трафика модуль, добавивший свой хук последним, будет обрабатывать пакеты первым, и наоборот - для исходящего модуль, добавивший свой хук первым будет обрабатывать пакеты первым.
Теперь надо определиться с порядком загрузки модулей в ядро:
модули при загрузке присоединяются к определенным подсистемам в ядре: эти подсистемы описаны в файле sys/kernel.h:
нас интересуют
    SI_SUB_PROTO_IF     = 0x8400000,    /* interfaces*/
    SI_SUB_PROTO_DOMAIN = 0x8800000,    /* domains (address families?)*/
    SI_SUB_PROTO_IFATTACHDOMAIN = 0x8800001,    /* domain dependent data init*/
Системы загружаются по очереди, то есть, чем меньше номер, тем раньше загрузится подсистема и тем раньше к ней присоединится модуль. Сам модуль может присоединиться к подсистеме, указывая ряд различных позиций (согласно аргументу order в функции DECLARE_MODULE):
enum sysinit_elem_order {
    SI_ORDER_FIRST      = 0x0000000,    /* first*/
    SI_ORDER_SECOND     = 0x0000001,    /* second*/
    SI_ORDER_THIRD      = 0x0000002,    /* third*/
    SI_ORDER_MIDDLE     = 0x1000000,    /* somewhere in the middle */
    SI_ORDER_ANY        = 0xfffffff /* last*/
};
Таким образом, смотрим на подсистему и порядок подключения к подсистеме у каждого интересующего модуля:
pf:
DECLARE_MODULE(pf, pf_mod, SI_SUB_PROTO_IFATTACHDOMAIN, SI_ORDER_FIRST);
ipfw:
DECLARE_MODULE(ipfw, ipfwmod, SI_SUB_PROTO_IFATTACHDOMAIN, SI_ORDER_ANY);
ipfilter:
DECLARE_MODULE(ipfilter, ipfiltermod, SI_SUB_PROTO_DOMAIN, SI_ORDER_ANY);

Согласно порядку загрузки подсистем, имеем  SI_SUB_PROTO_DOMAIN -> SI_SUB_PROTO_IFATTACHDOMAIN (ipfilter -> (ipfw, pf)), согласно порядку присоединения модулей к подсистеме имеем окончательный порядок загрузки модулей: ipfilter -> pf -> ipfw. Таким образом,
ipfw -> pf -> ipfilter -> stack	 - для входящего
stack -> ipfilter -> pf -> ipfw  - для исходящего
С другой стороны, pf часто загружают в качестве kld, а не компилируют в ядро статически, тогда, если kldstat показывает модуль pf.ko, то загрузился он после ipfw, и порядок прохождения пакетов тоже изменился:
pf -> ipfw -> ipfilter -> stack - для входящего
stack -> ipfilter -> ipfw -> pf - для исходящего
Эта деталь очень важна, особенно при составлении правил, включающих трансляцию адресов, так как фильтру, находящемуся дальше в цепочке приходит уже модифицированный пакет.


#### Порядок прохождения пакетов при одновременном использовании ipfilter, pf и ipfw: (Альтернативно) ####

http://www.opennet.ru/tips/info/1431.shtml (butcher)

При загрузке фильтров модулями, порядок будет определяться порядком загрузки модулей.
Причина здесь в том, что пакетные фильтры регистрируют себя в pfil(9)

При включении всех фильтров в ядро порядок будет определять SYSINIT.
Чтобы определить порядок, нужно открыть файл sys/kernel.h
В нём определён порядок инициализации определённых подсистем. Теперь, простейшее:
# grep DECLARE_MODULE netinet/ip_fw_pfil.c
DECLARE_MODULE(ipfw, ipfwmod, SI_SUB_PROTO_IFATTACHDOMAIN, SI_ORDER_ANY);
# grep DECLARE_MODULE contrib/pf/net/pf_ioctl.c
DECLARE_MODULE(pf, pf_mod, SI_SUB_PROTO_IFATTACHDOMAIN, SI_ORDER_FIRST);
# grep DECLARE_MODULE contrib/ipfilter/netinet/mlfk_ipl.c
DECLARE_MODULE(ipfilter, ipfiltermod, SI_SUB_PROTO_DOMAIN, SI_ORDER_ANY);
От сюда следует: первым будет ipfilter, затем pf, затем ipfw.

/* обычно фильтр регистрирует хук как на вход так и на выход, поэтому: */
1. Первым регистрируется ipfilter, ещё не было зарегистрировано ниодного фильтра, поэтому списки хуков выглядят так:
ph_in:  [ipfilter] 
ph_out: [ipfilter] 
2. Вторым регистрируется pf (изображая по порядку вызова слева на право):
ph_in:  [ipfilter]-[pf] 
ph_out: [pf]-[ipfilter] 
3. Третий регистрируется ipfw:
ph_in:  [ipfilter]-[pf]-[ipfw] 
ph_out: [ipfw]-[pf]-[ipfilter] 
Из этих схем видно, что для исходящих пакетов порядок вызова фильтров инвертирован по отношению ко входящим

#### Личные эксперименты: ####


Поскольку сетвая подсистема и ядро FreeBSD регулярно дорабатывается, могут возникать нюансы (*)
Поэтому самый достоверный (и надежный) путь определения порядка прохождения пакетов - эксперементальный.

Для этого, (например), добавляем в pf и ipfw правила:
pf: block drop quick proto icmp from any to any
ipfw: ipfw add 10 count icmp from any to any
И, делая пинг (как входящий так и исходящий), смотрим на показания счетчиков фаерволов.

В моем случае (pf,ipfw вкомпилены в ядро, тестировалось на 6.2-RELEASE, 6.4-PRERELEASE, 7.1-PRERELEASE)

При входящем пинге: пакет рубится на pf, счетчики ipfw остаются без изменения.
При исходящем пинге: счетчики ipfw увеличиваются, а пакет рубится на pf:
ping: sendto: Operation not permitted

т.е. схема прохождения пакетов:
inbound traffic: 
--> [pf] --> [ipfw] 

outbound traffic: 
--> [ipfw] --> [pf] 

#---------

Картинка о прохождении пакетов через pf flow.png
(оригинал: http://homepage.mac.com/quension/pf/flow.png )

получается что в случае использования pf and ipfw
пакет попадает на pf, проходит по цепочкам, попадает в KERNEL PROCESSING, дальше тут уже он попадает на ipfw, на нем обрабатывается (напр. dummynet), и далее из KERNEL PROCESSING попадает на выходные цепочки PF.


Update 051208 ( Спасибо Vadim Goncharov )
ipfw: порядок прохождения пакетов, сложные случаи (Vadim Goncharov) http://nuclight.livejournal.com/124348.html

(*) В 7-ке при передергивании sysctl net.inet.ip.fw.enable при отключении ipfw свои хуки снимает, при включении снова добавляет, соответственно порядок может поменяться.

Это всё применимо к L3, для L2 и tcpdump обработка выполняется отдельно. По исходникам 6.2:
in:  железо -> bpf(4)/tcpdump -> ng_ether -> ipfw layer2 -> pfil(9)
out: pfil(9) -> ng_ether -> ipfw layer2 -> bpf(4)/tcpdump -> железо 


Update 300109 ( Спасибо Vadim Goncharov )
http://groups.google.com.ua/group/uafug/browse_thread/thread/c4494abb082251b6/7b72d1083eacea79

"А оказалось вот что: pfctl -d/-e контролирует как раз регистрирование/разрегистрирование хука. То есть, получается, что при загрузке, что модуля, что вкомпилированным в ядро, pf никаких хуков не регистрирует вообще - это происходит позже в момент pfctl -e, и может получиться как у меня, что он всегда будет в одном и том же порядке. Значит, в 7-ке поведение ipfw при enable/disbale унифицировали с pf, и без всяких компиляций и манипуляций с модулями простой последовательностью команд можно иметь гарантированный порядок хуков, независимо от."

#### Итог: ####

- для входящего трафика модуль, добавивший свой хук последним, будет обрабатывать пакеты первым, и наоборот - для исходящего модуль, добавивший свой хук первым будет обрабатывать пакеты первым.
- при перезапуске фаерволов (/etc/rc.d/ipfw, /etc/rc.d/pf) т.е. (pfctl -d/-e или ipfw enable/disbale) происходит регистрирование/разрегистрирование хука, что меняет порядок прохождения пакетов.
- в связи с такой запутанной ситуацией, если вам нужно четко знать порядок прохождения пакетов, обязательно проводите эксперементальный опыт по описанной выше методике.
- поведение сетевой подсистемы имеет тенденцию меняться от релиза к релизу, на эту информацию нельзя полагать дословно, это всеголишь теортеическо-практические изыскания.
- крайне не рекомендуется использовать 2 (и более) фаервола одновременно! Конечно, если вы не имеете хобби искать себе приключения (геморой) на Ж ;)



Home

Sergej A. Kandyla sk.paix at gmail.com
Any comments and suggestions are welcome.
CentOS FreeBSD