conntrack hook函數分析
enum nf_ip_hook_priorities { NF_IP_PRI_FIRST = INT_MIN, NF_IP_PRI_CONNTRACK_DEFRAG = -400//優先級最大, 涉及到ip 分片重組 NF_IP_PRI_RAW = -300, NF_IP_PRI_SELINUX_FIRST = -225, NF_IP_PRI_CONNTRACK = -200, NF_IP_PRI_MANGLE = -150, NF_IP_PRI_NAT_DST = -100, NF_IP_PRI_FILTER = 0, NF_IP_PRI_SECURITY = 50, NF_IP_PRI_NAT_SRC = 100, NF_IP_PRI_SELINUX_LAST = 225, NF_IP_PRI_CONNTRACK_HELPER = 300, NF_IP_PRI_CONNTRACK_CONFIRM = INT_MAX, NF_IP_PRI_LAST = INT_MAX, };
連接跟蹤機制在Netfilter框架里所注冊的hook函數一共就五個:ipv4_conntrack_defrag()、ipv4_conntrack_in()、ipv4_conntrack_local()、ipv4_conntrack_help()和ipv4_confirm();
ipv4_conntrack_defrag 主要講的是 分片重組 可以參考ip分片重組這篇文章。
- 這樣數據包就會經過ipv4注冊的鈎子項,並調用nf_conntrack_in()函數建立連接表項,連接表項中的tuple由ipv4注冊的3/4層協議處理函數構建。
- ipv4_conntrack_in() 掛載在NF_IP_PRE_ROUTING點上。該函數主要功能是創建鏈接,即創建struct nf_conn結構,同時填充struct nf_conn中的一些必要的信息,例如鏈接狀態、引用計數、helper結構等。
- ipv4_confirm() 掛載在NF_IP_POST_ROUTING和NF_IP_LOCAL_IN點上。該函數主要功能是確認一個鏈接。對於一個新鏈接,在ipv4_conntrack_in()函數中只是創建了struct nf_conn結構,但並沒有將該結構掛載到鏈接跟蹤的Hash表中,因為此時還不能確定該鏈接是否會被NF_IP_FORWARD點上的鈎子函數過濾掉,所以將掛載到Hash表的工作放到了ipv4_confirm()函數中。同時,子鏈接的helper功能也是在該函數中實現的。
- ipv4_conntrack_local() 掛載在NF_IP_LOCAL_OUT點上。該函數功能與ipv4_conntrack_in()函數基本相同,但其用來處理本機主動向外發起的鏈接。
- nf_conntrack_ipv4_compat_init() --> register_pernet_subsys() --> ip_conntrack_net_init() 創建/proc文件ip_conntrack和ip_conntrack_expect
nf_conntrack_in 分析:
/* conntrack的工作主要是: 1. 由skb得到一個tuple,對數據包做合法性檢查。 2. 查找net->ct.hash表是否已記錄這個tuple。如果沒有記錄,則新建一個tuple及nf_conn並添加到unconfirmed鏈表中。 3. 對ct做一些協議相關的特定處理和檢查。 4. 更新conntrack的狀態ct->status和skb的狀態skb->nfctinfo。 */ unsigned int nf_conntrack_in(struct net *net, u_int8_t pf, unsigned int hooknum, struct sk_buff *skb) { struct nf_conn *ct, *tmpl = NULL; enum ip_conntrack_info ctinfo; struct nf_conntrack_l3proto *l3proto; struct nf_conntrack_l4proto *l4proto; unsigned int *timeouts; unsigned int dataoff; u_int8_t protonum; int set_reply = 0; int ret; if (skb->nfct) {nf_ct_get /* Previously seen (loopback or untracked)? Ignore. 有點不明白 忽略 難道就是解決 lo場景 當skb->nfct為有效值時,即意味着該skb已經經過了conn track,再次落到conn track時。 如注釋所說,比如發往回環的時候,會有這個情況。 */ tmpl = (struct nf_conn *)skb->nfct; if (!nf_ct_is_template(tmpl)) { NF_CT_STAT_INC_ATOMIC(net, ignore); return NF_ACCEPT; } skb->nfct = NULL; } /* rcu_read_lock()ed by nf_hook_slow 根據proto family,來得到l3的conn track。 這里對於netfilter來說,它將conn track的具體工作下發到具體的L3層的target處理。 這樣無論是ipv4還是ipv6,netfilter的處理可以保持一致。甚至其可以支持更多的L3協議。 對於ipv4來說,l3proto就是nf_conntrack_l3proto_ipv4。 */ l3proto = __nf_ct_l3proto_find(pf); /* 將L3的報文頭的偏移即數據包skb傳給l3->get_l4proto來得到L4的協議號即L4的起始位置。 這時L3的報文頭完全由具體的L3層的proto負責解析 */ ret = l3proto->get_l4proto(skb, skb_network_offset(skb), &dataoff, &protonum); if (ret <= 0) { pr_debug("not prepared to track yet or error occurred\n"); NF_CT_STAT_INC_ATOMIC(net, error); NF_CT_STAT_INC_ATOMIC(net, invalid); ret = -ret; goto out; } /* /* 與L3類似,同樣是通過L4的協議號得到具體的L4 proto的target。 實現與具體協議的解耦。 如nf_conntrack_l4proto_tcp4, nf_conntrack_l4proto_udp4等等 */ */ l4proto = __nf_ct_l4proto_find(pf, protonum); /* It may be an special packet, error, unclean... * inverse of the return code tells to the netfilter * core what to do with the packet. */ if (l4proto->error != NULL) {/* 對L4數據包進行正確性檢查,由具體的L4協議負責 */ ret = l4proto->error(net, tmpl, skb, dataoff, &ctinfo, pf, hooknum); if (ret <= 0) { NF_CT_STAT_INC_ATOMIC(net, error); NF_CT_STAT_INC_ATOMIC(net, invalid); ret = -ret; goto out; } /* ICMP[v6] protocol trackers may assign one conntrack. */ if (skb->nfct) goto out; } /* 根據skb L3和L4層的信息 得到一個nf_conn結構 */ ct = resolve_normal_ct(net, tmpl, skb, dataoff, pf, protonum, l3proto, l4proto, &set_reply, &ctinfo); if (!ct) { /* Not valid part of a connection */ NF_CT_STAT_INC_ATOMIC(net, invalid); ret = NF_ACCEPT; goto out; } if (IS_ERR(ct)) { /* Too stressed to deal. */ NF_CT_STAT_INC_ATOMIC(net, drop); ret = NF_DROP; goto out; } NF_CT_ASSERT(skb->nfct); /* Decide what timeout policy we want to apply to this flow. */ timeouts = nf_ct_timeout_lookup(net, ct, l4proto); /* 將數據包傳遞給具體的L4 target進行特定的操作。 如nf_conntrack_l4proto_udp4,會update連接conn track的age,保證不ageout 更新超時時間時 可以詳見 各個協議具體分析 對於nf_conntrack_l4proto_tcp4,會有更復雜的操作。 */ ret = l4proto->packet(ct, skb, dataoff, ctinfo, pf, hooknum, timeouts); 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 module\n"); nf_conntrack_put(skb->nfct); skb->nfct = NULL; NF_CT_STAT_INC_ATOMIC(net, invalid); if (ret == -NF_DROP) NF_CT_STAT_INC_ATOMIC(net, drop); ret = -ret; goto out; } /* 第一次收到應答,則設置IPS_SEEN_REPLY_BIT標記,原值為0,則需要記錄應答事件 */ if (set_reply && !test_and_set_bit(IPS_SEEN_REPLY_BIT, &ct->status)) nf_conntrack_event_cache(IPCT_REPLY, ct); out: if (tmpl) { /* Special case: we have to repeat this hook, assign the * template again to this packet. We assume that this packet * has no conntrack assigned. This is used by nf_ct_tcp. */ if (ret == NF_REPEAT)//set skb nfct 信息 skb->nfct = (struct nf_conntrack *)tmpl; else nf_ct_put(tmpl); } return ret; }
* * 通過協議類型 pf 的值來得到該協議的L3層的協議處理函數,對於IPv4來說也就是IP網絡層的處理函數 * 這些函數在 /net/ipv4/netfilter/nf_conntrack_l3proto_ipv4.c * 文件中的 nf_conntrack_l3proto_ipv4 結構體中注冊 */ /* * 通過L3層的 get_l4proto 函數獲取L4層的協議類型,對於IPv4來說也就是TCP或者UDP運輸層協議 * 對於TCP來說這些函數在 /net/netfilter/nf_conntrack_proto_tcp.c * 文件中的 nf_conntrack_l4proto_tcp4 結構體中注冊 */ /* * 調用L4層協議的 error 函數對數據包進行正確性檢查,對於TCP協議來說調用的是 * nf_conntrack_l4proto_tcp4 結構體中的 tcp_error 函數 */ /* 在函數resolve_normal_ct中對該數據包創建一個連接跟蹤記錄 */ /* L4proto->packet 對於TCP來說是設置TCP的各種狀態信息,對於UDP來說就只設置了一個超時時間 */
resolve_normal_ct分析:
resolve_normal_ct
函數首先會獲取該數據包的五元組信息,然后通過這個信息計算出hash值,通過這個hash值在hash表中查找,如果沒有找到則調用init_conntrack
函數創建一個新的連接跟蹤,要注意在創建連接跟蹤的時候會同時創建兩個方向的連接一個,一個是原始方向的稱為IP_CT_DIR_ORIGINAL
,另外一個是回復方向的稱為IP_CT_DIR_REPLY
,這樣當此條連接的回復報文過來后就可以很快確認數據包屬於哪條連接。回復方向的連接是在init_conntrack
函數中調用nf_ct_invert_tuple
函數創建的。最后調用nf_ct_set
函數設置此數據包的連接跟蹤標記。
ex:
原始方向:tuplehash[IP_CT_DIR_ORIGINAL] = {192.168.0.1:12345,200.200.
200
.200:80,TCP}
則回復方向為:tuplehash[IP_CT_DIR_REPLY] = {
200
.200
.200
.200:80,192.168.0.1:12345,TCP}
對於NAT 時 其五元組信息會改變。
static inline struct nf_conn * resolve_normal_ct(struct net *net, struct nf_conn *tmpl, 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) { const struct nf_conntrack_zone *zone; struct nf_conntrack_tuple tuple; struct nf_conntrack_tuple_hash *h; struct nf_conntrack_zone tmp; struct nf_conn *ct; u32 hash; /* 將數據包轉換成tuple */ /* 由skb得出一個original方向的tuple,賦值給tuple,這是一個struct nf_conntrack_tuple結構,這里給其所有成員都賦值了, 以TCP包為例: tuple->src.l3num = l3num; tuple->src.u3.ip = srcip; tuple->dst.u3.ip = dstip; tuple->dst.protonum = protonum; tuple->dst.dir = IP_CT_DIR_ORIGINAL; tuple->src.u.tcp.port = srcport; tuple->dst.u.tcp.port = destport; 調用L3proto->pkt_to_tuple() 以及 L4proto->pkt_to_tuple() 設置L3 L4信息 */ if (!nf_ct_get_tuple(skb, skb_network_offset(skb), dataoff, l3num, protonum, net, &tuple, l3proto, l4proto)) { pr_debug("Can't get tuple\n"); return NULL; } /* look for tuple match */ zone = nf_ct_zone_tmpl(tmpl, skb, &tmp); hash = hash_conntrack_raw(&tuple, net);//通過五元組信息計算一個 hash 值,是調用hash_conntrack函數,根據數據包對應的tuple實現的 /* 查找對應的tuple在連接跟蹤表中是否存在 */ h = __nf_conntrack_find_get(net, zone, &tuple, hash); if (!h) {/* 如果沒有找到,則創建一對新的tuple(兩個方向的tuple是同時創建的)及其 nf_conn結構,並添加到unconfirmed鏈表中。 */ h = init_conntrack(net, tmpl, &tuple, l3proto, l4proto, skb, dataoff, hash); if (!h) return NULL; if (IS_ERR(h)) return (void *)h; } ct = nf_ct_tuplehash_to_ctrack(h); /* It exists; we have (non-exclusive) reference. */ if (NF_CT_DIRECTION(h) == IP_CT_DIR_REPLY) { //是reply 放向 *ctinfo = IP_CT_ESTABLISHED_REPLY; /* Please set reply bit if this packet OK */ *set_reply = 1; } else { /* tuple為original方向,即初始的發送方向 */ /* Once we've had two way comms, always ESTABLISHED. */ if (test_bit(IPS_SEEN_REPLY_BIT, &ct->status)) { pr_debug("normal packet for %p\n", ct); /* 之前收到過REPLY,那么ctinfo為established*/ *ctinfo = IP_CT_ESTABLISHED; } else if (test_bit(IPS_EXPECTED_BIT, &ct->status)) { pr_debug("related packet for %p\n", ct);/* 表明這是一個相關 期望的conn track。如ICMP error或者FTP的data session?????????*/ *ctinfo = IP_CT_RELATED; } else { pr_debug("new packet for %p\n", ct);/* 表明這是一個新的conn track*/ *ctinfo = IP_CT_NEW; //init ip_ct_new } *set_reply = 0; } /* 在struct nf_conn結構體中,ct_general是其第一個成員,所以它的地址和整個結構體的地址相同, 所以skb->nfct的值實際上就是skb對應的conntrack條目的地址, 因此通過(struct nf_conn *)skb->nfct就可以通過skb得到它的conntrack條目 */ skb->nfct = &ct->ct_general; skb->nfctinfo = *ctinfo; return ct; }
/* Allocate a new conntrack: we return -ENOMEM if classification failed due to stress. Otherwise it really is unclassifiable. */ static struct nf_conntrack_tuple_hash * init_conntrack(struct net *net, struct nf_conn *tmpl, const struct nf_conntrack_tuple *tuple, struct nf_conntrack_l3proto *l3proto, struct nf_conntrack_l4proto *l4proto, struct sk_buff *skb, unsigned int dataoff, u32 hash) { struct nf_conn *ct; struct nf_conn_help *help; struct nf_conntrack_tuple repl_tuple; struct nf_conntrack_ecache *ecache; struct nf_conntrack_expect *exp = NULL; const struct nf_conntrack_zone *zone; struct nf_conn_timeout *timeout_ext; struct nf_conntrack_zone tmp; unsigned int *timeouts; /* 根據tuple制作一個repl_tuple。主要是調用L3和L4的invert_tuple方法 */ if (!nf_ct_invert_tuple(&repl_tuple, tuple, l3proto, l4proto)) { pr_debug("Can't invert tuple.\n"); return NULL; } /* 在cache中申請一個nf_conn結構,把tuple和repl_tuple賦值給ct的tuplehash[]數組, 並初始化ct.timeout定時器函數為death_by_timeout(),但不啟動定時器。 * */ zone = nf_ct_zone_tmpl(tmpl, skb, &tmp); ct = __nf_conntrack_alloc(net, zone, tuple, &repl_tuple, GFP_ATOMIC, hash); if (IS_ERR(ct)) return (struct nf_conntrack_tuple_hash *)ct; if (tmpl && nfct_synproxy(tmpl)) { nfct_seqadj_ext_add(ct); nfct_synproxy_ext_add(ct); } timeout_ext = tmpl ? nf_ct_timeout_find(tmpl) : NULL; if (timeout_ext) { timeouts = nf_ct_timeout_data(timeout_ext); if (unlikely(!timeouts)) timeouts = l4proto->get_timeouts(net); } else { timeouts = l4proto->get_timeouts(net); } /* 對tcp來說,下面函數就是將L4層字段如window, ack等字段 賦給ct->proto.tcp.seen[0],由於新建立的連接才調這里,所以 不用給reply方向的ct->proto.tcp.seen[1]賦值 */ if (!l4proto->new(ct, skb, dataoff, timeouts)) { nf_conntrack_free(ct); pr_debug("can't track with proto module\n"); return NULL; } if (timeout_ext) nf_ct_timeout_ext_add(ct, rcu_dereference(timeout_ext->timeout), GFP_ATOMIC); /* 為acct和ecache兩個ext分配空間。不過之后一般不會被初始化,所以用不到 */ nf_ct_acct_ext_add(ct, GFP_ATOMIC); nf_ct_tstamp_ext_add(ct, GFP_ATOMIC); nf_ct_labels_ext_add(ct); ecache = tmpl ? nf_ct_ecache_find(tmpl) : NULL; nf_ct_ecache_ext_add(ct, ecache ? ecache->ctmask : 0, ecache ? ecache->expmask : 0, GFP_ATOMIC); local_bh_disable(); /* 會在全局的期望連接鏈表expect_hash中查找是否有匹配新建tuple的期望連接。第一次過來的數據包肯定是沒有的, 於是走else分支,__nf_ct_try_assign_helper()函數去nf_ct_helper_hash哈希表中匹配當前tuple, 由於我們在本節開頭提到nf_conntrack_tftp_init()已經把tftp的helper extension添加進去了, 所以可以匹配成功,於是把找到的helper賦值給nfct_help(ct)->helper,而這個helper的help方法就是tftp_help()。 當tftp請求包走到ipv4_confirm的時候,會去執行這個help方法,即tftp_help(),也就是建立一個期望連接 當后續tftp傳輸數據時,在nf_conntrack_in里面,新建tuple后,在expect_hash表中查可以匹配到新建tuple的期望連接(因為只根據源端口來匹配), 因此上面代碼的if成立,所以ct->master被賦值為exp->master,並且,還會執行exp->expectfn()函數,這個函數上面提到是指向nf_nat_follow_master()的, 該函數根據ct的master來給ct做NAT,ct在經過這個函數處理前后的tuple分別為: */ /* 在helper 函數中 回生成expect 並加入全局鏈表 同時 expect_count++*/ if (net->ct.expect_count) { /* 如果在期望連接鏈表中 */ spin_lock(&nf_conntrack_expect_lock); exp = nf_ct_find_expectation(net, zone, tuple); /* 如果在期望連接鏈表中 */ if (exp) { pr_debug("expectation arrives ct=%p exp=%p\n", ct, exp); /* Welcome, Mr. Bond. We've been expecting you... */ __set_bit(IPS_EXPECTED_BIT, &ct->status); /* conntrack的master位指向搜索到的expected,而expected的sibling位指向conntrack……..解釋一下,這時候有兩個conntrack, 一個是一開始的初始連接(比如69端口的那個)也就是主連接conntrack1, 一個是現在正在處理的連接(1002)子連接conntrack2,兩者和expect的關系是: 1. expect的sibling指向conntrack2,而expectant指向conntrack1, 2. 一個主連接conntrack1可以有若干個expect(int expecting表示當前數量),這些 expect也用一個鏈表組織,conntrack1中的struct list_head sibling_list就是該 鏈表的頭。 3. 一個子連接只有一個主連接,conntrack2的struct ip_conntrack_expect *master 指向expect 通過一個中間結構expect將主連接和子連接關聯起來 */ /* exp->master safe, refcnt bumped in nf_ct_find_expectation */ ct->master = exp->master; if (exp->helper) {/* helper的ext以及help鏈表分配空間 */ help = nf_ct_helper_ext_add(ct, exp->helper, GFP_ATOMIC); if (help) rcu_assign_pointer(help->helper, exp->helper); } #ifdef CONFIG_NF_CONNTRACK_MARK ct->mark = exp->master->mark; #endif #ifdef CONFIG_NF_CONNTRACK_SECMARK ct->secmark = exp->master->secmark; #endif NF_CT_STAT_INC(net, expect_new); } spin_unlock(&nf_conntrack_expect_lock); } if (!exp) {// 如果不存在 從新賦值 ct->ext->...->help->helper = helper __nf_ct_try_assign_helper(ct, tmpl, GFP_ATOMIC); NF_CT_STAT_INC(net, new); } /* Now it is inserted into the unconfirmed list, bump refcount */ nf_conntrack_get(&ct->ct_general); /* 將這個tuple添加到unconfirmed鏈表中,因為數據包還沒有出去, 所以不知道是否會被丟棄,所以暫時先不添加到conntrack hash中 */ nf_ct_add_to_unconfirmed_list(ct); local_bh_enable(); if (exp) { if (exp->expectfn) exp->expectfn(ct, exp); nf_ct_expect_put(exp); } return &ct->tuplehash[IP_CT_DIR_ORIGINAL]; }
ipv4_confirm分析:
ipv4_confirm相關函數完成對連接的確認,並且將連接按照方向加入到對應的hash表中;
static unsigned int ipv4_confirm(void *priv, struct sk_buff *skb, const struct nf_hook_state *state) { struct nf_conn *ct; enum ip_conntrack_info ctinfo; ct = nf_ct_get(skb, &ctinfo);/* ct結構從下面獲得: (struct nf_conn *)skb->nfct; */ /* 未關聯,或者是 已建立連接的關聯連接的響應 不明白*/ if (!ct || ctinfo == IP_CT_RELATED_REPLY) goto out; /* adjust seqs for loopback traffic only in outgoing direction /* 有調整序號標記,且不是環回包,調整序號 */ */ if (test_bit(IPS_SEQ_ADJUST_BIT, &ct->status) && !nf_is_loopback_packet(skb)) { if (!nf_ct_seq_adjust(skb, ct, ctinfo, ip_hdrlen(skb))) { NF_CT_STAT_INC_ATOMIC(nf_ct_net(ct), drop); return NF_DROP; } } out: /* We've seen it coming out the other side: confirm it */ return nf_conntrack_confirm(skb); }
/* Confirm a connection: returns NF_DROP if packet must be dropped. */ static inline int nf_conntrack_confirm(struct sk_buff *skb) { struct nf_conn *ct = (struct nf_conn *)skb->nfct; int ret = NF_ACCEPT; if (ct && !nf_ct_is_untracked(ct)) {/* 未確認,則進行確認 */ if (!nf_ct_is_confirmed(ct)) ret = __nf_conntrack_confirm(skb); if (likely(ret == NF_ACCEPT)) /* accpet狀態事件通知 */ nf_ct_deliver_cached_events(ct);//不是很明白 } return ret; } /* Confirm a connection given skb; places it in hash table 將該conntrack條目從unconfirmed鏈表中刪除,並加入到已確認的鏈表中, 為該條目啟動定時器,即該條目已經生效了 */ int __nf_conntrack_confirm(struct sk_buff *skb) { const struct nf_conntrack_zone *zone; unsigned int hash, reply_hash; struct nf_conntrack_tuple_hash *h; struct nf_conn *ct; struct nf_conn_help *help; struct nf_conn_tstamp *tstamp; struct hlist_nulls_node *n; enum ip_conntrack_info ctinfo; struct net *net; unsigned int sequence; int ret = NF_DROP; ct = nf_ct_get(skb, &ctinfo); net = nf_ct_net(ct); /* 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. */ /* 如果不是original方向的包,直接返回 */ if (CTINFO2DIR(ctinfo) != IP_CT_DIR_ORIGINAL) return NF_ACCEPT; zone = nf_ct_zone(ct); local_bh_disable(); do { sequence = read_seqcount_begin(&nf_conntrack_generation); /* reuse the hash saved before */ hash = *(unsigned long *)&ct->tuplehash[IP_CT_DIR_REPLY].hnnode.pprev; hash = scale_hash(hash); reply_hash = hash_conntrack(net, &ct->tuplehash[IP_CT_DIR_REPLY].tuple); } while (nf_conntrack_double_lock(net, hash, reply_hash, sequence)); /* We're not in hash table, and we refuse to set up related * connections for unconfirmed conns. But packet copies and * REJECT will give spurious warnings here. */ /* NF_CT_ASSERT(atomic_read(&ct->ct_general.use) == 1); */ /* No external references means no one else could have * confirmed us. */ NF_CT_ASSERT(!nf_ct_is_confirmed(ct)); pr_debug("Confirming conntrack %p\n", ct); /* We have to check the DYING flag after unlink to prevent * a race against nf_ct_get_next_corpse() possibly called from * user context, else we insert an already 'dead' hash, blocking * further use of that particular connection -JM. 將 orig_tuple 從unconfirmd中刪除 */ nf_ct_del_from_dying_or_unconfirmed_list(ct); if (unlikely(nf_ct_is_dying(ct))) { nf_ct_add_to_dying_list(ct); goto dying; } /* 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. */ /* 待確認的連接如果已經在conntrack的hash表中(有一個方向存在就視為存在),就不再插入了,丟棄它 */ hlist_nulls_for_each_entry(h, n, &nf_conntrack_hash[hash], hnnode) if (nf_ct_key_equal(h, &ct->tuplehash[IP_CT_DIR_ORIGINAL].tuple, zone, net)) goto out; hlist_nulls_for_each_entry(h, n, &nf_conntrack_hash[reply_hash], hnnode) if (nf_ct_key_equal(h, &ct->tuplehash[IP_CT_DIR_REPLY].tuple, zone, net)) goto out; /* 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);//一個nf_conn的超時處理函數death_by_timeout(),即超時后會執行這個函數: atomic_inc(&ct->ct_general.use);//增加引用計數 ct->status |= IPS_CONFIRMED;//設置flag /* set conntrack timestamp, if enabled. */ tstamp = nf_conn_tstamp_find(ct); if (tstamp) { if (skb->tstamp.tv64 == 0) __net_timestamp(skb); tstamp->start = ktime_to_ns(skb->tstamp); } /* Since the lookup is lockless, hash insertion must be done after * starting the timer and setting the CONFIRMED bit. The RCU barriers * guarantee that no other CPU can find the conntrack before the above * stores are visible. 將 orig_tuple reply_tuple 添加到 nf_conntrack_hash */ __nf_conntrack_hash_insert(ct, hash, reply_hash); nf_conntrack_double_unlock(hash, reply_hash); NF_CT_STAT_INC(net, insert); local_bh_enable(); help = nfct_help(ct); if (help && help->helper) nf_conntrack_event_cache(IPCT_HELPER, ct); nf_conntrack_event_cache(master_ct(ct) ? IPCT_RELATED : IPCT_NEW, ct); return NF_ACCEPT; out: nf_ct_add_to_dying_list(ct); ret = nf_ct_resolve_clash(net, skb, ctinfo, h); dying: nf_conntrack_double_unlock(hash, reply_hash); NF_CT_STAT_INC(net, insert_failed); local_bh_enable(); return ret; } EXPORT_SYMBOL_GPL(__nf_conntrack_confirm);