您现在的位置是:首页 >技术杂谈 >netfilter+iptables 通过自定义match模块实现网络数据包过滤网站首页技术杂谈
netfilter+iptables 通过自定义match模块实现网络数据包过滤
基本原理
netfilter是Linux内核的一个防火墙模块,iptables是netfilter提供的用户态的工具。netfilter在网络协议栈中的几个地方都有相应的钩子函数(四表五链),符合条件的网络数据包在协议栈中会被相应的钩子函数进行处理。因此想要通过netfilter对数据包进行过滤有两种方式:
- 实现一个内核模块,注册一个自定义的钩子函数,在函数中对数据包进行过滤。
- 实现一个内核模块,注册一个用于netfilter的自定义match模块,然后通过iptables在filter表的FORWARD链添加一条规则,表示符合规则的数据包会进入该match模块进行匹配,根据匹配结果对包进行裁决。
本文使用的是第二种方式。
HOOK函数和match模块
五个链,INPUT、OUTPUT、PREROUTING、POSTROUTING、FORWARD其实就是协议栈中的五个HOOK调用位置。比如数据包在经过INPUT链时,会根据优先级依次调用在该链注册的HOOK函数。
对于在iptables中添加的规则,会添加到相应的数据结构中,然后相应的HOOK函数会一条条进行匹配处理。比如在filter表在FORWARD链的HOOK函数是ipt_do_table(skb, state, state->net->ipv4.iptable_filter),使用iptables在filter表的FORWARD链添加十条规则。在这个ipt_do_table函数里面,就会让数据包匹配十条规则来决定结果。
本文使用的自定义match模块然后通过iptables在filter-INPUT添加规则
需要的代码
- 内核模块,注册一个match模块,该模块中对数据包进行处理
- iptables的一个共享库(.so)文件,iptables加载了这个库,才能知道内核中有我们自定义的match模块
- 规则下发到内核的模块,用于发送规则到内核,还有接收内核过滤信息(该模块非必须,暂不实现)
版本
系统:CentOS 7
内核:3.10.0
内核模块
#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/skbuff.h>
#include <linux/netfilter.h>
#include <linux/ip.h>
#include <linux/netfilter/x_tables.h>
MODULE_LICENSE("GPL");
MODULE_AUTHOR("YC");
MODULE_DESCRIPTION("A simple example Linux module.");
MODULE_VERSION("0.1");
// 将内核的uint32_t ip地址转成字符串
char *
ip_to_str(uint32_t ip)
{
static char str[16];
memset(str, 0, sizeof(str));
uint8_t *p = (uint8_t *)&ip;
snprintf(str, sizeof(str), "%d.%d.%d.%d", p[0], p[1], p[2], p[3]);
return str;
}
// 核心处理函数
static bool
test_match(const struct sk_buff *skb,
struct xt_action_param *par)
{
printk("test match packet [%p]
", skb);
struct iphdr *iph = ip_hdr(skb);
uint8_t proto = iph->protocol;
uint32_t saddr = iph->saddr;
// printk("protocol [%d]
", proto);
if (proto == IPPROTO_ICMP) {;
char *p = ip_to_str(saddr);
printk("icmp -> saddr [%s]
", p);
}
return true;
}
// match 模块信息
static struct xt_match kernel_test_main_mt_reg[] __read_mostly = {
{
.name = "test_match",
.family = NFPROTO_IPV4,
.match = test_match,
.matchsize = 0,
.me = THIS_MODULE,
},
};
static void xt_match_module_init(void) {
int ret;
// ret = xt_register_matches(kernel_test_main_mt_reg, ARRAY_SIZE(kernel_test_main_mt_reg));
ret = xt_register_match(kernel_test_main_mt_reg);
printk("xt_register_matches ret=[%d]
", ret);
}
static int __init example_init(void) {
printk(KERN_INFO "Hello, Kernel World!
");
xt_match_module_init();
return 0;
}
static void __exit example_exit(void) {
xt_unregister_match(kernel_test_main_mt_reg);
printk(KERN_INFO "Goodbye, Kernel World!
");
}
module_init(example_init);
module_exit(example_exit);
obj-m += xt_test_match.o
all:
# make -C /root/kernel/build/ M=$(PWD) modules
make -C /lib/modules/3.10.0-1160.88.1.el7.x86_64/build/ M=$(PWD) modules
clean:
# make -C /root/kernel/build/ M=$(PWD) clean
make -C /lib/modules/3.10.0-1160.88.1.el7.x86_64/build/ M=$(PWD) clean
iptables 共享库
注意:需要安装iptables-devel才会有相应的xtables.h和libxtables文件
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <getopt.h>
#include <xtables.h>
#include <linux/netfilter/xt_comment.h>
static struct xtables_match match_init = {
.family = NFPROTO_IPV4,
.name = "test_match",
.version = XTABLES_VERSION,
.size = 0,
.userspacesize = 0
};
void _init(void)
{
xtables_register_match(&match_init);
}
编译命令:
gcc -shared -fPIC libipt_test_match.c -o libipt_test_match.so -lxtables
编译出来的 libipt_test_match.so 文件放到iptables的相关路径下,这样iptables才能使用这个库,我的是:
/lib64/xtables/
测试
需求功能:记录所有发往本机的icmp包,并在dmesg日志记录源ip地址信息
加载内核模块
insmod xt_test_match.ko
查看内核模块是否成功加载
lsmod | grep xt_test_match
加载iptables规则
iptables -I INPUT -m test_match -j ACCEPT
所有经过INPUT链的包都会进入该match模块,进行逻辑处理
注意:
如果提示test_match模块找不到,就是iptables不能通过so文件找到相应的内核match模块。这个是很容易踩的坑。建议文件和模块的命令都参考上文的例子,前后有些地方需要保持一致
查看dmesg日志
从172.16.0.33 ping 该主机172.16.33.33,发了三个icmp包
再次查看日志
dmesg中有相应日志,功能正常实现
总结
使用该方法,可以实现在内核网络协议栈对数据包的处理过滤,因为函数可以拿到sk_buff参数,从该结构体可以拿到数据包所有数据。如果要对应用层过滤,要知道应用层协议的数据包结构,以及数据没有被加密,才能进一步进行解析和处理过滤。
该种方式支持用户自定义防火墙过滤规则,加强了防火墙的功能。