我是靠谱客的博主 坚定睫毛,最近开发中收集的这篇文章主要介绍linux内核协议栈 netfilter 之连接跟踪子系统核心实现nf_conntrack_in() / nf_conntrack_confirm() / 定时器1 连接跟踪入口 nf_conntrack_in()2 连接跟踪子系统出口 nf_conntrack_confirm()3 连接超时机制,觉得挺不错的,现在分享给大家,希望可以做个参考。

概述

目录

1 连接跟踪入口 nf_conntrack_in()

1.1 连接跟踪信息块的获取 resolve_normal_ct()

1.1.1 nf_ct_get_tuple()

1.1.2 nf_conntrack_find_get()

1.1.3 init_conntrack()

2 连接跟踪子系统出口 nf_conntrack_confirm()

2.1 连接确认判断

2.2 连接确认 __nf_conntrack_confirm()【核心】

3 连接超时机制

3.1 定时器的初始化 nf_conntrack_alloc()

3.2 定时器超时回调 death_by_timeout()

3.2.1 连接删除 clean_from_llists()

3.3 定时器的更新nf_ct_refresh_acct()


1 连接跟踪入口 nf_conntrack_in()

数据包skb就是通过该函数进入连接跟踪子系统的,对于发送报文,从LOCAL_OUT点进入,对于接收报文,从PRE_ROUTING点进入。该函数的功能是实现对数据的连接跟踪,更新连接跟踪项的连接状态,目前就是根据五元组识别一条数据流。这个函数可以说是连接跟踪模块的重中之重。

首先它将数据包的类型按如下几个类型处理:

  1. 当这是一个新的数据流的第一个数据包时,则会根据该数据包的五元组信息创建一个新的连接跟踪项,并初始化该连接跟踪项的tuple_hash、helper的值,最后,将该连接跟踪项的原始方向的tuple_hash添加到unconfirmed链表中,且该连接不是期望连接
  2. 当这是一个新的数据流的第一个数据包时,则会根据该数据包的五元组信息创建一个新的连接跟踪项,并初始化该连接跟踪项的tuple_hash、helper的值,最后将该连接跟踪项的原始方向的tuple_hash添加到unconfirmed链表中,且该连接是期望连接。
  3. 当该数据包是原始方向的非第一个数据包,但当该数据包进入连接跟踪模块时,连接跟踪模块还没有收到reply方向的数据包。
  4. 当该数据包是原始方向的非第一个数据包, 且到改数据包进入连接跟踪模块时,连接跟踪 模块已经接收到reply方向的数据包。
  5. 当该数据包是reply方向的数据包。

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

  1. 当是第一类数据包时,则创建一个新的连接跟踪项,并作相应的初始化工作,且将数据包的nfctinfo设置为 IP_CT_NEW;
  2. 当是第二类数据包时,则创建一个新的连接跟踪项,并作相应的初始化工作,且将数据包的nfctinfo设置为 IP_CT_RELATED;
  3. 当时第三类数据包时,将数据包的nfctinfo设置为 IP_CT_NEW;
  4. 当是第四种数据包时,将数据包的nfctinfo设置为 IP_CT_ESTABLISHED;
  5. 当时第五种状态时,将数据包的的nfctinfo设置为 IP_CT_ESTABLISHED+IP_CT_IS_REPLY;

然后再调用四层协议相关的packet函数,根据数据包的四层内容值,更新四层协议相关的状态值(比如tcp协议,就会在其nf_conntrack_protocol->packet设置四层相关的状态改变)。

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

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

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


struct nf_conntrack_l3proto nf_conntrack_l3proto_ipv4 __read_mostly = {
	.l3proto	 = PF_INET,
	.name		 = "ipv4",
	.pkt_to_tuple	 = ipv4_pkt_to_tuple,
	.invert_tuple	 = ipv4_invert_tuple,
	.print_tuple	 = ipv4_print_tuple,
	.get_l4proto	 = ipv4_get_l4proto,
	...
	.me		 = THIS_MODULE,
};

