5.resolve_normal_ct resolve_normal_ct 函數(shù)是連接跟蹤中最重要的函數(shù)之一,它的主要功能就是判斷數(shù)據(jù)包在連接跟蹤表是否存在,如果不存在,則為數(shù)據(jù)包分配相應(yīng)的連接跟蹤節(jié)點(diǎn)空間并初始化,然后設(shè)置連接狀態(tài):
/* 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);
/*前面提到過,需要將一個數(shù)據(jù)包轉(zhuǎn)換成tuple,這個轉(zhuǎn)換,就是通過ip_ct_get_tuple函數(shù)實(shí)現(xiàn)的*/
if (!ip_ct_get_tuple(skb->nh.iph, skb, skb->nh.iph->ihl*4,
&tuple,proto))
return NULL;
/*查看數(shù)據(jù)包對應(yīng)的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;
}
/*根據(jù)hash表節(jié)點(diǎn),取得數(shù)據(jù)包對應(yīng)的連接跟蹤結(jié)構(gòu)*/
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;
}
/*設(shè)置skb的對應(yīng)成員,如使用計數(shù)器、數(shù)據(jù)包狀態(tài)標(biāo)記*/
skb->nfct = &ct->ct_general;
skb->nfctinfo = *ctinfo;
return ct;
}
這個函數(shù)包含了連接跟蹤中許多重要的步驟
n 調(diào)用ip_ct_get_tuple函數(shù),把數(shù)據(jù)包轉(zhuǎn)換為tuple;
n ip_conntrack_find_get函數(shù),根據(jù)tuple查找連接跟蹤表;
n init_conntrack函數(shù),初始化一條連接;
n 判斷連接方向,設(shè)置連接狀態(tài);
5.1 數(shù)據(jù)包的轉(zhuǎn)換
ip_ct_get_tuple 實(shí)現(xiàn)數(shù)據(jù)包至tuple的轉(zhuǎn)換,這個轉(zhuǎn)換,主要是根據(jù)數(shù)據(jù)包的套接字對來進(jìn)行轉(zhuǎn)換的:
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;
}
/*設(shè)置來源、目的地址*/
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);
}
回憶一下我們前面分析協(xié)議的初始化中協(xié)議初始化的部份,pkt_to_tuple 函數(shù)指針,以每種協(xié)議的不同而不同,以TCP協(xié)議為例:
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;
/*根據(jù)報頭的端口信息,設(shè)置tuple對應(yīng)成員*/
tuple->src.u.tcp.port = hp->source;
tuple->dst.u.tcp.port = hp->dest;
return 1;
}
TCP協(xié)議中,根據(jù)來源和目的端口設(shè)置,其它協(xié)議類似,讀者可以對比分析。
5.2 Hash 表的搜索
要對Hash表進(jìn)行遍歷,首要需要找到hash表的入口,然后來遍歷該入口指向的鏈表。每個鏈表的節(jié)點(diǎn)是struct ip_conntrack_tuple_hash,它封裝了tuple,所謂封裝,就是把待查找的tuple與節(jié)點(diǎn)中已存的tuple相比較,我們來看這一過程的實(shí)現(xiàn)。
計算hash值,是調(diào)用hash_conntrack函數(shù),根據(jù)數(shù)據(jù)包對應(yīng)的tuple實(shí)現(xiàn)的:
unsigned int hash = hash_conntrack(tuple);
這樣,tuple對應(yīng)的hash表入口即為ip_conntrack_hash[hash],也就是鏈表的首節(jié)點(diǎn),然后調(diào)用ip_conntrack_find_get函數(shù)進(jìn)行查找:
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) /*查找到了,使用計數(shù)器累加*/
atomic_inc(&tuplehash_to_ctrack(h)->ct_general.use);
READ_UNLOCK(&ip_conntrack_lock);
return h;
}
鏈表是內(nèi)核中一個標(biāo)準(zhǔn)的雙向鏈表,可以調(diào)用宏list_for_each_entry 進(jìn)遍歷鏈表:
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]為起始地址的鏈表中,逐個搜索其成員,比較這個節(jié)點(diǎn)中的tuple是否與待查找的tuple是否一致,這個比較過程,是通過conntrack_tuple_cmp 函數(shù)實(shí)現(xiàn)的:
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 函數(shù)主要是取連接跟蹤ip_conntrack中的連接方向,判斷它是否等于ignored_conntrack,對與這里的比較而言,ignored_conntrack傳遞過來的為NULL。
主要的比較函數(shù)是ip_ct_tuple_equal函數(shù),函數(shù)分為“來源”和“目的”進(jìn)行比較:
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地址之外,并沒有直接比較“端口”,這是因?yàn)橄馡CMP協(xié)議這樣的并沒有“端口”協(xié)議,struct ip_conntrack_tuple 結(jié)構(gòu)中,與協(xié)議相關(guān)的,如端口等,都定義成union類型,這樣,就可以直接使用u.all,而不用再去管TCP,UDP還是ICMP了。
5.3 連接初始化
內(nèi)核使用ip_conntrack結(jié)構(gòu)來描述一個數(shù)據(jù)包的連接狀態(tài),init_conntrack函數(shù)就是在連接狀態(tài)表中不存在當(dāng)前數(shù)據(jù)包時,初始化一個ip_conntrack結(jié)構(gòu),此結(jié)構(gòu)被Netfilter用來描述一條連接,前面分析hash表時,已經(jīng)分析了它的tuplehash成員:
struct ip_conntrack
{
/* 包含了使用計數(shù)器和指向刪除連接的函數(shù)的指針 */
struct nf_conntrack ct_general;
/* 連接狀態(tài)位,它通常是一個ip_conntrack_status類型的枚舉變量,如IPS_SEEN_REPLY_BIT等*/
unsigned long status;
/* 內(nèi)核的定時器,用于處理連接超時 */
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值的隨機(jī)數(shù)種子沒有被初始化,則初始化之*/
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);
}
}
/*根據(jù)當(dāng)前的tuple取反,計算該數(shù)據(jù)包的“應(yīng)答”的tuple*/
if (!ip_ct_invert_tuple(&repl_tuple, tuple, protocol)) {
DEBUGP("Can't invert tuple.\n");
return NULL;
}
/*為數(shù)據(jù)包對應(yīng)的連接分配空間*/
conntrack = kmem_cache_alloc(ip_conntrack_cachep, GFP_ATOMIC);
if (!conntrack) {
DEBUGP("Can't allocate conntrack.\n");
return ERR_PTR(-ENOMEM);
}
/*初始化該結(jié)構(gòu)*/
memset(conntrack, 0, sizeof(*conntrack));
/*使用計數(shù)器累加*/
atomic_set(&conntrack->ct_general.use, 1);
/*設(shè)置destroy函數(shù)指針*/
conntrack->ct_general.destroy = destroy_conntrack;
/*設(shè)置正反兩個方向的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;
}
/* 初始化時間計數(shù)器,并設(shè)置超時初始函數(shù) */
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節(jié)點(diǎn)*/
return &conntrack->tuplehash[IP_CT_DIR_ORIGINAL];
}
在前文中提到過,一條完整的連接,采用struct ip_conntrack 結(jié)構(gòu)描述,初始化函數(shù)的主要功能,就是分配一個這樣的空間,然后初始化它的一些成員。
在這個函數(shù)中,有三個重要的地方需要注意,一個是根據(jù)當(dāng)前tuple,計算出應(yīng)答方向的tuple,它是調(diào)用ip_ct_invert_tuple 函數(shù)實(shí)現(xiàn)的:
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);
}
這個函數(shù)事實(shí)上,與前面講的tuple的轉(zhuǎn)換是一樣的,只是來了個乾坤大挪移,把來源和目的,以及方向?qū)φ{(diào)了。
另一個重點(diǎn)的是函數(shù)對特殊協(xié)議的支持,我們這里暫時跳過了這部份。
第三個地方是調(diào)用協(xié)議的new函數(shù):
if (!protocol->new(conntrack, skb)) {
kmem_cache_free(ip_conntrack_cachep, conntrack);
return NULL;
}
new 函數(shù)指定在每個封包第一次創(chuàng)建連接時被調(diào)用,它根據(jù)協(xié)議的不同,所處理的過程不同,以ICMP協(xié)議為例:
/* 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協(xié)議而言,僅有ICMP 請求回顯、時間戳請求、信息請求(已經(jīng)很少用了)、地址掩碼請求這四個“請求”,可能是一個“新建”的連接,所以,ICMP協(xié)議的new函數(shù)判斷是否是一個全法的ICMP新建連接,如果是非法的,則返回0,否則,初始化協(xié)議使用計數(shù)器,返回1。
5.4 連接狀態(tài)的判斷
resolve_normal_ct 函數(shù)的最后一個重要的工作是對連接狀態(tài)的判斷,tuple中包含一個“方向”成員dst.dir,對于一個初始連接,它是IP_CT_DIR_ORIGINAL:
tuple->dst.dir = IP_CT_DIR_ORIGINAL;
而它的應(yīng)答包的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 就根據(jù)tuple中對應(yīng)成員的值,判斷數(shù)據(jù)包的方向,
/* If we're the first tuple, it's the original dir. */
#define DIRECTION(h) ((enum ip_conntrack_dir)(h)->tuple.dst.dir)
但是,還有一些特殊地方,比如TCP協(xié)議,它是一個面向連接的協(xié)議,所以,它的“初始”或“應(yīng)答”包,并不一定就是“新建”或單純的“應(yīng)答”包,而是在一個連接過程中的“已建連接包”,另一個,如FTP等 復(fù)雜協(xié)議,它們還存在一些“關(guān)聯(lián)”的連接,當(dāng)然這兩部份目前還沒有涉及到,但并不影響我們分析如下這段代碼:
/* 如果是一個應(yīng)答包 ,設(shè)置狀態(tài)為已建+應(yīng)答*/
if (DIRECTION(h) == IP_CT_DIR_REPLY) {
*ctinfo = IP_CT_ESTABLISHED + IP_CT_IS_REPLY;
/* 設(shè)置應(yīng)答標(biāo)志變量 */
*set_reply = 1;
} else {
/* 新建連接方過來的數(shù)據(jù)包,對面向連接的協(xié)議而言,可能是一個已建連接,判斷其標(biāo)志位*/
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; //關(guān)聯(lián)連接
} else {
DEBUGP("ip_conntrack_in: new packet for %p\n",
ct);
*ctinfo = IP_CT_NEW; //否則,則為一個新建連接
}
*set_reply = 0;
}
/*設(shè)置數(shù)據(jù)包skb與連接狀態(tài)的關(guān)聯(lián)*/
skb->nfct = &ct->ct_general;
/*每個sk_buff都將與ip_conntrack的一個狀態(tài)關(guān)聯(lián),所以從sk_buff可以得到相應(yīng)ip_conntrack的狀態(tài),即數(shù)據(jù)包的狀態(tài)*/
skb->nfctinfo = *ctinfo;
return ct;
以上的代表所表示的發(fā)送或應(yīng)答的狀態(tài)如下圖所示:
6. ip_confirm
以上的工作事實(shí)上都很簡單,基本思路是:
一個包來了,轉(zhuǎn)換其tuple,看其在連接跟蹤表中沒有,有的話,更新其狀態(tài),以其做一些與協(xié)議相關(guān)的工作,如果沒有,則分配一個新的連接表項(xiàng),并與skb_buff關(guān)連,但是問題是,這個表項(xiàng),還沒有被加入連接表當(dāng)中來。其實(shí)這樣做的理由很簡單,因?yàn)檫@個時候,這個包是否有機(jī)會活命還是個未知數(shù),例如被其它模塊給Drop了……所以,要等到一切安全了,再來將這個表項(xiàng)插入至連接跟蹤表。
這個“一切安全”當(dāng)然是Netfilter所有的模塊處理完了,最完全了。
當(dāng)數(shù)據(jù)包要離開Netfilter時,它會穿過NF_IP_POST_ROUTING Hook點(diǎn),狀態(tài)跟蹤模塊在這里注冊了ip_refrag函數(shù)(前面談到過它的優(yōu)先級是很低的)。這個Hook函數(shù)的工作,也可以猜測到了:“判斷表項(xiàng)是否已經(jīng)在連接跟蹤表中了,如果沒有,就將其插入表中”!
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函數(shù)用于處理將tuple加入hash表等重要的后續(xù)處理 */
if (ip_confirm(hooknum, pskb, in, out, okfn) != NF_ACCEPT)
return NF_DROP;
/* 在連接跟蹤開始之前,對分片包進(jìn)行了重組,這里判斷數(shù)據(jù)包是否需要分片,如果要分片,就調(diào)用ip_fragment分片函數(shù)將數(shù)據(jù)包分片發(fā)送出去,因?yàn)閿?shù)據(jù)包已經(jīng)被發(fā)送走了,所以,在它之后的任何Hook函數(shù)已經(jīng)沒有意思了 */
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 函數(shù)是狀態(tài)跟蹤的另一個重要的函數(shù):
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);
}
函數(shù)僅是轉(zhuǎn)向,將控制權(quán)轉(zhuǎn)交給ip_conntrack_confirm函數(shù):
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函數(shù)用于判斷數(shù)據(jù)包是否已經(jīng)被__ip_conntrack_confirm函數(shù)處理過了,它是通過IPS_CONFIRMED_BIT 標(biāo)志位來判斷,而這個標(biāo)志位當(dāng)然是在__ip_conntrack_confirm函數(shù)中來設(shè)置的:
[code
int
__ip_conntrack_confirm(struct sk_buff **pskb)
{
unsigned int hash, repl_hash;
struct ip_conntrack *ct;
enum ip_conntrack_info ctinfo;
/*取得數(shù)據(jù)包的連接狀態(tài)*/
ct = ip_conntrack_get(*pskb, &ctinfo);
/* 如果當(dāng)前包不是一個初始方向的封包,則直接返回. */
if (CTINFO2DIR(ctinfo) != IP_CT_DIR_ORIGINAL)
return NF_ACCEPT;
/*計算初始及應(yīng)答兩個方向tuple對應(yīng)的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表中查找初始及應(yīng)答的節(jié)點(diǎn)*/
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);
/*主要的工作就在于此了:將當(dāng)前連接表項(xiàng)(初始和應(yīng)答的tuple)添加進(jìn)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]
這樣,一條新建連接就被加入到表項(xiàng)當(dāng)中了。如果其有后續(xù)連接,如應(yīng)答,進(jìn)入連接跟蹤表,又轉(zhuǎn)換其tuple,然后查到此表項(xiàng),循環(huán)中……