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!



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?
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.
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”);
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?
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.
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.
It seem like I am connected to layer 3 and not layer 2 of the stack
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.
Okay, I see the problem. The setup for the hook function:
nfho.hook = hook_func;
nfho.hooknum = NF_PRE_ROUTING;
nfho.pf = PF_INET; < -------------- PF_INET
nfho.priority = NF_PRI_LAST;
The PF_INET states that we want IPv4. Our hook is passed the point in which we can view non IPv4 data. You will have to experiment with other options, to view other options:
http://lxr.linux.no/linux+v2.6.26.5/include/linux/socket.h#L158
I tried different options and none gave me all the packets.
I tried different options and none gave me the ARP packets either.
Is there any other way of capturing raw network packets.
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.
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).
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.
“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.