Linux下使用Netfilter框架编写内核模块

上篇文章我们从内核源码的角度分析Linux Netfilter框架下,hook钩子是如何被执行的,这次我们写个简单的示例代码,详细介绍下如何使用Netfilter框架编写内核模块。

Linux内核中对Netfilter的实现详细分析可以看我的系列文章:

Linux Netfilter介绍

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

linux Netfilter在网络层的实现详细分析(iptables)

上篇文章我们从数据结构层面分析了Netfilter框架hook中用到的数据结构,下面我画了一张图,从源码中具体的实例入手,看下自定义的钩子函数在内核中的位置:

内核中有个全局变量,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、编译并插入模块

[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]# 

系统日志如图:

 9、卸载模块

最后测试完成后,别忘记卸载内核模块:

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

到此大功告成。

  • 6
    点赞
  • 38
    收藏
    觉得还不错? 一键收藏
  • 7
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值