I have often been curious how a Linux computer can act as a firewall, looking at Smoothwall peaked my interest as to how the whole infrastructure is designed. Netfilter is the underlying kernel code that IPtables interacts with. When you make basic IPtables rules, match modules are referenced in kernel to actually do the parsing of the packets to deny or allow that packet. This is much more efficient than doing everything in userspace using libnet, and therefore you can avoid the cost of moving data between kernel and userspace.

In my mind the best way to understand how something works is to dive right in and mess around with it and see how the pieces fit together. I decided to write a basic filtering kernel module that uses the netfilter hooks to get the packet, then simply output certain packet types to syslog to ensure I am retrieving the packets correctly and that I can point to the correct offsets within a packet type. This seemed to be fairly straight-forward and thus I decided to add a /proc interface. This interface takes a binary value, if I pass in 0 then the kernel filtering module will accept the packet and allow it on its merry way, if I pass 1 to the module it will drop the packet. This binary toggle is very handy for testing and to do so is simply a echo 0 > /proc/skb_filter or echo the 1 to drop packets.

So without further ado, here is my source code for the kernel module. This module can be compiled against your kernel source, I wrote about this earlier for cross compilation here.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/netfilter.h>
#undef __KERNEL__
#include <linux/netfilter_ipv4.h>
#define __KERNEL__
#include <linux/netfilter_bridge.h>
#include <linux/skbuff.h>
#include <linux/udp.h>
#include <linux/ip.h>
#include <linux/icmp.h>
#include <net/ip.h>
#include <linux/proc_fs.h>  /* Necessary because we use the proc fs */
#include <asm/uaccess.h>	/* For copy_from_user  */
 
struct nf_hook_ops nfho;   //net filter hook option struct
struct sk_buff *sock_buff;
struct udphdr *udp_header;          // UDP header struct
struct iphdr *ip_header;            // IP header struct
struct icmphdr *icmp_header;		// ICMP Header
 
#define skb_filter_name "skb_filter"
 
static struct proc_dir_entry *skb_filter;
 
static int filter_value = 0;
 
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);
 
    if(!sock_buff)
        return NF_ACCEPT;
 
    if(ip_header->protocol == IPPROTO_UDP){
        udp_header = (struct udphdr *)(skb_transport_header(sock_buff) + ip_hdrlen(sock_buff));
        if(udp_header)
            printk(KERN_INFO "SRC: (%u.%u.%u.%u):%d --> DST: (%u.%u.%u.%u):%d\n",NIPQUAD(ip_header->saddr),ntohs(udp_header->source),NIPQUAD(ip_header->daddr),ntohs(udp_header->dest));
        else
            return NF_DROP;
    }
 
    if(ip_header->protocol == IPPROTO_ICMP){
        printk(KERN_INFO "---------- ICMP -------------\n");
        icmp_header = (struct icmphdr *)(skb_transport_header(sock_buff) + ip_hdrlen(sock_buff));
        if(icmp_header){		
            printk(KERN_INFO "SRC: (%u.%u.%u.%u) --> DST: (%u.%u.%u.%u)\n",NIPQUAD(ip_header->saddr),NIPQUAD(ip_header->daddr));
            printk(KERN_INFO "ICMP type: %d - ICMP code: %d\n",icmp_header->type, icmp_header->code);
        }else
            return NF_DROP;	
    }
 
    return filter_value == 0 ? NF_ACCEPT : NF_DROP;
}
 
int skb_read(char *page, char **start, off_t off,
            int count, int *eof, void *data)
{	
    int len;
 
    if(off > 0){
        *eof = 1;
        return 0;
    }
 
    if(count < sizeof(int)){
        *eof = 1;
        return -ENOSPC;
    }
 
    /* cpy to userspace */
    memcpy(page, &filter_value, sizeof(int));
    len = sizeof(int);
 
    return len;
}
 
int skb_write(struct file *file, const char *buffer, unsigned long len,
            void *data)
{
    unsigned char userData;
 
    if(len > PAGE_SIZE || len < 0){
        printk(KERN_INFO "SKB System: cannot allow space for data\n");
        return -ENOSPC;
    }
 
    /* write data to the buffer */
    if(copy_from_user(&userData, buffer, 1)){
        printk(KERN_INFO "SKB System: cannot copy data from userspace. OH NOES\n");
        return -EFAULT;
    }
 
    filter_value = simple_strtol(&userData, NULL, 10);
 
    return len;
}
 
int init_module()
{
    struct proc_dir_entry proc_root;
    int ret = 0;
 
    skb_filter = create_proc_entry( skb_filter_name, 0644, NULL);
 
    // If we cannot create the proc entry
    if(skb_filter == NULL){
        ret = -ENOMEM;
        if( skb_filter )
            remove_proc_entry( skb_filter_name, &proc_root);
 
        printk(KERN_INFO "SKB Filter: Could not allocate memory.\n");
        goto error;
 
    }else{		
        skb_filter->read_proc = skb_read;
        skb_filter->write_proc = skb_write;
        skb_filter->owner = THIS_MODULE;	
    }	
 
    // Netfilter hook information, specify where and when we get the SKB
    nfho.hook = hook_func;
    nfho.hooknum = NF_PRE_ROUTING;
    nfho.pf = PF_INET;
    nfho.priority = NF_PRI_LAST;
 
    nf_register_hook(&nfho);
 
    printk(KERN_INFO "Registering SK Parse Module\n");
 
error:
    return ret;
}
 
