Linux netfilter 学习笔记 之七 ip层netfilter的连接跟踪模块的

内核版本2.6.21

连接跟踪(CONNTRACK)就是跟踪并且记录连接状态。包括TCP、UDP、ICMP等协议类型的连接。其主要是判断该数据包是什么状态。根据数据包的源ip地址、目的ip地址、源端口、目的端口、协议号来确定一条连接。

因为连接跟踪支持TCP、UDP、ICMP等协议,而不同的协议,其处理上会有一些不同,因此增加了协议相关的连接跟踪结构,即nf_conntrack_protocol、nf_conntrack_l3proto,其中nf_conntrack_protocol是四层协议的连接跟踪相关的结构,这个结构定义了四层协议相关的接口函数,使用这些函数我们能从一个skb中,抽取到在协议层上唯一识别一个数据流的信息。而nf_conntrack_l3proto为连接跟踪中三层协议相关的结构,这个结构定义了三层协议相关的接口函数,使用这些函数我们能从一个skb中,抽取到在协议层上唯一识别一个数据流的信息。结合三层与四层协议的这些函数,我们就能唯一确定一条流了,这两个协议层相关的结构是实现数据流从个性到共性的归纳依据。

那么连接跟踪模块在netfilter中与哪些hook有关呢,即一个数据包从进入一个接口到从一个接口发出需要经历哪些过程呢?

1、连接跟踪模块的hook回调函数分别注册在NF_IP_PRE_ROUTING、

NF_IP_LOCAL_OUT、NF_IP_POST_ROUTING、NF_IP_LOCAL_IN,从netfilter的角 度来说,NF_IP_PRE_ROUTING、NF_IP_LOCAL_OUT为netfilter的入口,而 NF_IP_POST_ROUTING、NF_IP_LOCAL_IN为netfilter的出口hook点。

那连接跟踪模块就是在netfilter的入口处,最先处理进入的数据包,根据数据包三层与四层协议的特点抽象出一个连接跟踪项;而在数据包的出口处对一个连接跟踪项进行确认。

2、如果仅考虑连接跟踪模块的hook函数,则对于不同的数据包,其经过natfilter的路 径大概有三个,下面就按这三个路径进行分析,看不同路径下调用的连接跟踪

hook函数是否相同。

A)发往本机的数据包

ipv4_conntrack_defrag->ipv4_conntrack_in->ipv4_conntrack_help->ipv4_confirm

B)需要本机转发的数据包

ipv4_conntrack_defrag->ipv4_conntrack_in->ipv4_conntrack_help->ipv4_confirm

C)本机发出的数据包

ipv4_conntrack_local->ipv4_conntrack_help->ipv4_confirm

上面的函数中,ipv4_conntrack_defrag用于对分段数据进行重组的,ipv4_conntrack_in对数据流创建相应的连接跟踪项,而ipv4_conntrack_help函数实现期望连接的创建、协议相关ALG功能的实现等功能,ipv4_confirm则是将新的连接跟踪项添加到连接跟踪表中。

在开始分析连接跟踪前,还有两个问题需要描述一下:

1.有些应用层协议的c/s模式的设计中,需要两条通道,以ftp为例,需要建立一条serverport21的命令通道,还需要建立一条serverport20/(端口号大于1024)的数据通道用于数据传输。这时候如果还按照平常的流程分别为这两条数据通道建立两条连接跟踪项,而且每条连接跟踪项的状态都是从NEW_STATE开始进行转变,反而有些问题,因为先建立的命令通道相关的连接跟踪项已经从NEW_STATE转换为IP_CT_ESTABLISHED了,这时对于新建立的数据通道,其连接跟踪状态也应该可以直接设置成IP_CT_ESTABLISHED了,而不必从NEW_STATE开始经过一系列的转换了。

2.有些应用层协议会存放三层的ip地址,当我们对从lan侧出去的数据包的三层ip地址进行了SNAT映射后,数据包的三层源地址就转变成wan侧地址了,但是应用层协议中携带的源ip地址仍然是lan侧内网的地址,如果不把应用层携带了源ip地址也进行SNAT映射,则在应用层协议之间建立的数据通道的数据可能就无法进入到lan侧。

基于以上问题,连接跟踪模块提出了期望连接与helper函数两个概念。

其中期望连接就是解决第一个问题,当创建一个连接跟踪项时,会根据reply方向的tuple结构查找helpers链表,并与该连接跟踪项进行绑定,接着在调用函数ipv4_conntrack_help时就调用该连接跟踪项的helper函数,注册的helper函数如果有期望连接的概念,就会创建一个期望连接,并添加到期望连接表中。

