ip层netfilter的连接跟踪模块

内核版本 2.6.21

 一、ip层netfilter的连接跟踪模块的概念及相关的数据结构分析


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

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


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

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

NF_IP_LOCAL_OUTNF_IP_POST_ROUTINGNF_IP_LOCAL_IN,从netfilter的角 度来说,NF_IP_PRE_ROUTINGNF_IP_LOCAL_OUTnetfilter的入口,而 NF_IP_POST_ROUTINGNF_IP_LOCAL_INnetfilter的出口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为例,需要建立一条server port 21的命令通道,还需要建立一条server port 20 /(端口号大于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_l3protonf_conntrack_protocolnf_connnf_conntrack_tuplenf_conntrack_expect


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

1.1 nf_conn

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

 

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

{

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

struct nf_conntrack ct_general;

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

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

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

*/

struct nf_conntrack_tuple_hash tuplehash[IP_CT_DIR_MAX];

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

unsigned long status;

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

struct timer_list timeout;


#ifdef CONFIG_NF_CT_ACCT

/* Accounting Information (same cache line as other written members) */

struct ip_conntrack_counter counters[IP_CT_DIR_MAX];

#endif

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

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

*/

struct nf_conn *master;

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

unsigned int expecting;

 

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

unsigned int id;

 

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

实现ALG功能)*/

struct nf_conntrack_helper *helper;

 

u_int32_t features;

 

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

union nf_conntrack_proto proto;

 

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

#if defined(CONFIG_NF_CONNTRACK_MARK)

u_int32_t mark;

#endif

 

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

union nf_conntrack_help *help;

 

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

union {

struct nf_conntrack_ipv4 *ipv4;

} l3proto;

/*可变长度数组*/

void *data[0];

};

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

struct nf_conntrack {

atomic_t use;/*引用计数*/

void (*destroy)(struct nf_conntrack *);/*销毁函数*/

};

 

1.2 nf_conntrack_tuple

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

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

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

struct nf_conntrack_tuple

{

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

struct nf_conntrack_man src;

 

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

struct {

union {

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

u_int32_t all[NF_CT_TUPLE_L3SIZE];

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

u_int32_t ip;

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

u_int32_t ip6[4];

} u3;

union {

/* 当四层协议不是tcpudpicmpstcp时,则使用该成员存储四层协议识别信息*/

u_int16_t all;

 

struct {

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

u_int16_t port;
} tcp;

struct {

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

u_int16_t port;

} udp;

struct {

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

u_int8_t type, code;

} icmp;

struct {

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

} sctp;

} u;

 

/* 四层协议号 */

u_int8_t protonum;

 

/* The direction (for tuplehash) */

u_int8_t dir;

} dst;

};

 

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

1.2.1 nf_conntrack_man

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

struct nf_conntrack_man

{

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

union nf_conntrack_man_l3proto u3;

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

union nf_conntrack_man_proto u;

/* 三层协议号 */

u_int16_t l3num;

};

 

1.2.1.1 nf_conntrack_man_l3proto

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

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

union nf_conntrack_man_l3proto {

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

u_int32_t all[NF_CT_TUPLE_L3SIZE];

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

u_int32_t ip;

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

u_int32_t ip6[4];

};

 

1.2.1.2 nf_conntrack_man_proto

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

或者序列号等 

union nf_conntrack_man_proto

{

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

u_int16_t all;

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

struct {

u_int16_t port;

} tcp;

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

struct {

u_int16_t port;

} udp;

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

struct {

u_int16_t id;

} icmp;

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

struct {

u_int16_t port;

} 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中,对于一个刚创建的连接跟踪项,将其originreply方向的tuple_hash插入到数组nf_conntrack_hash[]的某一个链表中,当再有该数据流到来时,直接根据tuple值就可以很快的找到nf_conn

struct nf_conntrack_tuple_hash

{

/*链表结构*/

struct list_head list;

struct nf_conntrack_tuple tuple;

};

 

1.3 nf_conntrack_helper 

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

struct nf_conntrack_helper

{

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

struct list_head list; 

/* helper变量的名称*/

const char *name;

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

struct module *me;

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

unsigned int max_expected;

/*超时定时器*/

unsigned int timeout;

 

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

struct nf_conntrack_tuple tuple;

struct nf_conntrack_tuple mask;

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

int (*help)(struct sk_buff **pskb,

    unsigned int protoff,

    struct nf_conn *ct,

    enum ip_conntrack_info conntrackinfo);

 

int (*to_nfattr)(struct sk_buff *skb, const struct nf_conn *ct);

};

 

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

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

1.4 nf_conntrack_proto

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

union nf_conntrack_proto {

/* insert conntrack proto private data here */

struct ip_ct_sctp sctp;

struct ip_ct_tcp tcp;

struct ip_ct_icmp icmp;

struct nf_ct_icmpv6 icmpv6;

};

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

1.5 nf_conntrack_ipv4

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

union {

struct nf_conntrack_ipv4 *ipv4;

} l3proto;

struct nf_conntrack_ipv4 {

#ifdef CONFIG_IP_NF_NAT_NEEDED

struct nf_conntrack_ipv4_nat *nat;

#endif

};

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

 

1.5 nf_conntrack_l3proto

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

struct nf_conntrack_l3proto

{

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

struct list_head list;

/*三层协议号*/

u_int16_t l3proto;

/*协议名称*/

const char *name;

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

int (*pkt_to_tuple)(const struct sk_buff *skb, unsigned int nhoff,

    struct nf_conntrack_tuple *tuple);

 

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

新的tuple变量inverse

*/

int (*invert_tuple)(struct nf_conntrack_tuple *inverse,

    const struct nf_conntrack_tuple *orig);

 

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

int (*print_tuple)(struct seq_file *s,

   const struct nf_conntrack_tuple *);

 

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

int (*print_conntrack)(struct seq_file *s, const struct nf_conn *);

 

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

int (*packet)(struct nf_conn *conntrack,

      const struct sk_buff *skb,

      enum ip_conntrack_info ctinfo);

 

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

int (*new)(struct nf_conn *conntrack, const struct sk_buff *skb);

 

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

void (*destroy)(struct nf_conn *conntrack);

 

/*

 * Called before tracking. 

 * *dataoff: offset of protocol header (TCP, UDP,...) in *pskb

 * *protonum: protocol number

 */

int (*prepare)(struct sk_buff **pskb, unsigned int hooknum,

       unsigned int *dataoff, u_int8_t *protonum);

 

u_int32_t (*get_features)(const struct nf_conntrack_tuple *tuple);

 

int (*tuple_to_nfattr)(struct sk_buff *skb,

       const struct nf_conntrack_tuple *t);

 

int (*nfattr_to_tuple)(struct nfattr *tb[],

       struct nf_conntrack_tuple *t);

 

/* Module (if any) which this is connected to. */

struct module *me;

};

 

1.6  nf_conntrack_protocol

 

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

 

struct nf_conntrack_protocol

{

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

struct list_head list;

u_int16_t l3proto;/*三层协议号*/

 

u_int8_t proto;/*四层协议号*/

 

const char *name;/*协议名称*/

 

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

包括port num 或者type code值等*/       

int (*pkt_to_tuple)(const struct sk_buff *skb,

    unsigned int dataoff,

    struct nf_conntrack_tuple *tuple);

 

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

int (*invert_tuple)(struct nf_conntrack_tuple *inverse,

    const struct nf_conntrack_tuple *orig);

 

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

int (*print_tuple)(struct seq_file *s,

   const struct nf_conntrack_tuple *);

 

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

int (*print_conntrack)(struct seq_file *s, const struct nf_conn *);

 

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

int (*packet)(struct nf_conn *conntrack,

      const struct sk_buff *skb,

      unsigned int dataoff,

      enum ip_conntrack_info ctinfo,

      int pf,

      unsigned int hooknum);

 

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

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

int (*new)(struct nf_conn *conntrack, const struct sk_buff *skb,

   unsigned int dataoff);

 

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

void (*destroy)(struct nf_conn *conntrack);

/*错误处理函数*/

int (*error)(struct sk_buff *skb, unsigned int dataoff,

     enum ip_conntrack_info *ctinfo,

     int pf, unsigned int hooknum);

 

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

int (*to_nfattr)(struct sk_buff *skb, struct nfattr *nfa,

 const struct nf_conn *ct);

 

/* convert nfnetlink attributes to protoinfo */

int (*from_nfattr)(struct nfattr *tb[], struct nf_conn *ct);

 

int (*tuple_to_nfattr)(struct sk_buff *skb,

       const struct nf_conntrack_tuple *t);

int (*nfattr_to_tuple)(struct nfattr *tb[],

       struct nf_conntrack_tuple *t);

 

/* Module (if any) which this is connected to. */

struct module *me;

};

 

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

1.7 nf_conntrack_expect

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

struct nf_conntrack_expect

{

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

struct list_head list;

 

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

struct nf_conntrack_tuple tuple, mask;

/*相应的函数指针 */

void (*expectfn)(struct nf_conn *new,

 struct nf_conntrack_expect *this);

 

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

struct nf_conn *master;

/* 超时计数器*/

struct timer_list timeout;

/* 引用计数*/

atomic_t use;

/* Unique ID */

unsigned int id;

 

/* Flags */

unsigned int flags;

 

#ifdef CONFIG_NF_NAT_NEEDED

/* This is the original per-proto part, used to map the

 * expected connection the way the recipient expects. */

union nf_conntrack_manip_proto saved_proto;

/* Direction relative to the master connection. */

enum ip_conntrack_dir dir;

#endif

};

 

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

 struct list_head nf_conntrack_expect_list;