void cleanup_module()
{
    nf_unregister_hook(&nfho);
 
    if ( skb_filter )
        remove_proc_entry(skb_filter_name, NULL);
 
    printk(KERN_INFO "Unregistered the SK Parse Module\n");
}
 
MODULE_AUTHOR("Erik Schweigert");
MODULE_DESCRIPTION("SK Buff Parse Module");
MODULE_LICENSE("GPL");

The important pieces to take away from this are the functions hook_func which receives the skb from kernel, as well as doing the filtering and packet parsing, the other pieces are from line 128-131. This is how we tell the kernel module to hook into the netfilter framework. We say grab traffic when it hits the pre-routing table, and have the last priority, meaning allow all other match modules to do their filtering before calling this function. If you wanted to hook into other areas, such as a bridging code you could use NF_BR_PRE_ROUTING. Note the ‘BR’ addition. Happy coding!

16 Responses to Netfilter Hook: Basic Packet Filtering in Kernel

  1. Shabbir says:

    Your article is very helpful. However is there any way to get a hold of more than just the IP messages. The filter passes up network message type 0×0800, I would like to access ARP and other messages also. Is there any way?

    • erik says:

      Thanks. My example shows IP, ICMP, UDP packet information. If you want to look at the Ethernet layer and ARP specifically there are ways to get to differing layers of the packet, if you refer to: http://lxr.linux.no/linux+v2.6.30/include/linux/skbuff.h#L1249 for example. You will see that there is a set of function to point to differing layers of the packet. So you would probably want to use the skb_mac_header(const struct sk_buff *skb); function to retrieve the MAC layer, then verify that the ethertype == 0×0806 ( for ARP ). Then cast your pointer to the ARP header and voila.

  2. Shabbir says:

    I used the below code (for example) in my filter method, but all I get are IPV 4 messages.
    dataP = skb_mac_header(skb);
    msgType = ntohs((unsigned short)dataP[12]);

    if (msgType==0×0806)
    printk(KERN_INFO ” Got the ARP message\n”);
    if (msgType==0×0800)
    printk(KERN_INFO ” Internet Protocol, Version 4 (IPv4)\n”);

    • erik says:

      Are you certain that you you are sending ARP packets to your device? You are clearly receiving the ethernet frame as your ethertype is shown to be IPv4… Do you have arptables or ebtables present on your device?

      • Shabbir says:

        I am sending ping messages from the host computer out and the target is sending responses back. I am using a third computer with wireshark and am able to see the requests and responses; I can also verify the MAC and IP address on wireshark for both the host and the target. Also I have used a (user space app) for packet filter on the host and I do see the ARP message coming all the way up; however the Kernel space module does not work. I have not run the user space app and the kernel driver at the same time though.
        I am using Debian 5.0.8 (kernel 2.6.27) as I do need to do this for Kernel version 2.6.26.

        • erik says:

          Please run them at the same time. If you are doing a constant ping then you will not see the ARP’s. As long as the MAC address already exists in the ARP table there will be no ARP’s sent. I would suggest clearing the ARP table, then do a I would instead use ARPing and just ARPing the device, then you are guaranteed an ARP is sent every time.

  3. Shabbir says:

    It seem like I am connected to layer 3 and not layer 2 of the stack

    • Shabbir says:

      I cannot get arping to install and work the kernel version I am using is too old and the packages are not there. However I did run the packet capture (user space) app with the kernel level module and I was able to see ARP messages in the user app but none in the kernel module.

  4. Shabbir says:

    Is there any other way of capturing raw network packets.

    • erik says:

      Do you have to do this in kernel? If you want to have a userspace process doing this…you could write something with libpcap: http://commons.oreilly.com/wiki/index.php/Network_Security_Tools/Modifying_and_Hacking_Security_Tools/Writing_Network_Sniffers

      Although I recall you saying you had a process in userspace to listen. The other option in kernel would be to not use the Netfilter hooks and actually make a module to read directly from the skb_buff.

      • Shabbir says:

        I need to do this in Kernel space. What I need is to capture all network traffic on the wire, i.e. for my MAC address and other. And then process and route the traffic for a specific MAC to a separately allocated/registered net device (alloc_netdev, register_netdev).

        • erik says:

          Ah okay. It might be a good idea to pose this question on the Netfilter forums for some suggestions. I am sure it is do-able, I just don’t have time at the moment to delve deeply into this problem.

      • Shabbir says:

        “make a module to read directly from the skb_buff”, that sounds correct, but looking at the netdevice and device, I don’t see how I could wait/some thing until a message came in and then get a hold of the sk_buff for the received message.

Leave a Reply

Your email address will not be published. Required fields are marked *


one + = 2

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>