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!