This is the GSoC 2010 - Minix 3 Firewall project page. This page describes the design evolution and provides a documentation of the developed features.
Student: Stefano Cordio [ stefano (dot) cordio (at) gmail (dot) com ]
Mentors: Cristiano Giuffrida and Lorenzo Cavallaro
SVN branch name: src.r7062.firewall (Tracking Current)
Last compiled trunk: r9022
Last branch rebase: r9022
NetBSD version: 5.1 (GENERIC)
The goal of this project is to implement a firewall for Minix 3. It is preferable to port an existing firewall from another operating system or, alternatively, make a new one that has a similar approach to one that can not be ported.
The NetBSD kernel provides a Packet Filter Interface called pfil which supports the registration of callback functions to special hooks inside of the management logic of network packets.
This project can be divided in two steps:
To follow the firewall development, the guidelines are similar to those described in the TrackingCurrent page. You only need a few attentions:
# cd /usr # mv src src.trunk # mkdir src.firewall # chown bin src.firewall # ln -s src.firewall src
$ svn --username anonymous checkout https://gforge.cs.vu.nl/svn/minix/branches/src.r7062.firewall src.firewall
Revision 9591:
mkdir /usr/include/pfil/opt
Revision 9586:
mkdir /usr/include/pfil/minix
Revision 9071:
mkdir /usr/include/pfil mkdir /usr/include/pf mkdir /usr/include/pf/minix mkdir /usr/include/pf/net mkdir /usr/include/pf/opt
cp /usr/src/etc/pf.conf /etc/pf.conf cp /usr/src/etc/pf.os /etc/pf.os
Once the system is built and after a reboot, you can run the tests under the test/pf folder:
# cd /usr/src/test/pf # make run
The pfil is a framework that offers two main capabilities:
To understand how this framework works, its internals must be analyzed.
The NetBSD base system source code is in the directory /usr/src on a NetBSD box. The kernel is in the directory /usr/src/sys. All paths in this chapter are relative to the kernel source directory. TAILQ_* and LIST_* macros are defined in the <sys/queue.h> header file and refer to specific implementation of lists, so there is no need to deepen these now.
A hook is an entity that allows applications to run their callback functions at special points of the networking code. It is defined as following:
struct packet_filter_hook { TAILQ_ENTRY(packet_filter_hook) pfil_link; int (*pfil_func)(void *, struct mbuf **, struct ifnet *, int); void *pfil_arg; int pfil_flags; };
The pfil_link field is used to insert the packet_filter_hook object in the functions list of the head to which it belongs (see below).
The pfil_arg field is the pointer of the function argument.
The pfil_flags field determines when the function must be called, it may take the following values: «Anchor(pfil_flags)»
PFIL_IN call the function on incoming packets PFIL_OUT call the function on outgoing packets PFIL_ALL call the function on all of the above PFIL_IFADDR call the function on interface reconfiguration PFIL_IFNET call the function on interface attach/detach PFIL_WAITOK as reported in the pfil man page: "OK to call malloc with M_WAITOK" -- need more understanding here :)
The pfil_func field is the pointer of the associated function. A generic function is declared as following:
int pfil_func(void *, struct mbuf **, struct ifnet *, int);
The return value should be 0 if the packet processing is to continue or an errno value if the processing is to stop.
A head is a filtering point where it is possible to attach a hook. It is defined as following:
typedef TAILQ_HEAD(pfil_list, packet_filter_hook) pfil_list_t; struct pfil_head { pfil_list_t ph_in; pfil_list_t ph_out; pfil_list_t ph_ifaddr; pfil_list_t ph_ifnetevent; int ph_type; union { u_long phu_val; void *phu_ptr; } ph_un; LIST_ENTRY(pfil_head) ph_list; };
PFIL_TYPE_AF address family hook PFIL_TYPE_IFNET interface hook
Depending on the value assumed by the previous field, the ph_un union contains different values and the following macros simplify the access to it:
#define ph_af ph_un.phu_val /* the ph_un field contains an AF_* type (e.g. IPv4 type is AF_INET, IPv6 type is AF_INET6) */ #define ph_ifnet ph_un.phu_ptr /* the ph_un field contains an ifnet pointer */
Finally, the ph_list field is used to insert the pfil_head object in the global list of head objects named pfil_head_list and defined as follow:
LIST_HEAD(, pfil_head) pfil_head_list = LIST_HEAD_INITIALIZER(&pfil_head_list);
The framework offers two functions to place a new head:
int pfil_head_register(struct pfil_head *ph); int pfil_head_unregister(struct pfil_head *ph);
In both functions, the ph parameter is the object pointer to be register.
A network component can place a new head by setting the ph_type and ph_un fields and calling the pfil_head_register() function. In this way the new head is added to the global list of head and it is possible getting it to attach a new hook.
Instead, the call of the pfil_head_unregister() function causes the removal from the global list of the specified head .
An application can register its callback function by registering a new hook with an existing head. The functions provided for this purpose by the framework are:
struct pfil_head * pfil_head_get(int type, u_long val); int pfil_add_hook(int (*func)(void *, struct mbuf **, struct ifnet *, int), void *arg, int flags, struct pfil_head *ph); int pfil_remove_hook(int (*func)(void *, struct mbuf **, struct ifnet *, int), void *arg, int flags, struct pfil_head *ph);
At first, the app must identify the head that should be used by calling the pfil_head_get() function and specifying its type and value. Once the head is obtained, the recording of the new hook is done by calling the pfil_add_hook() function and passing as parameter the name of the target callback function.
Removing a registered hook is done by calling the pfil_remove_hook() function.
The framework provides the function pfil_run_hooks() to perform the callback functions registered to a head. It is the following:
int pfil_run_hooks(struct pfil_head *ph, struct mbuf **mp, struct ifnet *ifp, int dir);
The ph parameter is the target head.
The mp parameter holds the data received from the network (refer to the second argument explained here for further informations).
The ifp parameter represents the network device logically connected with this head (refer to the third argument explained here for further informations).
The dir parameter determines which list must be traversed (refer to the pfil_flags values of the packet_filter_hook structure).
This function loads the head and runs through the list of hooks, launching the associated functions.
Part of the IPv4 networking logic is implemented in the “netinet/ip_input.c” and “netinet/ip_output.c” source files. This file contains the steps that the kernel module crosses to ensure the support to the packet filters. The steps are:
struct pfil_head inet_pfil_hook;
inet_pfil_hook.ph_type = PFIL_TYPE_AF; inet_pfil_hook.ph_af = AF_INET; pfil_head_register(&inet_pfil_hook);
if (pfil_run_hooks(&inet_pfil_hook, &m, m->m_pkthdr.rcvif, PFIL_IN) != 0) return; /* stop processing the packet */ else /* continue processing the packet */
if (pfil_run_hooks(&inet_pfil_hook, &m, ifp, PFIL_OUT) != 0) return; /* stop processing the packet */ else /* continue processing the packet */
In the “net/if.c” source file there is part of the management of network interfaces. Here the kernel registers other heads with pfil, one for each active interface and a global one for all interfaces.
The global head is declared as follows:
struct pfil_head if_pfil;
and registered as follows:
if_pfil.ph_type = PFIL_TYPE_IFNET; if_pfil.ph_ifnet = NULL; pfil_head_register(&if_pfil);
Note that the field ph_ifnet is NULL because that head is not associated with any specific interface.
The hook execution code of this head can be found in three places:
int in_control(struct socket *so, u_long cmd, void *data, struct ifnet *ifp, struct lwp *l) { switch (cmd) { *.. case SIOCSIFADDR: /* set interface IP address */ pfil_run_hooks(&if_pfil, (struct mbuf **)SIOCSIFADDR, ifp, PFIL_IFADDR); *.. case SIOCAIFADDR: /* add interface IP address */ pfil_run_hooks(&if_pfil, (struct mbuf **)SIOCAIFADDR, ifp, PFIL_IFADDR); *.. case SIOCDIFADDR: /* delete interface IP address */ pfil_run_hooks(&if_pfil, (struct mbuf **)SIOCDIFADDR, ifp, PFIL_IFADDR); *.. } }
static void sppp_set_ip_addrs(struct sppp *sp, uint32_t myaddr, uint32_t hisaddr) { *.. pfil_run_hooks(&if_pfil, (struct mbuf **)SIOCAIFADDR, ifp, PFIL_IFADDR); *.. } static void sppp_clear_ip_addrs(struct sppp *sp) { *.. pfil_run_hooks(&if_pfil, (struct mbuf **)SIOCDIFADDR, ifp, PFIL_IFADDR); *.. }
The “net/if.c” source file contains also the function if.c::if_attach() which is called to attach an interface to the list of the active interfaces. The definition is:
void if_attach(struct ifnet *ifp);
The ifp argument represents the interface to be enabled.
The function initializes and registers one of the field in the struct ifnet, i.e. the if_pfil field of type struct pfil_head, as follows:
ifp->if_pfil.ph_type = PFIL_TYPE_IFNET; ifp->if_pfil.ph_ifnet = ifp; pfil_head_register(&ifp->if_pfil);
After the registration, there is the call to pfil_run_hooks() on the if_pfil because the hooks on this type of head should be invoked whenever the associated interface is attached/detached or reconfigured:
pfil_run_hooks(&if_pfil, (struct mbuf **)PFIL_IFNET_ATTACH, ifp, PFIL_IFNET);
The pf packet filter interacts with pfil in two ways:
The first type of recording is made in the “dist/pf/net/pf_ioctl.c” source file.
The pf_ioctl.c::pf_pfil_attach() function obtains the head pointer and then registers the pf_ioctl.c::pfil4_wrapper() function as a hook on it:
struct pfil_head *ph_inet; ph_inet = pfil_head_get(PFIL_TYPE_AF, AF_INET); pfil_add_hook((void *)pfil4_wrapper, NULL, PFIL_IN|PFIL_OUT, ph_inet);
The other recording type is made in the “dist/pf/net/pf_if.c” source file by the pf_if.c::pfi_initialize() function. Unlike the previous one, there is not the research phase of the head pointer because this is visible through the <net/pfil.h> header file which exports the object declared in the “net/if.c” source file. Therefore, it registers pf_if.c::pfil_ifnet_wrapper and pf_if.c::pfil_ifaddr_wrapper as hooks on the if_pfil head:
pfil_add_hook(pfil_ifnet_wrapper, NULL, PFIL_IFNET, &if_pfil); pfil_add_hook(pfil_ifaddr_wrapper, NULL, PFIL_IFADDR, &if_pfil);
Integrating pfil on Minix is achieved primarily by defining a new compile-time flag named PFIL_HOOKS, which allows to enable/disable packet filtering module, and by the insertion of the “net/pfil.h” and “net/pfil.c” source files in the inet source directory (servers/inet/). It is important to carefully choose where to initialize/register the interface/IPv4 heads and where to execute the hook callbacks in the networking flow. In order to reduce the changes to adapt pf, the best approach that can be followed is to keep the interface offered to it by pfil, using wrapper functions for any conversion between the interface and the real implementation.
The Minix3 base system source code is in the directory /usr/src on a Minix box, all paths in this chapter are relative to this source directory.
Work in progress…
Referring to the previous example, the positioning of the IPv4 head is achieved through the following steps:
servers/inet/generic/ip.c:
#ifdef PFIL_HOOKS #include "pfil.h" #endif ... #ifdef PFIL_HOOKS PUBLIC struct pfil_head inet_pfil_hook; #endif
servers/inet/generic/ip.c:
PUBLIC void ip_init() { int i; /* this declaration already exists*/ *.. #ifdef PFIL_HOOKS inet_pfil_hook.ph_type = PFIL_TYPE_IFNET; inet_pfil_hook.ph_ifnet = NULL; if ( (i = pfil_head_register(&inet_pfil_hook)) != 0) printf("inet: ip_init: WARNING: unable to register pfil hook, error %d\n", i); #endif }
servers/inet/generic/ip_read.c:
#ifdef PFIL_HOOKS #include "pfil.h" #endif #ifdef PFIL_HOOKS EXTERN struct pfil_head inet_pfil_hook; #endif PUBLIC void ip_arrived(ip_port, pack) ip_port_t *ip_port; acc_t *pack; { #ifdef PFIL_HOOKS struct mbuf *m; #endif *.. #ifdef PFIL_HOOKS /* mapping the incoming packet and the interface info in the mbuf * structure */ if (pfil_run_hooks(&inet_pfil_hook, &m, m->mb_pkthdr.rcvif, PFIL_IN) != 0) return; if (m == NULL) return; #endif /* PFIL_HOOKS */ dest= ip_hdr->ih_dst; if (dest == ip_port->ip_ipaddr) { *.. } PUBLIC void ip_arrived_broadcast(ip_port, pack) ip_port_t *ip_port; acc_t *pack; { #ifdef PFIL_HOOKS struct mbuf *m; #endif *.. #ifdef PFIL_HOOKS /* mapping the incoming packet and the interface info in the mbuf * structure */ if (pfil_run_hooks(&inet_pfil_hook, &m, m->mb_pkthdr.rcvif, PFIL_IN) != 0) return; if (m == NULL) return; #endif /* PFIL_HOOKS */ ip_port_arrive (ip_port, pack, ip_hdr); }
servers/inet/generic/ip_write.c:
#ifdef PFIL_HOOKS #include "pfil.h" #endif #ifdef PFIL_HOOKS EXTERN struct pfil_head inet_pfil_hook; #endif PUBLIC int ip_send(fd, data, data_len) int fd; acc_t *data; size_t data_len; { int r; /* this declaration already exists*/ #ifdef PFIL_HOOKS struct mbuf *m; struct ifnet *ifp; #endif /* PFIL_HOOKS */ *.. #ifdef PFIL_HOOKS /* mapping the outgoing packet in the mbuf structure and the * interface info in the ifnet structure */ /* * Run through list of hooks for output packets. */ if ((r = pfil_run_hooks(&inet_pfil_hook, &m, ifp, PFIL_OUT)) != 0) return (r); if (m == NULL) return (r); #endif /* PFIL_HOOKS */ ip_hdr_chksum(ip_hdr, hdr_len); /* this function call already exists*/ *.. }
Referring to the previous example, the positioning of the interface heads is achieved through the following steps:
This period is focused on the study of pfil (the NetBSD packet filter interface) and pf (the NetBSD packet filter), assessing their true portability on the Minix environment. This period is also focused on the analysis of the Minix networking framework, i.e. the inet server source code.
Completion of the wiki chapter: “The NetBSD Packet Filter Interface”.
Branch rebased to r7148. Created the new gsoc_test folder in the branch as standalone environment. Analyzed the dependencies of the NetBSD mbuf structure and adapted its source files in the gsoc_test folder.
Completion of the porting of the pfil framework in the gsoc_test folder, resolving all data dependencies of the structures. Branch rebased to r7268.
Inactivity due to a medical intervention.
Depth study of the Minix ip_send() and the NetBSD ip_output() functions. Completion of the positioning of the pfil_run_hooks() calls in the IPv4 packet processing flow. Branch rebased to r7538.
Merged the changes in inet, adding comments to the inet::ip_arrive(), inet::ip_arrive_broadcast() and inet::ip_send() functions. Branch rebased to r7657.
Planning of the mid-term project with the mentors: implementation of most of the functionality of the firewall sheep/goat project, using the pfil infrastructure. Creation of a new device, /dev/simplepf, with no functionality (i.e. no read(), no write(), no ioctl(), etc.).
Added open(), close and ioctl() support to the new simplepf device. Release of the sheep/goat project for the mid-term evaluation. Reorganization of the pf-related file hierarchy. Branch rebased to r7778.