原文链接:http://blog.csdn.net/sealyao/article/details/5934268
一、Netfilter和Iptables概述
netfilter/iptables IP 信息包过滤系统是一种功能强大的工具,可用于添加、编辑和除去规则,这些规则是在做信息包过滤决定时,防火墙所遵循和组成的规则。这些规则存储在专用的信息包过滤表中,而这些表集成在 Linux 内核中。在信息包过滤表中,规则被分组放在我们所谓的链(chain)中。
虽然 netfilter/iptables IP 信息包过滤系统被称为单个实体,但它实际上由两个组件 netfilter 和 iptables 组成。
netfilter 组件也称为内核空间(kernelspace),是内核的一部分,由一些信息包过滤表组成,这些表包含内核用来控制信息包过滤处理的规则集。
iptables 组件是一种工具,也称为用户空间(userspace),它使插入、修改和除去信息包过滤表中的规则变得容易。
iptables包含4个表,5个链。其中表是按照对数据包的操作区分的,链是按照不同的Hook点来区分的,表和链实际上是netfilter的两个维度。
4个表:filter,nat,mangle,raw,默认表是filter(没有指定表的时候就是filter表)。表的处理优先级:raw>mangle>nat>filter。
filter:一般的过滤功能
nat:用于nat功能(端口映射,地址映射等)
mangle:用于对特定数据包的修改
raw:有限级最高,设置raw时一般是为了不再让iptables做数据包的链接跟踪处理,提高性能
5个链:PREROUTING,INPUT,FORWARD,OUTPUT,POSTROUTING。
PREROUTING:数据包进入路由表之前
INPUT:通过路由表后目的地为本机
FORWARDING:通过路由表后,目的地不为本机
OUTPUT:由本机产生,向外转发
POSTROUTIONG:发送到网卡接口之前。如下图:
iptables中表和链的对应关系如下:
二、Iptables的使用:
iptables [-t 表名] -命令 –链表 [-匹配] [-j 动作/目标]
表名:filter;nat;mangle;raw
常用命令:
-A, --append chain rule-specification:添加
-D, --delete chain rule-specification:删除
-D, --delete chain rulenum:删除
-I, --insert chain [rulenum] rule-specification:插入
-R, --replace chain rulenum rule-specification:替换
-L, --list [chain]:显示
-F, --flush [chain]:刷新
链表:INPUT;OUTPUT;FORWORD;PREROUTING;POSTROUTING
匹配:
-p -protocal [!]protocol:协议
-s -source [!] address[/mask]:源地址
-d --destination [!] address[/mask]:目的地址
-j --jump target:
-i –in-interface [!][name]:入口
-o --out-interface [!][name]:出口
-f, --fragment:分片
匹配扩展:
指定-p tcp时:
--source-port [!] [port[:port]]:原端口(也作--sport)
--destionation-port [!] [port:[port]]:目的端口(也作--dport)
--tcp-flags [!] mask comp:匹配指定的TCP标记
[!] –syn:设置了SYN位而清除了ACK和FIN位的TCP包。
--tcp-option [!] number:设置了TCP选项的包
指定 –p udp时:
--source-port [!] [port:[port]]
--destination-port [!] [port:[port]]
常见动作:
ACCEPT:放行
DROP:拒绝
QUEUE:传递给应用层
REJECT:和DROP类似,只是REJECT还返回一个错误包消息
REDIRECT:在nat表PREROUTING链中使用,修改数据包为本机地址和用户指定的端口。
TPROXY:在mangle表的PREROUTING链中使用,不修改数据包包头,直接把数据传递给一个本地socket。
较完整的命令参数见下图:
三、NetFilter的内核实现
Netfilter中有两个要点:一个是Hooks点的实现,一个是表、链、规则的概念
Hooks
struct list_head nf_hooks[NFPROTO_NUMPROTO][NF_MAX_HOOKS]是其中核心的数据结构。nf_hooks的功能类似一个二维的函数指针数组。
nf_hooks数组的第一维是按照协议进行分类的,对于不同的协议有不同的hook点和hook函数,常见的协议包括ipv4,ipv6,arp,bridge等。
nf_hooks数组的第二维是按照hook点进行划分的,分为
NF_INET_PRE_ROUTING,NF_INET_LOCAL_IN,NF_INET_FORWARD,NF_INET_LOCAL_OUT,NF_INET_POST_ROUTING等5个hook点,与iptables的5个链相对应。
nf_hooks数组中的每一个元素可以理解为一个函数指针链表的链表头。这个函数指针链表是一个有序链表,按照函数hook的优先级进行排序。iptables的4个表分别对应不同的优先级:NF_IP_PRI_RAW、NF_IP_PRI_MANGLE、NF_IP_PRI_NAT_DST /NF_IP_PRI_NAT_SRC、NF_IP_PRI_LAST等。iptables中的表的优先级就是通过有序链表的方式来实现的。
链表元素的实际的数据结构是struct nf_hook_ops,其核心的数据成员就是一
个函数指针,还包含其他的一些属性。
struct nf_hook_ops
{
struct list_head list; //链表成员
/* User fills in from here down. */
nf_hookfn *hook; //钩子函数指针
struct module *owner;
int pf; //协议簇,对于ipv4而言,是PF_INET
int hooknum; //hook类型
int priority; //优先级
};
nf_hooks的全局结构示意图如下:
上图为ipv4协议的数据包,当数据包到达对应的hook点时,检查对应的nf_hooks[NFPROTO_NUMPROTO][NF_MAX_HOOKS]函数指针链表是否为空,如果为空则继续tcp/ip协议栈的后续流程;如果不为空,则按照链表中的顺序依次调用hook函数。
iptables的默认的核心hook函数是ipt_do_table(ip_tables.c),其核心功能是检查对应的协议的对应的表中的对应的链中的规则是否和当前的数据包相匹配,如果匹配则执行对应的动作。
Tabales
netfilter中提供了一系列的表(tables),每个表包含若干个链(chains),而每条链中包含由一条或若干条规则(rules),每一条规则都被用于数据包的检测。实际上netfilter是表的容器,表是链的容器,而链又是规则的容器。
相关的数据结构:
struct xt_table //表结构
{
struct list_head list;//表链
unsigned int valid_hooks;
struct xt_table_info *private;// iptable的数据区
struct module *me;
u_int8_t af; //协议簇
int priority; //优先级
const char name[XT_TABLE_MAXNAMELEN];// 表名,如"filter"、"nat"等,为了满足自动模块加载的设计,包含该表的模块应命名为iptable_'name'.o
};
struct ipt_table_info //表的实际数据结构
{
unsigned int size;//表大小
unsigned int number;//表中规则数
unsigned int initial_entries;//初识的规则数,用于模块计数
unsigned int hook_entry[NF_IP_NUMHOOKS];// 记录所影响的HOOK的规则入口相对于下面的entries变量的偏移量
unsigned int underflow[NF_IP_NUMHOOKS];//与hook_entry相对应的规则表上限偏移量,当无规则录入时,相应的hook_entry和underflow均为0
char entries[0] ____cacheline_aligned;//规则表入口
};
下面的3种数据额结构是被填充到struct ipt_table_info的规则表(entries开始的)中的。
ipt_entry结构如下图所示,其成员ip指向结构ipt_ip,该结构主要保存规则中标准匹配的内容 (IP、mask、interface、proto等),target_offset的值等于ipt_entry的长度与 ipt_entry_matches的长度之和,next_offset的值等于规则中三个部分的长度之和。通过target_offset与 next_offset可以实现规则的遍历。
struct ipt_entry
{
struct ipt_ip ip;/* 所要匹配的报文的IP头信息 */
unsigned int nfcache;/* 位向量,标示本规则关心报文的什么部分,暂未使用 */
u_int16_t target_offset;/* target区的偏移,通常target区位于match区之后,而match区则在ipt_entry的末尾;初始化为sizeof(struct ipt_entry),即假定没有match */
u_int16_t next_offset;/* 下一条规则相对于本规则的偏移,也即本规则所用空间的总和,初始化为sizeof(struct ipt_entry)+sizeof(struct ipt_target),即没有match */
unsigned int comefrom;/* 规则返回点,标记调用本规则的HOOK号,可用于检查规则的有效性 */
struct ipt_counters counters;/* 记录该规则处理过的报文数和报文总字节数 */
unsigned char elems[0];/*target或者是match的起始位置 */
}
ipt_entry_match主要保存规则中扩展匹配内容(tos、ttl、time等),其是 Netfilter中内核与用户态交互的关键数据结构,在其内核部分由一个函数指针指向一个ipt_match结构,该结构体中包含了对包做匹配的函数, 是真正对包做匹配的地方。ipt_entry_target结构与ipt_entry_match结构很类似。
struct xt_entry_
union {
struct {
__u16 match_size;
char name[XT_EXTENSION_MAXNAMELEN];
__u8 revision;
} user;
struct {
__u16 match_size;
struct xt_match *match;
} kernel;
__u16 match_size;
} u;
unsigned char data[0];
};
struct xt_entry_target {
union {
struct {
__u16 target_size;
char name[XT_EXTENSION_MAXNAMELEN];
__u8 revision;
} user;
struct {
__u16 target_size;
struct xt_target *target;
} kernel;
__u16 target_size;
} u;
unsigned char data[0];
};
上面各种数据结构是按照下图的形式进行关联的。这里被分成表、链、规则三级。
每个表(xt_tables)都有一个private指针指向一个ipt_table_info结构,在ipt_table_info结构之后紧接着的是是实际的规则。
从数据结构中看链结构不是很明显,但是规则是被封装在链中的。ipt_table_info结构中的hook_entry成员和underflow成员就是用于划分规则所在的链。
每个规则由一个ipt_entry结构,N个xt_entry_match结构和一个xt_entry_target结构组成。ipt_entry结构表示的是基本的匹配规则(协议、地址等),xt_entry_match结构表示的是扩展的匹配规则,xt_entry_target结构表示的是匹配之后的动作。
四、NetFilter的利用
由于netfilter在内核中预留了Hook点,因此可以通过加载模块的方式方便对其加以利用。下面代码可以在2.6.20上运行。
- //nethook.c
- #include <linux/module.h>
- #include <linux/kernel.h>
- #include <linux/netfilter.h>
- #include <linux/netfilter_ipv4.h>
- #include <linux/netdevice.h>
- #include <linux/skbuff.h>
- #include <linux/ip.h>
- #include <linux/tcp.h>
- static struct nf_hook_ops nfho;
- unsigned int hook_func(unsigned int hooknum,
- struct sk_buff **skb,
- const struct net_device *in,
- const struct net_device *out,
- int (*okfn)(struct sk_buff *))
- {
- #ifdef BASE_TEST
- return NF_DROP;
- #endif
- #ifdef INTF_TEST
- if(strcmp(in->name,"eth0") == 0){
- return NF_DROP;
- }
- #endif
- #ifdef ADDR_TEST
- static unsigned char *drop_ip = "/x0a/x08/x50/x6c";
- struct sk_buff *sk = *skb;
- if(sk->nh.iph->saddr == *(unsigned int *)drop_ip){
- return NF_DROP;
- }
- #endif
- #ifdef PORT_TEST
- unsigned char *deny_port = "/x00/x19"; /* port 25 */
- struct tcphdr *thead;
- if (!skb )
- return NF_ACCEPT;
- if (!(skb->nh.iph))
- return NF_ACCEPT;
- if (skb->nh.iph->protocol != IPPROTO_TCP) {
- return NF_ACCEPT;
- }
- thead = (struct tcphdr *)(skb->data +(skb->nh.iph->ihl * 4));
- if ((thead->dest) == *(unsigned short *)deny_port) {
- return NF_DROP;
- }
- #endif
- return NF_ACCEPT;
- }
- static int __init init_nethook(void)
- {
- nfho.hook = hook_func;
- nfho.hooknum = NF_IP_PRE_ROUTING;
- nfho.pf = PF_INET;
- nfho.priority = NF_IP_PRI_FIRST;
- nf_register_hook(&nfho);
- return 0;
- }
- static void __exit exit_nethook(void)
- {
- nf_unregister_hook(&nfho);
- }
- module_init(init_nethook);
- module_exit(exit_nethook);
Makefile:
- obj-m = nethook.o
- KVERSION = $(shell uname -r)
- all:
- make -C /lib/modules/$(KVERSION)/build M=$(PWD) modules
- clean:
- make -C /lib/modules/$(KVERSION)/build M=$(PWD) clean
利用上述模块可以简单实现对地址、端口的过滤