http://fcns.eu/2010/02/netfilter-hooks/
keyword:write new netfilter module
Note: This article was inspired by the lack of updated documentation on how to write proper netfilter kernel modules. At the time I’m writing this article, the latest stable release was 2.6.32.8. I am also assuming you are familiar with how LKMs (Loadable Kernel Modules) work. If you are not, then you might want to check this article first: tldp.org/HOWTO/Module-HOWTO/
In this article (Part 1) I will present how to create a simple Linux kernel module that implements a netfilter hook for a generic transport protocol (not one of the usual ones).
In Part 2, I plan to connect the module to the iptables rules generated on the userspace side.
What is netfilter?
For those of you who are not familiar with netfilter, all I can say is that it is actually a framework for packet mangling, outside the normal Berkeley socket interface. It’s the engine behind iptables – the popular firewall solution for Linux. It has four parts. Firstly, each protocol defines “hooks” (IPv4 defines 5) which are well-defined points in a packet’s traversal of that protocol stack. At each of these points, the protocol will call the netfilter framework with the packet and the hook number.
Secondly, parts of the kernel can register to listen to the different hooks for each protocol. So when a packet is passed to the netfilter framework, it checks to see if anyone has registered for that protocol and hook; if so, they each get a chance to examine (and possibly alter) the packet in order, then discard the packet (NF_DROP), allow it to pass (NF_ACCEPT), tell netfilter to forget about the packet (NF_STOLEN), or ask netfilter to queue the packet for userspace (NF_QUEUE).
The third part is that packets that have been queued are collected (by the ip_queue driver) for sending to userspace; these packets are handled asynchronously.
Netfilter Hooks in the Linux Kernel.
Netfilter modules can be loaded into the Linux kernel at runtime, so we need hooks in the actual routing code to enable dynamic hooking of functions. An integer identifier is allocated to each of these netfilter hooks. The identifiers of all hooks for each supported protocol are defined in the protocol-specific header file (<linux/netfilter_ipv4.h> or <linux/netfilter_ipv6.h>). The following five hooks are defined for IP Version 4 in <linux/netfilter_ipv4.h>:
- NF_IP_PRE_ROUTING (default value is 0): incoming packets pass this hook in the ip_rcv()(linux/net/ipv4/ip_input.c) function before they are processed by the routing code
- NF_IP_LOCAL_IN (default value is 1): all incoming packets addressed to the local computer pass this hook in the function ip_local_deliver()
- NF_IP_FORWARD (default value is 2): all incoming packets not addressed to the local computer pass this hook in the function ip_forward()
- NF_IP_LOCAL_OUT (default value is 3): all outgoing packets created in the local computer pass this hook in the function ip_build_and_send_pkt()
- NF_IP_POST_ROUTING (default value is 4): this hook in the ip_finish_output() function represents the last chance to access all outgoing (forwarded or locally created) packets before they leave the computer over a network device
Calling the NF_HOOK macro causes the routing code to process the filter functions hooked into a netfilter hook. More specifically, the NF_HOOK macro has the following arguments:
- pf (protocol family): This is the identifier of the protocol family: PF_INET for IP Version 4, PF_INET6for IP Version 6.
- hook: This is the hook identifier. All valid identifiers for each protocol family are defined in a header file (e.g., <linux/netfilter_ipv4.h>).
- skb: This is a pointer to the sk_buff structure with the packet to be handled.
- indev (input device): This is a pointer to the net_device structure of the network device that received the packet. It is set to NULL in the above example, because the packet is an outgoing packet.
- outdev (output device): This is a pointer to the net_device structure of the network device that should be used by the packet to leave the local computer. In the above example, the device used has to be determined first by use of the routing table (rt).
- okfn() (okay function): This function is invoked when all filter functions registered with this hook returned NF_ACCEPT, thereby okaying the packet’s transit.
Registering and Unregistering Packet-Filter Functions.
The packet-filter functions that are actually hooked into the netfilter hooks are so-called hook functions of the type nf_hookfn. The signature of a hook function is defined in <linux/netfilter.h> as follows:
typedef unsigned int nf_hookfn(unsigned int hooknum,
struct sk_buff **skb,
const struct net_device *in,
const struct net_device *out,
int (*okfn) (struct sk_buff *));
The return value of a packet-filter function specifies what should happen to the packet. It is of the typeunsigned int and can take any of the following values, defined in <linux/netfilter.h>:
- NF_DROP (default value is 0): The active rules list processing is stopped, and the packet is dropped
- NF_ACCEPT (default value is 1): The packet is passed to the next packet filter function in the rules list. Once the end of the list has been reached, the packet is released by okfn() for further processing
- NF_STOLEN (default value is 2): The packet filter function withholds the packet for further processing, so that the active rules list processing is stopped. In contrast to NF_DROP, however, the packet does not have to be explicitly dropped
- NF_QUEUE (default value is 3): The function nf_queue() (net/core/netfilter.c) puts the packet in a queue from which it can be removed and processed (e.g., by a user space program). Subsequently, nf_reinject() has to be invoked to return the packet to the Linux kernel for further processing by netfilter
- NF_REPEAT (default value is 4): In contrast to NF_ACCEPT, rather than a continuation of processing at the next packet-filter function, the current filter function is invoked again
nf_register_hook(), nf_unregister_hook() registers and unregisters a packet-filter function with the Linux kernel. The parameter passed is a nf_hook_ops structure, which includes all information required.
To register a new packet-filter function with the Linux kernel, we first have to initialize a structure of the type nf_hook_ops (linux/netfilter.h) with all of the management information required:
struct nf_hook_ops
{
struct list_head list;
/* User fills in from here down. */
nf_hookfn *hook;
int pf;
int hooknum;
/* Hooks are ordered in ascending priority. */
int priority;
};
The fields of this structure have the following meaning:
- list: the nf_hook_ops structures are maintained in a linked list within the Linux kernel
- hook(): this is a pointer to the actual packet-filter function of the type nf_hookfn
- pf: the protocol family identifier (e.g., PF_INET or PF_INET6)
- hooknum: the hook identifier (e.g., NF_IP_INPUT) are used to determine the hook for this packet-filter function
- priority: packet-filter functions within the rules list of a hook are sorted by the priority field in ascending order, so that they will be invoked in this order when a packet transits
Now that you’ve got the main idea on how netfilter hooks work, I suggest we check an example. The following code belongs to a personal project involving the design of a little protocol. The source code for the .c file can be found here, while the code for the .h file is here.
The basic idea behind this generic protocol is that it uses a header made of a port number and a type of message, both defined in the header file. My module is supposed to identify the protocol (based on the protocol field of the IP header) and process only packets arriving for this protocol. Once the match is made, the next thing to do is to recover the port number and type of message from our protocol’s header. Once we have all data, we can call our callback function to do whatever we want next.
There have been a couple of important changes since 2.6.22 (which most guides are written for):
- the params of the hook function have changed slightly: struct sk_buff *skb, instead of struct sk_buff **skb
- skb_network_header(const struct sk_buff *skb), skb_transport_header(const struct sk_buff *skb) and skb_mac_header(const struct sk_buff *skb)accessors – instead of nh, h and mac header fields of the sk_buff structure (/include/linux/skbuff.h)
One more important aspect you need to take into consideration is that both skb_network_header andskb_transport_header point to the same memory address. This means that if you want to access the transport header information, you will need to add (skip) to the location of the header in the memory by adding the IP header length (e.g.. rpmp_header = (struct rpmphdr *)(skb_transport_header(sock_buff)+sizeof(struct iphdr));). I don’t know why this happens, but it happens.
Anyway, here is the code:
Source code for my_module.h:
#include <linux/types.h> #define DRIVER_AUTHOR "Andrei SAMBRA <andrei.sambra@telecom-sudparis.eu>" #define DRIVER_DESC "Generic Protocol" #define IPPROTO_RPMP 150 struct rpmphdr { __be16 dport; __u16 type; };
Source code for my_module.c:
/* * Author: andrei.sambra@telecom-sudparis.eu * GPLv3 License applies to this code. * * */ #include <linux/module.h> /* Needed by all modules */ #include <linux/kernel.h> /* Needed for KERN_INFO */ #include <linux/init.h> /* Needed for the macros */ #include <linux/skbuff.h> #include <linux/netfilter.h> #include <linux/netfilter_ipv4.h> #include <linux/ip.h> #include <linux/udp.h> #include "rpmp.h" /* Needed for our structures */ #define DEBUG 0 struct sk_buff *sock_buff; struct iphdr *ip_header; struct udphdr *udp_header; struct rpmphdr *rpmp_header; static struct nf_hook_ops nfho; static unsigned int hook_func(unsigned int hooknum, struct sk_buff *skb, const struct net_device *in, const struct net_device *out, int (*okfn)(struct sk_buff *)) { sock_buff = skb; if (!sock_buff) { return NF_ACCEPT; } else { ip_header = (struct iphdr *)skb_network_header(sock_buff); if (!ip_header) { return NF_ACCEPT; } else { if (ip_header->protocol == IPPROTO_RPMP) { rpmp_header = (struct rpmphdr*)(skb_transport_header(sock_buff)+sizeof(struct iphdr)); #if DEBUG > 0 printk(KERN_INFO "[RPMP] DEBUG: th: 0p%p\n", rpmp_header); printk(KERN_INFO "[RPMP] DEBUG: nh: 0p%p\n",skb_network_header(sock_buff)); printk(KERN_INFO "[RPMP] DEBUG: mh: 0p%p\n",skb_mac_header(sock_buff)); printk(KERN_INFO "[RPMP] DEBUG: Length: rpmp_header=%d | dport=%d | type=%d.\n", sizeof(rpmp_header), sizeof(rpmp_header->dport), sizeof(rpmp_header->type)); printk(KERN_INFO "[RPMP] DEBUG: From IP address: %d.%d.%d.%dn", ip_header-saddr & 0x000000FF, (ip_header->saddr & 0x0000FF00) >> 8, (ip_header->saddr & 0x00FF0000) >> 16, (ip_header->saddr & 0xFF000000) >> 24); #endif printk(KERN_INFO "[RPMP] Got a RPMP packet for port=%d (type:%d).\n", ntohs(rpmp_header->dport), ntohs(rpmp_header->type)); /* Callback function here*/ return NF_DROP; } else { return NF_ACCEPT; } } } } static int __init init_main(void) { nfho.hook = hook_func; nfho.hooknum = 1; nfho.pf = PF_INET; nfho.priority = NF_IP_PRI_FIRST; nf_register_hook(&nfho); #if DEBUG > 0 printk(KERN_INFO "[RPMP] Successfully inserted protocol module into kernel.\n"); #endif return 0; } static void __exit cleanup_main(void) { nf_unregister_hook(&nfho); #if DEBUG > 0 printk(KERN_INFO "[RPMP] Successfully unloaded protocol module.\n"); #endif } module_init(init_main); module_exit(cleanup_main); /* * Declaring code as GPL. */ MODULE_LICENSE("GPLv3"); MODULE_AUTHOR(DRIVER_AUTHOR); /* Who wrote this module? */ MODULE_DESCRIPTION(DRIVER_DESC); /* What does this module do */