Netfilter 连接跟踪与状态检测的实现(二)
5.resolve_normal_ct resolve_normal_ct 函数是连接跟踪中最重要的函数之一,它的主要功能就是判断数据包在连接跟踪表是否存在,如果不存在,则为数据包分配相应的连接跟踪节点空间并初始化,然后设置连接状态:
- /* On success, returns conntrack ptr, sets skb->nfct and ctinfo */
- static inline struct ip_conntrack *
- resolve_normal_ct(struct sk_buff *skb,
- struct ip_conntrack_protocol *proto,
- int *set_reply,
- unsigned int hooknum,
- enum ip_conntrack_info *ctinfo)
- {
- struct ip_conntrack_tuple tuple;
- struct ip_conntrack_tuple_hash *h;
- struct ip_conntrack *ct;
- IP_NF_ASSERT((skb->nh.iph->frag_off & htons(IP_OFFSET)) == 0);
- /*前面提到过,需要将一个数据包转换成tuple,这个转换,就是通过ip_ct_get_tuple函数实现的*/
- if (!ip_ct_get_tuple(skb->nh.iph, skb, skb->nh.iph->ihl*4,
- &tuple,proto))
- return NULL;
- /*查看数据包对应的tuple在连接跟踪表中是否存在 */
- h = ip_conntrack_find_get(&tuple, NULL);
- if (!h) {
- /*如果不存在,初始化之*/
- h = init_conntrack(&tuple, proto, skb);
- if (!h)
- return NULL;
- if (IS_ERR(h))
- return (void *)h;
- }
- /*根据hash表节点,取得数据包对应的连接跟踪结构*/
- ct = tuplehash_to_ctrack(h);
- /* 判断连接的方向 */
- if (DIRECTION(h) == IP_CT_DIR_REPLY) {
- *ctinfo = IP_CT_ESTABLISHED + IP_CT_IS_REPLY;
- /* Please set reply bit if this packet OK */
- *set_reply = 1;
- } else {
- /* Once we've had two way comms, always ESTABLISHED. */
- if (test_bit(IPS_SEEN_REPLY_BIT, &ct->status)) {
- DEBUGP("ip_conntrack_in: normal packet for %p\n",
- ct);
- *ctinfo = IP_CT_ESTABLISHED;
- } else if (test_bit(IPS_EXPECTED_BIT, &ct->status)) {
- DEBUGP("ip_conntrack_in: related packet for %p\n",
- ct);
- *ctinfo = IP_CT_RELATED;
- } else {
- DEBUGP("ip_conntrack_in: new packet for %p\n",
- ct);
- *ctinfo = IP_CT_NEW;
- }
- *set_reply = 0;
- }
- /*设置skb的对应成员,如使用计数器、数据包状态标记*/
- skb->nfct = &ct->ct_general;
- skb->nfctinfo = *ctinfo;
- return ct;
- }
复制代码 这个函数包含了连接跟踪中许多重要的步骤n 调用ip_ct_get_tuple函数,把数据包转换为tuple;n ip_conntrack_find_get函数,根据tuple查找连接跟踪表;n init_conntrack函数,初始化一条连接;n 判断连接方向,设置连接状态;5.1 数据包的转换ip_ct_get_tuple 实现数据包至tuple的转换,这个转换,主要是根据数据包的套接字对来进行转换的:- int ip_ct_get_tuple(const struct iphdr *iph,
- const struct sk_buff *skb,
- unsigned int dataoff,
- struct ip_conntrack_tuple *tuple,
- const struct ip_conntrack_protocol *protocol)
- {
- /* Never happen */
- if (iph->frag_off & htons(IP_OFFSET)) {
- printk("ip_conntrack_core: Frag of proto %u.\n",
- iph->protocol);
- return 0;
- }
- /*设置来源、目的地址*/
- tuple->src.ip = iph->saddr;
- tuple->dst.ip = iph->daddr;
- tuple->dst.protonum = iph->protocol;
- tuple->dst.dir = IP_CT_DIR_ORIGINAL;
- return protocol->pkt_to_tuple(skb, dataoff, tuple);
- }
复制代码 回忆一下我们前面分析协议的初始化中协议初始化的部份,pkt_to_tuple 函数指针,以每种协议的不同而不同,以TCP协议为例:- static int tcp_pkt_to_tuple(const struct sk_buff *skb,
- unsigned int dataoff,
- struct ip_conntrack_tuple *tuple)
- {
- struct tcphdr _hdr, *hp;
- /* 获取TCP报头*/
- hp = skb_header_pointer(skb, dataoff, 8, &_hdr);
- if (hp == NULL)
- return 0;
- /*根据报头的端口信息,设置tuple对应成员*/
- tuple->src.u.tcp.port = hp->source;
- tuple->dst.u.tcp.port = hp->dest;
- return 1;
- }
复制代码 TCP协议中,根据来源和目的端口设置,其它协议类似,读者可以对比分析。5.2 Hash 表的搜索要对Hash表进行遍历,首要需要找到hash表的入口,然后来遍历该入口指向的链表。每个链表的节点是struct ip_conntrack_tuple_hash,它封装了tuple,所谓封装,就是把待查找的tuple与节点中已存的tuple相比较,我们来看这一过程的实现。计算hash值,是调用hash_conntrack函数,根据数据包对应的tuple实现的:- unsigned int hash = hash_conntrack(tuple);
- 这样,tuple对应的hash表入口即为ip_conntrack_hash[hash],也就是链表的首节点,然后调用ip_conntrack_find_get函数进行查找:
- struct ip_conntrack_tuple_hash *
- ip_conntrack_find_get(const struct ip_conntrack_tuple *tuple,
- const struct ip_conntrack *ignored_conntrack)
- {
- struct ip_conntrack_tuple_hash *h;
- READ_LOCK(&ip_conntrack_lock);
- /*搜索链表*/
- h = __ip_conntrack_find(tuple, ignored_conntrack);
- if (h) /*查找到了,使用计数器累加*/
- atomic_inc(&tuplehash_to_ctrack(h)->ct_general.use);
- READ_UNLOCK(&ip_conntrack_lock);
- return h;
- }
复制代码 链表是内核中一个标准的双向链表,可以调用宏list_for_each_entry 进遍历链表:- static struct ip_conntrack_tuple_hash *
- __ip_conntrack_find(const struct ip_conntrack_tuple *tuple,
- const struct ip_conntrack *ignored_conntrack)
- {
- struct ip_conntrack_tuple_hash *h;
- unsigned int hash = hash_conntrack(tuple);
- MUST_BE_READ_LOCKED(&ip_conntrack_lock);
- list_for_each_entry(h, &ip_conntrack_hash[hash], list) {
- if (conntrack_tuple_cmp(h, tuple, ignored_conntrack)) {
- CONNTRACK_STAT_INC(found);
- return h;
- }
- CONNTRACK_STAT_INC(searched);
- }
- return NULL;
- }
复制代码 list_for_each_entry在以&ip_conntrack_hash[hash]为起始地址的链表中,逐个搜索其成员,比较这个节点中的tuple是否与待查找的tuple是否一致,这个比较过程,是通过conntrack_tuple_cmp 函数实现的:- conntrack_tuple_cmp(const struct ip_conntrack_tuple_hash *i,
- const struct ip_conntrack_tuple *tuple,
- const struct ip_conntrack *ignored_conntrack)
- {
- MUST_BE_READ_LOCKED(&ip_conntrack_lock);
- return tuplehash_to_ctrack(i) != ignored_conntrack
- && ip_ct_tuple_equal(tuple, &i->tuple);
- }
复制代码 tuplehash_to_ctrack 函数主要是取连接跟踪ip_conntrack中的连接方向,判断它是否等于ignored_conntrack,对与这里的比较而言,ignored_conntrack传递过来的为NULL。主要的比较函数是ip_ct_tuple_equal函数,函数分为“来源”和“目的”进行比较:- static inline int ip_ct_tuple_src_equal(const struct ip_conntrack_tuple *t1,
- const struct ip_conntrack_tuple *t2)
- {
- return t1->src.ip == t2->src.ip
- && t1->src.u.all == t2->src.u.all;
- }
- static inline int ip_ct_tuple_dst_equal(const struct ip_conntrack_tuple *t1,
- const struct ip_conntrack_tuple *t2)
- {
- return t1->dst.ip == t2->dst.ip
- && t1->dst.u.all == t2->dst.u.all
- && t1->dst.protonum == t2->dst.protonum;
- }
- static inline int ip_ct_tuple_equal(const struct ip_conntrack_tuple *t1,
- const struct ip_conntrack_tuple *t2)
- {
- return ip_ct_tuple_src_equal(t1, t2) && ip_ct_tuple_dst_equal(t1, t2);
- }
复制代码 这里的比较,除了IP地址之外,并没有直接比较“端口”,这是因为像ICMP协议这样的并没有“端口”协议,struct ip_conntrack_tuple 结构中,与协议相关的,如端口等,都定义成union类型,这样,就可以直接使用u.all,而不用再去管TCP,UDP还是ICMP了。5.3 连接初始化内核使用ip_conntrack结构来描述一个数据包的连接状态,init_conntrack函数就是在连接状态表中不存在当前数据包时,初始化一个ip_conntrack结构,此结构被Netfilter用来描述一条连接,前面分析hash表时,已经分析了它的tuplehash成员:- struct ip_conntrack
- {
- /* 包含了使用计数器和指向删除连接的函数的指针 */
- struct nf_conntrack ct_general;
- /* 连接状态位,它通常是一个ip_conntrack_status类型的枚举变量,如IPS_SEEN_REPLY_BIT等*/
- unsigned long status;
- /* 内核的定时器,用于处理连接超时 */
- struct timer_list timeout;
- #ifdef CONFIG_IP_NF_CT_ACCT
- /* Accounting Information (same cache line as other written members) */
- struct ip_conntrack_counter counters[IP_CT_DIR_MAX];
- #endif
- /* If we were expected by an expectation, this will be it */
- struct ip_conntrack *master;
- /* Current number of expected connections */
- unsigned int expecting;
- /* Helper, if any. */
- struct ip_conntrack_helper *helper;
- /* Storage reserved for other modules: */
- union ip_conntrack_proto proto;
- union ip_conntrack_help help;
- #ifdef CONFIG_IP_NF_NAT_NEEDED
- struct {
- struct ip_nat_info info;
- #if defined(CONFIG_IP_NF_TARGET_MASQUERADE) || \
- defined(CONFIG_IP_NF_TARGET_MASQUERADE_MODULE)
- int masq_index;
- #endif
- } nat;
- #endif /* CONFIG_IP_NF_NAT_NEEDED */
- #if defined(CONFIG_IP_NF_CONNTRACK_MARK)
- unsigned long mark;
- #endif
- struct ip_conntrack_tuple_hash tuplehash[IP_CT_DIR_MAX];
- };
- static struct ip_conntrack_tuple_hash *
- init_conntrack(const struct ip_conntrack_tuple *tuple,
- struct ip_conntrack_protocol *protocol,
- struct sk_buff *skb)
- {
- struct ip_conntrack *conntrack;
- struct ip_conntrack_tuple repl_tuple;
- size_t hash;
- struct ip_conntrack_expect *exp;
- /*如果计算hash值的随机数种子没有被初始化,则初始化之*/
- if (!ip_conntrack_hash_rnd_initted) {
- get_random_bytes(&ip_conntrack_hash_rnd, 4);
- ip_conntrack_hash_rnd_initted = 1;
- }
- /*计算hash值*/
- hash = hash_conntrack(tuple);
-
- /*判断连接跟踪表是否已满*/
- if (ip_conntrack_max
- && atomic_read(&ip_conntrack_count) >= ip_conntrack_max) {
- /* Try dropping from this hash chain. */
- if (!early_drop(&ip_conntrack_hash[hash])) {
- if (net_ratelimit())
- printk(KERN_WARNING
- "ip_conntrack: table full, dropping"
- " packet.\n");
- return ERR_PTR(-ENOMEM);
- }
- }
- /*根据当前的tuple取反,计算该数据包的“应答”的tuple*/
- if (!ip_ct_invert_tuple(&repl_tuple, tuple, protocol)) {
- DEBUGP("Can't invert tuple.\n");
- return NULL;
- }
- /*为数据包对应的连接分配空间*/
- conntrack = kmem_cache_alloc(ip_conntrack_cachep, GFP_ATOMIC);
- if (!conntrack) {
- DEBUGP("Can't allocate conntrack.\n");
- return ERR_PTR(-ENOMEM);
- }
- /*初始化该结构*/
- memset(conntrack, 0, sizeof(*conntrack));
- /*使用计数器累加*/
- atomic_set(&conntrack->ct_general.use, 1);
- /*设置destroy函数指针*/
- conntrack->ct_general.destroy = destroy_conntrack;
- /*设置正反两个方向的tuple*/
- conntrack->tuplehash[IP_CT_DIR_ORIGINAL].tuple = *tuple;
- conntrack->tuplehash[IP_CT_DIR_REPLY].tuple = repl_tuple;
- if (!protocol->new(conntrack, skb)) {
- kmem_cache_free(ip_conntrack_cachep, conntrack);
- return NULL;
- }
- /* 初始化时间计数器,并设置超时初始函数 */
- init_timer(&conntrack->timeout);
- conntrack->timeout.data = (unsigned long)conntrack;
- conntrack->timeout.function = death_by_timeout;
- WRITE_LOCK(&ip_conntrack_lock);
- exp = find_expectation(tuple);
- if (exp) {
- DEBUGP("conntrack: expectation arrives ct=%p exp=%p\n",
- conntrack, exp);
- /* Welcome, Mr. Bond. We've been expecting you... */
- __set_bit(IPS_EXPECTED_BIT, &conntrack->status);
- conntrack->master = exp->master;
- #if CONFIG_IP_NF_CONNTRACK_MARK
- conntrack->mark = exp->master->mark;
- #endif
- nf_conntrack_get(&conntrack->master->ct_general);
- CONNTRACK_STAT_INC(expect_new);
- } else {
- conntrack->helper = ip_ct_find_helper(&repl_tuple);
- CONNTRACK_STAT_INC(new);
- }
- /* 这里,并没有直接就把该连接加入hash表,而是先加入到unconfirmed链表中. */
- list_add(&conntrack->tuplehash[IP_CT_DIR_ORIGINAL].list, &unconfirmed);
- atomic_inc(&ip_conntrack_count);
- WRITE_UNLOCK(&ip_conntrack_lock);
- if (exp) {
- if (exp->expectfn)
- exp->expectfn(conntrack, exp);
- destroy_expect(exp);
- }
- /*返回的是初始方向的hash节点*/
- return &conntrack->tuplehash[IP_CT_DIR_ORIGINAL];
- }
复制代码 在前文中提到过,一条完整的连接,采用struct ip_conntrack 结构描述,初始化函数的主要功能,就是分配一个这样的空间,然后初始化它的一些成员。在这个函数中,有三个重要的地方需要注意,一个是根据当前tuple,计算出应答方向的tuple,它是调用ip_ct_invert_tuple 函数实现的:- int
- ip_ct_invert_tuple(struct ip_conntrack_tuple *inverse,
- const struct ip_conntrack_tuple *orig,
- const struct ip_conntrack_protocol *protocol)
- {
- inverse->src.ip = orig->dst.ip;
- inverse->dst.ip = orig->src.ip;
- inverse->dst.protonum = orig->dst.protonum;
- inverse->dst.dir = !orig->dst.dir;
- return protocol->invert_tuple(inverse, orig);
- }
复制代码 这个函数事实上,与前面讲的tuple的转换是一样的,只是来了个乾坤大挪移,把来源和目的,以及方向对调了。另一个重点的是函数对特殊协议的支持,我们这里暂时跳过了这部份。第三个地方是调用协议的new函数: if (!protocol->new(conntrack, skb)) { kmem_cache_free(ip_conntrack_cachep, conntrack); return NULL; }new 函数指定在每个封包第一次创建连接时被调用,它根据协议的不同,所处理的过程不同,以ICMP协议为例:- /* Called when a new connection for this protocol found. */
- static int icmp_new(struct ip_conntrack *conntrack,
- const struct sk_buff *skb)
- {
- static u_int8_t valid_new[]
- = { [ICMP_ECHO] = 1,
- [ICMP_TIMESTAMP] = 1,
- [ICMP_INFO_REQUEST] = 1,
- [ICMP_ADDRESS] = 1 };
- if (conntrack->tuplehash[0].tuple.dst.u.icmp.type >= sizeof(valid_new)
- || !valid_new[conntrack->tuplehash[0].tuple.dst.u.icmp.type]) {
- /* Can't create a new ICMP `conn' with this. */
- DEBUGP("icmp: can't create new conn with type %u\n",
- conntrack->tuplehash[0].tuple.dst.u.icmp.type);
- DUMP_TUPLE(&conntrack->tuplehash[0].tuple);
- return 0;
- }
- atomic_set(&conntrack->proto.icmp.count, 0);
- return 1;
- }
复制代码 对于ICMP协议而言,仅有ICMP 请求回显、时间戳请求、信息请求(已经很少用了)、地址掩码请求这四个“请求”,可能是一个“新建”的连接,所以,ICMP协议的new函数判断是否是一个全法的ICMP新建连接,如果是非法的,则返回0,否则,初始化协议使用计数器,返回1。5.4 连接状态的判断resolve_normal_ct 函数的最后一个重要的工作是对连接状态的判断,tuple中包含一个“方向”成员dst.dir,对于一个初始连接,它是IP_CT_DIR_ORIGINAL:tuple->dst.dir = IP_CT_DIR_ORIGINAL;而它的应答包的tuple,则为IP_CT_DIR_REPLY:inverse->dst.dir = !orig->dst.dir;IP_CT_DIR_ORIGINAL 和IP_CT_DIR_REPLY都是枚举变量:- enum ip_conntrack_dir
- {
- IP_CT_DIR_ORIGINAL,
- IP_CT_DIR_REPLY,
- IP_CT_DIR_MAX
- };
复制代码 宏DIRECTION 就根据tuple中对应成员的值,判断数据包的方向,/* If we're the first tuple, it's the original dir. */#define DIRECTION(h) ((enum ip_conntrack_dir)(h)->tuple.dst.dir)但是,还有一些特殊地方,比如TCP协议,它是一个面向连接的协议,所以,它的“初始”或“应答”包,并不一定就是“新建”或单纯的“应答”包,而是在一个连接过程中的“已建连接包”,另一个,如FTP等 复杂协议,它们还存在一些“关联”的连接,当然这两部份目前还没有涉及到,但并不影响我们分析如下这段代码:- /* 如果是一个应答包 ,设置状态为已建+应答*/
- if (DIRECTION(h) == IP_CT_DIR_REPLY) {
- *ctinfo = IP_CT_ESTABLISHED + IP_CT_IS_REPLY;
- /* 设置应答标志变量 */
- *set_reply = 1;
- } else {
- /* 新建连接方过来的数据包,对面向连接的协议而言,可能是一个已建连接,判断其标志位*/
- if (test_bit(IPS_SEEN_REPLY_BIT, &ct->status)) {
- DEBUGP("ip_conntrack_in: normal packet for %p\n",
- ct);
- *ctinfo = IP_CT_ESTABLISHED;
- } else if (test_bit(IPS_EXPECTED_BIT, &ct->status)) {
- DEBUGP("ip_conntrack_in: related packet for %p\n",
- ct);
- *ctinfo = IP_CT_RELATED; //关联连接
- } else {
- DEBUGP("ip_conntrack_in: new packet for %p\n",
- ct);
- *ctinfo = IP_CT_NEW; //否则,则为一个新建连接
- }
- *set_reply = 0;
- }
-
- /*设置数据包skb与连接状态的关联*/
- skb->nfct = &ct->ct_general;
- /*每个sk_buff都将与ip_conntrack的一个状态关联,所以从sk_buff可以得到相应ip_conntrack的状态,即数据包的状态*/
- skb->nfctinfo = *ctinfo;
- return ct;
复制代码 以上的代表所表示的发送或应答的状态如下图所示:6. ip_confirm以上的工作事实上都很简单,基本思路是:一个包来了,转换其tuple,看其在连接跟踪表中没有,有的话,更新其状态,以其做一些与协议相关的工作,如果没有,则分配一个新的连接表项,并与skb_buff关连,但是问题是,这个表项,还没有被加入连接表当中来。其实这样做的理由很简单,因为这个时候,这个包是否有机会活命还是个未知数,例如被其它模块给Drop了……所以,要等到一切安全了,再来将这个表项插入至连接跟踪表。这个“一切安全”当然是Netfilter所有的模块处理完了,最完全了。当数据包要离开Netfilter时,它会穿过NF_IP_POST_ROUTING Hook点,状态跟踪模块在这里注册了ip_refrag函数(前面谈到过它的优先级是很低的)。这个Hook函数的工作,也可以猜测到了:“判断表项是否已经在连接跟踪表中了,如果没有,就将其插入表中”!- static unsigned int ip_refrag(unsigned int hooknum,
- struct sk_buff **pskb,
- const struct net_device *in,
- const struct net_device *out,
- int (*okfn)(struct sk_buff *))
- {
- struct rtable *rt = (struct rtable *)(*pskb)->dst;
- /* ip_confirm函数用于处理将tuple加入hash表等重要的后续处理 */
- if (ip_confirm(hooknum, pskb, in, out, okfn) != NF_ACCEPT)
- return NF_DROP;
- /* 在连接跟踪开始之前,对分片包进行了重组,这里判断数据包是否需要分片,如果要分片,就调用ip_fragment分片函数将数据包分片发送出去,因为数据包已经被发送走了,所以,在它之后的任何Hook函数已经没有意思了 */
- if ((*pskb)->len > dst_mtu(&rt->u.dst) &&
- !skb_shinfo(*pskb)->tso_size) {
- /* No hook can be after us, so this should be OK. */
- ip_fragment(*pskb, okfn);
- return NF_STOLEN;
- }
- return NF_ACCEPT;
- }
复制代码 ip_confirm 函数是状态跟踪的另一个重要的函数:- static unsigned int ip_confirm(unsigned int hooknum,
- struct sk_buff **pskb,
- const struct net_device *in,
- const struct net_device *out,
- int (*okfn)(struct sk_buff *))
- {
- return ip_conntrack_confirm(pskb);
- }
复制代码 函数仅是转向,将控制权转交给ip_conntrack_confirm函数:- static inline int ip_conntrack_confirm(struct sk_buff **pskb)
- {
- if ((*pskb)->nfct
- && !is_confirmed((struct ip_conntrack *)(*pskb)->nfct))
- return __ip_conntrack_confirm(pskb);
- return NF_ACCEPT;
- }
复制代码 is_comfirmed函数用于判断数据包是否已经被__ip_conntrack_confirm函数处理过了,它是通过IPS_CONFIRMED_BIT 标志位来判断,而这个标志位当然是在__ip_conntrack_confirm函数中来设置的:[codeint__ip_conntrack_confirm(struct sk_buff **pskb){ unsigned int hash, repl_hash; struct ip_conntrack *ct; enum ip_conntrack_info ctinfo; /*取得数据包的连接状态*/ ct = ip_conntrack_get(*pskb, &ctinfo); /* 如果当前包不是一个初始方向的封包,则直接返回. */ if (CTINFO2DIR(ctinfo) != IP_CT_DIR_ORIGINAL) return NF_ACCEPT;/*计算初始及应答两个方向tuple对应的hash值*/ hash = hash_conntrack(&ct->tuplehash[IP_CT_DIR_ORIGINAL].tuple); repl_hash = hash_conntrack(&ct->tuplehash[IP_CT_DIR_REPLY].tuple); /* IP_NF_ASSERT(atomic_read(&ct->ct_general.use) == 1); */ /* No external references means noone else could have confirmed us. */ IP_NF_ASSERT(!is_confirmed(ct)); DEBUGP("Confirming conntrack %p\n", ct); WRITE_LOCK(&ip_conntrack_lock); /* 在hash表中查找初始及应答的节点*/ if (!LIST_FIND(&ip_conntrack_hash[hash], conntrack_tuple_cmp, struct ip_conntrack_tuple_hash *, &ct->tuplehash[IP_CT_DIR_ORIGINAL].tuple, NULL) && !LIST_FIND(&ip_conntrack_hash[repl_hash], conntrack_tuple_cmp, struct ip_conntrack_tuple_hash *, &ct->tuplehash[IP_CT_DIR_REPLY].tuple, NULL)) { /* Remove from unconfirmed list */ list_del(&ct->tuplehash[IP_CT_DIR_ORIGINAL].list); /*主要的工作就在于此了:将当前连接表项(初始和应答的tuple)添加进hash表*/ list_prepend(&ip_conntrack_hash[hash], &ct->tuplehash[IP_CT_DIR_ORIGINAL]); list_prepend(&ip_conntrack_hash[repl_hash], &ct->tuplehash[IP_CT_DIR_REPLY]); /* Timer relative to confirmation time, not original setting time, otherwise we'd get timer wrap in weird delay cases. */ ct->timeout.expires += jiffies; add_timer(&ct->timeout); atomic_inc(&ct->ct_general.use); set_bit(IPS_CONFIRMED_BIT, &ct->status); CONNTRACK_STAT_INC(insert); WRITE_UNLOCK(&ip_conntrack_lock); return NF_ACCEPT; } CONNTRACK_STAT_INC(insert_failed); WRITE_UNLOCK(&ip_conntrack_lock); return NF_DROP;}[/code]这样,一条新建连接就被加入到表项当中了。如果其有后续连接,如应答,进入连接跟踪表,又转换其tuple,然后查到此表项,循环中……
没有评论:
发表评论