 struct list_head *nf_conntrack_hash;

static LIST_HEAD(helpers);

static LIST_HEAD(unconfirmed);

 

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

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

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

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

 

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

ip层netfilter的连接跟踪模块_第1张图片


二、 ip层netfilter的连接跟踪模块初始化


在上一节中分析了连接跟踪模块相关的数据结构,本节就开始分析连接跟踪模块相关的初始化,下一节理解连接跟踪模块的hook机制。

 

在分析连接跟踪模块代码之前,先说明几点:

1.连接跟踪模块的helper结构能够实现期望连接的建立以及相关协议的ALG功能。

2.连接跟踪为NAT或者状态防火墙的实现提供了依据

 

连接跟踪模块的初始化过程分别在三个地方进行,一个用于注册连接跟踪模块相关的hook回调函数;一个用于创建连接跟踪项与期望连接项相关的slab缓存;一个用于注册连接跟踪中协议相关的变量。

 

1.全局初始化

1.1 nf_conntrack_init

主要是 nf_conntrack_init进行初始化操作,该函数定义在nf_conntrack_core.c中。

主要完成以下功能:

a.设置nf_conntrack_htable_sizenf_conntrack_max的值

b.nf_conntrack_hash申请内存并初始化

c.为连接跟踪项与期望连接跟踪项创建slab缓存。

[cpp]  view plain copy print ?
  1.    
  2. int __init nf_conntrack_init(void)  
  3. {  
  4. unsigned int i;  
  5. int ret;  
  6.    
  7. 当nf_conntrack_htable_size的值没有设置时,则在内存小于1GB时,  
  8. 使用内存的1/16384作为hash数组的最大值;在内存大于1GB时  
  9. 则最大hash数组的值为8192  
  10. */  
  11. if (!nf_conntrack_htable_size) {  
  12. nf_conntrack_htable_size  
  13. = (((num_physpages << PAGE_SHIFT) / 16384)  
  14.    / sizeof(struct list_head));  
  15. if (num_physpages > (1024 * 1024 * 1024 / PAGE_SIZE))  
  16. nf_conntrack_htable_size = 8192;  
  17. if (nf_conntrack_htable_size < 16)  
  18. nf_conntrack_htable_size = 16;  
  19. }  
  20.    

[cpp]  view plain copy print ?
  1. /* 
  2. 设置nf_conntrack_max的值,即连接跟踪项的最大值。 
  3. 该值取决于nf_conntrack_hash[]数组的最大值,即为 
  4. nf_conntrack_htable_size的8倍。 
  5. */  
  6. nf_conntrack_max = 8 * nf_conntrack_htable_size;  
  7.    
  8. printk("nf_conntrack version %s (%u buckets, %d max)\n",  
  9.        NF_CONNTRACK_VERSION, nf_conntrack_htable_size,  
  10.        nf_conntrack_max);  
  11. /* 
  12. hash链表的初始化,申请nf_conntrack_htable_size个hash链表 
  13. */  
  14. nf_conntrack_hash = alloc_hashtable(nf_conntrack_htable_size,  
  15.     &nf_conntrack_vmalloc);  
  16. if (!nf_conntrack_hash) {  
  17. printk(KERN_ERR "Unable to create nf_conntrack_hash\n");  
  18. goto err_out;  
  19. }  
  20. /*调用函数nf_conntrack_register_cache创建连接跟踪项相关的slab缓存*/  
  21. ret = nf_conntrack_register_cache(NF_CT_F_BASIC, "nf_conntrack:basic",  
  22.   sizeof(struct nf_conn), NULL);  
  23. if (ret < 0) {  
  24. printk(KERN_ERR "Unable to create nf_conn slab cache\n");  
  25. goto err_free_hash;  
  26. }  
  27. /*创建期望连接跟踪项相关的slab缓存*/  
  28. nf_conntrack_expect_cachep = kmem_cache_create("nf_conntrack_expect",  
  29. sizeof(struct nf_conntrack_expect),  
  30. 0, 0, NULL, NULL);  
  31. if (!nf_conntrack_expect_cachep) {  
  32. printk(KERN_ERR "Unable to create nf_expect slab cache\n");  
  33. goto err_free_conntrack_slab;  
  34. }  
  35.    
  36. /* 将nf_ct_l3protos中每一个成员的变量都设置成nf_conntrack_generic_l3proto 
  37. ,然后不同的连接跟踪的三层协议初始化时,即会将相应的 
  38. 数组成员的值替换掉*/  
  39. write_lock_bh(&nf_conntrack_lock);  
  40.         for (i = 0; i < PF_MAX; i++)  
  41. nf_ct_l3protos[i] = &nf_conntrack_generic_l3proto;  
  42.         write_unlock_bh(&nf_conntrack_lock);  
  43.    
  44. /* For use by REJECT target */  
  45. ip_ct_attach = __nf_conntrack_attach;  
  46.    
  47. /*将nf_conntrack_untracked的使用计数设置为1,使该数据连接项能不被删除掉*/      
  48. atomic_set(&nf_conntrack_untracked.ct_general.use, 1);  
  49. /*设置变量nf_conntrack_untracked的状态为confirmed,当对一类数据包 
  50. 不想进行连接跟踪时,就会添加如下命令, 
  51. iptables -t raw -A PREROUTING -d x.x.x.x-j NOTRACK,这样在数据包首先进入PRE_ROUTING 
  52. 、OUTPUT链时,就会首先进入raw模块对应注册的hook函数,并将 
  53. 数据包的nfct指针指向存储nf_conntrack_untracked的内存地址*/  
  54. set_bit(IPS_CONFIRMED_BIT, &nf_conntrack_untracked.status);  
  55.    
  56. return ret;  
  57.    
  58. err_free_conntrack_slab:  
  59. nf_conntrack_unregister_cache(NF_CT_F_BASIC);  
  60. err_free_hash:  
  61. free_conntrack_hash(nf_conntrack_hash, nf_conntrack_vmalloc,  
  62.     nf_conntrack_htable_size);  
  63. err_out:  
  64. return -ENOMEM;  
  65. }  

1.2init_or_cleanup

nf_conntrack_standalone.c中定义的函数,主要是调用上1.1中介绍的函数进行初始化,然后在/proc/net文件系统中创建相应的文件以及在/proc/sys/net中创建连接跟踪模块相关的文件

/*

nf_conntrack_standalone的初始化与销毁函数

对于初始化:

1.调用nf_conntrack_init进行连接模块相关的初始化,主要是

  设置连接跟踪数的最大值、为连接跟踪项或者期望连接跟踪

  创建slab缓存等操作。

2.proc/net目录下创建连接跟踪相关的文件,主要nf_conntracknf_conntrack_expect

  查看如下:

[cpp]  view plain copy print ?
  1. # cat /proc/net/stat/nf_conntrack   
  2. entries  searched found new invalid ignore delete delete_list insert insert_failed drop early_drop icmp_error  expect_new expect_create expect_delete  
  3. 00000001  00000027 00000b0b 000035a4 000053e5 000049fb 000034d8 000003c3 000003ea 00000000 00000000 00000000 00000000  00000000 00000000 00000000   
  4. 00000001  00000000 000008e9 00000067 00007eec 00007d2b 00000132 0000008d 00000067 00000000 00000000 00000000 00000000  00000000 00000000 00000000   
  5.   
  6. # cat /proc/net/nf_conntrack  
  7. /proc/net/nf_conntrack         /proc/net/nf_conntrack_expect  
  8. # cat /proc/net/nf_conntrack  
  9. ipv4     2 unknown  2 492 src=192.168.1.1 dst=224.0.0.1 [UNREPLIED] src=224.0.0.1 dst=192.168.1.1 use=2  
  10.   
  11.   
  12. # cat /proc/net/nf_conntrack_expect  
  13.   
  14.   

3./proc/sys文件系统中创建连接跟踪相关的内容。 

*/

[cpp]  view plain copy print ?
  1. static int init_or_cleanup(int init)  
  2. {  
  3. #ifdef CONFIG_PROC_FS  
  4. struct proc_dir_entry *proc, *proc_exp, *proc_stat;  
  5. #endif  
  6. int ret = 0;  
  7.    
  8. if (!init) goto cleanup;  
  9.    
  10. ret = nf_conntrack_init();  
  11. if (ret < 0)  
  12. goto cleanup_nothing;  
  13.    
  14. #ifdef CONFIG_PROC_FS  
  15. proc = proc_net_fops_create("nf_conntrack", 0440, &ct_file_ops);  
  16. if (!proc) goto cleanup_init;  
  17.    
  18. proc_exp = proc_net_fops_create("nf_conntrack_expect", 0440,  
  19. &exp_file_ops);  
  20. if (!proc_exp) goto cleanup_proc;  
  21.    
  22. proc_stat = create_proc_entry("nf_conntrack", S_IRUGO, proc_net_stat);  
  23. if (!proc_stat)  
  24. goto cleanup_proc_exp;  
  25.    
  26. proc_stat->proc_fops = &ct_cpu_seq_fops;  
  27. proc_stat->owner = THIS_MODULE;  
  28. #endif  
  29. #ifdef CONFIG_SYSCTL  
  30. nf_ct_sysctl_header = register_sysctl_table(nf_ct_net_table, 0);  
  31. if (nf_ct_sysctl_header == NULL) {  
  32. printk("nf_conntrack: can't register to sysctl.\n");  
  33. ret = -ENOMEM;  
  34. goto cleanup_proc_stat;  
  35. }  
  36. #endif  
  37.    
  38. return ret;  
  39.    
  40.  cleanup:  
  41. #ifdef CONFIG_SYSCTL  
  42.   unregister_sysctl_table(nf_ct_sysctl_header);  
  43.  cleanup_proc_stat:  
  44. #endif  
  45. #ifdef CONFIG_PROC_FS  
  46. remove_proc_entry("nf_conntrack", proc_net_stat);  
  47.  cleanup_proc_exp:  
  48. proc_net_remove("nf_conntrack_expect");  
  49.  cleanup_proc:  
  50. proc_net_remove("nf_conntrack");  
  51.  cleanup_init:  
  52. #endif /* CNFIG_PROC_FS */  
  53. nf_conntrack_cleanup();  
  54.  cleanup_nothing:  
  55. return ret;  
  56. }  
  57.    

2 Ipv4 连接跟踪模块Hook回调函数的注册

 

Ipv4协议中,注册的连接跟踪模块相关的回调函数有如下几个:

/*

在连接跟踪PRE_ROUTING链上注册的hook回调函数,主要

是分段数据包进行重组。优先级高于ipv4_conntrack_in

*/

[cpp]  view plain copy print ?
  1. static struct nf_hook_ops ipv4_conntrack_defrag_ops = {  
  2. .hook = ipv4_conntrack_defrag,  
  3. .owner = THIS_MODULE,  
  4. .pf = PF_INET,  
  5. .hooknum = NF_IP_PRE_ROUTING,  
  6. .priority = NF_IP_PRI_CONNTRACK_DEFRAG,  
  7. };  
  8. /*PREROUTING链上注册ipv4_conntrack_in,主要是实现为一条数据流 
  9. 创建连接跟踪项等操作*/  
  10. static struct nf_hook_ops ipv4_conntrack_in_ops = {  
  11. .hook = ipv4_conntrack_in,  
  12. .owner = THIS_MODULE,  
  13. .pf = PF_INET,  
  14. .hooknum = NF_IP_PRE_ROUTING,  
  15. .priority = NF_IP_PRI_CONNTRACK,  
  16. };  
  17. /* 
  18. 在连接跟踪LOCAL_OUT链上注册的hook回调函数,主要 
  19. 是分段数据包进行重组。优先级高于ipv4_conntrack_in 
  20. */  
  21.    
  22. static struct nf_hook_ops ipv4_conntrack_defrag_local_out_ops = {  
  23. .hook           = ipv4_conntrack_defrag,  
  24. .owner          = THIS_MODULE,  
  25. .pf             = PF_INET,  
  26. .hooknum        = NF_IP_LOCAL_OUT,  
  27. .priority       = NF_IP_PRI_CONNTRACK_DEFRAG,  
  28. };  
  29. /*LOCALOUT链上注册ipv4_conntrack_in*/  
  30. /*LOCAL_OUT链上注册ipv4_conntrack_local,主要是实现为本机发出的 
  31. 一条数据流创建连接跟踪项等操作*/  
  32. static struct nf_hook_ops ipv4_conntrack_local_out_ops = {  
  33. .hook = ipv4_conntrack_local,  
  34. .owner = THIS_MODULE,  
  35. .pf = PF_INET,  
  36. .hooknum = NF_IP_LOCAL_OUT,  
  37. .priority = NF_IP_PRI_CONNTRACK,  
  38. };  
  39.    
  40. /* helpers */  
  41. /* 
  42. 在POST_ROUTING链上注册ipv4_conntrack_help,根据传递的数据包, 
  43. 找到该数据包关联的连接跟踪项,然后执行该连接跟踪 
  44. 项关联的helper函数,实现期望连接的创建以及ALG等功能。 
  45. */  
  46. static struct nf_hook_ops ipv4_conntrack_helper_out_ops = {  
  47. .hook = ipv4_conntrack_help,  
  48. .owner = THIS_MODULE,  
  49. .pf = PF_INET,  
  50. .hooknum = NF_IP_POST_ROUTING,  
  51. .priority = NF_IP_PRI_CONNTRACK_HELPER,  
  52. };  
  53. /* 
  54. 在LOCAL_IN链上注册ipv4_conntrack_help,根据传递的数据包, 
  55. 找到该数据包关联的连接跟踪项,然后执行该连接跟踪 
  56. 项关联的helper函数,实现期望连接的创建以及ALG等功能。 
  57. */  
  58.    
  59. static struct nf_hook_ops ipv4_conntrack_helper_in_ops = {  
  60. .hook = ipv4_conntrack_help,  
  61. .owner = THIS_MODULE,  
  62. .pf = PF_INET,  
  63. .hooknum = NF_IP_LOCAL_IN,  
  64. .priority = NF_IP_PRI_CONNTRACK_HELPER,  
  65. };  
  66.    
  67.    
  68. /* Refragmenter; last chance. */  
  69. /* 
  70. 在POST_ROUTING链上注册ipv4_confirm。若传递的数据包关联 
  71. 的连接跟踪项还没有被确认,则执行确认操作 
  72. */  
  73. static struct nf_hook_ops ipv4_conntrack_out_ops = {  
  74. .hook = ipv4_confirm,  
  75. .owner = THIS_MODULE,  
  76. .pf = PF_INET,  
  77. .hooknum = NF_IP_POST_ROUTING,  
  78. .priority = NF_IP_PRI_CONNTRACK_CONFIRM,  
  79. };  
  80. /* 
  81. 在PRE_ROUTING链上注册ipv4_confirm。若传递的数据包关联 
  82. 的连接跟踪项还没有被确认,则执行确认操作 
  83. */  
  84.    
  85. static struct nf_hook_ops ipv4_conntrack_local_in_ops = {  
  86. .hook = ipv4_confirm,  
  87. .owner = THIS_MODULE,  
  88. .pf = PF_INET,  
  89. .hooknum = NF_IP_LOCAL_IN,  
  90. .priority = NF_IP_PRI_CONNTRACK_CONFIRM,  
  91. };  
  92.    

然后在nf_conntrack_l3proto_ipv4.cinit_or_cleanup里,通过nf_register_hook将以上的nf_hook_ops添加到nf_hooks[][]数组的相应的链表中。

 

3.三层协议、四层协议中连接模块相关的全局变量定义

3.1 nf_conntrack_l3proto_ipv4 

定义ipv4协议相关的nf_conntrack_l3proto变量,其中比较重要的是ipv4_pkt_to_tuple

ipv4_invert_tupleipv4_tuple_to_nfattr、 ipv4_nfattr_to_tuple。下面一一分析之。

[cpp]  view plain copy print ?
  1. struct nf_conntrack_l3proto nf_conntrack_l3proto_ipv4 = {  
  2. .l3proto  = PF_INET,  
  3. .name  = "ipv4",  
  4. .pkt_to_tuple  = ipv4_pkt_to_tuple,  
  5. .invert_tuple  = ipv4_invert_tuple,  
  6. .print_tuple  = ipv4_print_tuple,  
  7. .print_conntrack = ipv4_print_conntrack,  
  8. .prepare  = ipv4_prepare,  
  9. .get_features  = ipv4_get_features,  
  10. #if defined(CONFIG_NF_CT_NETLINK) || \  
  11.     defined(CONFIG_NF_CT_NETLINK_MODULE)  
  12. .tuple_to_nfattr = ipv4_tuple_to_nfattr,  
  13. .nfattr_to_tuple = ipv4_nfattr_to_tuple,  
  14. #endif  
  15. .me  = THIS_MODULE,  
  16. };  

3.1.1 ipv4_pkt_to_tuple

功能:根据ip头部获取源ip地址与目的ip地址,并写入tuple变量中。

[cpp]  view plain copy print ?
  1. static int ipv4_pkt_to_tuple(const struct sk_buff *skb, unsigned int nhoff,  
  2.      struct nf_conntrack_tuple *tuple)  
  3. {  
  4. u_int32_t _addrs[2], *ap;  
  5. ap = skb_header_pointer(skb, nhoff + offsetof(struct iphdr, saddr),  
  6. sizeof(u_int32_t) * 2, _addrs);  
  7. if (ap == NULL)  
  8. return 0;  
  9.    
  10. tuple->src.u3.ip = ap[0];  
  11. tuple->dst.u3.ip = ap[1];  
  12.    
  13. return 1;  
  14. }  

3.1.2 ipv4_invert_tuple

功能:根据原始的tuple的源ip、目的ip值,设置replytuple值,新的tuple值的源、目的ip值与原始的tuple的源ip、目的ip值是反过来的。

 

[cpp]  view plain copy print ?
  1. static int ipv4_invert_tuple(struct nf_conntrack_tuple *tuple,  
  2.    const struct nf_conntrack_tuple *orig)  
  3. {  
  4. tuple->src.u3.ip = orig->dst.u3.ip;  
  5. tuple->dst.u3.ip = orig->src.u3.ip;  
  6.    
  7. return 1;  
  8. }  
  9.    

3.1.3 ipv4_prepare

/*

1. 计算数据包的三层数据部分相对于skb->data的偏移量。

2.获取四层协议的协议号

*/

[cpp]  view plain copy print ?
  1. static int  
  2. ipv4_prepare(struct sk_buff **pskb, unsigned int hooknum, unsigned int *dataoff,  
  3.      u_int8_t *protonum)  
  4. {  
  5. /* Never happen */  
  6. if ((*pskb)->nh.iph->frag_off & htons(IP_OFFSET)) {  
  7. if (net_ratelimit()) {  
  8. printk(KERN_ERR "ipv4_prepare: Frag of proto %u (hook=%u)\n",  
  9. (*pskb)->nh.iph->protocol, hooknum);  
  10. }  
  11. return -NF_DROP;  
  12. }  
  13. *dataoff = (*pskb)->nh.raw - (*pskb)->data + (*pskb)->nh.iph->ihl*4;  
  14. /*获取四层协议号*/  
  15. *protonum = (*pskb)->nh.iph->protocol;  
  16.    
  17. return NF_ACCEPT;  
  18. }  
  19.    

3.1.4 ipv4_tuple_to_nfattr

 

功能:tuple结构中的三层源ip、目的ip地址按照nfnetlink规定的形式进行填充

[cpp]  view plain copy print ?
  1. static int ipv4_tuple_to_nfattr(struct sk_buff *skb,  
  2. const struct nf_conntrack_tuple *tuple)  
  3. {  
  4. NFA_PUT(skb, CTA_IP_V4_SRC, sizeof(u_int32_t),  
  5. &tuple->src.u3.ip);  
  6. NFA_PUT(skb, CTA_IP_V4_DST, sizeof(u_int32_t),  
  7. &tuple->dst.u3.ip);  
  8. return 0;  
  9.    
  10. nfattr_failure:  
  11. return -1;  
  12. }  

3.1.5 ipv4_nfattr_to_tuple

功能:nfnetlink消息传递过来的变量,转换成tuple结构中的三层源ip、目的ip地址,

[cpp]  view plain copy print ?
  1. static int ipv4_nfattr_to_tuple(struct nfattr *tb[],  
  2. struct nf_conntrack_tuple *t)  
  3. {  
  4. if (!tb[CTA_IP_V4_SRC-1] || !tb[CTA_IP_V4_DST-1])  
  5. return -EINVAL;  
  6.    
  7. if (nfattr_bad_size(tb, CTA_IP_MAX, cta_min_ip))  
  8. return -EINVAL;  
  9.    
  10. t->src.u3.ip =  
  11. *(u_int32_t *)NFA_DATA(tb[CTA_IP_V4_SRC-1]);  
  12. t->dst.u3.ip =  
  13. *(u_int32_t *)NFA_DATA(tb[CTA_IP_V4_DST-1]);  
  14.    
  15. return 0;  
  16. }  
  17.    

然后在nf_conntrack_l3proto_ipv4.cinit_or_cleanup里,通过nf_conntrack_l3proto_registernf_conntrack_l3proto_ipv4的地址存放在nf_ct_l3protos[PF_INET]

 

3.2 nf_conntrack_protocol_tcp4

定义tcp协议相关的nf_conntrack_protocol 变量,其中比较重要的是tcp_pkt_to_tuple

