分类: LINUX
2009-06-15 09:38:08
最近一直在看Iptables和内核的Netfilter,也很想能够和在研究这些代码的朋友交流。因此我把我对Netfitler的相关研究总结拿出来,与大家分享,有错误的地方请大家及时指出,一起交流,共同进步。
非常感谢独孤九贱和端木隐等高手的优秀文章,对我们每个后来学习netfilter的朋友都大有帮助。
本文重点分析Netfilter中连接跟踪模块(ip_conntrack)的执行流程。和上一篇iptables源码的分析方法一样,这里并不注重分析具体的源码,而是侧重于流程的分析,并列出相应的执行结果。希望通过流程的分析,可以很快的熟悉链接跟踪的工作方法。
由于某些原因,我这里分析的内核代码比较老,还是2.4.22的。但是在大的执行流程上,应该和2.6的差不太多。
欢迎自由转载,但请保持该文的完整性,并注明出处。
Author:Godbach
E-mail:
一、连接跟踪的预备知识
连接跟踪的概念及作用,这里都不做介绍了。下面先说一下连接跟踪在Netfilter中起效的hook点以及对应的hook函数。
[Copy to clipboard] [ - ]
CODE:
/* [color=Blue]Connection tracking may drop packets, but never alters them, so
make it the first hook. */
static struct nf_hook_ops ip_conntrack_in_ops
= {
{ NULL, NULL }, ip_conntrack_in, PF_INET,
NF_IP_PRE_ROUTING,NF_IP_PRI_CONNTRACK
};
static struct nf_hook_ops ip_conntrack_local_out_ops
= {
{ NULL, NULL }, ip_conntrack_local, PF_INET,
NF_IP_LOCAL_OUT,NF_IP_PRI_CONNTRACK
}
/* Refragmenter; last chance. */
static struct nf_hook_ops ip_conntrack_out_ops
= {
{ NULL, NULL }, ip_refrag, PF_INET,
NF_IP_POST_ROUTING, NF_IP_PRI_LAST
};
static struct nf_hook_ops ip_conntrack_local_in_ops
= {
{ NULL, NULL }, ip_confirm, PF_INET,
NF_IP_LOCAL_IN, NF_IP_PRI_LAST-1
};
以上是连接跟踪在Netfilter中注册的hook点,对应的hook函数,以及函数调用的优先级,也可以通过图ip_conntrack_hook直观的看出来.
二、链接跟踪建立的三条路径
根据上面注册的Hook点,可以总结出链接跟踪的建立有三条路径:
1. 转发的包,见图Foward.jpg
如果是新的包,在PREROUTING处生成连接记录,通过POSTROUTING后加到hash表
2. 本地接收的包,见图localin.jpg
在PREROUTING处生成连接记录,在LOCAL_IN处把生成的连接记录加到hash表
3. 本地发送的包,见图localout.jpg
在LOCAL_OUT处生成连接记录,在POSTROUTING处把生成的连接记录加到hash表
下面就按照本地发包和接收包的路径来分析连接跟踪的整个流程。
三、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()进行处理。
四、连接跟踪的流程分析
以下以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创建一个struct ip_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。
该函数也返回一个struct ip_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的过程。