·ChinaUnix首页 ·论坛 ·博客 
Linux首页 | Linux新闻 | Linux论坛 | Linux文档 | Linux下载 | Linux博客 | Linux搜索 | 开源项目孵化平台 | 《开源时代》
新手入门 | 安装启动 | 管理员指南 | 开发手册 | 桌面应用 | 程序开发 | 数据库 | 网络技术| CentOS | Fedora | MySQL | Apache | Ubuntu | Gentoo| OSCON08
  Linux时代 >> 技术文档 >> 网络技术
 
Netfilter之连接跟踪的执行流程分析
来源: ChinaUnix博客  日期: 2009.06.15 09:38 (共有条评论) 我要评论
 

    最近一直在看Iptables和内核的Netfilter,也很想能够和在研究这些代码的朋友交流。因此我把我对Netfitler的相关研究总结拿出来,与大家分享,有错误的地方请大家及时指出,一起交流,共同进步。
    非常感谢独孤九贱和端木隐等高手的优秀文章,对我们每个后来学习netfilter的朋友都大有帮助。
    本文重点分析Netfilter中连接跟踪模块(ip_conntrack)的执行流程。和上一篇iptables源码的分析方法一样,这里并不注重分析具体的源码,而是侧重于流程的分析,并列出相应的执行结果。希望通过流程的分析,可以很快的熟悉链接跟踪的工作方法。
    由于某些原因,我这里分析的内核代码比较老,还是2.4.22的。但是在大的执行流程上,应该和2.6的差不太多。
欢迎自由转载,但请保持该文的完整性,并注明出处。
Author:Godbach
E-mail:
nylzhaowei@163.com
一、连接跟踪的预备知识
    连接跟踪的概念及作用,这里都不做介绍了。下面先说一下连接跟踪在Netfilter中起效的hook点以及对应的hook函数。
[Copy to clipboard] [ - ]
CODE:
/* 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的过程。


本文来自ChinaUnix博客,如果查看原文请点:http://blog.chinaunix.net/u2/82249/showart_1964332.html
  发表评论 查看评论(共有条评论) 我要提问
 
 


最新资讯更多>> 
· 谷歌劝说诺基亚采用Android操作..
· Apache 基金会确认退出 JCP 执..
· Chrome 10 新功能探秘:新增GP..
· 金山宣布开源其安全软件
· 女黑客在开源会议上抱受骚扰
· 21款值得关注的Linux游戏
· 马化腾:腾讯半年后彻底转型,..
· [多图] Chrome OS 预发布版本多..
· Lubuntu 11.04 默认应用抢先一览
· Red Hat宣布收购云计算软件提供..
论坛热点更多>> 
· do_execve时候用户栈中参数的..
· swapinfo -atm 问题
· Linux 的优点简述
· VM虚拟机上得Red Hat Linux上..
· 我看成了上海男人喜欢女人毛..
· 校车展览,看了你就知道
· 在遇到他之前,唯一需要做的..
· GRUB的疑问
· 从来没有人真正付足书价——..
· 云存储 vs 网盘
文档更新更多>> 
· orcale queue
· 谁可以推荐几本经典的操作系统的..
· 【北京】某物联网公司招云计算应..
· 【北京】某物联网公司招云计算应..
· 谁能推荐几本关于操作系统的书
· 如何添加网络接口eth1
· 葡萄牙语入门教材的选取与经验分享
· 葡萄牙语就业前景分析
· 葡萄牙语学习经验交流
· Щ
 
关于我们 | 联系方式 | 广告合作 | 诚聘英才 | 网站地图 | 友情链接 | 免费注册

Copyright © 2001-2009 ChinaUnix.net All Rights Reserved

感谢所有关心和支持过ChinaUnix的朋友们

京ICP证:060528号