天天看點

Linux下使用Netfilter架構編寫核心子產品

上篇文章我們從核心源碼的角度分析Linux Netfilter架構下,hook鈎子是如何被執行的,這次我們寫個簡單的示例代碼,詳細介紹下如何使用Netfilter架構編寫核心子產品。

Linux核心中對Netfilter的實作詳細分析可以看我的系列文章:

Linux Netfilter介紹

Linux netfilter hook源碼分析(基于核心代碼版本4.18.0-80)

linux Netfilter在網絡層的實作詳細分析(iptables)

上篇文章我們從資料結構層面分析了Netfilter架構hook中用到的資料結構,下面我畫了一張圖,從源碼中具體的執行個體入手,看下自定義的鈎子函數在核心中的位置:

Linux下使用Netfilter架構編寫核心子產品

核心中有個全局變量,net_namespace_list連結清單,系統中所有的網絡命名空間都挂在這個連結清單上,系統預設的網絡命名空間為init_net,核心啟動時,初始化網絡命名空間net_ns_init中調用setup_net将init_net挂在net_namespace_list連結清單中,我們新增hook鈎子時,一般來說都是将新增的鈎子挂在net_namespace_list連結清單對應的網絡命名空間上。

下面我們開始編寫一個簡單的基于netfilter架構的核心子產品。

目錄

1、聲明一個hook函數

2、定義一個nf_hook_ops

3、注冊該nf_hook_ops

4、自定義的hook函數的具體實作

5、登出自定義hook

6、編寫Makefile

7、所需的頭檔案

8、編譯并插入子產品

 9、解除安裝子產品

1、聲明一個hook函數

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、定義一個nf_hook_ops

①我們在ip層(網絡層)對網絡包進行處理,這裡.pf = NFPROTO_INET;

②hook點在PREROUTING鍊上,這裡.hooknum = NF_INET_PRE_ROUTING;

③hook函數在此鍊上執行的優先級設定為最高,即.priority = NF_IP_PRI_FIRST;

④設定hook函數為我們前面自定義的函數,即.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、注冊該nf_hook_ops

在核心子產品init函數中注冊該nf_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、自定義的hook函數的具體實作

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;

}
           

我這裡在hook函數裡面,僅僅隻是列印了下源、目的ip跟端口資訊。

5、登出自定義hook

在核心子產品退出時,要登出掉之前自定義的hook鈎子:

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、編寫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、所需的頭檔案

#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、編譯并插入子產品

[[email protected] simple_test]# ls
Makefile  simple_nf_test.c
[[email protected] 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'
[[email protected] simple_test]# 
[[email protected] simple_test]# 
[[email protected] simple_test]# 
[[email protected] simple_test]# insmod my_nf_lkm.ko 
[[email protected] simple_test]# 
           

系統日志如圖:

Linux下使用Netfilter架構編寫核心子產品

 9、解除安裝子產品

最後測試完成後,别忘記解除安裝核心子產品:

[[email protected] simple_test]# rmmod my_nf_lkm
[[email protected] simple_test]# 
           
Linux下使用Netfilter架構編寫核心子產品

到此大功告成。

繼續閱讀