Netfilter框架
目录
1网络通信
1.1网络通信的基本模型
1.2协议栈底层机制
2 Netfilter
2.1Netfilter介绍
2.2钩子函数返回值
2.3 hook点
2.4 协议栈切入Netfilter框架
3 Netfilter的实现方式
3.1nf_hooks[][]结构
3.2nf_hook_ops
3.3 增加新的钩子函数
3.4 基于源接口的数据包过滤钩子函数
4 iptables防火墙内核模块
4.1 iptables
4.2 Netfilter框架防火墙iptables hook函数分类
4.3 iptables基础
4.4 iptables命令格式
5连接跟踪机制
5.1.重要数据结构
5.2重要函数
5.3链接跟踪建立的三条路径
5.4 IP层接收和发送数据包进入连接跟踪钩子函数的入口
5.5连接跟踪的流程分析
1网络通信
1.1网络通信的基本模型
在数据的发送过程中,从上至下依次是“加头”的过程,每到达一层数据就被会加上该层的头部;与此同时,接受数据方就是个“剥头”的过程,从网卡收上包来之后,在往协议栈的上层传递过程中依次剥去每层的头部,最终到达用户那儿的就是裸数据了。
1.2协议栈底层机制
“栈”模式底层机制基本就是像下面这个样子:
对于收到的每个数据包,都从“A”点进来,经过路由判决,如果是发送给本机的就经过“B”点,然后往协议栈的上层继续传递;否则,如果该数据包的目的地是不本机,那么就经过“C”点,然后顺着“E”点将该包转发出去。
对于发送的每个数据包,首先也有一个路由判决,以确定该包是从哪个接口出去,然后经过“D”点,最后也是顺着“E”点将该包发送出去。
协议栈那五个关键点A,B,C,D和E就是我们Netfilter大展拳脚的地方了。
2 Netfilter
2.1Netfilter介绍
Netfilter是Linux 2.4.x引入的一个子系统,它作为一个通用的、抽象的框架,提供一整套的hook函数的管理机制,使得诸如数据包过滤、网络地址转换(NAT)和基于协议类型的连接跟踪成为了可能。Netfilter在内核中位置如下图所示:
这幅图,很直观的反应了用户空间的iptables和内核空间的基于Netfilter的ip_tables模块之间的关系和其通讯方式,以及Netfilter在这其中所扮演的角色。
Netfilter在netfilter_ipv4.h中将那五个关键点“ABCDE”上来。重新命名,如下图所示。
2.2钩子函数返回值
在每个关键点上,有很多已经按照优先级预先注册了的回调函数(这些函数称为“钩子函数”)埋伏在这些关键点,形成了一条链。对于每个到来的数据包会依次被那些回调函数“调戏”一番再视情况是将其放行,丢弃还是怎么滴。但是无论如何,这些回调函数最后必须向Netfilter报告一下该数据包的死活情况,因为毕竟每个数据包都是Netfilter从人家协议栈那儿借调过来给兄弟们Happy的,别个再怎么滴也总得“活要见人,死要见尸”吧。每个钩子函数最后必须向Netfilter框架返回下列几个值其中之一:
n NF_ACCEPT 继续正常传输数据报。这个返回值告诉Netfilter:到目前为止,该数据包还是被接受的并且该数据包应当被递交到网络协议栈的下一个阶段。
n NF_DROP 丢弃该数据报,不再传输。
n NF_STOLEN 模块接管该数据报,告诉Netfilter“忘掉”该数据报。该回调函数将从此开始对数据包的处理,并且Netfilter应当放弃对该数据包做任何的处理。但是,这并不意味着该数据包的资源已经被释放。这个数据包以及它独自的sk_buff数据结构仍然有效,只是回调函数从Netfilter 获取了该数据包的所有权。
n NF_QUEUE对该数据报进行排队(通常用于将数据报给用户空间的进程进行处理)
n NF_REPEAT 再次调用该回调函数,应当谨慎使用这个值,以免造成死循环。
上面提到的五个关键点后面我们就叫它们为hook点,每个hook点所注册的那些回调函数都将其称为hook函数。
2.3 hook点
Linux 2.6版内核的Netfilter目前支持IPv4、IPv6以及DECnet等协议栈,这里我们主要研究IPv4协议。关于协议类型,hook点,hook函数,优先级,通过下面这个图给大家做个详细展示:
对于每种类型的协议,数据包都会依次按照hook点的方向进行传输,每个hook点上Netfilter又按照优先级挂了很多hook函数。这些hook函数就是用来处理数据包用的。
Netfilter使用NF_HOOK(include/linux/netfilter.h)宏在协议栈内部切入到Netfilter框架中。相比于2.4版本,2.6版内核在该宏的定义上显得更加灵活一些,定义如下:
#define NF_HOOK(pf, hook,skb, indev, outdev, okfn) \
NF_HOOK_THRESH(pf, hook, skb, indev, outdev, okfn, INT_MIN)
关于宏NF_HOOK各个参数的解释说明:
1) pf:协议族名,Netfilter架构同样可以用于IP层之外,因此这个变量还可以有诸如PF_INET6,PF_DECnet等名字。
2) hook:HOOK点的名字,对于IP层,就是取上面的五个值;
3) skb:不解释;
4) indev:数据包进来的设备,以structnet_device结构表示;
5) outdev:数据包出去的设备,以structnet_device结构表示;
(后面可以看到,以上五个参数将传递给nf_register_hook中注册的处理函数。)
6) okfn:是个函数指针,当所有的该HOOK点的所有登记函数调用完后,转而走此流程。
NF_HOOK_THRESH又是一个宏:#define NF_HOOK_THRESH(pf, hook, skb, indev, outdev, okfn,thresh)
我们发现NF_HOOK_THRESH宏只增加了一个thresh参数,这个参数就是用来指定通过该宏去遍历钩子函数时的优先级,同时,该宏内部又调用了nf_hook_thresh函数:
这个函数又只增加了一个参数cond,该参数为0则放弃遍历,并且也不执行okfn函数;为1则执行nf_hook_slow去完成钩子函数okfn的顺序遍历(优先级从小到大依次执行)。
在net/netfilter/core.h文件中定义了一个二维的结构体数组,用来存储不同协议栈钩子点的回调处理函数。
struct list_headnf_hooks[NPROTO][NF_MAX_HOOKS];
其中,行数NPROTO为32,即目前内核所支持的最大协议簇;列数NF_MAX_HOOKS为挂载点的个数,目前在2.6内核中该值为8。nf_hooks数组的最终结构如下图所示。
在include/linux/socket.h中IP协议AF_INET(PF_INET)的序号为2,因此我们就可以得到TCP/IP协议族的钩子函数挂载点为:
PRE_ROUTING: nf_hooks[2][0]
LOCAL_IN: nf_hooks[2][1]
FORWARD: nf_hooks[2][2]
LOCAL_OUT: nf_hooks[2][3]
POST_ROUTING: nf_hooks[2][4]
2.4 协议栈切入Netfilter框架
在2.6内核的IP协议栈里,从协议栈正常的流程切入到Netfilter框架中,然后顺序、依次去调用每个HOOK点所有的钩子函数的相关操作有如下几处:
1)、net/ipv4/ip_input.c里的ip_rcv函数。该函数主要用来处理网络层的IP报文的入口函数,它到Netfilter框架的切入点为:
NF_HOOK(PF_INET,NF_IP_PRE_ROUTING, skb, dev, NULL,ip_rcv_finish)
根据前面的理解,这句代码意义已经很直观明确了。那就是:如果协议栈当前收到了一个IP报文(PF_INET),那么就把这个报文传到Netfilter的NF_IP_PRE_ROUTING过滤点,去检查[R]在那个过滤点(nf_hooks[2][0])是否已经有人注册了相关的用于处理数据包的钩子函数。如果有,则挨个去遍历链表nf_hooks[2][0]去寻找匹配的match和相应的target,根据返回到Netfilter框架中的值来进一步决定该如何处理该数据包(由钩子模块处理还是交由ip_rcv_finish函数继续处理)。
[R]:刚才说到所谓的“检查”。其核心就是nf_hook_slow()函数。该函数本质上做的事情很简单,根据优先级查找双向链表nf_hooks[][],找到对应的回调函数来处理数据包:
struct list_head **i;
list_for_each_continue_rcu(*i, head) {
struct nf_hook_ops *elem = (structnf_hook_ops *)*i;
if (hook_thresh > elem->priority)
continue;
verdict = elem->hook(hook, skb, indev, outdev, okfn);
if (verdict != NF_ACCEPT) { … … }
return NF_ACCEPT;
}
上面的代码是net/netfilter/core.c中的nf_iterate()函数的部分核心代码,该函数被nf_hook_slow函数所调用,然后根据其返回值做进一步处理。
2)、net/ipv4/ip_forward.c中的ip_forward函数,它的切入点为:
NF_HOOK(PF_INET, NF_IP_FORWARD, skb,skb->dev, rt->u.dst.dev,ip_forward_finish);
在经过路由抉择后,所有需要本机转发的报文都会交由ip_forward函数进行处理。这里,该函数由NF_IP_FOWARD过滤点切入到Netfilter框架,在nf_hooks[2][2]过滤点执行匹配查找。最后根据返回值来确定ip_forward_finish函数的执行情况。
3)、net/ipv4/ip_output.c中的ip_output函数,它切入Netfilter框架的形式为:
NF_HOOK_COND(PF_INET, NF_IP_POST_ROUTING,skb, NULL, dev,ip_finish_output,
!(IPCB(skb)->flags & IPSKB_REROUTED));
这里我们看到切入点从无条件宏NF_HOOK改成了有条件宏NF_HOOK_COND,调用该宏的条件是:如果协议栈当前所处理的数据包skb中没有重新路由的标记,数据包才会进入Netfilter框架。否则直接调用ip_finish_output函数走协议栈去处理。除此之外,有条件宏和无条件宏再无其他任何差异。
如果需要陷入Netfilter框架则数据包会在nf_hooks[2][4]过滤点去进行匹配查找。
4)、还是在net/ipv4/ip_input.c中的ip_local_deliver函数。该函数处理所有目的地址是本机的数据包,其切入函数为:
NF_HOOK(PF_INET, NF_IP_LOCAL_IN, skb,skb->dev, NULL,ip_local_deliver_finish);
发给本机的数据包,首先全部会去nf_hooks[2][1]过滤点上检测是否有相关数据包的回调处理函数,如果有则执行匹配和动作,最后根据返回值执行ip_local_deliver_finish函数。
5)、net/ipv4/ip_output.c中的ip_push_pending_frames函数。该函数是将IP分片重组成完整的IP报文,然后发送出去。进入Netfilter框架的切入点为:
NF_HOOK(PF_INET, NF_IP_LOCAL_OUT, skb,NULL, skb->dst->dev, dst_output);
对于所有从本机发出去的报文都会首先去Netfilter的nf_hooks[2][3]过滤点去过滤。一般情况下来来说,不管是路由器还是PC中端,很少有人限制自己机器发出去的报文。因为这样做的潜在风险也是显而易见的,往往会因为一些不恰当的设置导致某些服务失效,所以在这个过滤点上拦截数据包的情况非常少。当然也不排除真的有特殊需求的情况。
小节:整个Linux内核中Netfilter框架的HOOK机制可以概括如下:
在数据包流经内核协议栈的整个过程中,在一些已预定义的关键点上PRE_ROUTING、LOCAL_IN、FORWARD、LOCAL_OUT和POST_ROUTING会根据数据包的协议簇PF_INET到这些关键点去查找是否注册有钩子函数。如果没有,则直接返回okfn函数指针所指向的函数继续走协议栈;如果有,则调用nf_hook_slow函数,从而进入到Netfilter框架中去进一步调用已注册在该过滤点下的钩子函数,再根据其返回值来确定是否继续执行由函数指针okfn所指向的函数。
3 Netfilter的实现方式
3.1 nf_hooks[][]结构
用于存储不同协议簇在每个hook点上所注册的hook函数链的二维数组nf_hooks[][],其类型为list_head:
structlist_head nf_hooks[NPROTO][NF_MAX_HOOKS];
list_head{}结构体定义在include/linux/list.h头文件中
struct list_head {
struct list_head *next, *prev;
};
这是Linux内核中处理双向链表的标准方式。当某种类型的数据结构需要被组织成双向链表时,会在该数据结构的第一个字段放置一个list_head{}类型的成员。在后面的使用过程中可以通过强制类型转换来实现双向链表的遍历操作。
3.2 nf_hook_ops
在Netfilter中一个非常重要的数据结构是nf_hook_ops{}<include/linux/netfilter.h>:
struct nf_hook_ops
{
struct list_headlist;
/* User fills infrom here down. */
nf_hookfn *hook;
struct module*owner;
int pf;
int hooknum;
/* Hooks areordered in ascending priority. */
int priority;
};
对该结构体中的成员参数做一下解释:
n list:因为在一个HOOK点有可能注册多个钩子函数,因此这个变量用来将某个HOOK点所注册的所有钩子函数组织成一个双向链表;
n hook:该参数是一个指向nf_hookfn类型的函数的指针,由该函数指针所指向的回调函数在该hook被激活时调用【nf_hookfn在后面做解释】;
n owner:表示这个hook是属于哪个模块的
n pf:该hook函数所处理的协议。目前我们主要处理IPv4,所以该参数总是PF_INET;
n hooknum:钩子函数的挂载点,即HOOK点;
n priority:优先级。前面也说过,一个HOOK点可能挂载了多个钩子函数,当Netfilter在这些HOOK点上遍历查找所注册的钩子函数时,这些钩子函数的先后执行顺序便由该参数来制定。
nf_hookfn所定义的回调函数的原型在include/linux/netfilter.h文件中:
typedef unsigned int nf_hookfn(unsigned inthooknum, //HOOK点
structsk_buff**skb, //不解释
const structnet_device *in, //数据包的网络如接口
const structnet_device *out, //数据包的网络出接口
int (*okfn)(struct sk_buff *)); //后续的处理函数
我们可以到,上面这五个参数最后将由NF_HOOK或NF_HOOK_COND宏传递到Netfilter框架中去。
3.3 增加新的钩子函数
如果要增加新的钩子函数到Netfilter中相应的过滤点,我们要做的工作其实很简单:
1)、编写自己的钩子函数;
2)、实例化一个struct nf_hook_ops{}结构,并对其进行适当的填充,第一个参数list并不是用户所关心的,初始化时必须设置成{NULL,NULL};
3)、用nf_register_hook()<net/netfilter/core.c>函数将我们刚刚填充的nf_hook_ops结构体注册到相应的HOOK点上,即nf_hooks[prot][hooknum]。
内核在网络协议栈的关键点引入NF_HOOK宏,从而搭建起了整个Netfilter框架。但是NF_HOOK宏仅仅只是一个跳转而已,更重要的内容是“内核是如何注册钩子函数的呢?这些钩子函数又是如何被调用的呢?谁来维护和管理这些钩子函数?”
3.4 基于源接口的数据包过滤钩子函数
/*
* 安装一个丢弃所有进入我们指定接口的数据包的Netfilter hook 函数的示例代码
/* 用于注册我们的函数的数据结构*/
static structnf_hook_ops nfho;
/*丢弃的数据包来自的接口的名字*/
static char*drop_if = "lo";
/* 注册的hook 函数的实现*/
unsigned int hook_func(unsigned inthooknum,
struct sk_buff**skb,
const structnet_device *in,
const structnet_device *out,
int(*okfn)(struct sk_buff *))
{
if(strcmp(in->name, drop_if) == 0) {
printk("Droppedpacket on %s...\n", drop_if);
return NF_DROP;
} else {
returnNF_ACCEPT;
}
}
/* 初始化程序*/
int init_module()
{
/* 填充我们的hook 数据结构*/
nfho.hook =hook_func; /* 处理函数*/
nfho.hooknum =NF_IP_PRE_ROUTING; /* 使用IPv4 的第一个hook */
nfho.pf =PF_INET;
nfho.priority =NF_IP_PRI_FIRST; /* 让我们的函数首先执行*/
nf_register_hook(&nfho);
return 0;
}
4 iptables防火墙内核模块
4.1 Iptables
netfilter/iptablesIP 信息包过滤系统是一种功能强大的工具,可用于添加、编辑和除去规则,这些规则是在做信息包过滤决定时,防火墙所遵循和组成的规则。这些规则存储在专用的信息包过滤表中,而这些表集成在 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:发送到网卡接口之前。
4.2 Netfilter框架防火墙iptables hook函数分类
Netfilter框架为内核模块参与IP层数据包处理提供了很大的方便,内核的防火墙模块(ip_tables)正是通过把自己所编写的一些钩子函数注册到Netfilter所监控的五个关键点(NF_IP_PRE_ROUTING,NF_IP_LOCAL_IN,NF_IP_FORWARD,NF_IP_LOCAL_OUT和NF_IP_POST_ROUTING)这种方式介入到对数据包的处理。
这些钩子函数功能非常强大,按功能可分为四大类:连接跟踪、数据包的过滤、网络地址转换(NAT)和数据包的修改,其中,NAT还分为SNAT和DNAT,分别表示源网络地址转换和目的网络地址转换。它们之间的关系,以及和Netfilter、ip_tables难舍难分的缠绵可以用下图来表示:
从上图我们可以看出,ip_tables模块它是防火墙的核心模块,负责维护防火墙的规则表,通过这些规则,实现防火墙的核心功能。归纳起来,主要有三种功能:包过滤(filter)、NAT以及包处理(mangle)。同进该模块留有与用户空间通讯的接口。
在内核中我们习惯将上述的filter,nat和mangle等称之为模块。连接跟踪conntrack有些特殊,它是NAT模块和状态防火墙的功能基础,其实现机制我们也会在后面详细分析的。
按照其功能结构划分,hook函数总结如下:
包过滤子功能:包过滤一共定义了四个hook函数,这四个hook函数本质最后都调用了ipt_do_table()函数。
网络地址转换子功能:该模块也定义了四个hook函数,其中有三个最终也都调用了ip_nat_fn()函数,ip_nat_adjust()有自己另外的功能。
连接跟踪子功能:这里连接跟踪应该称其为一个子系统更合适些。它也定义四个hook函数,其中ip_conntrack_local()最后其实也调用了ip_conntrack_in()函数。
以上便是Linux的防火墙---iptables在内核中定义的所有hook函数。接下来我们再梳理一下这些hook函数分别是被挂载在哪些hook点上的。还是先贴个三维框图,因为我觉得这个图是理解Netfilter内核机制最有效,最直观的方式了,所以屡用不爽!
然后,我们拿一把大刀,从协议栈的IPv4点上顺着hook点延伸的方向一刀切下去,就会得到一个平面,如上图所示。前面这些hook函数在这个平面上的分布情况如下所示:
这幅图彻底暴露了ip_tables内核模块中那些hook函数在各个hook点分布情况。与此同时,这个图还告诉了我们很多信息:
所有由网卡收上来的数据包率先被ip_conntrack_defrag处理;链接跟踪系统的入口函数以-200的优先级被注册到了PRE_ROUTING和LOCAL_OUT两个hook点上,且其优先级高于mangle操作,NAT和包过滤等其他模块;
DNAT可以在PRE_ROUTING和LOCAL_OUT两个hook点来做,SNAT可以在LOCAL_IN和POST_ROUTING两个hook点上。
4.3 iptables基础
规则(rules)其实就是网络管理员预定义的条件,规则一般的定义为“如果数据包头符合这样的条件,就这样处理这个数据包”。规则存储在内核空间的信息包过滤表中,这些规则分别指定了源地址、目的地址、传输协议(如TCP、UDP、ICMP)和服务类型(如HTTP、FTP和SMTP)等。当数据包与规则匹配时,iptables就根据规则所定义的方法来处理这些数据包,如放行(accept)、拒绝(reject)和丢弃(drop)等。配置防火墙的主要工作就是添加、修改和删除这些规则。
链(chains)是数据包传播的路径,每一条链其实就是众多规则中的一个检查清单,每一条链中可以有一条或数条规则。当一个数据包到达一个链时,iptables就会从链中第一条规则开始检查,看该数据包是否满足规则所定义的条件。如果满足,系统就会根据该条规则所定义的方法处理该数据包;否则iptables将继续检查下一条规则,如果该数据包不符合链中任一条规则,iptables就会根据该链预先定义的默认策略来处理数据包。
表(tables)提供特定的功能,iptables内置了4个表,即filter表、nat表、mangle表和raw表,分别用于实现包过滤,网络地址转换、包重构(修改)和数据跟踪处理。
Iptables表、链、规则(如下图)
Netfilter 中默认表filter 在建立时则在NF_IP_LOCAL_IN,NF_IP_FORWARD 钩子点注册了钩子函数ipt_hook() , 在NF_IP_LOCAL_OUT 这个点注册了钩子函数ipt_local_out_hook(),两个钩子函数都会调用ipt_do_table()对相对应的表和钩子点的规则进行遍历。
4.4 Iptables命令格式
iptables的语法
1.定义默认策略
当数据包不符合链中任一条规则时,iptables将根据该链预先定义的默认策略来处理数据包,默认策略的定义格式如下。
iptables [-t表名] <-P> <链名> <动作> ?参数说明如下。
[-t表名]:指默认策略将应用于哪个表,可以使用filter、nat和mangle,如果没有指定使用哪个表,iptables就默认使用filter表。
<-P>:定义默认策略。
<链名>:指默认策略将应用于哪个链,可以使用INPUT、OUTPUT、FORWARD、PREROUTING、OUTPUT和POSTROUTING。
<动作>:处理数据包的动作,可以使用ACCEPT(接受数据包)和DROP(丢弃数据包)。
2.查看iptables规则
查看iptables规则的命令格式为:
iptables [-t表名] <-L> [链名]
参数说明如下。
[-t表名]:指查看哪个表的规则列表,表名用可以使用filter、nat和mangle,如果没有指定使用哪个表,iptables就默认查看filter表的规则列表。
<-L>:查看指定表和指定链的规则列表。
[链名]:指查看指定表中哪个链的规则列表,可以使用INPUT、OUTPUT、FORWARD、PREROUTING、OUTPUT和POSTROUTING,如果不指明哪个链,则将查看某个表中所有链的规则列表。
3.增加、插入、删除和替换规则
相关规则定义的格式为:
iptables [-t表名] <-A | I | D | R> 链名 [规则编号] [-i | o 网卡名称] [-p 协议类型] [-s 源IP地址 | 源子网] [--sport 源端口号] [-d目标IP地址 | 目标子网] [--dport目标端口号] <-j动作>
参数说明如下。
[-t表名]:定义默认策略将应用于哪个表,可以使用filter、nat和mangle,如果没有指定使用哪个表,iptables就默认使用filter表。
-A:新增加一条规则,该规则将会增加到规则列表的最后一行,该参数不能使用规则编号。
-I:插入一条规则,原本该位置上的规则将会往后顺序移动,如果没有指定规则编号,则在第一条规则前插入。
-D:从规则列表中删除一条规则,可以输入完整规则,或直接指定规则编号加以删除。
-R:替换某条规则,规则被替换并不会改变顺序,必须要指定替换的规则编号。
<链名>:指定查看指定表中哪个链的规则列表,可以使用INPUT、OUTPUT、FORWARD、PREROUTING、OUTPUT和POSTROUTING。
[规则编号]:规则编号用于插入、删除和替换规则时用,编号是按照规则列表的顺序排列,规则列表中第一条规则的编号为1。
[-i | o 网卡名称]:i是指定数据包从哪块网卡进入,o是指定数据包从哪块网卡输出。网卡名称可以使用ppp0、eth0和eth1等。
[-p 协议类型]:可以指定规则应用的协议,包含TCP、UDP和ICMP等。
[-s 源IP地址 | 源子网]:源主机的IP地址或子网地址。
[--sport 源端口号]:数据包的IP的源端口号。
[-d目标IP地址 | 目标子网]:目标主机的IP地址或子网地址。
[--dport目标端口号]:数据包的IP的目标端口号。
<-j动作>:处理数据包的动作,各个动作的详细说明可以参考前面的说明。
4.清除规则和计数器
在新建规则时,往往需要清除原有的、旧的规则,以免它们影 ?响新设定的规则。如果规则比较多,一条条删除就会十分麻烦, ?这时可以使用iptables提供的清除规则参数达到快速删除所有的规 ?则的目的。
定义参数的格式为:
iptables [-t表名] <-F | Z>
参数说明如下。
[-t表名]:指定默认策略将应用于哪个表,可以使用filter、nat和mangle,如果没有指定使用哪个表,iptables就默认使用filter表。
-F:删除指定表中所有规则。
-Z:将指定表中的数据包计数器和流量计数器归零。
5连接跟踪机制
连接跟踪机制是基于Netfilter架构实现的,其在Netfilter的不同钩子点中注册了相应的钩子函数,下面描绘了连接跟踪在Netfilter架构中注册的钩子函数。
5.1.重要数据结构
1.连接记录
在Linux内核中,连接记录由ip_conntrack结构表示,其结构如图3-1所示。在该结构中,包含一个nf_conntrack类型的结构,其记录了连接记录被公开应用的计数,也方便其他地方对连接跟踪的引用。每个连接记录都对应一个指向连接超时的函数指针,当较长时间内未使用该连接,将调用该指针所指向的函数。如果针对某种协议的连接跟踪需要扩展模块的辅助,则在连接记录中会有一指向ip_conntrack_helper结构体的指针。连接记录中的结构体ip_conntrack_tuple_hash实际记录了连接所跟踪的地址信息(源和目的地址)和协议的特定信息(端口)。所有连接记录的ip_conntrack_tuple_hash以散列形式保存在连接跟踪表中。
图 ip_conntrack结构
2.连接跟踪表
连接跟踪表是记录所有连接记录的散列表,其由全局变量ip_conntrack_hash所指向。连接跟踪表实际是一个以散列值排列的双向链表数组,链表中的元素即为连接记录所包含的ip_conntrack_tuple_hash结构,表的结构如下图所示。
图连接跟踪表
3.连接跟踪辅助模块
在连接跟踪的实现机制中提供了helper辅助模块以扩展连接跟踪功能,一个辅助模块由一个结构体ip_conntrack_helper保存,该结构如下图3-3所示,所有注册的模块由全局变量helpers所指向的链表保存。函数ip_conntrack_helper_register()和ip_conntrack_helper_unregister()用于在链表中添加和删除ip_conntrack_helper类型的结构。活动的FTP协议就使用了相应的helper模块来实现。
ip_conntrack_helper结构
4.期望连接
在连接跟踪机制中为了实现对活动协议的支持,还使用到了结构体ip_conntrack_expect,其用于将预期连接分配给现有连接,有关于活动协议(如FTP)的分析在此不做分析。ip_conntrack_expect结构如下图3-4所示。所有的ip_conntrack_expect结构由全局变量ip_conntrack_expect_list指向的全局链表保存。
3.5.传输协议
连接跟踪机制可以支持多种传输协议,不同的协议所采用的跟踪方式会有所不同。传输协议用结构ip_conntrack_protocol保存,所有的已注册的传输协议列表由全局变量ip_ct_protos所指向的一维数组保存,且按照协议号的顺序依次排列。函数ip_conntrack_protocol_register()和ip_conntrack_protocol_unregister()用于向协议列表中添加或删除一个协议。传输协议列表的结构如下图3-5所示。
5.2重要函数
1. ip_conntrack_defrag():
ip_conntrack_defrag()函数对分片的包进行重组,其调用ip_ct_gather_frag()收集已经到达的分片包,然后再调用函数ip_defrag()实现数据分片包的重组。ip_conntrack_defrag()被挂载在钩子点NF_IP_PRE_ROUTING和NF_IP_LOCAL_OUT,即从外面进来的数据包或本地主机生成的数据包会首先调用该函数。该函数只操作数据包的内容,对连接跟踪记录没有影响也没有操作,如果不需要进行重组操作则直接返回NF_ACCEPT。
2. ip_conntrack_in():
函数ip_conntrack_in()被挂载在钩子点NF_IP_PRE_ROUTING,同时该函数也被挂载在钩子点NF_IP_LOCAL_OUT的函数ip_conntrack_local()调用,连接跟踪模块在这两个钩子点挂载的函数对数据包的处理区别仅在于对分片包的重组方式有所不同。
函数ip_conntrack_in()首先调用__ip_conntrack_proto_find(),根据数据包的协议找到其应该使用的传输协议的连接跟踪模块,接下来调用协议模块的error()对数据包进行正确性检查,然后调用函数resolve_normal_ct()选择正确的连接跟踪记录,如果没有,则创建一个新纪录。接着调用协议模块的packet()函数,如果返回失败,则nf_conntrack_put()将释放连接记录。ip_conntrack_in()函数的源码如下,函数resolve_normal_ct()实际操作了数据包和连接跟踪表的内容。
3. ip_conntrack_help():
函数ip_conntrack_help()被挂载在钩子点NF_IP_LOCAL_IN和NF_IP_POST_ROUTING,其首先根据传来的sk_buff结构查找连接跟踪记录,如果该包所属连接有辅助模块helper,且包符合一定的状态要求,则调用相应辅助模块的函数help()处理数据包。
4. ip_confirm():
函数ip_confirm()被挂载在钩子点NF_IP_LOCAL_IN和NF_IP_POST_ROUTING,其对数据包再次进行连接跟踪记录确认,并将新建的连接跟踪记录加到表中。考虑到包可能被过滤掉,之前新建的连接跟踪记录实际上并未真正加到连接跟踪表中,而在最后由函数ip_confirm()确认后真正添加,实际对传来的sk_buff进行确认的函数是__ip_conntrack_confirm()。在该函数中首先调用函数ip_conntrack_get()查找相应的连接跟踪记录,如果数据包不是IP_CT_DIR_ORIGINAL方向的包,则直接ACCEPT,否则接着调用hash_conntrack()计算所找到的连接跟踪记录的ip_conntrack_tuple类型的hash值,且同时计算两个方向的值。然后根据这两个hash值分别查找连接跟踪记录的hash表,如果找到了,则返回NF_DROP,如果未找到,则调用函数__ip_conntrack_hash_insert()将两个方向的连接跟踪记录加到hash表中。
4.5.ip_conntrack_local()
函数ip_conntrack_local()被挂载在钩子点NF_IP_LOCAL_OUT,该函数会调用ip_conntrack_in()
5.3链接跟踪建立的三条路径
根据上面注册的Hook点,可以总结出链接跟踪的建立有三条路径:
1. 转发的包,见图Foward.jpg
如果是新的包,在PREROUTING处生成连接记录,通过POSTROUTING后加到hash表2. 本地接收的包,见图localin.jpg
在PREROUTING处生成连接记录,在LOCAL_IN处把生成的连接记录加到hash表
3. 本地发送的包,见图localout.jpg
在LOCAL_OUT处生成连接记录,在POSTROUTING处把生成的连接记录加到hash表
下面就按照本地发包和接收包的路径来分析连接跟踪的整个流程。
5.4 IP层接收和发送数据包进入连接跟踪钩子函数的入口
整个分析是基于IP层进行的。
1. IP层接收数据包的函数为:ip_rcv()ip_input.c
该函数执行到最后:
return NF_HOOK(PF_INET, NF_IP_PRE_ROUTING, skb, dev, NULL,
ip_rcv_finish);
执行所有NF_IP_PRE_ROUTING上的钩子,仅当所有钩子函数都返回NF_ACCEPT,接着执行ip_rcv_finish。由于连接跟踪的优先级最高,所以会执行连接跟踪的钩子函数ip_conntrack_in()。
2. IP层发送数据包(TCP包)的函数为:
int ip_queue_xmit(struct sk_buff *skb) ip_output.c
该函数执行到最后:
returnNF_HOOK(PF_INET, NF_IP_LOCAL_OUT, skb, NULL, rt->u.dst.dev,
ip_queue_xmit2);
执行所有NF_IP_LOCAL_OUT上的钩子,仅当所有钩子函数都返回NF_ACCEPT,接着执行ip_queue_xmit2。由于连接跟踪的优先级最高,所以先执行连接跟踪的钩子函数ip_conntrack_local(),该函数处理一下Raw Socket之后,接着调用ip_conntrack_in()进行处理。
5.5连接跟踪的流程分析
以下以TCP包为例,整理一下连接跟踪的建立。前面已经讲了大致的流程,这里主要分析数据包到达ip_conntrack_in和ip_confirm的处理,借此了解连接跟踪状态的转换。
1. TCP SYN包,c -> s.本地主机为c,远程主机为s
(1) Hook点为NF_IP_LOCAL_OUT,仍旧是最高的优先级,先进入ip_conntrack_local,然后调用ip_conntrack_in,该函数将该数据包对应的协议结构取出来,这里是TCP的协议结构。
(2) 调用resolve_normal_ct函数查找该数据包是否对应的有连接记录。该函数里首先通过skb参数获得数据包对应的tuple,通过调用ip_conntrack_find_get查找全局变量ip_conntrack_hash表中是否有该tuple。
a) 有匹配的tuple,则将该返回该tuple对应的struct ip_conntrack_tuple_hash结构。
b) 如果没有匹配的,则通过init_conntrack创建一个structip_conntrack结构。由于我们这里是有c发送的SYN包。所以应该查不到匹配的tuple。因此要创建一个ip_conntrack. 该函数里完成了SYN对应的正向tuple和方向tuple的计算,ip_conntrack结构的初始化等等。重要的是给数组tuplehash[0]和tuplehash[1]的赋值,以及对成员conntrack->infos的赋值即
conntrack->infos.master=&conntrack->ct_general;
这样将新建的conntrack结构体的首地址保存在了conntrack->infos.master。
该函数也返回一个structip_conntrack_tuple_hash结构
c) 处理完数据包是否有匹配的tuple之后,继续函数resolve_normal_ct的执行。接着判断该tuple_hash结构h的方向。很显然该包是一个新建立的包,所以
*ctinfo = IP_CT_NEW;
*setreply = 0;
最后将skb->nfct =&h->ctrack->infos[*ctinfo];
相当于把当前的连接跟踪结构体的地址保存在了skb中。至此,resolve_normal_ct函数执行结束。该函数返回的是一个ip_conntrack 结构体ct,对应当前数据的连接跟踪记录。
d) 接着ip_conntrack_in的处理。对ct进行指针检查之后,进入对应协议的packet函数处理。由于这里是TCP协议,所以 proto->packet函数指针指向了tcp_packet(ip_conntrack_proto_tcp.c).该函数对SYN包进行的相应处理,这里我们只需要知道
conntrack->proto.tcp.state= TCP_CONNTRACK_SYN_SENT
e) ip_conntrack_in函数执行完毕,数据包接着被其他Hook函数处理。
(3) 对数据包进行ip_conntrack_local处理之后,最后就等着数据包离开本机了。数据包离开本机之前,要经过NF_IP_POSTROUTING点,钩子函数为ip_refrag. 该函数首先调用
ip_confirm-> ip_conntrack_confirm-> __ip_conntrack_confirm。__ip_conntrack_confirm函数最终对数据包进行实际的处理。
__ip_conntrack_confirm首先检查数据包的方向,这里本地发出的包,应该为IP_CT_DIR_ORIGINAL(当且仅当使用 REJECT target时,会出现不是ORIGINAL的情形,其它非ORIGINAL数据包在ip_conntrack_confirm函数中被返回 NF_ACCEPT,不会进入到该函数的处理中),然后计算该数据包的正反两个方向tuple的hash值。如果ip_conntrack_hash静态表中没有这两个tuple,则加入进去。并将超时处理挂到time_list标上,同时将ip_conntrack结构的ct_general成员加1,并将ip_conntrac结构的status的第IPS_CONFIRMED_BIT=3位置位. 随后返回NF_ACCEPT,将该数据包发送出去。
至此,SYN包的连接记录已经建立,连接记录的超时处理函数也挂在了全局timer_list里面。
2. TCP SYN+ACK包,s -> c (这里的处理步骤可以比照1中的相应处理步骤)
(1) 连接跟踪注册在NF_IP_PRE_ROUTING点的hook函数为:ip_conntrack_in, 用来处理接受到的数据包。该函数同样取出协议结构,这里为TCP。
(2) 调用resolve_normal_ct该函数,通过计算skb相关参数计算出对应的tuple,很明显这里可以查找到全局变量ip_conntrack_hash已经有该tuple的存在。
a) 有匹配的tuple,则返回该tuple对应的struct ip_conntrack_tuple_hash结构。
b) 继续函数resolve_normal_ct的执行。接着判断该tuple_hash结构h的方向。可以得出该数据包的方向是IP_CT_DIR_REPLY,因此
*ctinfo = IP_CT_ESTABLISHED +IP_CT_IS_REPLY;
/* Please set reply bit if this packet OK*/
*set_reply = 1;
最后将skb->nfct =&h->ctrack->infos[*ctinfo]; 相当于把当前的连接跟踪结构体的地址保存在了skb中。至此,resolve_normal_ct函数执行结束。该函数返回的是一个 ip_conntrack 结构体ct,对应当前数据的连接跟踪记录。
c) 接着ip_conntrack_in的处理。对ct进行指针检查之后,进入对应协议的packet函数处理。由于这里是TCP协议,所以proto->packet函数指针指向了tcp_packet.
进入该函数,
oldtcpstate = conntrack->proto.tcp.state = SYNC_SENT(2);
newconntrack = tcp_conntracks[CTINFO2DIR(ctinfo)] [get_conntrack_index(tcph)][oldtcpstate];
= tcp_conntracks[1][0][2]
= sSR = TCP_CONNTRACK_SYN_RECV(3)
并且conntrack->proto.tcp.state= TCP_CONNTRACK_SYN_RECV (3)
然后:
conntrack->proto.tcp.handshake_ack =htonl(ntohl(tcph->seq)+ 1);
这里是将连接跟踪的的握手信号置位当前数据包的seq+1, 即相当于接收方将要发送数据包的seq_ack,留待以后做比较之用。
d) 根据当前数据包的情况,执行代码:
set_bit(IPS_SEEN_REPLY_BIT, &ct->status);
接着ip_conntrack_in函数执行完毕,数据包接着被其他Hook函数处理。
(3) 对数据包的ip_conntrack_in处理之后,就等着数据包进入本机的Hook点,NF_IP_LOACL_IN。连接跟踪在这里注册的函数是ip_confirm,该函数是调用ip_conntrack_confirm,这里应该直接返回NF_ACCEPT。
至此,由于有相应连接记录的存在,这里只对连接记录的若干状态进行修改,然后就接收到了本地主机c上。这里也表明了连接跟踪将相关的连接都归于同一条连接记录上。
3. TCP ACK包, c-> s (这里的处理步骤可以比照1和2中的相应处理步骤)
(1) 由于这次又是本地发出的数据包,所以经过的hook点以及hook函数和1中的保持一致。按照1.(1)处理完毕之后,进行2.(2)的处理,根据当前数据包的状况,resolve_normal_ct函数对数据包的处理,我们主要关注以下两行:
*ctinfo = IP_CT_ESTABLISHED;
*set_reply = 0;
然后同样是skb中保存连接记录的地址,完成resolve_normal_ct函数的执行。
(2)接着ip_conntrack_in的处理。对ct进行指针检查之后,进入对应协议的packet函数处理。由于这里是TCP协议,所以proto->packet函数指针指向了tcp_packet. 进入该函数,参照2.(2).c):
oldtcpstate = conntrack->proto.tcp.state
= TCP_CONNTRACK_SYN_RECV
newconntrack =tcp_conntracks[CTINFO2DIR(ctinfo)] [get_conntrack_index(tcph)][oldtcpstate];
= tcp_conntracks[0][2][3]
= sES = TCP_CONNTRACK_ESTABLISHED
并且conntrack->proto.tcp.state= TCP_CONNTRACK_ESTABLISHED
根据数据包的状况,tcp_packet还将执行以下两行代码:
/*设置status*/
set_bit(IPS_ASSURED_BIT, &conntrack->status);
*更新连接记录的超时时间*/
ip_ct_refresh(conntrack, tcp_timeouts[newconntrack]);
结束tcp_packet的执行,返回NF_ACCEPT。
继续ip_conntrack_in函数的执行,该函数也接着返回NF_ACCEPT. 至此,连接跟踪在该Hook点已经执行完毕。
(3) 对数据包进行ip_conntrack_in处理之后,最后就等着数据包离开本机了。数据包离开本机之前,要经过NF_IP_POSTROUTING点,钩子函数为ip_refrag. 该函数首先调用
ip_confirm-> ip_conntrack_confirm,对于本次数据包,该函数应该直接返回NF_ACCEPT。
至此,TCP的连接跟踪记录已经确定下来,以后数据进行传输的过程,应该就是重复2和3的过程。
联系客服