struct nf_conntrack_l4proto nf_conntrack_l4proto_tcp4 __read_mostly =
{
	.l3proto		= PF_INET,
	.l4proto 		= IPPROTO_TCP,
	.name 			= "tcp",
	.pkt_to_tuple 		= tcp_pkt_to_tuple,
	.invert_tuple 		= tcp_invert_tuple,
	.print_tuple 		= tcp_print_tuple,
	.print_conntrack 	= tcp_print_conntrack,
	.packet 		= tcp_packet,
	.new 			= tcp_new,
	.error			= tcp_error,
	...
};
EXPORT_SYMBOL_GPL(nf_conntrack_l4proto_tcp4);

struct nf_conntrack_l4proto nf_conntrack_l4proto_udp4 __read_mostly =
{
	.l3proto		= PF_INET,
	.l4proto		= IPPROTO_UDP,
	.name			= "udp",
	.pkt_to_tuple		= udp_pkt_to_tuple,
	.invert_tuple		= udp_invert_tuple,
	.print_tuple		= udp_print_tuple,
	.packet			= udp_packet,
	.new			= udp_new,
	.error			= udp_error,
	...
};
EXPORT_SYMBOL_GPL(nf_conntrack_l4proto_udp4);


struct nf_conntrack_l4proto nf_conntrack_l4proto_icmp __read_mostly =
{
	.l3proto		= PF_INET,
	.l4proto		= IPPROTO_ICMP,
	.name			= "icmp",
	.pkt_to_tuple		= icmp_pkt_to_tuple,
	.invert_tuple		= icmp_invert_tuple,
	.print_tuple		= icmp_print_tuple,
	.packet			= icmp_packet,
	.new			= icmp_new,
	.error			= icmp_error,
	.destroy		= NULL,
	.me			= NULL,
	...
};


================================================================================
unsigned int nf_conntrack_in(int pf, unsigned int hooknum, struct sk_buff *skb)
{
	struct nf_conn *ct;
	enum ip_conntrack_info ctinfo;
	struct nf_conntrack_l3proto *l3proto;
	struct nf_conntrack_l4proto *l4proto;
	unsigned int dataoff;
	u_int8_t protonum;
	int set_reply = 0;
	int ret;

    /*如果数据包的nfct项不为空,则说明该数据包已经关联一个nf_conn项了有两种情况:
    1.数据在PRE_ROUTING的raw模块相关的hook函数时,将该数据包与un_conntrack关联了,
       因此就不再进行连接模块的跟踪了
    2.如果发送数据包的设备为loopback设备,说明数据包已经被跟踪一次了,此处也
       不再进行跟踪了。*/
	if (skb->nfct) {
		NF_CT_STAT_INC_ATOMIC(ignore);
		return NF_ACCEPT;
	}

	//从全局数组nf_ct_l3protos[]中找到该协议族注册的L3协议
	l3proto = __nf_ct_l3proto_find((u_int16_t)pf);

	//调用L3协议的get_l4proto()回调解析skb,获取L4协议
	//编号protonum和L4协议报文首部距skb起始位置的偏移量dataoff
	ret = l3proto->get_l4proto(skb, skb_network_offset(skb), &dataoff, &protonum);
	if (ret <= 0) {
		pr_debug("not prepared to track yet or error occuredn");
		NF_CT_STAT_INC_ATOMIC(error);
		NF_CT_STAT_INC_ATOMIC(invalid);
		return -ret;
	}
	/*根据三层协议类型与四层协议号,在全局数组nf_ct_protos中,获取四层协议的相应的        
      nf_conntrack_l4proto 结构*/
	l4proto = __nf_ct_l4proto_find((u_int16_t)pf, protonum);

	//如果L4协议提供了校验回调error(),对skb进行校验
	if (l4proto->error != NULL &&
	    (ret = l4proto->error(skb, dataoff, &ctinfo, pf, hooknum)) <= 0) {
		NF_CT_STAT_INC_ATOMIC(error);
		NF_CT_STAT_INC_ATOMIC(invalid);
		return -ret;
	}

	//核心函数,获取该skb所属的连接跟踪信息块nf_conn,见下文。
	//如果数据包skb属于reply方向,set_reply会被设置为1,否则为0
	ct = resolve_normal_ct(skb, dataoff, pf, protonum, l3proto, l4proto,
			       &set_reply, &ctinfo);
	if (!ct) {
		/* Not valid part of a connection */
		NF_CT_STAT_INC_ATOMIC(invalid);
		return NF_ACCEPT;
	}
	if (IS_ERR(ct)) {
		//连接跟踪子系统本意不会过滤数据包,但是在一些异常情况也会丢包
		NF_CT_STAT_INC_ATOMIC(drop);
		return NF_DROP;
	}
	NF_CT_ASSERT(skb->nfct);
	//调用L4协议的packet回调函数决定连接跟踪子系统给Netfilter框架的返回值
	ret = l4proto->packet(ct, skb, dataoff, ctinfo, pf, hooknum);
	if (ret < 0) {
		/* Invalid: inverse of the return code tells
		 * the netfilter core what to do */
		pr_debug("nf_conntrack_in: Can't track with proto modulen");
		nf_conntrack_put(skb->nfct);
		skb->nfct = NULL;
		NF_CT_STAT_INC_ATOMIC(invalid);
		return -ret;
	}
	
    /*对于一个新创建的连接跟踪项后,当我们第一次收取到reply方向的数据包后,
    则会设置nf_conn->status的IPS_SEEN_REPLY_BIT位为1,当设置成功且IPS_SEEN_REPLY_BIT位的        
    原来值为0时,则调用nf_conntrack_event_cache ,由nfnetlink模块处理状态改变的事件。
    */
	if (set_reply && !test_and_set_bit(IPS_SEEN_REPLY_BIT, &ct->status))
		nf_conntrack_event_cache(IPCT_STATUS, skb);
	return ret;
}
EXPORT_SYMBOL_GPL(nf_conntrack_in);

