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