An IP filter is used to filter inbound or outbound IP traffic. It resides within the IP protocol stack, as shown in Figure 5-1. For inbound traffic, it is called after an IP packet has been reassembled. For outbound traffic, it is called just prior to IP fragmentation. If IPSec processing is required for a given packet, the filter is called twice—immediately before and after any IPSec processing.
The Anatomy of an IP Filter
There are two basic categories of IP filters: IPv4 filters and IPv6 filters. With the exception of their handling of addresses, they are essentially equivalent. The same basic data structure,
ipf_filter, is used to describe both.
The data structure contains five fields:
The first field,
cookie, can contain arbitrary data. Your KEXT assigns it a value when it attaches the filter to the IP stack. The IP stack then passes that value as an argument whenever the networking stack calls any function in your KEXT. This allows a single filter to have multiple behaviors depending on where it is attached by testing values stored in the cookie.
The structure referenced by this field can be arbitrarily defined by your KEXT. As far as the kernel is concerned, it is essentially a void pointer. This mechanism is commonly used to store information about memory allocations associated with a particular filter instance.
The second field,
name, is the name of your filter. This is used only for debugging purposes, but should always be filled in. It should contain either the identifier for the KEXT or something similar, for ease of identification.
The remaining fields,
ipf_detach, are pointers to callback functions in your KEXT. Those callbacks are called whenever your filter is asked to handle inbound packets, handle outbound packets, or detach, respectively.
ipf_detach function pointers are described in their data type declarations—respectively,
ipf_input_func callback will be called as soon as a packet has been identified as being a IP packet and reassembled. Similarly, your
ipf_output_func function will be called just prior to sending it to the data link interface layer (where it may be further processed by interface filters). However, in some cases, such as IPSec encapsulation, your IP filter will be called once as each layer of encapsulation is decoded.
A registered filter is identified by the opaque type
ipfilter_t. This is used later when you unregister the filter.
IP Filter Gotchas
There are several quirks specific to modifying traffic in an IP filter. Some of these include:
- Reinjecting modified traffic
If your filter modifies the protocol of inbound traffic or the destination of outbound traffic, the packet may be misdelivered as a result of caching in the IP stack.
To prevent this problem, your filter must use
ipf_inject_output, as appropriate. Your
ipf_output_funccallback should then swallow the previous version by returning
- Packet Fragmentation
IP filters only receive reassembled packets. It is not possible to filter on packet fragments.
- Filter Loops
It is possible to create filter loops in which one filter changes a value and reinjects the packet, which causes a second filter to change the value back and reinject it in an endless loop.
To reduce the likelihood of such a loop, when reinjecting packets, your filter should always specify itself as the