Introduction To Network Filters – Linux

One of the great advantages of Linux is its networking features. Many networking products such as routers, switches  are based on Embedded Linux operating system.
Linux is open source so you can see and change the code (under the licensing limitations) and fit it to your needs.

Network filtering is a great infrastructure in Linux kernel, gives us the ability to filter and manipulate packets inside the network stack. You can build a network filter for firewall filtering , log the packets, encrypt/decrypt and more.

We can build the network filter as a kernel module and load/unload it functionality dynamically.

The post assumes you know how to write and load a simple kernel module , if not start with this post

Simple Example:

#include <linux/module.h>
#include <linux/skbuff.h>          
#include <linux/init.h>
#include <net/sock.h>
#include <linux/inet.h>
#include <linux/ip.h>             
#include <linux/kernel.h> 
#include <linux/netfilter.h>
#include <uapi/linux/netfilter_ipv4.h> 

 
unsigned int main_hook(unsigned int hooknum,
                       struct sk_buff *skb,
                       const struct net_device *in,
                       const struct net_device *out,
                       int (*okfn)(struct sk_buff*))
{
    struct iphdr *iph;
 
    iph = ip_hdr(skb);
    if(iph->saddr == in_aton("192.168.0.2"))
    { 
        return NF_DROP; 
    }
    return NF_ACCEPT;
}

static struct nf_hook_ops netfops;                    

int __init my_module_init(void)
{
    netfops.hook              =       main_hook;
    netfops.pf                =       PF_INET;        
    netfops.hooknum           =       0;
    netfops.priority          =       NF_IP_PRI_FIRST;
    nf_register_hook(&netfops);
 
    return 0;
}

void  my_module_exit(void) 
{ 
    nf_unregister_hook(&netfops); 
}
 
module_init(my_module_init);
module_exit(my_module_exit);
MODULE_LICENSE("GPL");

This is a simple filter example that drops all the packets from IP 192.168.0.2

The module init function register the filter in the input path before routing. To register a filter we need to specify:

  • The hook function – main_hook
  • The socket family (IPv4) – PF_INET
  • The hook type – input path before routing – 0
  • The hook priority – in case we register more than one hook in the same place – NP_IP_PRI_FIRST

The parameters depend on the family – in IPv4 there are 5 points you can register a filter:

  • Input path – before routing
  • Input path – after routing
  • Output path – before routing
  • Output path – after routing
  • Forwarding from one network adapter to another

To build the module we need to configure the kernel with net filter support:

Build the module and test it:

  • Run ping command from IP: 192.168.0.2 – should get correct reply – keep it running
  • Insert the module to the local kernel (insmod ./filter.ko)
  • You shouldn’t  see the reply now
  • Remove the module – again you can see correct reply

 

Each packet is passing through the hook function main_hook.  Lets look inside in more details:

unsigned int main_hook(unsigned int hooknum,
                       struct sk_buff *skb,
                       const struct net_device *in,
                       const struct net_device *out,
                       int (*okfn)(struct sk_buff*))
{
    struct iphdr *iph;
 
    iph = ip_hdr(skb);
    if(iph->saddr == in_aton("192.168.0.2"))
    { 
        return NF_DROP; 
    }
    return NF_ACCEPT;
}

The first parameter is the hook number – we can use the same function for more than one hook for example, if we want to log all the messages in the input and output path before routing

The second parameter is the socket buffer – data structure representing the packet

Next we have tow network adapters – in for input, out for output and both in case of forwarding

The last parameter is a function pointer to the last filter – calling it will cancel all other filters next to this one (this is why we have priorities)

The implementation

We start with setting a pointer to the packet IP header. Now we can access the source and destination IP and all other IP header fields.

Then we check the source address and drop the packet in case it is from 192.168.0.2. In any other case we accept the packet and the next filter will be called

