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!



Latest Comments