您现在的位置是:首页 >技术杂谈 >netfilter+iptables 通过自定义match模块实现网络数据包过滤网站首页技术杂谈

netfilter+iptables 通过自定义match模块实现网络数据包过滤

竹剑单 2024-08-14 12:01:03
简介netfilter+iptables 通过自定义match模块实现网络数据包过滤

基本原理

netfilter是Linux内核的一个防火墙模块,iptables是netfilter提供的用户态的工具。netfilter在网络协议栈中的几个地方都有相应的钩子函数(四表五链),符合条件的网络数据包在协议栈中会被相应的钩子函数进行处理。因此想要通过netfilter对数据包进行过滤有两种方式:

  1. 实现一个内核模块,注册一个自定义的钩子函数,在函数中对数据包进行过滤。
  2. 实现一个内核模块,注册一个用于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添加规则

需要的代码

  1. 内核模块,注册一个match模块,该模块中对数据包进行处理
  2. iptables的一个共享库(.so)文件,iptables加载了这个库,才能知道内核中有我们自定义的match模块
  3. 规则下发到内核的模块,用于发送规则到内核,还有接收内核过滤信息(该模块非必须,暂不实现)

版本

系统: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日志

image.png
从172.16.0.33 ping 该主机172.16.33.33,发了三个icmp包
image.png

再次查看日志

dmesg中有相应日志,功能正常实现
image.png

总结

使用该方法,可以实现在内核网络协议栈对数据包的处理过滤,因为函数可以拿到sk_buff参数,从该结构体可以拿到数据包所有数据。如果要对应用层过滤,要知道应用层协议的数据包结构,以及数据没有被加密,才能进一步进行解析和处理过滤。
该种方式支持用户自定义防火墙过滤规则,加强了防火墙的功能。

风语者!平时喜欢研究各种技术,目前在从事后端开发工作,热爱生活、热爱工作。