Порядок прохождения пакетов по различным пакетным фильтрам в 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