上篇文章我們從核心源碼的角度分析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、編譯并插入子產品
[[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]#
系統日志如圖:
9、解除安裝子產品
最後測試完成後,别忘記解除安裝核心子產品:
[[email protected] simple_test]# rmmod my_nf_lkm
[[email protected] simple_test]#
到此大功告成。