Linux内核:网络过滤器简介与示例代码

本文介绍了如何在Linux内核中构建网络过滤器,用于丢弃特定源IP的数据包。通过示例代码展示了如何编写内核模块,动态加载和卸载,并解释了内核模块的网络过滤功能。此外,还提到了用户空间处理,通过libnetfilter_queue库将数据包传递给用户空间进行进一步处理。

目录

简单的例子

实施

用户空间处理


 

Linux的一大优点是其网络功能。路由器,交换机等许多网络产品均基于嵌入式Linux操作系统。

网络过滤是Linux内核中很好的基础结构,它使我们能够过滤和操作网络堆栈中的数据包。您可以构建用于防火墙过滤的网络过滤器,记录数据包,加密/解密等等。

我们可以将网络过滤器构建为内核模块,并动态地加载/卸载它的功能。

这篇文章假定您知道如何编写和加载一个简单的内核模块,如果不是从本文开始的话

 

简单的例子


#include <linux/module.h>
#include <linux/skbuff.h>          
#include <linux/init.h>
#include <net/sock.h>
#include <linux/inet.h>
#include <linux/ip.h>             
#include <linux/kernel.h> 
#include <linux/netfilter.h>
#include <uapi/linux/netfilter_ipv4.h> 
 
 
unsigned int main_hook(unsigned int hooknum,
                       struct sk_buff *skb,
                       const struct net_device *in,
                       const struct net_device *out,
                       int (*okfn)(struct sk_buff*))
{
    struct iphdr *iph;
 
    iph = ip_hdr(skb);
    if(iph->saddr == in_aton("192.168.0.2"))
    { 
        return NF_DROP; 
    }
    return NF_ACCEPT;
}
 
static struct nf_hook_ops netfops;                    
 
int __init my_module_init(void)
{
    netfops.hook              =       main_hook;
    netfops.pf                =       PF_INET;        
    netfops.hooknum           =       0;
    netfops.priority          =       NF_IP_PRI_FIRST;
    nf_register_hook(&netfops);
 
    return 0;
}
 
void  my_module_exit(void) 
{ 
    nf_unregister_hook(&netfops); 
}
 
module_init(my_module_init);
module_exit(my_module_exit);
MODULE_LICENSE("GPL");

这是一个简单的过滤器示例,它将丢弃来自IP 192.168.0.2的所有数据包

模块初始化功能在路由之前在输入路径中注册过滤器。要注册过滤器,我们需要指定:

  • 挂钩函数– main_hook
  • 套接字系列(IPv4)– PF_INET
  • 挂钩类型–路由之前的输入路径– 0
  • 挂钩优先级–如果我们在同一位置注册多个挂钩– NP_IP_PRI_FIRST

参数取决于系列–在IPv4中,您可以注册5点过滤器:

  • 输入路径–路由之前
  • 输入路径–路由之后
  • 输出路径–布线之前
  • 输出路径–路由之后
  • 从一个网络适配器转发到另一个

要构建该模块,我们需要为内核配置网络过滤器支持:

构建模块并对其进行测试:

  • 从IP运行ping命令:192.168.0.2 –应该得到正确的答复–保持运行
  • 将模块插入本地内核(insmod ./filter.ko)
  • 您现在不应该看到回复
  • 删除模块-再次看到正确的回复

每个数据包都通过挂钩函数main_hook传递。让我们详细了解一下:

unsigned int main_hook(unsigned int hooknum,
                       struct sk_buff *skb,
                       const struct net_device *in,
                       const struct net_device *out,
                       int (*okfn)(struct sk_buff*))
{
    struct iphdr *iph;
 
    iph = ip_hdr(skb);
    if(iph->saddr == in_aton("192.168.0.2"))
    { 
        return NF_DROP; 
    }
    return NF_ACCEPT;
}

第一个参数是钩子编号–例如,如果我们想在路由之前将所有消息记录在输入和输出路径中,则可以对多个钩子使用相同的函数

第二个参数是套接字缓冲区–表示数据包的数据结构

接下来,我们有两个网络适配器–输入用于输入,输出用于输出,以及在转发的情况下两者

最后一个参数是指向最后一个过滤器的函数指针-调用它会取消该过滤器旁边的所有其他过滤器(这就是为什么我们有优先级的原因)

 

实施