而helper函数的另一个功能就是用来解决第二个问题的,但是要想完整的解决第二个问题,还需要一个nat转换函数,用于对应用层中携带的ip地址进行NAT转换操作。

1.相应的数据结构

老规矩,还是先来分析相应的数据结构,结合代码与上面的分析,我们梳理出如下主要的数据结构:nf_conntrack_l3proto、nf_conntrack_protocol、nf_conn、nf_conntrack_tuple、nf_conntrack_expect

下面我们就分析下这几个数据结构。

1.1nf_conn

这个结构即是连接跟踪项的抽象,这个结构中包括数据连接项的两个方向的tuple值,tuple变量可以唯一确定一个数据包属于哪一条数据连接,而且也用来查找已注册的helper函数、NAT转换也以tuple变量中的ip地址、端口号等值实现功能。

根据数据连接抽象出来的数据结构structnf_conn

{

/*连接跟踪的引用计数及指向销毁一个连接跟踪项的函数指针*/

structnf_conntrackct_general;

/*一个数据连接对应的tuple结构变量,包括数据包的原始发送方向与数据包的

应答方向的tuple,也就是唯一确定一条数据连接的五元组信息

NAT操作就是通过这个功能起作用

*/

structnf_conntrack_tuple_hashtuplehash[IP_CT_DIR_MAX];

/*连接跟踪项的状态,包括连接跟踪项的状态以及NAT转换是否设置的状态*/

unsignedlongstatus;

/*连接跟踪的超时时间*/

structtimer_listtimeout;

#ifdefCONFIG_NF_CT_ACCT

/*AccountingInformation(samecachelineasotherwrittenmembers)*/

structip_conntrack_countercounters[IP_CT_DIR_MAX];

#endif

/*当一个连接跟踪模块是一个期望连接跟踪项时,则该成员

指向该连接跟踪项的主连接跟踪项。这个牵扯到期望连接

*/

structnf_conn*master;

/*如果该连接有期望连接,则该值统计期望连接的个数*/

unsignedintexpecting;

/*数据连接跟踪项的id*/

unsignedintid;

/*数据连接的helper结构,通过helper结构的help函数,能够实现ALG等功能(sip 协议就需要通过help函数

实现ALG功能)。*/

structnf_conntrack_helper*helper;

u_int32_tfeatures;

/*该数据连接跟踪项所关联的四层协议相关数据结构*/

unionnf_conntrack_protoproto;

/*用于防火墙的mark,通过iptables的mark模块,能够实现对数据流打mark的功 能*/

#ifdefined(CONFIG_NF_CONNTRACK_MARK)

u_int32_tmark;

#endif

/*ftp协议相关的数据结构*/

unionnf_conntrack_help*help;

/*该数据连接跟踪项所关联的三层协议相关数据结构*/

union{

structnf_conntrack_ipv4*ipv4;

}l3proto;

/*可变长度数组*/

void*data[0];

};

这个函数几乎将所有连接跟踪相关的数据结构都包含了,首先是数据结构nf_conntrack,该数据结构主要是用于对连接跟踪项的引用计数以及销毁连接跟踪项的函数指针。

structnf_conntrack{

atomic_tuse;/*引用计数*/

void(*destroy)(structnf_conntrack*);/*销毁函数*/

};

1.2nf_conntrack_tuple

这个数据结构虽然没有显式的出现在上面的数据结构中,其被包裹在数据结构nf_conntrack_tuple_hash中了,我们先分析nf_conntrack_tuple,然后再顺带分析nf_conntrack_tuple_hash

该数据结构为将数据流抽象成连接跟踪项的归纳依据,根据该结构成员值,我们能唯一确定一个数据流,下面我们分析一下这个数据结构。

该结构成员包括源ip地址、目的ip地址、源端口号/源序列、目的端口号/目的序列号、协议号等信息。以及最重要的,该tuple结构在所属的连接中是属于origin还是reply方向

structnf_conntrack_tuple