The hook function must return one of the following values:

 

  • NF_DROP – drop the packet – and free the resources
  • NF_ACCEPT – accept the packet – continue to the next filter
  • NF_STOLEN – do not continue processing the packet but don’t free it – it is the my responsibility to free it
  • NF_QUEUE – queue the packet for user space handling
  • NF_REPEAT – call this filter again

User Space Handling

If we return NF_QUEUE , the control is delivered to user space. To implement it in user space we should use the libraries :nfnetlink , netfilter_queue

I will cover the libraries in a future post but for this post this is a very simple example only to understand the concept(without error checks and complex handling):

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <netinet/in.h>
#include <linux/types.h>
#include <linux/netfilter.h>		
#include <libnetfilter_queue/libnetfilter_queue.h>


static int filter_fn(struct nfq_q_handle *qh, struct nfgenmsg *nfmsg, struct nfq_data *nfa, void *data)
{
	int id;
        struct nfqnl_msg_packet_hdr *ph;
	ph = nfq_get_msg_packet_hdr(nfa);	
	id = ntohl(ph->packet_id);
	printf("filter function id=%d\n",id);
	if ( id % 3 == 0)
		return nfq_set_verdict(qh, id, NF_DROP, 0, NULL);
	return nfq_set_verdict(qh, id, NF_ACCEPT, 0, NULL);
}

int main(int argc, char **argv)
{
	struct nfq_handle *h;
	struct nfq_q_handle *qh;
	int fd;
	int rv;
	char buf[4096];

	h = nfq_open();

	nfq_bind_pf(h, AF_INET);

	qh = nfq_create_queue(h,  0, filter_fn, NULL);
	nfq_set_mode(qh, NFQNL_COPY_PACKET, 0xffff);

	fd = nfq_fd(h);

	while ((rv = recv(fd, buf, sizeof(buf), 0)))
	{
		printf("received\n");
		nfq_handle_packet(h, buf, rv);
	}

	nfq_destroy_queue(qh);

	nfq_close(h);

	exit(0);
}

In the main function we open  the connection , create the queue and register the function filter_fn as our filter, then start a receive loop. In the filter function we can drop or accept the packet

Change the kernel code to return NF_QUEUE:

#include <linux/module.h>
#include <linux/skbuff.h>          
#include <linux/init.h>
#include <net/sock.h>
#include <linux/inet.h>
#include <linux/ip.h>             
#include <linux/kernel.h> 
#include <linux/netfilter.h>
#include <uapi/linux/netfilter_ipv4.h> 

 
unsigned int main_hook(unsigned int hooknum,
                       struct sk_buff *skb,
                       const struct net_device *in,
                       const struct net_device *out,
                       int (*okfn)(struct sk_buff*))
{
    struct iphdr *iph;
 
    iph = ip_hdr(skb);
    if(iph->saddr == in_aton("192.168.0.2"))
    { 
        return NF_QUEUE; 
    }
    return NF_ACCEPT;
}

static struct nf_hook_ops netfops;                    

int __init my_module_init(void)
{
    netfops.hook              =       main_hook;
    netfops.pf                =       PF_INET;        
    netfops.hooknum           =       0;
    netfops.priority          =       NF_IP_PRI_FIRST;
    nf_register_hook(&netfops);
 
    return 0;
}
void  my_module_exit(void) 
{ 
    nf_unregister_hook(&netfops); 
}
 
module_init(my_module_init);
module_exit(my_module_exit);
MODULE_LICENSE("GPL");

to build the user space application:

# gcc -o app ./simple.c -lnfnetlink -lnetfilter_queue

To test the code , build and insert the module and then run the app

$ sudo insmod ./netfildrv.ko 
$ sudo ./app 
received
filter function id=1
received
filter function id=2
received
filter function id=3
received
...

 

Tagged , ,

1 thought on “Introduction To Network Filters – Linux

  1. Can I add my IOCTL here in this driver? Suppose my user mode application want to send the IP to be blocked.

Comments are closed.