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

有了上面的知识,可以实现自己的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_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等协议的应答,后到的数据包会被协议丢弃,从而达到阻断应用的目的。
用户态开发调试效率高,但对采集性能提出很高的要求。但是在某些嵌入式环境下,由于资源限制,无法高效采集数据包。