梳理下关键步骤:

  1. 根据skb找到能够处理该skb的L3协议和L4协议;
  2. 如果有,调用L4协议的error()回调进行报文校验,校验通过继续,否则结束;
  3. 调用resolve_normal_ct()查询该skb是否属于某个已有连接,没有则创建一个;
  4. 调用L4协议的packet()回调,该回调的返回值将作为该Netfilter钩子的返回值,一般应该都是NF_ACCEPT。

1.1 连接跟踪信息块的获取 resolve_normal_ct()

对于连接跟踪子系统来讲,每个数据包都应该归属某一条“连接”。如上,数据包通过一些检查后,就会调用该函数检查数据包是否属于已有的连接,如果该数据包不属于任何一个连接,那么它是一个“新连接”的数据包,这时该函数就会创建一个“连接”对该数据包进行跟踪,具体实现细节如下:

  1. 若连接跟踪项不存在时,则根据已知条件创建一个nf_conn,并放入unconfirm表中,执行c)
  2. 若连接跟踪项已存在,则执行c)
  3. 更新连接跟踪项的状态
  4. 设置传入数据包的nfct指针
@dataoff: L4报文的偏移量
@set_reply: 输出参数,
/* On success, returns conntrack ptr, sets skb->nfct and ctinfo */
static inline struct nf_conn* resolve_normal_ct(struct sk_buff *skb,
		unsigned int dataoff, u_int16_t l3num, u_int8_t protonum,
		struct nf_conntrack_l3proto *l3proto, struct nf_conntrack_l4proto *l4proto,
		int *set_reply, enum ip_conntrack_info *ctinfo)
{
	struct nf_conntrack_tuple tuple;
	struct nf_conntrack_tuple_hash *h;
	struct nf_conn *ct;
    
    /*根据三、四层协议的nf_conntrack结构变量及skb,为该数据包计算相应的
    nf_conntrack_tuple值*/
	if (!nf_ct_get_tuple(skb, skb_network_offset(skb),
			dataoff, l3num, protonum, &tuple, l3proto, l4proto)) {
		pr_debug("resolve_normal_ct: Can't get tuplen");
		return NULL;
	}
	
    //从全局的哈希表nf_conntrack_hash中寻找是否有一致的tuple_hash
	h = nf_conntrack_find_get(&tuple);
	if (!h) {
		//如果没找到,说明这是一个新的连接,新创建一个连接,顺带也就创建了tuple_hash
		h = init_conntrack(&tuple, l3proto, l4proto, skb, dataoff);
		if (!h)
			return NULL;
		if (IS_ERR(h))
			return (void *)h;
	}

	//tuple_hash就在连接跟踪信息块中,这里由tuple_hash获取连接跟踪信息块。
	//注意:连接跟踪信息块的分配是在init_conntrack()中完成的,这里只是转换
	ct = nf_ct_tuplehash_to_ctrack(h);

	//特别注意下面ctinfo的设定,它最后会被设置到skb->nfctinfo中,
	//表示当处理完该skb后,skb所属连接的状态
    
    /*根据tuple的dst.dir,确定当前进来的数据包对应于连接跟踪项的哪一个方向,
    然后设置set_reply的值*/
	if (NF_CT_DIRECTION(h) == IP_CT_DIR_REPLY) {
		//收到的是reply方向的数据包,记录状态为ESTABLISHED
		*ctinfo = IP_CT_ESTABLISHED + IP_CT_IS_REPLY;
		/* Please set reply bit if this packet OK */
		*set_reply = 1;
	} else {
		//初始方向的数据包,但是在该连接的reply方向上也已经收到过了数据包
		//那么skb依然是属于ESTABLISHED连接的

        /*当是连接跟踪项原始方向的数据包时,则有以下几种情况
        1.当发送的数据包到达连接跟踪模块时,其reply方向没有收到对应的数据包之前
          a)连接跟踪项不是期望连接,此时将skb->nfctinfo设置为IP_CT_NEW
          b)连接跟踪项是期望连接,此时将skb->nfctinfo设置为IP_CT_RELATED
        2.当原始方向发送的数据包到达连接跟踪模块时,其reply方向已经收到过对应的数据包
          即连接跟踪项的状态的IPS_SEEN_REPLY_BIT位已经置位了。
          此时将skb->nfctinfo设置为IP_CT_ESTABLISHED
        */
		if (test_bit(IPS_SEEN_REPLY_BIT, &ct->status)) {
			pr_debug("nf_conntrack_in: normal packet for %pn", ct);
			*ctinfo = IP_CT_ESTABLISHED;
		} else if (test_bit(IPS_EXPECTED_BIT, &ct->status)) {
			pr_debug("nf_conntrack_in: related packet for %pn", ct);
			*ctinfo = IP_CT_RELATED;
		} else {
			//初始方向的第一个数据包
			pr_debug("nf_conntrack_in: new packet for %pn", ct);
			*ctinfo = IP_CT_NEW;
		}
		*set_reply = 0;
	}
	//设置skb中的引用计数和连接跟踪状态
	skb->nfct = &ct->ct_general;
	skb->nfctinfo = *ctinfo;
	//返回连接跟踪信息块指针
	return ct;
}

