Порядок загрузки модулей, осуществляющих фильтрацию пакетов, определяет, в каком порядке будут проходить пакеты через них (это определяется тем, когда модуль зарегистрировал хук у 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 - для исходящего
Эта деталь очень важна, особенно при составлении правил, включающих трансляцию адресов, так как фильтру, находящемуся дальше в цепочке приходит уже модифицированный пакет.
Классно; это полезная информация, многим может пригодиться.
ReplyDeleteСпасибо.
ReplyDeleteThis comment has been removed by a blog administrator.
ReplyDelete