Paul Kiddie

Creating a Netfilter kernel module which filters UDP packets

November 02, 2009

Last time we created a Netfilter kernel module which simply dropped all packets which gave the structure and functions that need to be implemented for a Netfilter module to work.

This time, we’ll extend the functionality to poke into the IP header, specifically the protocol field, in order to perform functionality specific to a packet type. I used an old article as the basis of this which can be found at http://www.linuxjournal.com/article/7184, but as you’ll see in this article, access to packet headers has changed significantly between Linux kernel 2.4 and 2.6. This tutorial is written for Ubuntu 8.04, with the development environment set up as specified in part 1, so for those of you with other distributions, YMMV.

Specifically, in kernel 2.6, access to network, transport and mac headers are now not through the fields nh, h and mac on the skbuff structure any more, but through new accessors: skb_network_header, skb_transport_header and skb_mac_header. To determine this, I had to look at the header files for the given data structure, in this case, skbuff. Having installed the linux-headers package earlier, we simply navigate to the installation folder, /lib/modules/2.6.24-23-generic/build/include/linux, and checking out skbuff.h we see that in the definition of the sk_buff data structure that nh, h and mac fields no longer exist. What we do see though are several accessor methods:

static inline unsigned char *skb_transport_header(const struct sk_buff *skb) { ... }

static inline unsigned char *skb_network_header(const struct sk_buff *skb) { ... }

static inline unsigned char *skb_mac_header(const struct sk_buff *skb) { ... }.

In this example, we will use the skb_network_header accessor method to access the ip packet’s protocol number. If the packet’s protocol number corresponds to UDP (17, from http://www.iana.org/assignments/protocol-numbers/), I log this fact in the /var/log/messages. Additional header files are included to cast the return values from skb_network_header and skb_transport_header to the appropriate iphdr and udphdr structures. Note in this example no operations are performe udp packet header we cast, but you can access any of the fields available to you — check out udp.h and ip.h for the available fields.

So, without further ado, the complete code:

//'Hello World' v2 netfilter hooks example
//For any packet, get the ip header and check the protocol field
//if the protocol number equal to UDP (17), log in var/log/messages
//default action of module to let all packets through
#include #include #include #include #include #include #include static struct nf\_hook\_ops nfho; //net filter hook option struct
struct sk\_buff \*sock\_buff;
struct udphdr \*udp\_header; //udp header struct (not used)
struct iphdr \*ip\_header; //ip header struct
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;
ip\_header = (struct iphdr \*)skb\_network\_header(sock\_buff); //grab network header using accessor
if(!sock\_buff) { return NF\_ACCEPT;}
if (ip\_header->protocol==17) {
udp\_header = (struct udphdr \*)skb\_transport\_header(sock\_buff); //grab transport header
printk(KERN\_INFO "got udp packet \\n"); //log we've got udp packet to /var/log/messages
return NF\_DROP;
}
return NF\_ACCEPT;
}
int init\_module()
{
nfho.hook = hook\_func;
nfho.hooknum = NF\_IP\_PRE\_ROUTING;
nfho.pf = PF\_INET;
nfho.priority = NF\_IP\_PRI\_FIRST;
nf\_register\_hook(&nfho);
return 0;
}
void cleanup\_module()
{
nf\_unregister\_hook(&nfho);
}

So whats changed from the example in part 2?

  • now using header files, skbuff.h, ip.h and udp.h, which allows us to cast the relevant data structures we need to poke around
  • new logic in hook_func to get the protocol number, and if this is equal to 17, to log that we have received a UDP packet to /var/log/messages

In the final part, we will seperate the kernel module into a user space daemon and a smaller kernel module. This brings many advantages, such as easier debugging and less likelihood of causing a kernel panic from the seperation of logic (such as from common programming mistakes leading to seg faults). To acheive this communication between the user space and kernel module, we will use the standard mechanism: netlink.


👋 I'm Paul Kiddie, a software engineer working in London. I'm currently working as a Principal Engineer at trainline.