Determining order of passing packets throught packet filters in FreeBSD
The order of loading kernel modules of packet filters affects the order of registering hooks in pfil interface.
So this code shows how pfil calls specified hook:
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;
}
}
So we check all hooks begining from TAILQ_FIRST and pass packet to each hook as parameter.
If hook drops packet, we do not check other hooks.
This code register hooks in pfil interface:
/*
* 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);
So for inbound traffic module that adds its hook last would process packets first, and for outbound traffic module that adds its hook first would process packets first.
Now let's determine load order of kernel modules.
Modules on load attach to subsystems, that are described in 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*/
Subsystems are loading in order of their number, first are loaded subsystems with lesser number.
Module on load can be attached to subsystem in different module too, this order is declared in macro 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*/
};
So we should check subsystem of module and order of attachement to subsystem for each packet filter module:
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);
Subsystems loads in this order: SI_SUB_PROTO_DOMAIN -> SI_SUB_PROTO_IFATTACHDOMAIN (ipfilter -> (ipfw, pf)), according to order of attachement of modules to subsystems the chain of loading must look like ipfilter -> pf -> ipfw.
Process order for inbound traffic:
ipfw -> pf -> ipfilter -> stack
and for outbound:
stack -> ipfilter -> pf -> ipfw
On the other hand ps is often loaded as kld, and is not statically compiled in the kernel, so if you can see pf.ko in loaded modules list (kldstat), pf is loaded after ipfw, not before. So traffic pass chains must look like this:
pf -> ipfw -> ipfilter -> stack - for inbound
stack -> ipfilter -> ipfw -> pf - for outbound
The order of passing trafic throught packet filters is very important in case of NAT, as next filter in chain can receive modified packet.
So this code shows how pfil calls specified hook:
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;
}
}
So we check all hooks begining from TAILQ_FIRST and pass packet to each hook as parameter.
If hook drops packet, we do not check other hooks.
This code register hooks in pfil interface:
/*
* 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);
So for inbound traffic module that adds its hook last would process packets first, and for outbound traffic module that adds its hook first would process packets first.
Now let's determine load order of kernel modules.
Modules on load attach to subsystems, that are described in 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*/
Subsystems are loading in order of their number, first are loaded subsystems with lesser number.
Module on load can be attached to subsystem in different module too, this order is declared in macro 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*/
};
So we should check subsystem of module and order of attachement to subsystem for each packet filter module:
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);
Subsystems loads in this order: SI_SUB_PROTO_DOMAIN -> SI_SUB_PROTO_IFATTACHDOMAIN (ipfilter -> (ipfw, pf)), according to order of attachement of modules to subsystems the chain of loading must look like ipfilter -> pf -> ipfw.
Process order for inbound traffic:
ipfw -> pf -> ipfilter -> stack
and for outbound:
stack -> ipfilter -> pf -> ipfw
On the other hand ps is often loaded as kld, and is not statically compiled in the kernel, so if you can see pf.ko in loaded modules list (kldstat), pf is loaded after ipfw, not before. So traffic pass chains must look like this:
pf -> ipfw -> ipfilter -> stack - for inbound
stack -> ipfilter -> ipfw -> pf - for outbound
The order of passing trafic throught packet filters is very important in case of NAT, as next filter in chain can receive modified packet.


Leave a comment