Using Netfilter framework to write kernel module under Linux

Posted by vanzkee on Sun, 02 Jan 2022 18:15:38 +0100

Last article Analysis of Linux netfilter hook source code From the perspective of kernel source code, we analyze how hook hooks are executed under the Linux Netfilter framework. This time, we write a simple example code to introduce in detail how to write kernel modules using the Netfilter framework.

In the previous article, we analyzed the data structures used in the Netfilter framework hook from the data structure level. Below, I drew a diagram, starting with the specific examples in the source code:

There is a global variable in the kernel, net_namespace_list linked list. All network namespaces in the system are hung on this linked list. The system default network namespace is init_net, when we add a hook, we hang the new hook on its corresponding linked list.

Let's start writing a simple kernel module based on the netfilter framework.

catalogue

1. Declare a hook function

2. First, you need to define an nf_hook_ops

3. Register this nf_hook_ops

4. Implementation of hook function

5. Sign out of custom hook

6. Write Makefile

7. Required header file

8. Compile and insert modules

9. Unloading module

1. Declare a hook function

unsigned int packet_filter(unsigned int hooknum, struct sk_buff *skb,
               const struct net_device *in, const struct net_device *out,
               int (*okfn)(struct sk_buff *));

2. First, you need to define an nf_hook_ops

① We process network packets at the ip layer (network layer), where. pf = NFPROTO_INET;

② The hook point is on the preouting chain, here hooknum = NF_INET_PRE_ROUTING;

③ hook functions are executed on this chain with the highest priority, i.e priority = NF_IP_PRI_FIRST;

④ Set the hook function to the function we defined earlier, that is hook = (nf_hookfn *)packet_filter.

static struct nf_hook_ops packet_simple_nf_opt = {
        .hook = (nf_hookfn *)packet_filter,
        .pf = NFPROTO_INET,
        .hooknum = NF_INET_PRE_ROUTING,
        .priority = NF_IP_PRI_FIRST,
};

3. Register this nf_hook_ops

Register the NF in the init function of the kernel module_ hook_ ops

static int simple_nf_init(void)
{
#if LINUX_VERSION_CODE >= KERNEL_VERSION(4,3,0)
        nf_register_net_hook(&init_net, &packet_simple_nf_opt);
#else
        nf_register_hook(&packet_simple_nf_opt);
#endif

        printk("[simple_nf_test] network hooks success.\n");

        return 0;

}

4. Implementation of hook function

unsigned int packet_filter(unsigned int hooknum, struct sk_buff *skb,
                                const struct net_device *in, const struct net_device *out,
                                int (*okfn)(struct sk_buff *))
{
        int ret = NF_DROP;
        struct iphdr *iph;
        struct tcphdr *tcph;
        struct udphdr *udph;

        printk("[simple_nf_test] %s. start.....\n", __func__);

        if(skb == NULL)
                return NF_ACCEPT;

        iph = ip_hdr(skb);
        if(iph == NULL)
                return NF_ACCEPT;

        printk("[simple_nf_test] %s. protocol is [%d].\n", __func__, iph->protocol);
        printk("[simple_nf_test] %s. source addr is [%pI4].\n", __func__, &iph->saddr);
        printk("[simple_nf_test] %s. dest addr is [%pI4].\n", __func__, &iph->daddr);

        switch(iph->protocol)
        {
                case IPPROTO_TCP:
                        tcph = (struct tcphdr *)(skb->data + (iph->ihl * 4));
                        printk("[simple_nf_test] %s. tcp source port is [%d].\n", __func__, ntohs(tcph->source));
                        printk("[simple_nf_test] %s. tcp dest port is [%d].\n", __func__, ntohs(tcph->dest));
                        break;

                case IPPROTO_UDP:
                        udph = (struct udphdr *)(skb->data + (iph->ihl * 4));
                        printk("[simple_nf_test] %s. udp source port is [%d].\n", __func__, ntohs(udph->source));
                        printk("[simple_nf_test] %s. udp dest port is [%d].\n", __func__, ntohs(udph->source));
                        break;

                default :
                        return NF_ACCEPT;
        }

        printk("[simple_nf_test] %s. end.\n\n\n", __func__);
        return NF_ACCEPT;

}

In the hook function, I only print the source, destination ip and port information.

5. Sign out of custom hook

To log off the previously customized hook when the kernel module exits:

static void simple_nf_exit(void)
{
#if LINUX_VERSION_CODE >= KERNEL_VERSION(4,3,0)
        nf_unregister_net_hook(&init_net, &packet_simple_nf_opt);
#else
        nf_unregister_hook(&packet_simple_nf_opt);
#endif

        printk("[simple_nf_test] remove hook lkm success!\n");
}

6. Write Makefile

obj-m += my_nf_lkm.o
my_nf_lkm-objs := simple_nf_test.o

all:
        make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules
         
clean:
        make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean

7. Required header file

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/version.h>

#include <linux/ip.h>
#include <linux/tcp.h>
#include <linux/udp.h>

#include <linux/netfilter.h>
#include <linux/netfilter_ipv4.h>

8. Compile and insert modules

[root@yg-test-centos8 simple_test]# ls
Makefile  simple_nf_test.c
[root@yg-test-centos8 simple_test]# make
make -C /lib/modules/4.18.0-80.el8.x86_64/build M=/home/nf_test/simple_test modules
make[1]: Entering directory '/usr/src/kernels/4.18.0-80.el8.x86_64'
  CC [M]  /home/nf_test/simple_test/simple_nf_test.o
  LD [M]  /home/nf_test/simple_test/my_nf_lkm.o
  Building modules, stage 2.
  MODPOST 1 modules
  CC      /home/nf_test/simple_test/my_nf_lkm.mod.o
  LD [M]  /home/nf_test/simple_test/my_nf_lkm.ko
make[1]: Leaving directory '/usr/src/kernels/4.18.0-80.el8.x86_64'
[root@yg-test-centos8 simple_test]# 
[root@yg-test-centos8 simple_test]# 
[root@yg-test-centos8 simple_test]# 
[root@yg-test-centos8 simple_test]# insmod my_nf_lkm.ko 
[root@yg-test-centos8 simple_test]# 

The system log is shown in the figure below:

9. Unloading module

After the final test, don't forget to uninstall the kernel module:

[root@yg-test-centos8 simple_test]# rmmod my_nf_lkm
[root@yg-test-centos8 simple_test]# 

That's it.

Topics: Linux netfilter