{

/*源方向上的三层、四层协议相关的识别信息*/

structnf_conntrack_mansrc;

/*下面的信息时目的方向的三层、四层协议相关的识别信息*/

struct{

union{

/*当三层协议不是ipv4、ipv6时,使用该成员存储*/

u_int32_tall[NF_CT_TUPLE_L3SIZE];

/*当三层协议是ipv4时,使用该成员存储ipv4地址*/

u_int32_tip;

/*当三层协议是ipv6成员时,使用该成员存储ipv6地址*/

u_int32_tip6[4];

}u3;

union{

/*当四层协议不是tcp、udp、icmp、stcp时,则使用该成员存储四层协议识别信息*/

u_int16_tall;

struct{

/*对于tcp来说,则通过端口号进行识别*/

u_int16_tport;}tcp;

struct{

/*对于udp来说,则通过端口号进行识别*/

u_int16_tport;

}udp;

struct{

/*对于icmp来说,则需要根据类型与代码号进行判断*/

u_int8_ttype,code;

}icmp;

struct{

u_int16_tport;//对于sctp,也是根据端口号进行识别

}sctp;

}u;

/*四层协议号*/

u_int8_tprotonum;

/*Thedirection(fortuplehash)*/

u_int8_tdir;

}dst;

};

以上仔细分析了目的方向上的三层、四层协议的信息,源方向上的三层、四层协议识别信息的结构体nf_conntrack_man,对我们来说还很神秘,下面分析之。

1.2.1nf_conntrack_man

该结构体主要用来存储一个三层ip地址、四层端口号、三层协议号等信息。

structnf_conntrack_man

{

/*三层协议相关的识别信息*/

unionnf_conntrack_man_l3protou3;

/*四层协议相关的识别信息*/

unionnf_conntrack_man_protou;

/*三层协议号*/

u_int16_tl3num;

};

1.2.1.1nf_conntrack_man_l3proto

这是一个联合体,定义如下:

该联合体主要用来存储ipv4地址或者v6地址或者其他三层协议的识别信息。

unionnf_conntrack_man_l3proto{

/*当三层协议非ipv4、ipv6时,使用该成员值存储三层识别信息*/

u_int32_tall[NF_CT_TUPLE_L3SIZE];

/*当三层协议是ipv4时,使用该成员存储ipv4地址*/

u_int32_tip;

/*当三层协议是ipv6时,使用该成员存储ipv6地址*/

u_int32_tip6[4];

};

1.2.1.2nf_conntrack_man_proto

该联合体主要用来存储四层协议的识别信息,包括端口号

或者序列号等

unionnf_conntrack_man_proto

{

/*其他四层协议对应的识别信息,当不是tcp、udp、icmp、sctp时,使用该成员存储四层协议的识别信息*/

u_int16_tall;

/*当是tcp协议时,使用该成员存储四层协议识别信息,即端口号*/

struct{

u_int16_tport;

}tcp;

/*当是udp协议时,使用该成员存储四层协议识别信息,即端口号*/

struct{

u_int16_tport;

}udp;

/*当是icmp协议时,使用该成员存储四层协议识别信息,即类型与代码值*/

struct{

u_int16_tid;

}icmp;

/*当是sctp协议时,使用该成员存储四层协议识别信息,即端口号*/

struct{

u_int16_tport;

}sctp;

};

当我们一层一层剥开nf_conntrack_man时,发现其与nf_conntrack_tuple中的三层、四层目的协议识别成员的定义几乎是一致的,哎。。。。

nf_conntrack_tuple结构介绍完了,那我们就顺带把nf_conntrack_tuple_hash介绍一下,定义如下:

与nf_conntrack_tuple相比,就多了一个链表成员,这个链接成员主要是起到将nf_conntrack_tuple_hash链接到数组nf_conntrack_hash[]的某一个链表中。主要是在ipv4_confirm中,对于一个刚创建的连接跟踪项,将其origin、reply方向的tuple_hash插入到数组nf_conntrack_hash[]的某一个链表中,当再有该数据流到来时,直接根据tuple值就可以很快的找到nf_conn。

structnf_conntrack_tuple_hash

{

/*链表结构*/

structlist_headlist;

structnf_conntrack_tupletuple;

};

1.3nf_conntrack_helper

接下来分析一下这个数据结构,这个数据结构也是很重要的,通过这个结构的help指针,能够实现期望连接的建立以及ALG的功能,下面来看下这个函数的定义。

structnf_conntrack_helper

{

/*链表结构,实现将所有的nf_conntrack_helper变量链接在一起*/

structlist_headlist;

/*helper变量的名称*/

constchar*name;

/*标识该变量是否属于一个模块*/

structmodule*me;

/*允许的最大期望连接*/

unsignedintmax_expected;

/*超时定时器*/

unsignedinttimeout;

/*该helper结构属于哪几条数据流,通过tuple与mask结构,能够判断出一个连接跟踪项是否可以拥有该helper变量。*/

structnf_conntrack_tupletuple;

structnf_conntrack_tuplemask;

/*help函数指针,实现创建期望连接与ALG等功能的函数*/

int(*help)(structsk_buff**pskb,

unsignedintprotoff,

structnf_conn*ct,

enumip_conntrack_infoconntrackinfo);

int(*to_nfattr)(structsk_buff*skb,conststructnf_conn*ct);

};

