This chapter includes:
In principle, the pseudo-devices involved with packet filtering are as follows:
The pf pseudo-device is implemented using pfil hooks; bpf is implemented as a tap in all the network drivers. We'll discuss them briefly from the point of view of their attachment to the rest of the stack.
If you're using QNX Neutrino 6.4.1 or earlier,
you should use ioctl_socket() instead of ioctl()
in your packet-filtering code.
With the microkernel message-passing architecture, ioctl() calls
that have pointers embedded in them need to be handled specially.
The ioctl_socket() function uses ioctl()
for functionality that doesn't require special handling.
In QNX Neutrino 6.5.0 and later, ioctl() handles embedded pointers, so you don't have to use ioctl_socket() instead. |
The pfil interface is purely in the stack and supports packet-filtering hooks. Packet filters can register hooks that are called when packet processing is taking place; in essence, pfil is a list of callbacks for certain events. In addition to being able to register a filter for incoming and outgoing packets, pfil provides support for interface attach/detach and address change notifications.
The pfil interface is one of a number of different layers that a user-supplied application can register for, to operate within the stack process context. These modules, when compiled, are called Loadable Shared Modules (lsm) in QNX Neutrino nomenclature, or Loadable Kernel Modules (lkm) in BSD nomenclature.
There are two levels of registration required with io-pkt:
In Neutrino, shared modules are dynamically loaded into the stack. You can do this by specifying them on the command line when you start io-pkt, using the -p option, or you can add them subsequently to an existing io-pkt process by using the mount command.
The application module must include an initial module entry point defined as follows:
#include "sys/io-pkt.h" #include "nw_datastruct.h" int mod_entry( void *dll_hdl, struct _iopkt_self *iopkt, char *options) { }
The calling parameters to the entry function are:
The header files aren't installed as OS header files, and you must include them from the relevant place in the networking source tree (available from Foundry27). |
This is followed by the registration structure that the stack will look for after calling dlopen() to load the module to retrieve the entry point:
struct _iopkt_lsm_entry IOPKT_LSM_ENTRY_SYM(mod) = IOPKT_LSM_ENTRY_SYM_INIT(mod_entry);
This entry point registration is used by all shared modules, regardless of which layer the remainder of the code is going to hook into. Use the following functions to register with the pfil layer:
#include <sys/param.h> #include <sys/mbuf.h> #include <net/if.h> #include <net/pfil.h> struct pfil_head * pfil_head_get(int af, u_long dlt); struct packet_filter_hook * pfil_hook_get(int dir, struct pfil_head *ph); int pfil_add_hook(int (*func)(), void *arg, int flags, struct pfil_head *ph); int pfil_remove_hook(int (*func)(), void *arg, int flags, struct pfil_head *ph); int (*func)(void *arg, struct mbuf **mp, struct ifnet *, int dir);
The head_get() function returns the start of the appropriate pfil hook list used for the hook functions. The af argument can be either PFIL_TYPE_AF (for an address family hook) or PFIL_TYPE_IFNET (for an interface hook) for the “standard” interfaces.
If you specify PFIL_TYPE_AF, the Data Link Type (dlt) argument is a protocol family. The current implementation has filtering points for only AF_INET (IPv4) or AF_INET6 (IPv6).
When you use the interface hook (PFIL_TYPE_IFNET), dlt is a pointer to a network interface structure. All hooks attached in this case will be in reference to the specified network interface.
Once you've selected the appropriate list head, you can use pfil_add_hook() to add a hook to the filter list. This function takes as arguments a filter hook function, an opaque pointer (which is passed into the user-supplied filter arg function), a flags value (described below), and the associated list head returned by pfil_head_get().
The flags value indicates when the hook function should be called and may be one of:
When a filter is invoked, the packet appears just as if it came off the wire. That is, all protocol fields are in network-byte order. The filter returns a nonzero value if the packet processing is to stop, or zero if the processing is to continue.
For interface hooks, the flags argument can be one of:
Here's an example of what a simple pfil hook would look like. It shows when an interface is attached or detached. Upon a detach (ifconfig iface destroy), the filter is unloaded.
#include <sys/types.h> #include <errno.h> #include <sys/param.h> #include <sys/conf.h> #include <sys/socket.h> #include <sys/mbuf.h> #include <net/if.h> #include <net/pfil.h> #include <netinet/in.h> #include <netinet/ip.h> #include "sys/io-pkt.h" #include "nw_datastruct.h" static int in_bytes = 0; static int out_bytes = 0; static int input_hook(void *arg, struct mbuf **m, struct ifnet *ifp, int dir) { in_bytes += (*m)->m_len; return 0; } static int output_hook(void *arg, struct mbuf **m, struct ifnet *ifp, int dir) { out_bytes += (*m)->m_len; return 0; } static int deinit_module(void); static int iface_hook(void *arg, struct mbuf **m, struct ifnet *ifp, int dir) { printf("Iface hook called ... "); if ( (int)m == PFIL_IFNET_ATTACH) { printf("Interface attached\n"); printf("%d bytes in, %d bytes out\n", in_bytes, out_bytes); } else if ((int)m == PFIL_IFNET_DETACH) { printf("Interface detached\n"); printf("%d bytes in, %d bytes out\n", in_bytes, out_bytes); deinit_module(); } return 0; } static int ifacecfg_hook(void *arg, struct mbuf **m, struct ifnet *ifp, int dir) { printf("Iface cfg hook called with 0x%08X\n", (int)(m)); return 0; } static int deinit_module(void) { struct pfil_head *pfh_inet; pfh_inet = pfil_head_get(PFIL_TYPE_AF, AF_INET); if (pfh_inet == NULL) { return ESRCH; } pfil_remove_hook(input_hook, NULL, PFIL_IN | PFIL_WAITOK, pfh_inet); pfil_remove_hook(output_hook, NULL, PFIL_OUT | PFIL_WAITOK, pfh_inet); pfh_inet = pfil_head_get(PFIL_TYPE_IFNET, 0); if (pfh_inet == NULL) { return ESRCH; } pfil_remove_hook(ifacecfg_hook, NULL, PFIL_IFNET, pfh_inet); pfil_remove_hook(iface_hook, NULL, PFIL_IFNET | PFIL_WAITOK, pfh_inet); printf("Unloaded pfil hook\n" ); return 0; } int pfil_entry(void *dll_hdl, struct _iopkt_self *iopkt, char *options) { struct pfil_head *pfh_inet; pfh_inet = pfil_head_get(PFIL_TYPE_AF, AF_INET); if (pfh_inet == NULL) { return ESRCH; } pfil_add_hook(input_hook, NULL, PFIL_IN | PFIL_WAITOK, pfh_inet); pfil_add_hook(output_hook, NULL, PFIL_OUT | PFIL_WAITOK, pfh_inet); pfh_inet = pfil_head_get(PFIL_TYPE_IFNET,0); if (pfh_inet == NULL) { return ESRCH; } pfil_add_hook(iface_hook, NULL, PFIL_IFNET, pfh_inet); pfil_add_hook(ifacecfg_hook, NULL, PFIL_IFADDR, pfh_inet); printf("Loaded pfil hook\n" ); return 0; } struct _iopkt_lsm_entry IOPKT_LSM_ENTRY_SYM(pfil) = IOPKT_LSM_ENTRY_SYM_INIT(pfil_entry);
You can use pfil hooks to implement an io-net filter; for more information, see the Migrating from io-net appendix in this guide.
The pfil interface is used by the Packet Filter (pf) to hook into the packet stream for implementing firewalls and NAT. This is a loadable module specific to either the v4 or v6 version of the stack (lsm-pf-v4.so or lsm-pf-v6.so). When loaded (e.g. mount -Tio-pkt /lib/dll/lsm-pf-v4.so), the module creates a pf pseudo-device.
The pf pseudo-device provides roughly the same functionality as ipfilter, another filtering and NAT suite that also uses the pfil hooks.
If you've downloaded the source code from Foundry27, you can find the portion of pf that loads into io-pkt in sys/dist/pf/net. The source for the accompanying utilities is located under dist/pf. For more information, see the following in the Utilities Reference:
To start pf, use the pfctl utility, which issues a DIOCSTART ioctl() command. This causes pf to call pf_pfil_attach(), which runs the necessary pfil attachment routines. The key routines after this are pf_test() and pf_test6(), which are called for IPv4 and IPv6 packets respectively. These functions test which packets should be sent, received, or dropped. The packet filter hooks, and therefore the whole of pf, are disabled with the DIOCSTOP ioctl() command, usually issued with pfctl -d.
For more information about using PF, see ftp://ftp3.usa.openbsd.org/pub/OpenBSD/doc/pf-faq.pdf in the OpenBSD documentation. Certain portions of the document (related to packet queueing, CARP and others) don't apply to our stack, but the general configuration information is relevant. This document covers both firewalling and NAT configurations that you can apply using PF.
The Berkeley Packet Filter (BPF) (in sys/net/bpf.c in the downloaded source) provides link-layer access to data available on the network through interfaces attached to the system. To use BPF, open a device node, /dev/bpf, and then issue ioctl() commands to control the operation of the device. A popular example of a tool using BPF is tcpdump (see the Utilities Reference).
The device /dev/bpf is a cloning device, meaning you can open it multiple times. It is in principle similar to a cloning interface, except BPF provides no network interface, only a method to open the same device multiple times.
To capture network traffic, you must attach a BPF device to an interface. The traffic on this interface is then passed to BPF for evaluation. To attach an interface to an open BPF device, use the BIOCSETIF ioctl() command. The interface is identified by passing a struct ifreq, which contains the interface name in ASCII encoding. This is used to find the interface from the kernel tables. BPF registers itself to the interface's struct ifnet field, if_bpf, to inform the system that it's interested in traffic on this particular interface. The listener can also pass a set of filtering rules to capture only certain packets, for example ones matching a given combination of host and port.
BPF captures packets by supplying a bpf_tap() tapping interface to link layer drivers, and by relying on the drivers to always pass packets to it. Drivers honor this request and commonly have code which, along both the input and output paths, does:
#if NBPFILTER > 0 if (ifp->if_bpf) bpf_mtap(ifp->if_bpf, m0); #endif
This passes the mbuf to the BPF for inspection. BPF inspects the data and decides if anyone listening to this particular interface is interested in it. The filter inspecting the data is highly optimized to minimize the time spent inspecting each packet. If the filter matches, the packet is copied to await being read from the device.
The BPF tapping feature and the interfaces provided by pfil provide similar services, but their functionality is disjoint. The BPF mtap wants to access packets right off the wire without any alteration and possibly copy them for further use. Callers linking into pfil want to modify and possibly drop packets. The pfil interface is more analogous to io-net's filter interface.
BPF has quite a rich and complex syntax (e.g. http://www.rawether.net/support/bpfhelp.htm) and is a standard interface that is used by a lot of networking software. It should be your interface of first choice when packet interception / transmission is required. It will also be a slightly lower performance interface given that it does operate across process boundaries with filtered packets being copied before being passed outside of the stack domain and into the application domain. The tcpdump and libpcap library operate using the BPF interface to intercept and display packet activity. For those of you currently using something like the nfm-nraw interface in io-net, BPF provides the equivalent functionality, with some extra complexity involved in setting things up, but with much more versatility in configuration.