概述
目录
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点进入。该函数的功能是实现对数据的连接跟踪,更新连接跟踪项的连接状态,目前就是根据五元组识别一条数据流。这个函数可以说是连接跟踪模块的重中之重。
首先它将数据包的类型按如下几个类型处理:
- 当这是一个新的数据流的第一个数据包时,则会根据该数据包的五元组信息创建一个新的连接跟踪项,并初始化该连接跟踪项的tuple_hash、helper的值,最后,将该连接跟踪项的原始方向的tuple_hash添加到unconfirmed链表中,且该连接不是期望连接。
- 当这是一个新的数据流的第一个数据包时,则会根据该数据包的五元组信息创建一个新的连接跟踪项,并初始化该连接跟踪项的tuple_hash、helper的值,最后将该连接跟踪项的原始方向的tuple_hash添加到unconfirmed链表中,且该连接是期望连接。
- 当该数据包是原始方向的非第一个数据包,但当该数据包进入连接跟踪模块时,连接跟踪模块还没有收到reply方向的数据包。
- 当该数据包是原始方向的非第一个数据包, 且到改数据包进入连接跟踪模块时,连接跟踪 模块已经接收到reply方向的数据包。
- 当该数据包是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方向,且置位连接跟踪项的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);
梳理下关键步骤:
- 根据skb找到能够处理该skb的L3协议和L4协议;
- 如果有,调用L4协议的error()回调进行报文校验,校验通过继续,否则结束;
- 调用resolve_normal_ct()查询该skb是否属于某个已有连接,没有则创建一个;
- 调用L4协议的packet()回调,该回调的返回值将作为该Netfilter钩子的返回值,一般应该都是NF_ACCEPT。
1.1 连接跟踪信息块的获取 resolve_normal_ct()
对于连接跟踪子系统来讲,每个数据包都应该归属某一条“连接”。如上,数据包通过一些检查后,就会调用该函数检查数据包是否属于已有的连接,如果该数据包不属于任何一个连接,那么它是一个“新连接”的数据包,这时该函数就会创建一个“连接”对该数据包进行跟踪,具体实现细节如下:
- 若连接跟踪项不存在时,则根据已知条件创建一个nf_conn,并放入unconfirm表中,执行c)
- 若连接跟踪项已存在,则执行c)
- 更新连接跟踪项的状态
- 设置传入数据包的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()
该接口的核心逻辑如下:
- 首先判断该数据连接跟踪项是否已经被确认,即是判断该连接跟踪项的 status 中的 IPS_CONFIRMED_BIT 位是否为 1 。
- 若连接跟踪项尚未有确认,则调用函数 __nf_conntrack_confirm() 进行确认。
- 调用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时。具体操作如下:
- 调用CTINFO2DIR,判断是否是原始方向发送的数据包(仅对原始方向的数据包对应的连接跟踪项进行确认操作。)
- 若1中判断通过后,分别计算 original、reply 方向上的 tuple 变量对应的 hash 值,分别为 hash、repl_hash。
- 若连接跟踪项的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 连接超时机制所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复