 tcp_invert_tuplenf_ct_port_tuple_to_nfattr、 nf_ct_port_nfattr_to_tuple。这几个函数实现的功能与nf_conntrack_l3proto_ipv4中相应的函数相似,只不过ipv4中针对的是ip地址,此处针对的是端口号而已。

而函数tcp_packet主要是针对tcp协议的状态变化而定义的,主要用于状态防火墙的。

[cpp]  view plain copy print ?
  1. struct nf_conntrack_protocol nf_conntrack_protocol_tcp4 =  
  2. {  
  3. .l3proto = PF_INET,  
  4. .proto  = IPPROTO_TCP,  
  5. .name  = "tcp",  
  6. .pkt_to_tuple  = tcp_pkt_to_tuple,  
  7. .invert_tuple  = tcp_invert_tuple,  
  8. .print_tuple  = tcp_print_tuple,  
  9. .print_conntrack  = tcp_print_conntrack,  
  10. .packet  = tcp_packet,  
  11. .new  = tcp_new,  
  12. .error = tcp_error4,  
  13. #if defined(CONFIG_NF_CT_NETLINK) || \  
  14.     defined(CONFIG_NF_CT_NETLINK_MODULE)  
  15. .to_nfattr = tcp_to_nfattr,  
  16. .from_nfattr = nfattr_to_tcp,  
  17. .tuple_to_nfattr = nf_ct_port_tuple_to_nfattr,  
  18. .nfattr_to_tuple = nf_ct_port_nfattr_to_tuple,  
  19. #endif  
  20. };  
  21.    

然后在nf_conntrack_l3proto_ipv4.cinit_or_cleanup里,通过nf_conntrack_protocol_registernf_conntrack_protocol_tcp4的地址存放在nf_ct_protos[PF_INET][TCP]

 

至此,分析完了连接跟踪模块的初始化代码部分。


三、 ip层netfilter的连接跟踪模块代码分析


上一节分析了连接跟踪模块相关的初始化代码,本节分析连接中hook函数。在分析之前,我们再次回顾一下数据包在连接跟踪模块中的走向。

 

发往本地的数据:

对于连接跟踪来说,只在hookNF_IP_PRE_ROUTINGNF_IP_LOCAL_IN两个hook点进入到连接跟踪模块,按优先级顺序,依次调用

ipv4_conntrack_defrag->ipv4_conntrack_in->ipv4_conntrack_help->ipv4_confirm,其中ipv4_conntrack_defrag主要是对分段数据包进行重组操作;接着调用ipv4_conntrack_in为数据包的nfct指针进行赋值以及更新连接跟踪项的状态等;然后调用ipv4_conntrack_help执行数据包对应的连接跟踪项的helper指针,实现创建期望连接或者ALG等功能;最后调用ipv4_confirm将一个未确认的连接跟踪项进行确认操作,并加入确认链表中。

 ip层netfilter的连接跟踪模块_第2张图片

本地转发的数据:

对于连接跟踪来说,只在hookNF_IP_PRE_ROUTINGNF_IP_POST_ROUTING两个hook点进入到连接跟踪模块,按优先级顺序,依次调用

ipv4_conntrack_defrag->ipv4_conntrack_in->ipv4_conntrack_help->ipv4_confirm,我们发现调用的函数与发往本机的数据包调用的函数及顺序是一样的。

 ip层netfilter的连接跟踪模块_第3张图片

本机发出的数据:

对于连接跟踪来说,只在hookNF_IP_LOCALNF_IP_POST_ROUTING两个hook点进入到连接跟踪模块,按优先级顺序,依次调用

ipv4_conntrack_defrag->ipv4_conntrack_local->ipv4_conntrack_help->ipv4_confirm,我们发现调用的函数与发往本机的数据包调用的函数及顺序相比,只有ipv4_conntrack_local与ipv4_conntrack_in不一样,相比于ipv4_conntrack_in,ipv4_conntrack_local增加了对数据包大小的判断。

 ip层netfilter的连接跟踪模块_第4张图片

  

下面我们就对这些函数进行分析。

 

1.ipv4_conntrack_defrag

功能:对分段数据包实现数据重组操作。

主要是调用函数 nf_ct_ipv4_gather_frags实现数据包重组。

[cpp]  view plain copy print ?
  1. static unsigned int ipv4_conntrack_defrag(unsigned int hooknum,  
  2.   struct sk_buff **pskb,  
  3.   const struct net_device *in,  
  4.   const struct net_device *out,  
  5.   int (*okfn)(struct sk_buff *))  
  6. {  
  7. #if !defined(CONFIG_IP_NF_NAT) && !defined(CONFIG_IP_NF_NAT_MODULE)  
  8. /* Previously seen (loopback)?  Ignore.  Do this before 
  9.    fragment check. */  
  10. if ((*pskb)->nfct)  
  11. return NF_ACCEPT;  
  12. #endif  
  13.    
  14. /* Gather fragments. */  
  15. if ((*pskb)->nh.iph->frag_off & htons(IP_MF|IP_OFFSET)) {  
  16. /*对数据包进行分段重组*/  
  17. *pskb = nf_ct_ipv4_gather_frags(*pskb,  
  18. hooknum == NF_IP_PRE_ROUTING ?  
  19. IP_DEFRAG_CONNTRACK_IN :  
  20. IP_DEFRAG_CONNTRACK_OUT);  
  21. if (!*pskb)  
  22. return NF_STOLEN;  
  23. }  
  24. return NF_ACCEPT;  
  25. }  
  26.    

1.1  nf_ct_ipv4_gather_frags
nf_ct_ipv4_gather_frags主要是通过函数ip_defrag实现分段重组的。

[cpp]  view plain copy print ?
  1. static struct sk_buff *  
  2. nf_ct_ipv4_gather_frags(struct sk_buff *skb, u_int32_t user)  
  3. {  
  4. skb_orphan(skb);  
  5.    
  6.         local_bh_disable();  
  7.         skb = ip_defrag(skb, user);  
  8.         local_bh_enable();  
  9.    
  10.         if (skb)  
  11. ip_send_check(skb->nh.iph);  
  12.    
  13.         return skb;  
  14. }  


2.ipv4_conntrack_in

这个数据主要实现以下功能:

a)当是一个新的数据包时,则为该数据包创建对应的连接跟踪项,创建成果后进入c

b)当已经为传递过来的数据包创建了连接跟踪项,则进入c

c)更新连接跟踪项的状态

d)为数据包的nfct指针赋值

该函数只是简单的调用nf_conntrack_in,看来实现上述功能的函数即是nf_conntrack_in

[cpp]  view plain copy print ?
  1. static unsigned int ipv4_conntrack_in(unsigned int hooknum,  
  2.       struct sk_buff **pskb,  
  3.       const struct net_device *in,  
  4.       const struct net_device *out,  
  5.       int (*okfn)(struct sk_buff *))  
  6. {  
  7. return nf_conntrack_in(PF_INET, hooknum, pskb);  
  8. }  
  9.    

该函数主要被PREROUTING、与LOCAL_OUT两个hook点时,才会被调用

功能实现对数据的连接跟踪,更新连接跟踪项的连接状态

目前就是根据五元组识别一条数据流

 

这个函数可以说是连接跟踪模块的重中之重,

首先它将数据包的类型按如下

几个类型处理:

1.当这是一个新的数据流的第一个数据包时,则会根据该数据包的五元组信息

   创建一个新的连接跟踪项,并初始化该连接跟踪项的tuple_hashhelper的值,最后

   将该连接跟踪项的原始方向的tuple_hash添加到unconfirmed链表中,且该连接不是期望 连接

2.当这是一个新的数据流的第一个数据包时,则会根据该数据包的五元组信息

   创建一个新的连接跟踪项,并初始化该连接跟踪项的tuple_hashhelper的值,最后

   将该连接跟踪项的原始方向的tuple_hash添加到unconfirmed链表中,且该连接是期望连 接

   

3. 当该数据包是原始方向的非第一个数据包,但当该数据包进入连接跟踪模块时,连接跟踪模块还没有收到reply方向的数据包

4.当该数据包是原始方向的非第一个数据包, 且到改数据包进入连接跟踪模块时,连接跟踪 模块已经接收到reply方向的数据包

5.当该数据包是reply方向的数据包。

 

主要也就对数据包进行上面五种分类

当是第一类数据包时,则创建一个新的连接跟踪项,并作相应的初始化工作,且

将数据包的nfctinfo设置为IP_CT_NEW

 

当是第二类数据包时,则创建一个新的连接跟踪项,并作相应的初始化工作,且

将数据包的nfctinfo设置为IP_CT_RELATED

 

当时第三类数据包时,将数据包的nfctinfo设置为IP_CT_NEW

 

当是第四种数据包时,将数据包的nfctinfo设置为IP_CT_ESTABLISHED

 

当时第五种状态时,将数据包的的nfctinfo设置为IP_CT_ESTABLISHED+IP_CT_IS_REPLY

 

然后再调用四层协议相关的packet函数,根据数据包的四层内容值,更新四层协议相关的

状态值(比如tcp协议,就会在其nf_conntrack_protocol->packet设置四层相关的状态改变)

 

当数据包是reply方向,且置位连接跟踪项的statusIPS_SEEN_REPLY_BIT后,发现IPS_SEEN_REPLY_BIT位原来的值为0,则会调用nf_conntrack_event_cache设置事件通知为IPCT_STATUS,在ipv4_confirm函数里,会调用函数nf_ct_deliver_cached_events

通过通知链的回调函数,将需要更新的status通过netlink机制发送给nfnetlink模块,由其再执行更新状态操作。

 最后,为skbnfct指针赋值,即将skbnfct指向该数据包对应的连接跟踪项,当数据包再进入NAT模式时,即可以根据这个指针获取到数据包对应的连接跟踪项,从而实现NAT相关的操作。

[cpp]  view plain copy print ?
  1. unsigned int  
  2. nf_conntrack_in(int pf, unsigned int hooknum, struct sk_buff **pskb)  
  3. {  
  4. struct nf_conn *ct;  
  5. enum ip_conntrack_info ctinfo;  
  6. struct nf_conntrack_l3proto *l3proto;  
  7. struct nf_conntrack_protocol *proto;  
  8. unsigned int dataoff;  
  9. u_int8_t protonum;  
  10. int set_reply = 0;  
  11. int ret;  
  12.    
  13. /* Previously seen (loopback or untracked)?  Ignore. */  
  14. /*如果数据包的nfct项不为空,则说明该数据包已经关联一个nf_conn项了 
  15. 有两种情况: 
  16. 1.数据在PRE_ROUTING的raw模块相关的hook函数时,将该数据包与un_conntrack关联了, 
  17.    因此就不再进行连接模块的跟踪了 
  18. 2.如果发送数据包的设备为loopback设备,说明数据包已经被跟踪一次了,此处也 
  19.    不再进行跟踪了。*/  
  20. if ((*pskb)->nfct) {  
  21. NF_CT_STAT_INC(ignore);  
  22. return NF_ACCEPT;  
  23. }  
  24.    
  25. /*根据三层协议类型,在nf_ct_l3protos数组中,获取三层协议的nf_conntrack_l3proto结构的实例*/  
  26. l3proto = __nf_ct_l3proto_find((u_int16_t)pf);  
  27. /* 
  28. 调用这个函数,主要有两个目的 
  29. a)获取skb buff中三层协议的数据部分相对于skb->data的偏移量 
  30. */  
  31. if ((ret = l3proto->prepare(pskb, hooknum, &dataoff, &protonum)) <= 0) {  
  32. DEBUGP("not prepared to track yet or error occured\n");  
  33. return -ret;  
  34. }  
  35.    
  36. /*根据三层协议类型与四层协议号,在全局数组nf_ct_protos中,获取四层协议的相应的nf_conntrack_protocol结构*/  
  37. proto = __nf_ct_proto_find((u_int16_t)pf, protonum);  
  38.    
  39. /* It may be an special packet, error, unclean... 
  40.  * inverse of the return code tells to the netfilter 
  41.  * core what to do with the packet. */  
  42.  /*根据四层协议数据结构nf_conntrack_protocol提供的error函数,对数据包进行四层格式检查等*/  
  43. if (proto->error != NULL &&  
  44.     (ret = proto->error(*pskb, dataoff, &ctinfo, pf, hooknum)) <= 0) {  
  45. NF_CT_STAT_INC(error);  
  46. NF_CT_STAT_INC(invalid);  
  47. return -ret;  
  48. }  
  49.    

[cpp]  view plain copy print ?
  1. /* 
  2. 功能: 
  3. 1、若存在一个符合条件的nf_conn,则返回该变量的地址 
  4. 2、若不存在,则根据已知条件创建一个nf_conn,并放入unconfirm表中 
  5. */  
  6. ct = resolve_normal_ct(*pskb, dataoff, pf, protonum, l3proto, proto,  
  7.        &set_reply, &ctinfo);  
  8. if (!ct) {  
  9. /* Not valid part of a connection */  
  10. NF_CT_STAT_INC(invalid);  
  11. return NF_ACCEPT;  
  12. }  
  13.    
  14. if (IS_ERR(ct)) {  
  15. /* Too stressed to deal. */  
  16. NF_CT_STAT_INC(drop);  
  17. return NF_DROP;  
  18. }  
  19.    
  20. NF_CT_ASSERT((*pskb)->nfct);  
  21. /* 
  22. 创建连接跟踪项后,再调用四层协议的packet函数进行处理。 
  23. */  
  24. ret = proto->packet(ct, *pskb, dataoff, ctinfo, pf, hooknum);  
  25. if (ret < 0) {  
  26. /* Invalid: inverse of the return code tells 
  27.  * the netfilter core what to do */  
  28. DEBUGP("nf_conntrack_in: Can't track with proto module\n");  
  29. nf_conntrack_put((*pskb)->nfct);  
  30. (*pskb)->nfct = NULL;  
  31. NF_CT_STAT_INC(invalid);  
  32. return -ret;  
  33. }  
  34. /*对于一个新创建的连接跟踪项后,当我们第一次收取到reply方向的数据包后, 
  35. 则会设置nf_conn->status的IPS_SEEN_REPLY_BIT位为1,当设置成功且IPS_SEEN_REPLY_BIT位的原来值为0时,则调用nf_conntrack_event_cache ,由nfnetlink模块处理状态改变的事件。 
  36. */  
  37. if (set_reply && !test_and_set_bit(IPS_SEEN_REPLY_BIT, &ct->status))  
  38. nf_conntrack_event_cache(IPCT_STATUS, *pskb);  
  39.    
  40. return ret;  
  41. }  

该函数的流程图如下,其中__nf_ct_l3proto_find就是发现该数据包对应的三层协议数据结构,对于ipv4就是我们上一节所说的nf_conntrack_l3proto_ipv4

而l3proto->prepare就是nf_conntrack_l3proto_ipv4 ipv4_prepare函数;

__nf_ct_proto_find就是找到该数据包对应的四层协议对应的数据结构,对于tcp就是变量nf_conntrack_protocol_tcp4;

resolve_normal_ct函数实现连接跟踪项的创建以及连接跟踪项状态改变等操作;

proto->packet函数用于更新该连接跟踪项对应的四层协议的状态,对于tcp则是函数 tcp_packet。

 

由此,我们知道resolve_normal_ct是连接跟踪模块中协议无关处理函数,且对连接跟踪模块来说非常的重要,我们分析一下。

 

 ip层netfilter的连接跟踪模块_第5张图片

 

 

 

2.1 resolve_normal_ct

该函数的功能是

a)若连接跟踪项不存在时,则根据已知条件创建一个nf_conn,并放入unconfirm表中,执行c

b)若连接跟踪项已存在,则执行c

c)更新连接跟踪项的状态

d)设置传入数据包的nfct指针

[cpp]  view plain copy print ?
  1. static inline struct nf_conn *  
  2. resolve_normal_ct(struct sk_buff *skb,  
  3.   unsigned int dataoff,  
  4.   u_int16_t l3num,  
  5.   u_int8_t protonum,  
  6.   struct nf_conntrack_l3proto *l3proto,  
  7.   struct nf_conntrack_protocol *proto,  
  8.   int *set_reply,  
  9.   enum ip_conntrack_info *ctinfo)  
  10. {  
  11. struct nf_conntrack_tuple tuple;  
  12. struct nf_conntrack_tuple_hash *h;  
  13. struct nf_conn *ct;  
  14. /*根据三、四层协议的nf_conntrack结构变量及skb,为该数据包计算相应的 
  15. nf_conntrack_tuple值*/  
  16. if (!nf_ct_get_tuple(skb, (unsigned int)(skb->nh.raw - skb->data),  
  17.      dataoff, l3num, protonum, &tuple, l3proto,  
  18.      proto)) {  
  19. DEBUGP("resolve_normal_ct: Can't get tuple\n");  
  20. return NULL;  
  21. }  
  22.    
  23. /* look for tuple match */  
  24. /*根据上面获取的nf_conntrack_tuple变量,在hash数组nf_conntrack_hash中相应的hash链表中 
  25. 查找是否有满足条件的nf_conntrack_tuple_hash*/  
  26. h = nf_conntrack_find_get(&tuple, NULL);  
  27. if (!h) {  
  28. /*没有找到符合条件的nf_conntrack_tuple_hash,则调用init_conntrack创建一个新的 
  29. nf_conntrack_tuple_hash,并对其helper指针进行赋值*/  
  30. h = init_conntrack(&tuple, l3proto, proto, skb, dataoff);  
  31. if (!h)  
  32. return NULL;  
  33. if (IS_ERR(h))  
  34. return (void *)h;  
  35. }  
  36. /*根据tuple值,获取nf_conn*/  
  37. ct = nf_ct_tuplehash_to_ctrack(h);  
  38.    
  39. /* 
  40. 根据tuple的dst.dir,确定当前进来的数据包对应于连接跟踪项的哪一个方向, 
  41. 然后设置set_reply的值, 
  42. */  
  43. /* It exists; we have (non-exclusive) reference. */  
  44. if (NF_CT_DIRECTION(h) == IP_CT_DIR_REPLY) {  
  45. /*当是reply方向的数据包时,则skb->nfctinfo可以设置为IP_CT_ESTABLISHED + IP_CT_IS_REPLY, 
  46. 同时需要将连接跟踪项的status的IPS_SEEN_REPLY_BIT位置为1。 
  47. */  
  48. *ctinfo = IP_CT_ESTABLISHED + IP_CT_IS_REPLY;  
  49. /* Please set reply bit if this packet OK */  
  50. *set_reply = 1;  
  51. else {  
  52. /* Once we've had two way comms, always ESTABLISHED. */  
  53. /*当是连接跟踪项原始方向的数据包时,则有以下几种情况 
  54. 1.当发送的数据包到达连接跟踪模块时,其reply方向没有收到对应的数据包之前 
  55.   a)连接跟踪项不是期望连接,此时将skb->nfctinfo设置为IP_CT_NEW 
  56.   b)连接跟踪项是期望连接,此时将skb->nfctinfo设置为IP_CT_RELATED 
  57. 2.当原始方向发送的数据包到达连接跟踪模块时,其reply方向已经收到过对应的数据包 
  58.   即连接跟踪项的状态的IPS_SEEN_REPLY_BIT位已经置位了。 
  59.   此时将skb->nfctinfo设置为IP_CT_ESTABLISHED 
  60. */  
  61. if (test_bit(IPS_SEEN_REPLY_BIT, &ct->status)) {  
  62. DEBUGP("nf_conntrack_in: normal packet for %p\n", ct);  
  63. *ctinfo = IP_CT_ESTABLISHED;  
  64. else if (test_bit(IPS_EXPECTED_BIT, &ct->status)) {  
  65. DEBUGP("nf_conntrack_in: related packet for %p\n", ct);  
  66. *ctinfo = IP_CT_RELATED;  
  67. else {  
  68. DEBUGP("nf_conntrack_in: new packet for %p\n", ct);  
  69. *ctinfo = IP_CT_NEW;  
  70. }  
  71. *set_reply = 0;  
  72. }  
  73. skb->nfct = &ct->ct_general;  
  74. skb->nfctinfo = *ctinfo;  
  75. return ct;  
  76. }  
  77.    

其中nf_ct_get_tuple主要是根据数据包的三层、四层协议获取相应的五元组值;

nf_conntrack_find_get主要是根据传入的tuple变量,在hash数组nf_conntrack_hash中相应的hash链表中查找是否有满足条件的nf_conntrack_tuple_hash,对于一个已确认的连接跟踪项,是能根据其tuple值在hash数组nf_conntrack_hash中相应的hash链表中查找到满足条件的nf_conntrack_tuple_hash变量的,若没有查找到,则说明还没有创建该tuple变量对应的连接跟踪项。

当连接跟踪项没有创建时,则会调用函数init_conntrack创建连接跟踪项;

接着调用nf_ct_tuplehash_to_ctrack获取相应的连接跟踪项;

然后根据数据包对应于连接跟踪项的方向,设置连接跟踪项的状态;

最后设置skb->nfctinfoskb->nfct

下面一一分析上面使用到的函数。

2.1.1 nf_ct_get_tuple

[cpp]  view plain copy print ?
  1. int  
  2. nf_ct_get_tuple(const struct sk_buff *skb,  
  3. unsigned int nhoff,  
  4. unsigned int dataoff,  
  5. u_int16_t l3num,  
  6. u_int8_t protonum,  
  7. struct nf_conntrack_tuple *tuple,  
  8. const struct nf_conntrack_l3proto *l3proto,  
  9. const struct nf_conntrack_protocol *protocol)  
  10. {  
  11. NF_CT_TUPLE_U_BLANK(tuple);  
  12.    
  13. tuple->src.l3num = l3num;  
  14. /* 
  15. 调用函数ipv4_pkt_to_tuple,设置tuple结构的源、目的ip地址,对于ipv4,则是调用函数ipv4_pkt_to_tuple 
  16. */  
  17. if (l3proto->pkt_to_tuple(skb, nhoff, tuple) == 0)  
  18. return 0;  
  19.    
  20. /* 
  21. 调用四层函数l4_pkt_to_tuple,设置tuple结构中的四层相关的源、目的值。 */  
  22. tuple->dst.protonum = protonum;  
  23. tuple->dst.dir = IP_CT_DIR_ORIGINAL;  
  24. /* 
  25. 调用四层函数l4_pkt_to_tuple,设置tuple结构中的四层相关的源、目的值。对于tcp协议来说,则是调用函数tcp_pkt_to_tuple 
  26. */  
  27. return protocol->pkt_to_tuple(skb, dataoff, tuple);  
  28. }  
  29.    

2.1.2 nf_conntrack_find_get

该函数的作用是主要是根据传递的nf_conntrack_tuple类型的变量,在nf_conntrack_hash[]中相关的链表中,查找是否有满足要求的nf_conntrack_tuple_hash类型的变量。

nf_conntrack_tuple_hash查找函数

该函数主要是__nf_conntrack_find的封装函数,相比于__nf_conntrack_find,增加了锁机制。

[cpp]  view plain copy print ?
  1. struct nf_conntrack_tuple_hash *  
  2. nf_conntrack_find_get(const struct nf_conntrack_tuple *tuple,  
  3.       const struct nf_conn *ignored_conntrack)  
  4. {  
  5. struct nf_conntrack_tuple_hash *h;  
  6.    
  7. read_lock_bh(&nf_conntrack_lock);  
  8. h = __nf_conntrack_find(tuple, ignored_conntrack);  
  9. if (h)  
  10. atomic_inc(&nf_ct_tuplehash_to_ctrack(h)->ct_general.use);  
  11. read_unlock_bh(&nf_conntrack_lock);  
  12.    
  13. return h;  
  14. }  

那我们就分析下__nf_conntrack_find

nf_conntrack_tuple_hash查找函数

1、根据nf_conntrack_tuple计算hash

2、根据hash值,从数组nf_conntrack_hash中取出相应的hash链表

3、遍历hash链表,查找是否存在nf_conntrack_tuple变量相关的nf_conntrack_tuple_hash

若存在则返回相应的值;若不存在,则返回NULL

[cpp]  view plain copy print ?
  1. struct nf_conntrack_tuple_hash *  
  2. __nf_conntrack_find(const struct nf_conntrack_tuple *tuple,  
  3.     const struct nf_conn *ignored_conntrack)  
  4. {  
  5. struct nf_conntrack_tuple_hash *h;  
  6. unsigned int hash = hash_conntrack(tuple);  
  7.    
  8. ASSERT_READ_LOCK(&nf_conntrack_lock);  
  9. list_for_each_entry(h, &nf_conntrack_hash[hash], list) {  
  10. if (conntrack_tuple_cmp(h, tuple, ignored_conntrack)) {  
  11. NF_CT_STAT_INC(found);  
  12. return h;  
  13. }  
  14. NF_CT_STAT_INC(searched);  
  15. }  
  16.    
  17. return NULL;  
  18. }  
  19.    

如果找到相应的tuple_hash,也就是找到相应的数据连接跟踪项。因为我们可以根据

nf_ct_tuplehash_to_ctrack函数,找到tuple_hash所属的数据连接跟踪项,其实就是通过contain_of实现的。

2.1.3 init_conntrack

这一次分析init_conntrack函数,这个函数包含的信息量也是蛮大的。这个函数主要实现以下功能:

A)调用函数__nf_conntrack_alloc创建一个数据连接跟踪项

B)根据刚创建的连接跟踪项的原始方向的nf_conntrack_tuple变量,查找期望连接链表,看能否找到符合条件的期望连接跟踪。

若查找到,则更新新创建连接跟踪项的master指针,以及调用exp->expectfn

若没有查找到:

则遍历helpers链表,找到符合条件的nf_conntrack_helper变量后,则更新连接跟踪项的helper指针。

   C)将新创建的连接跟踪项插入到unconfirmed链表中。

对于连接跟踪项,是使用定时器超时机制来实现异步垃圾回收的。所以也要对连接跟踪项的垃圾回收机制进行分析下。

那我们先分析下这个函数,具体的代码分析如下:

 

功能:根据nf_conntrack_tuple结构变量、nf_conntrack_l3protonf_conntrack_protocol结构变量创建一个新的nf_conntrack_tuple_hash变量

[cpp]  view plain copy print ?
  1. static struct nf_conntrack_tuple_hash *  
  2. init_conntrack(const struct nf_conntrack_tuple *tuple,  
  3.        struct nf_conntrack_l3proto *l3proto,  
  4.        struct nf_conntrack_protocol *protocol,  
  5.        struct sk_buff *skb,  
  6.        unsigned int dataoff)  
  7. {  
  8. struct nf_conn *conntrack;  
  9. struct nf_conntrack_tuple repl_tuple;  
  10. struct nf_conntrack_expect *exp;  
  11. /*根据入口nf_conntrack_tuple变量和三层、四层nf_conntrack_proto变量的invert函数构造出口nf_conntrack_tuple变量,对于ipv4来说,就是ipv4_invert_tuple,对于tcp协议来说即是 tcp_invert_tuple*/  
  12. if (!nf_ct_invert_tuple(&repl_tuple, tuple, l3proto, protocol)) {  
  13. DEBUGP("Can't invert tuple.\n");  
  14. return NULL;  
  15. }  
  16. /*为新的nf_conn申请内存,并根据入口、出口tuple以及三层nf_conntrack_prot进行初始*/  
  17. conntrack = __nf_conntrack_alloc(tuple, &repl_tuple, l3proto);  
  18. if (conntrack == NULL || IS_ERR(conntrack)) {  
  19. DEBUGP("Can't allocate conntrack.\n");  
  20. return (struct nf_conntrack_tuple_hash *)conntrack;  
  21. }  
  22. /*调用四层nf_conntrack_prot的new函数为新的nf_conn进行四层初始化*/  
  23. if (!protocol->new(conntrack, skb, dataoff)) {  
  24. nf_conntrack_free(conntrack);  
  25. DEBUGP("init conntrack: can't track with proto module\n");  
  26. return NULL;  
  27. }  
  28.    
  29. write_lock_bh(&nf_conntrack_lock);  
  30. /*在nf_conntrack_expect_list链表中,查找新建的nf_conn是否为一个已建立的nf_conn的期望连接*/  
  31. exp = find_expectation(tuple);  
  32. if (exp) {  
  33. DEBUGP("conntrack: expectation arrives ct=%p exp=%p\n",  
  34. conntrack, exp);  
  35. /* Welcome, Mr. Bond.  We've been expecting you... */  
  36. /*若是期望链接,则更新链接的状态,并对master指针进行赋值。并 
  37. 增加对主数据连接跟踪项的引用计数*/  
  38. __set_bit(IPS_EXPECTED_BIT, &conntrack->status);  
  39. conntrack->master = exp->master;  
  40. #ifdef CONFIG_NF_CONNTRACK_MARK  
  41. conntrack->mark = exp->master->mark;  
  42. #endif  
  43. nf_conntrack_get(&conntrack->master->ct_general);  
  44. NF_CT_STAT_INC(expect_new);  
  45. else {  
  46. /*若不是期望链接,则调用__nf_ct_helper_find,从链表helpers中查找符合条件的helper函数*/  
  47. conntrack->helper = __nf_ct_helper_find(&repl_tuple);  
  48.    
  49. NF_CT_STAT_INC(new);  
  50.         }  
  51. /*conntrack->tuplehash添加到unconfirmed链表中*/  
  52. /* Overload tuple linked list to put us in unconfirmed list. */  
  53. list_add(&conntrack->tuplehash[IP_CT_DIR_ORIGINAL].list, &unconfirmed);  
  54.    
  55. write_unlock_bh(&nf_conntrack_lock);  
  56. if (exp) {  
  57. if (exp->expectfn)  
  58. exp->expectfn(conntrack, exp);  
  59. nf_conntrack_expect_put(exp);  
  60. }  
  61.    
  62. return &conntrack->tuplehash[IP_CT_DIR_ORIGINAL];  
  63. }  

2.1.2.1__nf_conntrack_alloc

接下来分析这个函数,其功能如下:

1.调用kmem_cache_alloc,创建一个新的连接跟踪项

2.设置连接跟踪项的originreply方向的tuple

3.初始化连接跟踪项的定时器,设置超时处理函数为death_by_timeout

4.设置连接跟踪项的销毁函数为destroy_conntrack

5.全局连接跟踪项统计值nf_conntrack_count1

[cpp]  view plain copy print ?
  1. static struct nf_conn *  
  2. __nf_conntrack_alloc(const struct nf_conntrack_tuple *orig,  
  3.      const struct nf_conntrack_tuple *repl,  
  4.      const struct nf_conntrack_l3proto *l3proto)  
  5. {  
  6. struct nf_conn *conntrack = NULL;  
  7. u_int32_t features = 0;  
  8.    
  9. if (!nf_conntrack_hash_rnd_initted) {  
  10. get_random_bytes(&nf_conntrack_hash_rnd, 4);  
  11. nf_conntrack_hash_rnd_initted = 1;  
  12. }  
  13.    
  14. /*若当前以创建的连接跟踪项已经超过了最大值了, 
  15. 则根据要创建的连接的origin方向的tuple变量计算hash值为h,然后 
  16. 调用early_drop,遍历链表nf_conntrack_hash[h],对于连接跟踪项的status的IPS_ASSURED_BIT位没有被置位的连接跟踪项,则强制删除。 
  17. 最后若early_drop返回0,则没有找到可删除的项,程序返回创建连接跟踪项失败; 
  18. 若early_drop 返回1,则说明已经删除了一个连接跟踪项,则可以继续创建新的 
  19. 连接跟踪项。 
  20. */  
  21. if (nf_conntrack_max  
  22.     && atomic_read(&nf_conntrack_count) >= nf_conntrack_max) {  
  23. unsigned int hash = hash_conntrack(orig);  
  24. /* Try dropping from this hash chain. */  
  25. if (!early_drop(&nf_conntrack_hash[hash])) {  
  26. if (net_ratelimit())  
  27. printk(KERN_WARNING  
  28.        "nf_conntrack: table full, dropping"  
  29.        " packet.\n");  
  30. return ERR_PTR(-ENOMEM);  
  31. }  
  32. }  
  33.    
  34. /*  find features needed by this conntrack. */  
  35. features = l3proto->get_features(orig);  
  36. read_lock_bh(&nf_conntrack_lock);  
  37. if (__nf_ct_helper_find(repl) != NULL)  
  38. features |= NF_CT_F_HELP;  
  39. read_unlock_bh(&nf_conntrack_lock);  
  40.    
  41. DEBUGP("nf_conntrack_alloc: features=0x%x\n", features);  
  42.    
  43. read_lock_bh(&nf_ct_cache_lock);  
  44.    
  45. if (!nf_ct_cache[features].use) {  
  46. DEBUGP("nf_conntrack_alloc: not supported features = 0x%x\n",  
  47. features);  
  48. goto out;  
  49. }  
  50.    
  51. conntrack = kmem_cache_alloc(nf_ct_cache[features].cachep, GFP_ATOMIC);  
  52. if (conntrack == NULL) {  
  53. DEBUGP("nf_conntrack_alloc: Can't alloc conntrack from cache\n");  
  54. goto out;  
  55. }  
  56.    
  57. memset(conntrack, 0, nf_ct_cache[features].size);  
  58. conntrack->features = features;  
  59. if (nf_ct_cache[features].init_conntrack &&  
  60.     nf_ct_cache[features].init_conntrack(conntrack, features) < 0) {  
  61. DEBUGP("nf_conntrack_alloc: failed to init\n");  
  62. kmem_cache_free(nf_ct_cache[features].cachep, conntrack);  
  63. conntrack = NULL;  
  64. goto out;  
  65. }  
  66.    
  67. atomic_set(&conntrack->ct_general.use, 1);  
  68. conntrack->ct_general.destroy = destroy_conntrack;  
  69. conntrack->tuplehash[IP_CT_DIR_ORIGINAL].tuple = *orig;  
  70. conntrack->tuplehash[IP_CT_DIR_REPLY].tuple = *repl;  
  71. /* Don't set timer yet: wait for confirmation */  
  72. init_timer(&conntrack->timeout);  
  73. conntrack->timeout.data = (unsigned long)conntrack;  
  74. conntrack->timeout.function = death_by_timeout;  
  75.    
  76. atomic_inc(&nf_conntrack_count);  
  77. out:  
  78. read_unlock_bh(&nf_ct_cache_lock);  
  79. return conntrack;  
  80. }  
  81.    

2.1.2.2 __nf_ct_helper_find

功能:根据传入的nf_conntrack_tuple类型的参数,查找符合条件的helper

static struct nf_conntrack_helper *

__nf_ct_helper_find(const struct nf_conntrack_tuple *tuple)

{

return LIST_FIND(&helpers, helper_cmp,

 struct nf_conntrack_helper *,

 tuple);

}

2.1.2.3 连接跟踪项的异步垃圾处理机制

ipv4_confirm中,对新的连接跟踪项进行确认时,会启动连接跟踪项的超时定时器,以后再收到数据包时,会在nf_conntrack_in中调用四层协议的packet函数里调用函数nf_ct_refresh_acct更新定时器的超时处理时间,这样的话,只要一个连接跟踪项对应的数据流一直有数据发收发,则连接跟踪项就不会超时,若在一定时间内没有再收到数据包,则会超时,从而调用超时处理函数,用于释放该数据连接跟踪项占用的内存。

所以我们就跟着超时处理函数,看下是如何释放连接跟踪项的内存的。

2.1.2.3.1death_by_timeout

连接跟踪项的超时处理函数

1. 调用clean_from_lists将该连接跟踪项的两个方向的tuple变量从ip_conntrack_hash[x]链表中删除

2. 调用nf_ct_put,自减nf_conn的引用计数,若自减后的引用计数为0,则调用

    nf_conn->ct_general->destroy销毁nf_conn变量

[cpp]  view plain copy print ?
  1. static void death_by_timeout(unsigned long ul_conntrack)  
  2. {  
  3. struct nf_conn *ct = (void *)ul_conntrack;  
  4.    
  5. write_lock_bh(&nf_conntrack_lock);  
  6. /* Inside lock so preempt is disabled on module removal path. 
  7.  * Otherwise we can get spurious warnings. */  
  8. NF_CT_STAT_INC(delete_list);  
  9. clean_from_lists(ct);  
  10. write_unlock_bh(&nf_conntrack_lock);  
  11. nf_ct_put(ct);  
  12. }  

可以看到是在nf_ct_put中触发的对连接跟踪项所占内存的释放

2.1.2.3.2nf_ct_put&nf_conntrack_put

/*

nf_conn的引用计数减一

*/

[cpp]  view plain copy print ?
  1. static inline void nf_ct_put(struct nf_conn *ct)  
  2. {  
  3. NF_CT_ASSERT(ct);  
  4. nf_conntrack_put(&ct->ct_general);  
  5. }  

/*

1.nfct的引用计数减一

2.若减一后的引用计数值为0,则调用nfct->destroy销毁nf_conn

*/

[cpp]  view plain copy print ?
  1. static inline void nf_conntrack_put(struct nf_conntrack *nfct)  
  2. {  
  3. if (nfct && atomic_dec_and_test(&nfct->use))  
  4. nfct->destroy(nfct);  
  5. }  

我们在函数__nf_conntrack_alloc知道nfct->destroy对应的函数为destroy_conntrack

继续分析

 

2.1.2.3.3 destroy_conntrack

/*

功能:销毁一个连接跟踪项

1.发送destory消息给nfnetlink模块,让nfnetlink模块执行该连接跟踪相关联的内容

2.调用连接跟踪项中三、四层协议相关的销毁函数

3.因为未确认的连接跟踪项是放在unconntrack链表中的,因此对于未确认的连接跟踪

   项,还需要将该连接跟踪项origin方向的tupleunconntrack链表上删除

4.若该连接为期望连接,调用nf_ct_put,减去对主连接的引用

5.调用nf_conntrack_free释放连接跟踪项占用的内存。

*/

[cpp]  view plain copy print ?
  1. static void  
  2. destroy_conntrack(struct nf_conntrack *nfct)  
  3. {  
  4. struct nf_conn *ct = (struct nf_conn *)nfct;  
  5. struct nf_conntrack_l3proto *l3proto;  
  6. struct nf_conntrack_protocol *proto;  
  7.    
  8. DEBUGP("destroy_conntrack(%p)\n", ct);  
  9. NF_CT_ASSERT(atomic_read(&nfct->use) == 0);  
  10. NF_CT_ASSERT(!timer_pending(&ct->timeout));  
  11.    
  12. nf_conntrack_event(IPCT_DESTROY, ct);  
  13. set_bit(IPS_DYING_BIT, &ct->status);  
  14.    
  15. /* To make sure we don't get any weird locking issues here: 
  16.  * destroy_conntrack() MUST NOT be called with a write lock 
  17.  * to nf_conntrack_lock!!! -HW */  
  18. l3proto = __nf_ct_l3proto_find(ct->tuplehash[IP_CT_DIR_REPLY].tuple.src.l3num);  
  19. if (l3proto && l3proto->destroy)  
  20. l3proto->destroy(ct);  
  21.    
  22. proto = __nf_ct_proto_find(ct->tuplehash[IP_CT_DIR_REPLY].tuple.src.l3num, ct->tuplehash[IP_CT_DIR_REPLY].tuple.dst.protonum);  
  23. if (proto && proto->destroy)  
  24. proto->destroy(ct);  
  25.    
  26. if (nf_conntrack_destroyed)  
  27. nf_conntrack_destroyed(ct);  
  28.    
  29. write_lock_bh(&nf_conntrack_lock);  
  30. /* Expectations will have been removed in clean_from_lists, 
  31.  * except TFTP can create an expectation on the first packet, 
  32.  * before connection is in the list, so we need to clean here, 
  33.  * too. */  
  34. nf_ct_remove_expectations(ct);  
  35.    
  36. /* We overload first tuple to link into unconfirmed list. */  
  37. /*若该连接还未被确认,则该连接跟踪项只有origin tuple添加到了unconntrack的 
  38. 链表上了,所以只删除origin方向的tuple连接*/  
  39. if (!nf_ct_is_confirmed(ct)) {  
  40. BUG_ON(list_empty(&ct->tuplehash[IP_CT_DIR_ORIGINAL].list));  
  41. list_del(&ct->tuplehash[IP_CT_DIR_ORIGINAL].list);  
  42. }  
  43.    
  44. NF_CT_STAT_INC(delete);  
  45. write_unlock_bh(&nf_conntrack_lock);  
  46.    
  47. if (ct->master)  
  48. nf_ct_put(ct->master);  
  49. /*调用nf_conntrack_free释放nf_conn变量占用的内存*/  
  50. DEBUGP("destroy_conntrack: returning ct=%p to slab\n", ct);  
  51. nf_conntrack_free(ct);  
  52. }  

至此,即为异步清理的整个过程。

 

我们在__nf_conntrack_alloc里有看到一个通过清理机制,那也分析下。

 

2.1.2.4 连接跟踪项的同步垃圾处理机制

同步垃圾处理是通过函数early_drop实现的。

/*

1.调用LIST_FIND_B遍历链表chain中的每一个tuple_hash,查找符合

   删除条件的tuple_hahs

2.若找到符合条件的tuple_hash,则获取该tuple_hash所属的连接跟踪项

3.调用death_by_timeout,强制删除该连接跟踪项

*/

[cpp]  view plain copy print ?
  1. static int early_drop(struct list_head *chain)  
  2. {  
  3. /* Traverse backwards: gives us oldest, which is roughly LRU */  
  4. struct nf_conntrack_tuple_hash *h;  
  5. struct nf_conn *ct = NULL;  
  6. int dropped = 0;  
  7.    
  8. read_lock_bh(&nf_conntrack_lock);  
  9. /*遍历链表chain,对每一个tuple_hash都调用函数unreplied,若unreplied返回1, 
  10. 则表明可以删除该tuple_hash对应的连接跟踪项*/  
  11. h = LIST_FIND_B(chain, unreplied, struct nf_conntrack_tuple_hash *);  
  12. if (h) {  
  13. ct = nf_ct_tuplehash_to_ctrack(h);  
  14. atomic_inc(&ct->ct_general.use);  
  15. }  
  16. read_unlock_bh(&nf_conntrack_lock);  
  17.    
  18. if (!ct)  
  19. return dropped;  
  20.    
  21. if (del_timer(&ct->timeout)) {  
  22. death_by_timeout((unsigned long)ct);  
  23. dropped = 1;  
  24. NF_CT_STAT_INC(early_drop);  
  25. }  
  26. nf_ct_put(ct);  
  27. return dropped;  
  28. }  

接着就是由nf_ct_put接管同步垃圾回收了,剩下的流程就会异步垃圾处理机制是一样的了。

 

至此,函数ipv4_conntrack_in的分析告一段落。

接下来我们分析ipv4_conntrack_help

 

3.ipv4_conntrack_help

这个函数还是比较简单的,首先根据传入的数据包;然后找到该数据包对应的数据连接跟踪项;接着若该数据连接跟踪项的helper指针不为空,则调用该helper结构对应的help函数,实现创建期望连接或者相关协议的ALG功能(即对应用层协议中的数据部分如果有ip地址,且该数据连接跟踪项对应的数据流有开启NAT机制时,则将应用层协议中的数据部分的ip地址也进行NAT操作)。

 

/*

1.skb->nfct中获取该数据包对应的连接跟踪项

2.如果该连接管跟踪项的helper指针不为0,则调用

  ct->helper->help执行该连接跟踪项对应的helper函数,用来创建

  期望连接或者对数据包的应用层携带的ip地址信息进行nat操作

*/

[cpp]  view plain copy print ?
  1. static unsigned int ipv4_conntrack_help(unsigned int hooknum,  
  2.       struct sk_buff **pskb,  
  3.       const struct net_device *in,  
  4.       const struct net_device *out,  
  5.       int (*okfn)(struct sk_buff *))  
  6. {  
  7. struct nf_conn *ct;  
  8. enum ip_conntrack_info ctinfo;  
  9.    
  10. /* This is where we call the helper: as the packet goes out. */  
  11. ct = nf_ct_get(*pskb, &ctinfo);  
  12. if (ct && ct->helper) {  
  13. unsigned int ret;  
  14. ret = ct->helper->help(pskb,  
  15.        (*pskb)->nh.raw - (*pskb)->data  
  16.        + (*pskb)->nh.iph->ihl*4,  
  17.        ct, ctinfo);  
  18. if (ret != NF_ACCEPT)  
  19. return ret;  
  20. }  
  21. return NF_ACCEPT;  

4.ipv4_confirm&nf_conntrack_confirm

进入到这个函数里的数据包,说明其连接跟踪项均已创建,此时需要做的就是对于还没有进行确认的连接跟踪项,实现确认操作,即是将该连接跟踪项对应的原始方向与应答方向的nf_conntrack_tuple_hash变量插入到nf_conntrack_hash[]数组对应的hash链表中。

/*

1.确认一个数据包对应的连接跟踪项

*/

[cpp]  view plain copy print ?
  1.  static unsigned int ipv4_confirm(unsigned int hooknum,  
  2.  struct sk_buff **pskb,  
  3.  const structnet_device *in,  
  4.  const struct net_device *out,  
  5.  int (*okfn)(struct sk_buff *))  
  6. {  
  7. /* We've seen it coming out the other side: confirm it */  
  8. return nf_conntrack_confirm(pskb);  
  9. }  

其实就是调用函数nf_conntrack_confirm,下面分析一下:

1.首先判断该数据连接跟踪项是否已经被确认,即是判断该连接跟踪项的status中的IPS_CONFIRMED_BIT位是否为1

2.若连接跟踪项尚未有确认,则调用函数__nf_conntrack_confirm进行确认

3.调用nf_ct_deliver_cached_events触发连接跟踪项对应的消息通知回调,判断是否需要向nfnetlink模块发送相应的消息。

[cpp]  view plain copy print ?
  1. static inline int nf_conntrack_confirm(struct sk_buff **pskb)  
  2. {  
  3. struct nf_conn *ct = (struct nf_conn *)(*pskb)->nfct;  
  4. int ret = NF_ACCEPT;  
  5.    
  6. if (ct) {  
  7. /*若一个连接跟踪项还没有被确认,则调用函数 
  8. __nf_conntrack_confirm对一个连接跟踪项执行确认操作。*/  
  9. if (!nf_ct_is_confirmed(ct))  
  10. ret = __nf_conntrack_confirm(pskb);  
  11. /*通过调用该函数,由通知block ctnl_notifier的回调函数决定是否将该事件发送出去*/  
  12. nf_ct_deliver_cached_events(ct);  
  13. }  
  14. return ret;  
  15. }  

4.1 __nf_conntrack_confirm

 

该函数主要确认一个连接跟踪项,确认操作只会发生在连接跟踪项的状态不为reply时。

 

1.调用CTINFO2DIR,判断是否是原始方向发送的数据包(仅对原始方向的数据包对应的

   连接跟踪项进行确认操作。)

   在

2. 1中判断通过后,分别计算originalreply方向上的tuple变量对应的hash值,分别

   为hashrepl_hash

3.   若连接跟踪项的originalreply方向上的tuple_hash变量均没有对应的nf_conntrack_hash[]链表中

    则首先将original方向上的tuple_hashunconntrack链表中删除,然后将连接跟踪项的original

    reply方向上的tuple_hash变量添加到相对应的nf_conntrack_hash[]链表中

    置位连接跟踪项的status中的IPS_CONFIRMED_BIT,并启动超时定时器,用于实现对连接

    跟踪项所占内存的超时回收功能。

    

*/

[cpp]  view plain copy print ?
  1. int  
  2. __nf_conntrack_confirm(struct sk_buff **pskb)  
  3. {  
  4. unsigned int hash, repl_hash;  
  5. struct nf_conn *ct;  
  6. enum ip_conntrack_info ctinfo;  
  7.    
  8. ct = nf_ct_get(*pskb, &ctinfo);  
  9.    
  10. /* ipt_REJECT uses nf_conntrack_attach to attach related 
  11.    ICMP/TCP RST packets in other direction.  Actual packet 
  12.    which created connection will be IP_CT_NEW or for an 
  13.    expected connection, IP_CT_RELATED. */  
  14.    
  15. /*该函数只对dir为IP_CT_DIR_ORIGINAL状态时的nf_conn进行confirm操作 
  16. 对于dir为IP_CT_DIR_REPLY状态的nf_conn,其状态已经设置过confirm,所以 
  17. 不需要操作*/  
  18. if (CTINFO2DIR(ctinfo) != IP_CT_DIR_ORIGINAL)  
  19. return NF_ACCEPT;  
  20.    
  21. /*根据tuple值获取来、去的hash值*/  
  22. hash = hash_conntrack(&ct->tuplehash[IP_CT_DIR_ORIGINAL].tuple);  
  23. repl_hash = hash_conntrack(&ct->tuplehash[IP_CT_DIR_REPLY].tuple);  
  24.    
  25. /* We're not in hash table, and we refuse to set up related 
  26.    connections for unconfirmed conns.  But packet copies and 
  27.    REJECT will give spurious warnings here. */  
  28. /* NF_CT_ASSERT(atomic_read(&ct->ct_general.use) == 1); */  
  29.    
  30. /* No external references means noone else could have 
  31.    confirmed us. */  
  32. NF_CT_ASSERT(!nf_ct_is_confirmed(ct));  
  33. DEBUGP("Confirming conntrack %p\n", ct);  
  34.    
  35. write_lock_bh(&nf_conntrack_lock);  
  36.    
  37. /* See if there's one in the list already, including reverse: 
  38.    NAT could have grabbed it without realizing, since we're 
  39.    not in the hash.  If there is, we lost race. */  
  40.    
  41. /* 
  42.   
  43. 若在nf_conntrack_hash链表中没有发现符合条件的nf_conn 
  44. 则说明该nf_conn还没有在nf_conntrack_hash表中。 
  45. a)则需要将该nf_conn从unconfirmed链表中删除,并加入到nf_conntrack_hash 
  46. 链表中 
  47. b)设置nf_conn超时时间并启动定时器 
  48. c)设置该nf_conn的状态为confirm状态 
  49. d)若对于该nf_conn,有设置SNAT/DNAT操作,则设置event状态为IPCT_NATINFO, 
  50.    用于netlink通知链处理 
  51. e)若该nf_conn链接为一个期望链接,则设置event状态为IPCT_RELATED,否则 
  52.    设置为IPCT_NEW 
  53.    对于上面的d)、e)只是设置了event状态,而并没有将改事件通知给netlink, 
  54.    主要是在nf_conntrack_confirm的最后通过调用nf_ct_deliver_cached_events,才将事件通知发送给netlink的nf_conntrack_chain,由该通知链的回调函数根据事件类型,设置相应的组并决定是否将事件通知发送出去。并根据连接跟踪项的status中IPCT_HELPER、IPS_SRC_NAT_DONE_BIT、IPS_SRC_NAT_DONE_BIT的值,来决定是否向nfnetlink模块发送消息。 
  55. */     
  56.    
  57. if (!LIST_FIND(&nf_conntrack_hash[hash],  
  58.        conntrack_tuple_cmp,  
  59.        struct nf_conntrack_tuple_hash *,  
  60.        &ct->tuplehash[IP_CT_DIR_ORIGINAL].tuple, NULL)  
  61.     && !LIST_FIND(&nf_conntrack_hash[repl_hash],  
  62.   conntrack_tuple_cmp,  
  63.   struct nf_conntrack_tuple_hash *,  
  64.   &ct->tuplehash[IP_CT_DIR_REPLY].tuple, NULL)) {  
  65. /* Remove from unconfirmed list */  
  66. list_del(&ct->tuplehash[IP_CT_DIR_ORIGINAL].list);  
  67.    
  68. __nf_conntrack_hash_insert(ct, hash, repl_hash);  
  69. /* Timer relative to confirmation time, not original 
  70.    setting time, otherwise we'd get timer wrap in 
  71.    weird delay cases. */  
  72. ct->timeout.expires += jiffies;  
  73. add_timer(&ct->timeout);  
  74. atomic_inc(&ct->ct_general.use);  
  75. set_bit(IPS_CONFIRMED_BIT, &ct->status);  
  76. NF_CT_STAT_INC(insert);  
  77. write_unlock_bh(&nf_conntrack_lock);  
  78. if (ct->helper)  
  79. nf_conntrack_event_cache(IPCT_HELPER, *pskb);  
  80. #ifdef CONFIG_NF_NAT_NEEDED  
  81. if (test_bit(IPS_SRC_NAT_DONE_BIT, &ct->status) ||  
  82.     test_bit(IPS_DST_NAT_DONE_BIT, &ct->status))  
  83. nf_conntrack_event_cache(IPCT_NATINFO, *pskb);  
  84. #endif  
  85. nf_conntrack_event_cache(master_ct(ct) ?  
  86.  IPCT_RELATED : IPCT_NEW, *pskb);  
  87. return NF_ACCEPT;  
  88. }  
  89.    
  90. NF_CT_STAT_INC(insert_failed);  
  91. write_unlock_bh(&nf_conntrack_lock);  
  92. return NF_DROP;  
  93. }  
  94.     

至此将连接跟踪模块相应的hook函数都分析完了

四、总结

对于linux代码,如果说只是为了理解连接跟踪模块的工作原理,总感觉是少点东西的,还需要分析其编码思想。

下面就分析一下连接跟踪模块的一些编程理解

      1. 使用链表的方式将期望连接跟踪项、连接跟踪项连接在一起,且对于连接跟踪项来说,对于未被确认的连接跟踪项,专门放置在一个链表中而已被确认的连接跟踪项放置在确认连接跟踪表中

     2.需要维护连接跟踪项的状态,即需要设计好连接跟踪项的状态机机制

     3.因为连接跟踪项是需要占用内存的,那肯定要有一个垃圾回收机制,既要有异步垃圾回收,也要有同步垃圾回收,还要考虑好什么时候更新异步垃圾回收对应的超时定时器等

       功能。

在内核模块中链表被大量的应用,作为阅读代码的基础,首先需要理解链表原理以及内核中的实现机制。而在网络模块中,一般都要涉及到状态机的设计以及垃圾回收机制等(邻居子系统中也使用的状态机以及垃圾回收机制,而CAM表同样设计垃圾回收机制,所以当我们设计网络相关模块时,一定要考虑垃圾回收机制等)。




你可能感兴趣的:(Linux)