我们首先设置一个指向数据包IP标头的指针。现在,我们可以访问源IP和目标IP以及所有其他IP标头字段。

然后我们检查源地址,并丢弃来自192.168.0.2的数据包。在其他情况下,我们接受该数据包,则将调用下一个过滤器

挂钩函数必须返回以下值之一:

  • NF_DROP –丢弃数据包–释放资源
  • NF_ACCEPT –接受数据包–继续下一个过滤器
  • NF_STOLEN –不要继续处理数据包但不要释放它–我的责任是释放它
  • NF_QUEUE –将数据包排队以进行用户空间处理
  • NF_REPEAT –再次调用此过滤器

 

用户空间处理


如果返回NF_QUEUE,则控件将交付给用户空间。要在用户空间中实现它,我们应该使用库:nfnetlinknetfilter_queue

我将在以后的文章中介绍这些库,但是对于这篇文章,这是一个非常简单的示例,仅用于理解概念(没有错误检查和复杂的处理):

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <netinet/in.h>
#include <linux/types.h>
#include <linux/netfilter.h>		
#include <libnetfilter_queue/libnetfilter_queue.h>
 
 
static int filter_fn(struct nfq_q_handle *qh, struct nfgenmsg *nfmsg, struct nfq_data *nfa, void *data)
{
	int id;
        struct nfqnl_msg_packet_hdr *ph;
	ph = nfq_get_msg_packet_hdr(nfa);	
	id = ntohl(ph->packet_id);
	printf("filter function id=%d\n",id);
	if ( id % 3 == 0)
		return nfq_set_verdict(qh, id, NF_DROP, 0, NULL);
	return nfq_set_verdict(qh, id, NF_ACCEPT, 0, NULL);
}
 
int main(int argc, char **argv)
{
	struct nfq_handle *h;
	struct nfq_q_handle *qh;
	int fd;
	int rv;
	char buf[4096];
 
	h = nfq_open();
 
	nfq_bind_pf(h, AF_INET);
 
	qh = nfq_create_queue(h,  0, filter_fn, NULL);
	nfq_set_mode(qh, NFQNL_COPY_PACKET, 0xffff);
 
	fd = nfq_fd(h);
 
	while ((rv = recv(fd, buf, sizeof(buf), 0)))
	{
		printf("received\n");
		nfq_handle_packet(h, buf, rv);
	}
 
	nfq_destroy_queue(qh);
 
	nfq_close(h);
 
	exit(0);
}

在主函数中,我们打开连接,创建队列,然后将函数filter_fn注册为我们的过滤器,然后启动接收循环。

在过滤功能中,我们可以丢弃或接受数据包。

更改内核代码以返回NF_QUEUE:

#include <linux/module.h>
#include <linux/skbuff.h>          
#include <linux/init.h>
#include <net/sock.h>
#include <linux/inet.h>
#include <linux/ip.h>             
#include <linux/kernel.h> 
#include <linux/netfilter.h>
#include <uapi/linux/netfilter_ipv4.h> 
 
 
unsigned int main_hook(unsigned int hooknum,
                       struct sk_buff *skb,
                       const struct net_device *in,
                       const struct net_device *out,
                       int (*okfn)(struct sk_buff*))
{
    struct iphdr *iph;
 
    iph = ip_hdr(skb);
    if(iph->saddr == in_aton("192.168.0.2"))
    { 
        return NF_QUEUE; 
    }
    return NF_ACCEPT;
}
 
static struct nf_hook_ops netfops;                    
 
int __init my_module_init(void)
{
    netfops.hook              =       main_hook;
    netfops.pf                =       PF_INET;        
    netfops.hooknum           =       0;
    netfops.priority          =       NF_IP_PRI_FIRST;
    nf_register_hook(&netfops);
 
    return 0;
}
void  my_module_exit(void) 
{ 
    nf_unregister_hook(&netfops); 
}
 
module_init(my_module_init);
module_exit(my_module_exit);
MODULE_LICENSE("GPL");

构建用户空间应用程序:

# gcc -o app ./simple.c -lnfnetlink -lnetfilter_queue

要测试代码,请构建并插入模块,然后运行应用程序

$ sudo insmod ./netfildrv.ko 
$ sudo ./app 
received
filter function id=1
received
filter function id=2
received
filter function id=3
received
...

 

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值