上篇文章我们从内核源码的角度分析Linux Netfilter框架下,hook钩子是如何被执行的,这次我们写个简单的示例代码,详细介绍下如何使用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函数
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]#
到此大功告成。