1.1.1 nf_ct_get_tuple()

        参见:《linux内核协议栈 netfilter 之连接跟踪子系统核心数据结构 struct nf_conn》

1.1.2 nf_conntrack_find_get()

       参见:《linux内核协议栈 netfilter 之连接跟踪子系统核心数据结构 struct nf_conn》

1.1.3 init_conntrack()

       参见:《linux内核协议栈 netfilter 之连接跟踪子系统核心数据结构 struct nf_conn》

2 连接跟踪子系统出口 nf_conntrack_confirm()

该接口的核心逻辑如下:

  1. 首先判断该数据连接跟踪项是否已经被确认,即是判断该连接跟踪项的 status 中的 IPS_CONFIRMED_BIT 位是否为 1 。
  2. 若连接跟踪项尚未有确认,则调用函数 __nf_conntrack_confirm() 进行确认。
  3. 调用nf_ct_deliver_cached_events()  触发连接跟踪项对应的消息通知回调,判断是否需要向 nfnetlink 模块发送相应的消息。
/* Confirm a connection: returns NF_DROP if packet must be dropped. */
static inline int nf_conntrack_confirm(struct sk_buff *skb)
{
	//skb所属连接的连接跟踪信息块
	struct nf_conn *ct = (struct nf_conn *)skb->nfct;
	int ret = NF_ACCEPT;

	if (ct) {
		if (!nf_ct_is_confirmed(ct) && !nf_ct_is_dying(ct))
			ret = __nf_conntrack_confirm(skb);
		//向外部模块发送缓存的事件
		nf_ct_deliver_cached_events(ct);
	}
	return ret;
}