相比于连接跟踪项是根据数据包创建的,而helper变量则是在系统初始化或者模块初始化时定义并注册的,通过该结构的tuple、mask变量,能够抽象出一类应用层协议所具有的特性,比如ftp协议,我们可以只定义server的端口值为21,所有符合四层server的端口值为21的连接跟踪项均可以将其nf_conn->helper指针指向该helper变量的首地址。

helper变量的注册函数为nf_conntrack_helper_register,通过该函数将一个已初始化的helper变量插入到helpers链表中,在创建新的数据连接项时,通过遍历helpers链表,并比较tuple、mask,即可以找到符合要求的helper变量。

1.4nf_conntrack_proto

该结构为四层协议相关的数据结构,定义如下:

unionnf_conntrack_proto{

/*insertconntrackprotoprivatedatahere*/

structip_ct_sctpsctp;

structip_ct_tcptcp;

structip_ct_icmpicmp;

structnf_ct_icmpv6icmpv6;

};

由于四层协议相关的连接跟踪代码,我还没有理解,此处先不分析这个结构。

1.5nf_conntrack_ipv4

/该数据连接跟踪项所关联的三层协议相关数据结构

union{

structnf_conntrack_ipv4*ipv4;

}l3proto;

structnf_conntrack_ipv4{

#ifdefCONFIG_IP_NF_NAT_NEEDED

structnf_conntrack_ipv4_nat*nat;

#endif

};

这个数据结构的用法我还不清楚,希望后面分析代码时能补充。

1.5nf_conntrack_l3proto

该结构为三层协议相关的连接跟踪相关的数据结构,通过该函数,我们可以从一个数据包中抽象出tuple结构中三层协议相关的成员变量等。

structnf_conntrack_l3proto

{

/*链表指针,指向下一个nf_conntrack_l3proto结构变量*/

structlist_headlist;

/*三层协议号*/

u_int16_tl3proto;

/*协议名称*/

constchar*name;

/*从一个数据包中抽象出三层协议相关的五元组值*/

int(*pkt_to_tuple)(conststructsk_buff*skb,unsignedintnhoff,

structnf_conntrack_tuple*tuple);

对于一个给定的tuple结构,对其三层相关元组进行取反操作并赋值给

新的tuple变量inverse

*/

int(*invert_tuple)(structnf_conntrack_tuple*inverse,

conststructnf_conntrack_tuple*orig);

/*打印一个tuple变量中三层相关的信息*/

int(*print_tuple)(structseq_file*s,

conststructnf_conntrack_tuple*);

/*打印一个数据连接变量中三层相关的信息*/

int(*print_conntrack)(structseq_file*s,conststructnf_conn*);

/*对数据包进行三层相关的处理,并修改数据连接相关的值*/

int(*packet)(structnf_conn*conntrack,

conststructsk_buff*skb,

enumip_conntrack_infoctinfo);

/*当创建一个新的数据连接时,调用该函数进行初始化*/

int(*new)(structnf_conn*conntrack,conststructsk_buff*skb);

/*删除一个数据连接变量时被调用*/

void(*destroy)(structnf_conn*conntrack);

/*

*Calledbeforetracking.

* *dataoff:offsetofprotocolheader(TCP,UDP,…)in*pskb

* *protonum:protocolnumber

*/

int(*prepare)(structsk_buff**pskb,unsignedinthooknum,

unsignedint*dataoff,u_int8_t*protonum);

u_int32_t(*get_features)(conststructnf_conntrack_tuple*tuple);

int(*tuple_to_nfattr)(structsk_buff*skb,

conststructnf_conntrack_tuple*t);

int(*nfattr_to_tuple)(structnfattr*tb[],

structnf_conntrack_tuple*t);

/*Module(ifany)whichthisisconnectedto.*/

structmodule*me;

};

1.6nf_conntrack_protocol

该结构为四层协议相关的连接跟踪相关的数据结构,通过该函数,我们可以从一个数据包中抽象出tuple结构中四层协议相关的成员变量等。

structnf_conntrack_protocol

