Building a Stateless Firewall Using Netfilter in Linux


The Linux kernel has a Netfilter framework that allows us to perform various networking-related operations. This article is a simple tutorial on how to build firewall modules using Netfilter.

The Netfilter framework is a collection of hooks or handlers in the Linux kernel, which helps to filter or capture socket buffers. We can implement packet filtering at the input or output, or even at the forwarding path of a network packet. Iptables is a popular tool that is implemented using the Netfilter framework.

As shown in Figure 1, a packet can be filtered or processed at five different stages. So there are five possible hooks where programmers can attach a customised handler and implement their own firewall. These hooks are (only for Linux kernel 5.10 or above):

Processing stages
Figure 1: Processing stages
  • NF_INET_PRE_ROUTING: This hook is called once a network packet enters the stack, before any routing decision takes place.
  • NF_INET_LOCAL_IN: After routing, if it is found that the packet is for a local network, this hook is triggered.
  • NF_INET_FORWARD: This hook is called if, after routing, it is found that the packet is for another networking domain, and not for a local process.
  • NF_INET_LOCAL_OUT: This is called in case the packet is sent from a local process using send or sendto (POSIX calls).
  • NF_INET_POST_ROUTING: This handler is called just before any local or forwarded packet is about to hit the interface after handling by the entire stack is over.

I have written an example code to show how to build a firewall using the Netfilter framework. I have used Linux kernel 5.10. In this example I have blocked all ICMP and HTTP/HTTPS packet sending from a local process. This program has to run from kernel space, and not in user space. So a kernel module has been developed.

The entire code is available at You can download the code files to check and understand the implementation.

Packet filtering

We have created a firewall.c file that is available in the download from the above link. In firewall.c, the netfilter_ops is a struct nf_hook_ops variable. In the init-module section, netfilter_ops is initialised with the following:

netfilter_ops.hook = main_hook; //the handler function = PF_INET; //tells the Protocol is IPv4
netfilter_ops.hooknum = NF_INET_POST_ROUTING; //process at post-routing stage
netfilter_ops.priority = NF_IP_PRI_FIRST; //priority

Given below is the snippet from firewall.c:

static struct nf_hook_ops netfilter_ops;

/* This function is called by hook. */

static unsigned int main_hook(void *priv, struct sk_buff *skb, const struct nf_hook_state *state)
//struct udphdr *udp_header;
int dstPort;
struct tcphdr *hdr;
struct iphdr *ip_header = (struct iphdr *)skb_network_header(skb);

if (ip_header->protocol == IPPROTO_ICMP) {
// udp_header = (struct udphdr *)skb_transport_header(skb);
printk(KERN_INFO “Drop icmp packet.\n”);
return NF_DROP;

if (ip_header->protocol == IPPROTO_TCP) {
hdr = (struct tcphdr *) skb_transport_header(skb);
dstPort = ntohs(hdr->dest);
if((dstPort==443) || (dstPort==80)) /*drop https and http*/ {
printk(“Drop HTTPS/HTTP packet\n”);
return NF_DROP;
return NF_ACCEPT;

main_hook is the name of the handler function for the NF_INET_POST_ROUTING hook. In this function, any packet (whether forwarded or sent from the local interface with ICMP protocol) will be dropped using the first ‘if’ statement, where it checks for the IP_PROTOCOL in the IPv4 header of the socket buffer. As the hook is returning NF_DROP, it tells the kernel driver not to proceed with the packet.

The second ‘if’ statement checks whether any TCP packet with destination port number 443 (for HTTPS) or with port number 80 (for HTTP) will be dropped.

Now we can use the Make statement to compile this module (filewall.c). Figure 2 shows that we can ping an IP of a local network device with IP address successfully just before we have implemented firewall.c.

Successful ping
Figure 2: Successful ping

But after insertion of the module, ping starts to fail (as shown in Figure 3).

Unsuccessful ping
Figure 3: Unsuccessful ping

This indicates that in the POST_ROUTING hook, the packet is dropped and not sent to wire. A log from dmesg command is shown in Figure 4 and describes the functionality of the module.

Output of dmesg
Figure 4: Output of dmesg

In this example you can also see the use of NF_DROP or NF_ACCEPT return values. The meaning of these values is self-explanatory. But there are a few more return values as well, which include:

  • NF_REPEAT: Repeat the hook function.
  • NF_QUEUE: Queue the packet for user space processing.  To implement this in user space, we need to use the libraries nfnetlink and netfilter_queue.
  • NF_STOLEN: Further processing of the packet and freeing memory is up to your module.

Packet mangling

Netfilter can also be used for packet mangling or modification. In the same URL (, you can find one more file Mangle.c. The corresponding makefile is Makefile_mangle.

In this hook, the ICMP ping packet source IP is modified just before sending out the packet. You can see the code below:

/* This function to be called by hook. */

static unsigned int main_hook(void *priv, struct sk_buff *skb, const struct nf_hook_state *state)

struct iphdr *ip_header = (struct iphdr *)skb_network_header(skb);

if (ip_header->protocol == IPPROTO_ICMP) {
printk(KERN_INFO “Mangle icmp packet. %x\n”,ip_header->saddr);
ip_header->saddr = 0xd01da8c0;

return NF_ACCEPT;

The output of Wireshark capture shown in Figure 5 depicts that before loading this module, the ping request to destination IP goes from the original IP of the interface, i.e., But after loading the module, the ping request goes from the modified IP of the interface, i.e., However, the physical interface IP is unchanged.

Output of Wireshark
Figure 5: Output of Wireshark

Compiling the code

To compile and test the downloaded module, just use:

$ make

$ sudo insmod firewall

To remove it, use the following command:

$ sudo rmmod firewall

This article is a simple tutorial on building firewall modules using Netfilter. You can also do packet capturing by simply using the NF_INET_PRE_ROUTING hook number in this example. You can even use this example to simulate a man-in-the-middle attack for your devices, to test the cybersecurity.


Please enter your comment!
Please enter your name here