netfilter数据包过滤

    iptables可以很方便的构建系统防火墙,那它是如何实现的呢?Linux内核添加了netfilter机制,在IP协议栈上传递过程中,选择了5个检查点。利用5个检测点,查阅用户注册的回调处理函数,根据用户自定义回调函数监视进出的网络数据包。

  

 netfilter数据包过滤

有了上面的知识,可以实现自己的iptables。

一.编码

    该示例简单拦截所有到达本机的http请求。

#ifndef __KERNEL__
#define __KERNEL__
#endif  /* __KERNEL__ */

#include <linux/module.h>
#include <linux/init.h>
#include <linux/types.h>
#include <linux/string.h>
#include <asm/uaccess.h>
#include <linux/netdevice.h>
#include <linux/netfilter_ipv4.h>  // ip4 netfilter,ipv6则需引入相应 linux/netfilter_ipv6.h
#include <linux/ip.h>
#include <linux/tcp.h>

#define PORT 80

// 过滤http数据包
static int filter_http(char *type,struct sk_buff *pskb)
{
	int retval = NF_ACCEPT;
	struct sk_buff *skb = pskb;
	
	struct iphdr *iph = ip_hdr(skb);  // 获取ip头
	struct tcphdr *tcp = NULL;
	char *p = NULL;

	// 解析TCP数据包
	if( iph->protocol == IPPROTO_TCP )
	{
		tcp = tcp_hdr(skb);
                p = (char*)(skb->data+iph->tot_len); // 注:sk_buff的data字段数据从ip头开始,不包括以太网数据帧
		printk("%s: "
				"%d.%d.%d.%d => %d.%d.%d.%d "
				"%u -- %u\n"
				type,
				(iph->saddr&0x000000FF)>>0,
				(iph->saddr&0x0000FF00)>>8,
				(iph->saddr&0x00FF0000)>>16,
				(iph->saddr&0xFF000000)>>24,
				(iph->daddr&0x000000FF)>>0,
				(iph->daddr&0x0000FF00)>>8,
				(iph->daddr&0x00FF0000)>>16,
				(iph->daddr&0xFF000000)>>24,
				htons(tcp->source),
				htons(tcp->dest)
				);

		if( htons(tcp->dest) == PORT )	 // 当目标端口为80,则丢弃
		{
			retval = NF_DROP;
		}
	}

	return retval;
}


static unsigned int NET_HookLocalIn(unsigned int hook,
		struct sk_buff *pskb,
		const struct net_device *in,
		const struct net_device *out,
		int (*okfn)(struct sk_buff*))
{
	return filter_http("in",pskb);
}


static unsigned int NET_HookLocalOut(unsigned int hook,
		struct sk_buff *pskb,
		const struct net_device *in,
		const struct net_device *out,
		int (*okfn)(struct sk_buff*))
{
	return filter_http("out",pskb);
}


static unsigned int NET_HookPreRouting(unsigned int hook,
		struct sk_buff *pskb,
		const struct net_device *in,
		const struct net_device *out,
		int (*okfn)(struct sk_buff*))
{
	return NF_ACCEPT;
}


static unsigned int NET_HookPostRouting(unsigned int hook,
		struct sk_buff *pskb,
		const struct net_device *in,
		const struct net_device *out,
		int (*okfn)(struct sk_buff*))
{
	return NF_ACCEPT;
}


static unsigned int NET_HookForward(unsigned int hook,
		struct sk_buff *pskb,
		const struct net_device *in,
		const struct net_device *out,
		int (*okfn)(struct sk_buff*))
{
	return NF_ACCEPT;
}


// 钩子数组
static struct nf_hook_ops net_hooks[] = {
	{
		.hook 		= NET_HookLocalIn,		// 发往本地数据包
		.owner		= THIS_MODULE,
		.pf			= PF_INET,
		.hooknum	=	NF_INET_LOCAL_IN,
		.priority	= NF_IP_PRI_FILTER-1,
	},
	{
		.hook 		= NET_HookLocalOut,		// 本地发出数据包
		.owner		= THIS_MODULE,
		.pf			= PF_INET,
		.hooknum	=	NF_INET_LOCAL_OUT,
		.priority	= NF_IP_PRI_FILTER-1,
	},
	{
		.hook 		= NET_HookForward,		// 转发的数据包
		.owner		= THIS_MODULE,
		.pf			= PF_INET,
		.hooknum	=	NF_INET_FORWARD,
		.priority	= NF_IP_PRI_FILTER-1,
	},
	{
		.hook		= NET_HookPreRouting,	// 进入本机路由前
		.owner		= THIS_MODULE,			
		.pf			= PF_INET,				
		.hooknum	= NF_INET_PRE_ROUTING,		
		.priority	= NF_IP_PRI_FILTER-1,		
	},
	{
		.hook		= NET_HookPostRouting,	// 本机发出包经路由后
		.owner		= THIS_MODULE,			
		.pf			= PF_INET,				
		.hooknum	= NF_INET_POST_ROUTING,		
		.priority	= NF_IP_PRI_FILTER-1,		
	},
};


static int __init nf_init(void) 
{
	int ret = 0;

	ret = nf_register_hooks(net_hooks,ARRAY_SIZE(net_hooks));	// 安装钩子
	if(ret)
	{
		printk(KERN_ERR "register hook failed\n");
		return -1;
	}

	return 0;
}


static void __exit nf_exit(void)
{
	nf_unregister_hooks(net_hooks,ARRAY_SIZE(net_hooks));	// 卸载钩子
}


module_init(nf_init);
module_exit(nf_exit);


MODULE_LICENSE("Dual BSD/GPL");
MODULE_AUTHOR("kettas");
MODULE_DESCRIPTION("Netfilter Demo");
MODULE_VERSION("1.0.1");
MODULE_ALIAS("Netfilter 01");

二.测试

 netfilter数据包过滤

上图发现当没启动netfilter_test.ko时,192.168.4.190可以正常接收192.168.5.212发送过来的hello,当启动驱动后,192.168.4.190无法收到来自192.168.5.212发送的world字符串。

三. 应用拦截

在用户上网行为管控场景下,需要对识别的应用放行或拦截,有以下两种方案实现。

1. 内核态

通过netfilter直接DROP数据包。内核态性能最优,但开发调试难度较大。

2. 用户态

旁路方式采集数据包,然后直接伪造HTTP,TCP,UDP等协议的应答,后到的数据包会被协议丢弃,从而达到阻断应用的目的。

用户态开发调试效率高,但对采集性能提出很高的要求。但是在某些嵌入式环境下,由于资源限制,无法高效采集数据包。

相关推荐