2.1 连接确认判断

并不是每一个skb都需要执行确认,一条连接只有初始方向上的第一个skb需要被确认,并且连接必须是有效的才需要确认。

//检查该连接是否已经被确认过了
static inline int nf_ct_is_confirmed(struct nf_conn *ct)
{
	return test_bit(IPS_CONFIRMED_BIT, &ct->status);
}
//检查连接是否依然有效
static inline int nf_ct_is_dying(struct nf_conn *ct)
{
	return test_bit(IPS_DYING_BIT, &ct->status);
}

2.2 连接确认 __nf_conntrack_confirm()【核心】

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

  1. 调用CTINFO2DIR,判断是否是原始方向发送的数据包(仅对原始方向的数据包对应的连接跟踪项进行确认操作。)
  2. 若1中判断通过后,分别计算 original、reply 方向上的 tuple 变量对应的 hash 值,分别为 hash、repl_hash。
  3. 若连接跟踪项的original、reply方向上的tuple_hash变量均没有对应的网络命名空间的 net->ct.hash[] 链表中,则首先将original方向上的 tuple_hash 从 unconntrack 链表(net->ct.unconfirmed)中删除,然后将连接跟踪项的original、reply方向上的tuple_hash变量添加到相对应的 net->ct.hash[[] 链表中置位连接跟踪项的status中的IPS_CONFIRMED_BIT,并启动超时定时器,用于实现对连接跟踪项所占内存的超时回收功能。
/* Confirm a connection given skb; places it in hash table */
int __nf_conntrack_confirm(struct sk_buff *skb)
{
	unsigned int hash, repl_hash;
	struct nf_conntrack_tuple_hash *h;
	struct nf_conn *ct;
	struct nf_conn_help *help;
	struct hlist_node *n;
	enum ip_conntrack_info ctinfo;

	ct = nf_ct_get(skb, &ctinfo);
	/* ipt_REJECT uses nf_conntrack_attach to attach related
	   ICMP/TCP RST packets in other direction.  Actual packet
	   which created connection will be IP_CT_NEW or for an
	   expected connection, IP_CT_RELATED. */

	/*该函数只对dir为IP_CT_DIR_ORIGINAL状态时的nf_conn进行confirm操作
      对于dir为IP_CT_DIR_REPLY状态的nf_conn,其状态已经设置过confirm,所以
      不需要操作*/
	if (CTINFO2DIR(ctinfo) != IP_CT_DIR_ORIGINAL)
		return NF_ACCEPT;

	//计算初始方向和reply方向的tuple的哈希值
	hash = hash_conntrack(&ct->tuplehash[IP_CT_DIR_ORIGINAL].tuple);
	repl_hash = hash_conntrack(&ct->tuplehash[IP_CT_DIR_REPLY].tuple);

	spin_lock_bh(&nf_conntrack_lock);
	/* See if there's one in the list already, including reverse:
	   NAT could have grabbed it without realizing, since we're
	   not in the hash.  If there is, we lost race. */

	//检查全局的hash表中是否已经保存了对应的tuple或者reply_tuple,
	//如果已经保存了,那么是一种异常,结束处理过程
	hlist_for_each_entry(h, n, &nf_conntrack_hash[hash], hnode)
		if (nf_ct_tuple_equal(&ct->tuplehash[IP_CT_DIR_ORIGINAL].tuple, &h->tuple))
			goto out;
	hlist_for_each_entry(h, n, &nf_conntrack_hash[repl_hash], hnode)
		if (nf_ct_tuple_equal(&ct->tuplehash[IP_CT_DIR_REPLY].tuple, &h->tuple))
			goto out;

	//将初始方向上的tuple_hash从net->ct.unconfirmed 链表中删除,添加的时候也只添加了初始方向的tuple
	hlist_del(&ct->tuplehash[IP_CT_DIR_ORIGINAL].hnode);

	//将tuple和reply_tuple都加入到网络命名空间的net->ct.hash中,完成一条连接的跟踪
	__nf_conntrack_hash_insert(ct, hash, repl_hash);
	/* 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);
	NF_CT_STAT_INC(insert);
	spin_unlock_bh(&nf_conntrack_lock);
	help = nfct_help(ct);
	if (help && help->helper)
		nf_conntrack_event_cache(IPCT_HELPER, skb);
#ifdef CONFIG_NF_NAT_NEEDED
	if (test_bit(IPS_SRC_NAT_DONE_BIT, &ct->status) ||
	    test_bit(IPS_DST_NAT_DONE_BIT, &ct->status))
		nf_conntrack_event_cache(IPCT_NATINFO, skb);
#endif
	nf_conntrack_event_cache(master_ct(ct) ? IPCT_RELATED : IPCT_NEW, skb);
	return NF_ACCEPT;
out:
	NF_CT_STAT_INC(insert_failed);
	spin_unlock_bh(&nf_conntrack_lock);
	return NF_DROP;
}
EXPORT_SYMBOL_GPL(__nf_conntrack_confirm);

3 连接超时机制

一个连接被建立后,如果长时间没有数据交互,那么应该将该连接从连接跟踪子系统中清除,因为毕竟内存是有限的,不可能长时间跟踪这些空闲的连接。为了实现这个目的,连接跟踪子系统为每个连接维护了一个定时器,一旦该定时器超时,那么就将该连接从系统中清除。

3.1 定时器的初始化 nf_conntrack_alloc()

定时器是在连接跟踪信息块的分配函数nf_conntrack_alloc()中初始化的。

struct nf_conn *nf_conntrack_alloc(const struct nf_conntrack_tuple *orig,
				   const struct nf_conntrack_tuple *repl)
{
...
	/* Don't set timer yet: wait for confirmation */
	setup_timer(&ct->timeout, death_by_timeout, (unsigned long)ct);
...
}

如上所见,定时器是在nf_conntrack_confirmed()中激活的。

3.2 定时器超时回调 death_by_timeout()

定时器超时回调函数为death_by_timeout(),其实现就是将连接跟踪信息块从系统中删除。

static void death_by_timeout(unsigned long ul_conntrack)
{
	struct nf_conn *ct = (void *)ul_conntrack;
	struct nf_conn_help *help = nfct_help(ct);
	struct nf_conntrack_helper *helper;
	//如果该连接有helper模块,先调用该helper模块的销毁函数
	if (help) {
		rcu_read_lock();
		helper = rcu_dereference(help->helper);
		if (helper && helper->destroy)
			helper->destroy(ct);
		rcu_read_unlock();
	}
	spin_lock_bh(&nf_conntrack_lock);
	/* Inside lock so preempt is disabled on module removal path.
	 * Otherwise we can get spurious warnings. */
	NF_CT_STAT_INC(delete_list);
	//将连接跟踪信息块、可能存在的期望连接一并从系统中删除
	clean_from_lists(ct);
	spin_unlock_bh(&nf_conntrack_lock);
	//递减连接信息跟踪块的引用计数,当引用计数变为0后会被其
	nf_ct_put(ct);
}

3.2.1 连接删除 clean_from_llists()

具体的删除由 clean_from_llists() 实现:

static void clean_from_lists(struct nf_conn *ct)
{
	//删除了tuple也就从全局的hash表中删除了连接跟踪信息块
	hlist_del_rcu(&ct->tuplehash[IP_CT_DIR_ORIGINAL].hnode);
	hlist_del_rcu(&ct->tuplehash[IP_CT_DIR_REPLY].hnode);
	//销毁所有的期望连接
	nf_ct_remove_expectations(ct);
}

3.3 定时器的更新nf_ct_refresh_acct()

当然,当收到数据包后,也应该更新该定时器,防止其超时,这是通过调用nf_ct_refresh_acct()刷新的。这个刷新动作的调用由L4协议的paket()回调负责完成,这同时也表示将超时时间的决定权给了L4协议,如下代码所示以udp_packet() 为例。

/* Returns verdict for packet, and may modify conntracktype */
static int udp_packet(struct nf_conn *ct,
		      const struct sk_buff *skb,
		      unsigned int dataoff,
		      enum ip_conntrack_info ctinfo,
		      u_int8_t pf,
		      unsigned int hooknum)
{
	/* If we've seen traffic both ways, this is some kind of UDP
	   stream.  Extend timeout. */
	if (test_bit(IPS_SEEN_REPLY_BIT, &ct->status)) {
        ...
	} else
		nf_ct_refresh_acct(ct, ctinfo, skb, nf_ct_udp_timeout);

	return NF_ACCEPT;
}

/* Refresh conntrack for this many jiffies and do accounting */
static inline void nf_ct_refresh_acct(struct nf_conn *ct,
	enum ip_conntrack_info ctinfo, const struct sk_buff *skb,
	unsigned long extra_jiffies)
{
	__nf_ct_refresh_acct(ct, ctinfo, skb, extra_jiffies, 1);
}
/* Refresh conntrack for this many jiffies and do accounting if do_acct is 1 */
void __nf_ct_refresh_acct(struct nf_conn *ct, enum ip_conntrack_info ctinfo,
	const struct sk_buff *skb, unsigned long extra_jiffies, int do_acct)
{
	int event = 0;

	NF_CT_ASSERT(ct->timeout.data == (unsigned long)ct);
	NF_CT_ASSERT(skb);
	spin_lock_bh(&nf_conntrack_lock);
	//设定了该标记的连接的超时值将无法被更新
	if (test_bit(IPS_FIXED_TIMEOUT_BIT, &ct->status))
		goto acct;
	/* If not in hash table, timer will not be active yet */
	if (!nf_ct_is_confirmed(ct)) {
		//连接跟踪信息块还没有被确认时,该定时器就还没有被激活,此时
		//重新设定超时时间,认为是重新更新超时时间戳
		ct->timeout.expires = extra_jiffies;
		event = IPCT_REFRESH;
	} else {
		//传入超时值是当前时间的相对值
		unsigned long newtime = jiffies + extra_jiffies;
		//只有当新的超时值至少超过当前超时值1s时才重新更新定时器(避免频繁更新)
		if ((newtime - ct->timeout.expires >= HZ) && del_timer(&ct->timeout)) {
			ct->timeout.expires = newtime;
			add_timer(&ct->timeout);
			event = IPCT_REFRESH;
		}
	}
acct:
#ifdef CONFIG_NF_CT_ACCT
	//做数据统计
	if (do_acct) {
		ct->counters[CTINFO2DIR(ctinfo)].packets++;
		ct->counters[CTINFO2DIR(ctinfo)].bytes +=
			skb->len - skb_network_offset(skb);
		if ((ct->counters[CTINFO2DIR(ctinfo)].packets & 0x80000000)
		    || (ct->counters[CTINFO2DIR(ctinfo)].bytes & 0x80000000))
			event |= IPCT_COUNTER_FILLING;
	}
#endif
	spin_unlock_bh(&nf_conntrack_lock);
	/* must be unlocked when calling event cache */
	if (event)
		nf_conntrack_event_cache(event, skb);
}
EXPORT_SYMBOL_GPL(__nf_ct_refresh_acct);

 

最后

以上就是坚定睫毛为你收集整理的linux内核协议栈 netfilter 之连接跟踪子系统核心实现nf_conntrack_in() / nf_conntrack_confirm() / 定时器1 连接跟踪入口 nf_conntrack_in()2 连接跟踪子系统出口 nf_conntrack_confirm()3 连接超时机制的全部内容,希望文章能够帮你解决linux内核协议栈 netfilter 之连接跟踪子系统核心实现nf_conntrack_in() / nf_conntrack_confirm() / 定时器1 连接跟踪入口 nf_conntrack_in()2 连接跟踪子系统出口 nf_conntrack_confirm()3 连接超时机制所遇到的程序开发问题。

如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(44)

评论列表共有 0 条评论

立即
投稿
返回
顶部