User Tools

Site Tools


soc:2010:firewall

Minix Firewall

About

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)

Abstract

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.

Project Idea

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:

  • The first one is to port pfil on Minix, making appropriate links between it and the inet server and exporting functions that allow packet filters to register themselves.
  • The second is to port the pf packet filter.

Tracking Current

To follow the firewall development, the guidelines are similar to those described in the TrackingCurrent page. You only need a few attentions:

  • Prepare a directory for the new /usr/src tree and move the current /usr/src tree to a safe place. For example, you can create a src.firewall directory and use symbolic links to choose the current tree:
# cd /usr
# mv src src.trunk
# mkdir src.firewall
# chown bin src.firewall
# ln -s src.firewall src
  • Do the SVN checkout of the branch of the source:
$ svn --username anonymous checkout https://gforge.cs.vu.nl/svn/minix/branches/src.r7062.firewall src.firewall
  • Follow the instructions in the next section before recompiling (in addition to those specified in docs/UPDATING).

"docs/UPDATING" addendum

Revision 9591:

  • Create the folder for the new header files:
mkdir /usr/include/pfil/opt

Revision 9586:

  • Create the folder for the new header files:
mkdir /usr/include/pfil/minix

Revision 9071:

  • Create the folders for the new header files:
mkdir /usr/include/pfil
mkdir /usr/include/pf
mkdir /usr/include/pf/minix
mkdir /usr/include/pf/net
mkdir /usr/include/pf/opt
  • Copy the default configuration files:
cp /usr/src/etc/pf.conf /etc/pf.conf
cp /usr/src/etc/pf.os /etc/pf.os

Does It Work?

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

NetBSD

The pfil is a framework that offers two main capabilities:

  • Placement of the head on which the hooks could be registered, capability used by the network side of the operating system.
  • Registration of the hooks, capability used by the packet filter applications.

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.

The Hook

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 first is a generic parameter which the caller can pass to the function.
  • The second is the entity which holds the data received from the network. This structure is defined in the <sys/mbuf.h> header file.
  • The third represents the network device on the system. This structure is defined in the <net/if.h> header file and contains information like the interface name, the state of the interface, its capabilities and a group of function pointers.
  • The last represents when the function was called, according to the pfil_flag values.

The return value should be 0 if the packet processing is to continue or an errno value if the processing is to stop.

The Head

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);

Placement of a Head

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 .

Registration of a Hook

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.

Execution of the Hooks

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.

Example: IPv4 Head in the Kernel Networking Module

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:

  • declaration of a module's new head:
struct pfil_head inet_pfil_hook;
  • insertion of the new head in pfil_head_list:
inet_pfil_hook.ph_type = PFIL_TYPE_AF;
inet_pfil_hook.ph_af   = AF_INET;
pfil_head_register(&inet_pfil_hook);
  • inclusion of the hook execution code in the ip_input.c::ip_input() function, performed at each incoming IP packet:
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 */
  • inclusion of the hook execution code in the ip_output.c::ip_output() function, performed at each outgoing IP packet:
if (pfil_run_hooks(&inet_pfil_hook, &m, ifp, PFIL_OUT) != 0)
   return;   /* stop processing the packet */
else
   /* continue processing the packet */

Example: Interface Head in the Kernel Networking Module

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:

  • the code management block of the ioctl operations in the “netinet/in.c” source file:
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);
            *..
   }
}
  • the Synchronous PPP/Cisco link level subroutines in the “net/if_spppsubr.c” source file:
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 attachment/detachment process of the interfaces (see below).

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);

Example: Registration of pf Hooks

The pf packet filter interacts with pfil in two ways:

  • registers one callback function for incoming and outgoing packets.
  • registers two additional callback functions, one for the reconfiguration of the interfaces and one for their attachment/detachment process.

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);

Packet Filter Interface and pf on Minix

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.

Where to Change (draft)

Work in progress…

Placement of the IPv4 Head

Referring to the previous example, the positioning of the IPv4 head is achieved through the following steps:

  • inclusion of the servers/inet/pfil.h header file and declaration of the head:

servers/inet/generic/ip.c:

#ifdef PFIL_HOOKS
#include "pfil.h"
#endif
...
#ifdef PFIL_HOOKS
PUBLIC struct pfil_head inet_pfil_hook;
#endif
  • initialization and registration at the end of the ip.c::ip_init() function:

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
}
  • placement of the execution code for incoming packets, just before the delivery decision in the ip_read.c::ip_arrived() function and before the ip_port_arrive() call in the ip_read.c::ip_arrived_broadcast() function:

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);
}
  • placement of the execution code for outgoing packets, just before the checksum calculation in the ip_write.c::ip_send() function:

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*/

      *..
}

Placement of the Interface Head

Referring to the previous example, the positioning of the interface heads is achieved through the following steps:

To Do on This Page

  • [The NetBSD Packet Filter Interface] Insert a new subchapter to expand ifnet description - Depeen PFIL_WAITOK.
  • [Packet Filter Interface and pf on Minix] Justify the choices of positioning.
  • MASSIVE UPDATE!!!

Weekly Status

Community Bonding Period (27.4.2010 - 23.5.2010)

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.

Week 1

Completion of the wiki chapter: “The NetBSD Packet Filter Interface”.

Week 2

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.

Week 3

Completion of the porting of the pfil framework in the gsoc_test folder, resolving all data dependencies of the structures. Branch rebased to r7268.

Week 4

Inactivity due to a medical intervention.

Week 5

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.

Week 6

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.

Week 7

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.).

Week 8 (Mid-term Evaluation)

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.

Week 9

soc/2010/firewall.txt · Last modified: 2014/11/11 14:52 (external edit)