netfilter是Linux内核的一个防火墙模块,iptables是netfilter提供的用户态的工具。netfilter在网络协议栈中的几个地方都有相应的钩子函数(四表五链),符合条件的网络数据包在协议栈中会被相应的钩子函数进行处理。因此想要通过netfilter对数据包进行过滤有两种方式:
本文使用的是第二种方式。
五个链,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添加规则
系统:CentOS 7
内核:3.10.0
#include
#include
#include
#include
#include
#include
#include
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]\n", skb);
struct iphdr *iph = ip_hdr(skb);
uint8_t proto = iph->protocol;
uint32_t saddr = iph->saddr;
// printk("protocol [%d]\n", proto);
if (proto == IPPROTO_ICMP) {;
char *p = ip_to_str(saddr);
printk("icmp -> saddr [%s]\n", 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]\n", ret);
}
static int __init example_init(void) {
printk(KERN_INFO "Hello, Kernel World!\n");
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!\n");
}
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-devel才会有相应的xtables.h和libxtables文件
#include
#include
#include
#include
#include
#include
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 -I INPUT -m test_match -j ACCEPT
所有经过INPUT链的包都会进入该match模块,进行逻辑处理
注意:
如果提示test_match模块找不到,就是iptables不能通过so文件找到相应的内核match模块。这个是很容易踩的坑。建议文件和模块的命令都参考上文的例子,前后有些地方需要保持一致
从172.16.0.33 ping 该主机172.16.33.33,发了三个icmp包
使用该方法,可以实现在内核网络协议栈对数据包的处理过滤,因为函数可以拿到sk_buff参数,从该结构体可以拿到数据包所有数据。如果要对应用层过滤,要知道应用层协议的数据包结构,以及数据没有被加密,才能进一步进行解析和处理过滤。
该种方式支持用户自定义防火墙过滤规则,加强了防火墙的功能。