{

/*用于将已注册的四层协议的nf_conntrack连接在一起*/

structlist_headlist;

u_int16_tl3proto;/*三层协议号*/

u_int8_tproto;/*四层协议号*/

constchar*name;/*协议名称*/

/*根据数据包的四层协议特点,抽象出数据包在四层上的五元组信息

包括portnum或者type与code值等*/

int(*pkt_to_tuple)(conststructsk_buff*skb,

unsignedintdataoff,

structnf_conntrack_tuple*tuple);

/*根据原始的五元组信息,计算出一个反转五元组信息*/

int(*invert_tuple)(structnf_conntrack_tuple*inverse,

conststructnf_conntrack_tuple*orig);

/*打印一个五元组信息对应的nf_conntrack_tuple变量*/

int(*print_tuple)(structseq_file*s,

conststructnf_conntrack_tuple*);

/*打印nf_conn的相关信息,不同的四层协议选择打印的内容可能不同*/

int(*print_conntrack)(structseq_file*s,conststructnf_conn*);

/*对数据连接结构中协议层相关的参数进行配置。*/

int(*packet)(structnf_conn*conntrack,

conststructsk_buff*skb,

unsignedintdataoff,

enumip_conntrack_infoctinfo,

intpf,

unsignedinthooknum);

/*当刚创建一个该协议的数据连接时,需要调用该函数

对数据连接中该协议层相关的值进行初始化*/

int(*new)(structnf_conn*conntrack,conststructsk_buff*skb,

unsignedintdataoff);

/*当删除一个该协议相关的nf_conn时,需要调用该函数*/

void(*destroy)(structnf_conn*conntrack);

/*错误处理函数*/

int(*error)(structsk_buff*skb,unsignedintdataoff,

enumip_conntrack_info*ctinfo,

intpf,unsignedinthooknum);

/*nfnetink相关的变量,这部分代码我还没有阅读到,先搁置*/

int(*to_nfattr)(structsk_buff*skb,structnfattr*nfa,

conststructnf_conn*ct);

/*convertnfnetlinkattributestoprotoinfo*/

int(*from_nfattr)(structnfattr*tb[],structnf_conn*ct);

int(*tuple_to_nfattr)(structsk_buff*skb,

conststructnf_conntrack_tuple*t);

int(*nfattr_to_tuple)(structnfattr*tb[],

structnf_conntrack_tuple*t);

/*Module(ifany)whichthisisconnectedto.*/

structmodule*me;

};

通过1.5、1.6的这两个协议相关的数据结构,我们就可以从一个数据流中,归纳出一个头数据连接跟踪项,且该连接跟踪项能够唯一确定一条流。

1.7nf_conntrack_expect

该数据结构是对一个期望连接的抽象

structnf_conntrack_expect

{

/*将该变量连接到一个链表中*/

structlist_headlist;

/*确定一条期望连接所对应的tuple与mask,与helper结构体中的tuple、mask的作用类似*/

structnf_conntrack_tupletuple,mask;

/*相应的函数指针*/

void(*expectfn)(structnf_conn*new,

structnf_conntrack_expect*this);

/*指向主数据连接跟踪项*/

structnf_conn*master;

/*超时计数器*/

structtimer_listtimeout;

/*引用计数*/

atomic_tuse;

/*UniqueID*/

unsignedintid;

/*Flags*/

unsignedintflags;

#ifdefCONFIG_NF_NAT_NEEDED

/*Thisistheoriginalper-protopart,usedtomapthe

*expectedconnectionthewaytherecipientexpects.*/

unionnf_conntrack_manip_protosaved_proto;

/*Directionrelativetothemasterconnection.*/

enumip_conntrack_dirdir;

#endif

};

以上就是连接跟踪所需要的主要数据结构,另外还有几个全局变量,需要说明一下。

structlist_headnf_conntrack_expect_list;

structlist_head*nf_conntrack_hash;

staticLIST_HEAD(helpers);

staticLIST_HEAD(unconfirmed);

这四个变量都是list_head类型的变量,其中链表nf_conntrack_expect_list用于将所有已注册的nf_conntrack_expect变量连接在一起

链表helpers用于将所有已注册的nf_conntrack_helper连接在一起

链表unconfirmed用于将所有刚创建的,且没有confirm的连接跟踪项的origintuple连接在一起

数组nf_conntrack_hash中的每一个链表,都用来连接以创建成功的,且已经confirm的连接跟踪项。

至此,已经将连接跟踪模块相关的数据结构都介绍完了,最后一张图来结束本节,这张图将上面介绍的主要数据结构之间的关系,以及它们与上面四个全局变量之间的关系都描述了。

期待遇上一位撑着油纸伞,结着忧愁丁香一样的姑娘;或者在春暖花开时,

Linux netfilter 学习笔记 之七 ip层netfilter的连接跟踪模块的

相关文章:

你感兴趣的文章:

标签云: