连接跟踪

5020阅读 0评论2016-01-19 chenmeng11
分类:LINUX

连接跟踪,顾名思义,就是识别一个连接上双方向的数据包,同时记录状态。下面看一下它的数据结构:
struct nf_conn {
        /* Usage count in here is 1 for hash table/destruct timer, 1 per skb, plus 1 for any connection(s) we are `master' for */
        struct  nf_conntrack  ct_general;                /* 连接跟踪的引用计数 */
        spinlock_t  lock;

        /* Connection tracking(链接跟踪)用来跟踪、记录每个链接的信息(目前仅支持IP协议的连接跟踪)。
            每个链接由“tuple”来唯一标识,这里的“tuple”对不同的协议会有不同的含义,例如对tcp,udp
                 来说就是五元组: (源IP,源端口,目的IP, 目的端口,协议号),对ICMP协议来说是: (源IP, 目
            的IP, id, type, code), 其中id,type与code都是icmp协议的信息。链接跟踪是防火墙实现状态检
            测的基础,很多功能都需要借助链接跟踪才能实现,例如NAT、快速转发、等等。*/

        struct  nf_conntrack_tuple_hash  tuplehash[IP_CT_DIR_MAX];
        
        unsigned long  status;                             /* 可以设置由enum ip_conntrack_status中描述的状态 */

        struct  nf_conn  *master;                        /* 如果该连接是某个连接的子连接,则master指向它的主连接 */
        /* Timer function; drops refcnt when it goes off. */
        struct  timer_list  timeout;

        union nf_conntrack_proto proto;                /* 用于保存不同协议的私有数据 */
        /* Extensions */
        struct nf_ct_ext *ext;                        /* 用于扩展结构 */
};
这个结构非常简单,其中最主要的就是tuplehash(跟踪连接双方向数据)和status(记录连接状态),这也连接跟踪最主要的功能。

在status中可以设置的标志,由下面的enum ip_conntrack_status描述,它们可以共存。这些标志设置后就不会再被清除。
enum ip_conntrack_status {
        IPS_EXPECTED_BIT = 0,                /* 表示该连接是个子连接 */
        IPS_SEEN_REPLY_BIT = 1,                /* 表示该连接上双方向上都有数据包了 */
        IPS_ASSURED_BIT = 2,                /* TCP:在三次握手建立完连接后即设定该标志。UDP:如果在该连接上的两个方向都有数据包通过,
                                                                则再有数据包在该连接上通过时,就设定该标志。ICMP:不设置该标志 */

        IPS_CONFIRMED_BIT = 3,                /* 表示该连接已被添加到net->ct.hash表中 */
        IPS_SRC_NAT_BIT = 4,                /*在POSTROUTING处,当替换reply tuple完成时, 设置该标记 */
        IPS_DST_NAT_BIT = 5,                /* 在PREROUTING处,当替换reply tuple完成时, 设置该标记 */
        /* Both together. */
        IPS_NAT_MASK = (IPS_DST_NAT | IPS_SRC_NAT),
        /* Connection needs TCP sequence adjusted. */
        IPS_SEQ_ADJUST_BIT = 6,
        IPS_SRC_NAT_DONE_BIT = 7,        /* 在POSTROUTING处,已被SNAT处理,并被加入到bysource链中,设置该标记 */
        IPS_DST_NAT_DONE_BIT = 8,        /* 在PREROUTING处,已被DNAT处理,并被加入到bysource链中,设置该标记 */
        /* Both together */
        IPS_NAT_DONE_MASK = (IPS_DST_NAT_DONE | IPS_SRC_NAT_DONE),
        IPS_DYING_BIT = 9,                /* 表示该连接正在被释放,内核通过该标志保证正在被释放的ct不会被其它地方再次引用。有了这个标志,当某个连接要被删除时,即使它还在net->ct.hash中,也不会再次被引用。*/
        IPS_FIXED_TIMEOUT_BIT = 10,        /* 固定连接超时时间,这将不根据状态修改连接超时时间。通过函数nf_ct_refresh_acct()修改超时时间时检查该标志。 */
        IPS_TEMPLATE_BIT = 11,                /* 由CT target进行设置(这个target只能用在raw表中,用于为数据包构建指定ct,并打上该标志),用于表明这个ct是由CT target创建的 */
};

连接跟踪对该连接上的每个数据包表现为以下几种状态之一,由enum ip_conntrack_info表示,被设置在skb->nfctinfo中。
enum ip_conntrack_info {
IP_CT_ESTABLISHED(0),         /* 表示这个数据包对应的连接在两个方向都有数据包通过,并且这是ORIGINAL初始方向数据包(无论是TCP、UDP、ICMP数据包,只要在该连接的两 个方向上已有数据包通过,就会将该连接设置为IP_CT_ESTABLISHED状态。不会根据协议中的标志位进行判断,例如TCP的SYN等)。但它表 示不了这是第几个数据包,也说明不了这个CT是否是子连接。*/
IP_CT_RELATED(1),                /* 表示这个数据包对应的连接还没有REPLY方向数据包,当前数据包是ORIGINAL方向数据包。并且这个连接关联一个已有的连接,是该已有连接的子连 接,(即status标志中已经设置了IPS_EXPECTED标志,该标志在init_conntrack()函数中设置)。但无法判断是第几个数据包 (不一定是第一个)*/
IP_CT_NEW(2),                        /* 表示这个数据包对应的连接还没有REPLY方向数据包,当前数据包是ORIGINAL方向数据包,该连接不是子连接。但无法判断是第几个数据包(不一定是第一个)*/
IP_CT_IS_REPLY(3),                /* 这个状态一般不单独使用,通常以下面两种方式使用 */
IP_CT_ESTABLISHED + IP_CT_IS_REPLY(3),        /* 表示这个数据包对应的连接在两个方向都有数据包通过,并且这是REPLY应答方向数据包。但它表示不了这是第几个数据包,也说明不了这个CT是否是子连接。*/
IP_CT_RELATED + IP_CT_IS_REPLY(4),                /* 这个状态仅在nf_conntrack_attach()函数中设置,用于本机返回REJECT,例如返回一个ICMP目的不可达报文, 或返回一个reset报文。它表示不了这是第几个数据包。*/
IP_CT_NUMBER = IP_CT_IS_REPLY * 2 - 1(5)        /* 可表示状态的总数 */
};

以上就是连接跟踪里最重要的数据结构了,用于跟踪连接、记录状态、并对该连接的每个数据包设置一种状态。

除了上面的主要数据结构外,还有一些辅助数据结构,用于处理不同协议的私有信息、处理子连接、对conntrack进行扩展等。

三层协议(IPv4/IPv6)利用nf_conntrack_proto.c文件中的 nf_conntrack_l3proto_register(struct nf_conntrack_l3proto *proto)和nf_conntrack_l3proto_unregister(struct nf_conntrack_l3proto *proto)在nf_ct_l3protos[]数组中注册自己的三层协议处理函数。
a.png 

四层协议(TCP/UDP)利用nf_conntrack_proto.c文件中的 nf_conntrack_l4proto_register(struct nf_conntrack_l4proto *l4proto)和nf_conntrack_l4proto_unregister(struct nf_conntrack_l4proto *l4proto)在nf_ct_protos[]数组中注册自己的四层协议处理函数。
b.png 

处理一个连接的子连接协议,利用nf_conntrack_helper.c文件中的 nf_conntrack_helper_register(struct nf_conntrack_helper *me)来注册nf_conntrack_helper结构,和nf_conntrack_expect.c文件中的 nf_ct_expect_related_report(struct nf_conntrack_expect *expect, u32 pid, int report)来注册nf_conntrack_expect结构。
c.png 

扩展连接跟踪结构(nf_conn)利用nf_conntrack_extend.c文件中的nf_ct_extend_register(struct nf_ct_ext_type *type)和nf_ct_extend_unregister(struct nf_ct_ext_type *type)进行扩展,并修改连接跟踪相应代码来利用这部分扩展功能。
d.png


了解了上面的数据结构,我们下面来看一下nf_conntrack的执行流程以及如何利用这些数据结构的。首先来看一下nf_conntrack模块加载时的初始化流程。

e.png 

nf_conntrack的初始化,就是初始化上面提到的那些数据结构,它在内核启动时调用 nf_conntrack_standalone_init()函数进行初始化的。初始化完成后,构建出如下图所示的结构图,只是不包含下图中与连接有关 的信息(nf_conn和nf_conntrack_expect结构)。

f.png 

上图中有三个HASH桶,ct_hash、expect_hash、helper_hash这三个HASH桶大小在初始化时就已确定,后面不能再更改。其 中ct_hash、expect_hash可在加载nf_conntrack.ko模块时通过参数hashsize和expect_hashsize进行 设定,而helper_hash不能通过参数修改,它的默认值是page/sizeof(helper_hash)。

下面再来看一个当创建子连接时,各个数据结构之间的关系。

g.png 

nf_conn和nf_conntrack_expect都有最大个数限制。nf_conn通过全局变量nf_conntrack_max限制,可通过 /proc/sys/net/netfilter/nf_conntrack_max文件在运行时修改。nf_conntrack_expect通过全局 变量nf_ct_expect_max限制,可通过/proc/sys/net/netfilter/ nf_conntrack_expect_max文件在运行时修改。nf_conntrack_helper没有最大数限制,因为这个是通过注册不同协议 的模块添加的,大小取决于动态协议跟踪模块的多少,一般不会很大。


上面两幅数据结构图中,大部分都已介绍过,下面介绍一下netns_ct数据结构,该结构主要用于linux的网络命名空间,表示nf_conntrack在不同的命名空间中都有一套独立的数据信息(这是另一个话题,这里就不再深入讨论了)。
struct netns_ct {
        atomic_t                        count;                                /* 当前连接表中连接的个数 */
        unsigned int                expect_count;                        /* nf_conntrack_helper创建的期待子连接nf_conntrack_expect项的个数 */
        unsigned int                htable_size;                        /* 存储连接(nf_conn)的HASH桶的大小 */
        struct kmem_cache        *nf_conntrack_cachep;                /* 指向用于分配nf_conn结构而建立的高速缓存(slab)对象 */
        struct hlist_nulls_head        *hash;                                /* 指向存储连接(nf_conn)的HASH桶 */
        struct hlist_head                *expect_hash;                        /* 指向存储期待子连接nf_conntrack_expect项的HASH桶 */
        struct hlist_nulls_head        unconfirmed;                        /* 对于一个链接的第一个包,在init_conntrack()函数中会将该包original方向的tuple结构挂入该链,这是因为在此时还不确定该链 接会不会被后续的规则过滤掉,如果被过滤掉就没有必要挂入正式的链接跟踪表。在ipv4_confirm()函数中,会将unconfirmed链中的 tuple拆掉,然后再将original方向和reply方向的tuple挂入到正式的链接跟踪表中,即init_net.ct.hash中,这是因为 到达ipv4_confirm()函数时,应经在钩子NF_IP_POST_ROUTING处了,已经通过了前面的filter表。 通过 cat  /proc/net/nf_conntrack显示连接,是不会显示该链中的连接的。但总的连接个数(net->ct.count)包含 该链中的连接。当注销l3proto、l4proto、helper、nat等资源或在应用层删除所有连接(conntrack -F)时,除了释放confirmed连接(在net->ct.hash中的连接)的资源,还要释放unconfirmed连接(即在该链中的连 接)的资源。*/
        struct hlist_nulls_head        dying;                                /* 释放连接时,通告DESTROY事件失败的ct被放入该链中,并设置定时器,等待下次通告。 通过cat  /proc/net /nf_conntrack显示连接,是不会显示该链中的连接的。但总的连接个数(net->ct.count)包含该链中的连接。当注销连接跟踪 模块时,同时要清除正再等待被释放的连接(即该链中的连接)*/
        struct ip_conntrack_stat        __percpu *stat;                        /* 连接跟踪过程中的一些状态统计,每个CPU一项,目的是为了减少锁 */
        int                        sysctl_events;                        /* 是否开启连接事件通告功能 */
        unsigned int                sysctl_events_retry_timeout;        /* 通告失败后,重试通告的间隔时间,单位是秒 */
        int                        sysctl_acct;                        /* 是否开启每个连接数据包统计功能 */
        int                        sysctl_checksum;
        unsigned int                sysctl_log_invalid;                 /* Log invalid packets */
#ifdef CONFIG_SYSCTL
        struct ctl_table_header        *sysctl_header;
        struct ctl_table_header        *acct_sysctl_header;
        struct ctl_table_header        *event_sysctl_header;
#endif
        int                        hash_vmalloc;                        /* 存储连接(nf_conn)的HASH桶是否是使用vmalloc()进行分配的 */
        int                        expect_vmalloc;                        /* 存储期待子连接nf_conntrack_expect项的HASH桶是否是使用vmalloc()进行分配的 */
        char                        *slabname;                        /* 用于分配nf_conn结构而建立的高速缓存(slab)对象的名字 */
};

从 nf_conntrack的框架来看,它可用于跟踪任何三层和四协议的连接,但目前在三层协议只实现了IPv4和IPv6的连接跟踪,下面我们以IPv4 为例,介绍一下该协议是如何利用nf_conntrack框架和netfilter实现连接跟踪的。有关netfilter框架,可参考我的另一个帖子


首先介绍一下IPv4协议连接跟踪模块的初始化。

Ipv4连接跟踪模块注册了自己的3层协议,和IPv4相关的三个4层协议TCP、UDP、ICMP。注册后的结构图如下图所示:

h.png 

在netfilter框架中利用nf_register_hook(struct nf_hook_ops *reg)、nf_unregister_hook(struct nf_hook_ops *reg)函数注册自己的钩子项,调用nf_conntrack_in()函数来建立相应连接。

i.png 

如 上面所示,IPv4连接跟踪模块已初始化完成,下面我们来看一下它创建连接的流程图。上图中连接的建立主要由三个函数来完成,即 ipv4_conntrack_in(),ipv4_confirm()与ipv4_conntrack_local()。其中 ipv4_conntrack_in()与ipv4_conntrack_local()都是通过调用函数nf_conntrack_in()来实现的, 所以下面我们主要关注nf_conntrack_in()与ipv4_confirm()这两个函数。nf_conntrack_in()函数主要完成创 建链接、添加链接的扩展结构(例如helper, acct结构)、设置链接状态等。ipv4_confirm()函数主要负责确认链接(即将链接挂入到正式的链接表中)、执行helper函数、启动链接 超时定时器等。另外还有一个定时器函数death_by_timeout(), 该函数负责链接到期时删除该链接。

nf_conntrack_in()函数流程图
j.png 

ipv4_confirm()函数流程图
k.png 

death_by_timeout()函数流程图
l.png 
上图中有一点需要说明,由于skb会引用nf_conn,同时会增加它的引用计数,所以当skb被释放时,也要释放nf_conn的引用计数,并且在nf_conn引用计数为0时,要释放全部资源。


当数据包经过nf_conntrack_in()和ipv4_confirm()函数处理流程后,就会建立起3楼第二幅结构图所示的连接nf_conn。 同时这两个函数已经包含了子连接的处理流程,即流程图中help和exp的处理。子连接建立后的结构图如3楼第三幅结构图,主链接与子连接通过 helper和expect关联起来。


连接跟踪到此就介绍完了,下面介绍IPv4基于nf_conntrack框架适合实现NAT转换的。先介绍IPv4-NAT初始化的资源,然后处理流程。

IPv4-NAT连接跟踪相关部分通过函数nf_nat_init()初始化



IPv4-NAT功能的iptables部分通过函数nf_nat_standalone_init()初始化
根据上面介绍,可以看到IPv4-NAT的主要是通过nf_nat_fn()钩子函数处理的,下面我就来看看nf_nat_fn()函数的处理流程。
q.png 

针对上图中的nf_nat_setup_info()函数进一步描述
r.png 

下面对NAT转换算法中重要部分做一些文字说明
下面介绍有子连接的NAT实现。有两个关键点:1.主链接能正确的构建出NAT后的expect来识别子连接。2.能够修改主链接数据通道的信息为NAT后的信息。这两点都在动态协议的help中完成,下面我们来看一下它的流程图:
s.png 


下面针对有无子连接的NAT做一下对比

无子连接的NAT

有子连接的NAT
上一篇:generic netlink
下一篇:Kbuild