- 論壇徽章:
- 0
|
之所以叫“淺析”,主要是分析其流程,很多細(xì)節(jié)的地方?jīng)]有一一注解出來,之所以以tftp為范本來剖析,主要是因?yàn)樗唵,呵呵,這篇貼子,作為舊貼
http://www.72891.cn/viewthread.php?tid=815129&extra=page%3D1%26filter%3Ddigest
的一個(gè)補(bǔ)充,好為對Netfliter的狀態(tài)跟蹤分析的結(jié)束……也希望,下一步“Netfliter的地址轉(zhuǎn)換的實(shí)現(xiàn)”能早點(diǎn)寫出來……
注:這些貼子,包括iptables,Netfilter的包過濾,Netfliter的狀態(tài)檢測,都只是筆記性質(zhì)的貼子,供有共同興趣的朋友一起討論,其中有不少錯(cuò)誤的地方,希望大家指正,(并不是謙虛,我自己也在不斷地改正和完善 )!另,照舊,源碼版本是2.6.12
1、模塊的注冊
源碼在ip_conntrack_tftp.c中:
init函數(shù)中定義了
- static struct ip_conntrack_helper tftp[MAX_PORTS];
復(fù)制代碼 并初始化它,并注冊它:
- memset(&tftp[i], 0, sizeof(struct ip_conntrack_helper));
- ……
- ret=ip_conntrack_helper_register(&tftp[i]);
復(fù)制代碼
tftp是一個(gè)數(shù)組,最大允許MAX_PORTS個(gè),并且變量ports_c決定其個(gè)數(shù),因?yàn)樗鰹樽詴r(shí)for循環(huán)的終值,目前,只注冊了一個(gè)tftp。
tftp是一個(gè)ip_conntrack_helper類型,我在后文中,會(huì)把它叫做“helper”模塊,也就是說,初始化函數(shù)中,調(diào)用ip_conntrack_helper_register函數(shù)注冊了一個(gè)tftp的helper模塊。
在tftp的成員的賦初始化值的時(shí)候,我們可以對照理解struct ip_conntrack_helper結(jié)構(gòu)的許多重要的成員:
- tftp[i].tuple.dst.protonum = IPPROTO_UDP; //協(xié)議
- tftp[i].tuple.src.u.udp.port = htons(ports[i]); //目標(biāo)端口,即69,這樣,UDP:69成為認(rèn)識(shí)tftp的唯一標(biāo)志
- tftp[i].mask.dst.protonum = 0xFF; //目標(biāo)地址掩碼,以及下面一個(gè)源端口掩碼,以做比較之用
- tftp[i].mask.src.u.udp.port = 0xFFFF;
- tftp[i].max_expected = 1; //最大expect,這是什么東東?后面會(huì)詳解
- tftp[i].timeout = 5 * 60; /* 5 minutes */ //超時(shí)時(shí)間
- tftp[i].me = THIS_MODULE;
- tftp[i].help = tftp_help; //這個(gè)函數(shù)指針是最重要的東東了,后面再來分析它的具體作用
復(fù)制代碼
ip_conntrack_helper_register函數(shù)實(shí)質(zhì)上是把該模塊添加進(jìn)以全局變量helpers為首的鏈表中去:
- int ip_conntrack_helper_register(struct ip_conntrack_helper *me)
- {
- BUG_ON(me->timeout == 0);
- WRITE_LOCK(&ip_conntrack_lock);
- list_prepend(&helpers, me);
- WRITE_UNLOCK(&ip_conntrack_lock);
- return 0;
- }
復(fù)制代碼
OK,tftp的helper模塊被注冊了,它什么時(shí)候被調(diào)用?以及它有什么用呢??
回憶在連接跟蹤的初時(shí)化時(shí),注冊的兩個(gè)鉤子:
/*連接跟蹤初始化時(shí),注冊helper Hook*/
static struct nf_hook_ops ip_conntrack_helper_out_ops = {
.hook = ip_conntrack_help,
.owner = THIS_MODULE,
.pf = PF_INET,
.hooknum = NF_IP_POST_ROUTING,
.priority = NF_IP_PRI_CONNTRACK_HELPER, /*此優(yōu)先級比同Hook上的ip_confirm的高*/
};
static struct nf_hook_ops ip_conntrack_helper_in_ops = {
.hook = ip_conntrack_help,
.owner = THIS_MODULE,
.pf = PF_INET,
.hooknum = NF_IP_LOCAL_IN,
.priority = NF_IP_PRI_CONNTRACK_HELPER,
};
對于中轉(zhuǎn)包過濾來講,我們關(guān)心第一個(gè)鉤子,它注冊在NF_IP_POST_ROUTING Hook上,并且,比我們講過的ip_confirm優(yōu)先級要高。
這樣,也就是數(shù)據(jù)包經(jīng)過這個(gè)Hook點(diǎn)時(shí),ip_conntrack_help 函數(shù)將被調(diào)用。
2.我的例子
結(jié)合一個(gè)實(shí)際的tftp傳輸來分析代碼,先來看這個(gè)例子(該例取自《TCP/IP詳解卷一》p161)
- 1. 192.168.0.1:1106 -> 192.168.1.1:69 udp 19 PRQ "test1.c"
- 2. 192.168.1.1:1077 -> 192.168.0.1:1106 udp 516
- 3. 192.168.0.1:1106 -> 192.168.1.1:1077 udp 4
- 4. 192.168.1.1:1077 -> 192.168.0.1:1106 udp 454
- 5. 192.168.0.1:1106 -> 192.168.1.1:1077 udp 4
復(fù)制代碼
第1行,是192.168.0.1發(fā)出了一個(gè)“讀請求”,文件名是test1.c;
第2行是,192.168.1.1 回應(yīng)了讀請求,將文件的數(shù)據(jù),共516字節(jié)發(fā)送給請求者,注意,這里的來源端口不是69,而變成了1077;
第3行是一個(gè)回應(yīng)包
第4,5行類似;
對于第1行,即新請求一個(gè)連接,回憶我前一次的描述,連接跟蹤模塊會(huì)執(zhí)行以下函數(shù):
在NF_IP_PRE_ROUTING Hook處調(diào)用鉤子函數(shù)ip_conntrack_in,接著進(jìn)入resolve_normal_ct函數(shù),由于這是個(gè)新連接,所以,找不
到與之對應(yīng)的tuple,于是進(jìn)入了init_conntrack,初始化一個(gè)連接。
- 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_expect *exp;
-
- ……
- 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);
- }
- ……
- }
復(fù)制代碼
exp是一個(gè)struct ip_conntrack_expect類型,find_expectation看樣子應(yīng)該是根據(jù)該數(shù)據(jù)包對應(yīng)的tuple,查找一個(gè)struct ip_conntrack_expect類型的節(jié)點(diǎn),expect是什么東東?暫時(shí)不管它,因?yàn)槲覀兡壳斑沒有提到它,所以,find_expectation什么也查不到,那么接下來那個(gè)if...else...則會(huì)進(jìn)入else判斷:
- else
- {
- conntrack->helper = ip_ct_find_helper(&repl_tuple);
- CONNTRACK_STAT_INC(new);
- }
復(fù)制代碼
ip_ct_find_helper函數(shù)根據(jù)當(dāng)前數(shù)據(jù)包對應(yīng)的repl_tuple,在helpers鏈表中查找是否有相應(yīng)的helper模塊:
PS:當(dāng)前數(shù)據(jù)包的tuple是:
192.168.0.1:1106 192.168.1.1:69 udp
則repl_tuple為:
192.168.1.1:69 192.168.0.1:1106 udp
- static struct ip_conntrack_helper *ip_ct_find_helper(const struct ip_conntrack_tuple *tuple)
- {
- return LIST_FIND(&helpers, helper_cmp,
- struct ip_conntrack_helper *,
- tuple);
- }
復(fù)制代碼
比較函數(shù)是helper_cmp:
- static inline int helper_cmp(const struct ip_conntrack_helper *i,
- const struct ip_conntrack_tuple *rtuple)
- {
- return ip_ct_tuple_mask_cmp(rtuple, &i->tuple, &i->mask);
- }
復(fù)制代碼
實(shí)際轉(zhuǎn)向給了ip_ct_tuple_mask_cmp函數(shù):
- static inline int ip_ct_tuple_mask_cmp(const struct ip_conntrack_tuple *t,
- const struct ip_conntrack_tuple *tuple,
- const struct ip_conntrack_tuple *mask)
- {
- return !(((t->src.ip ^ tuple->src.ip) & mask->src.ip)
- || ((t->dst.ip ^ tuple->dst.ip) & mask->dst.ip)
- || ((t->src.u.all ^ tuple->src.u.all) & mask->src.u.all)
- || ((t->dst.u.all ^ tuple->dst.u.all) & mask->dst.u.all)
- || ((t->dst.protonum ^ tuple->dst.protonum)
- & mask->dst.protonum));
- }
復(fù)制代碼
對照一下tftp模塊初始化時(shí)的helper的各成員值和當(dāng)前數(shù)據(jù)包repl_tuple(192.168.1.1:69 192.168.0.1:1106 udp),可以發(fā)現(xiàn),最終tftp注冊的helper模塊將被正確地查找出來!
這樣,當(dāng)前tftp的連接conntrack的helper指針就指向了tftp模塊。這一點(diǎn)非常重要。
- conntrack->helper = ip_ct_find_helper(&repl_tuple);
復(fù)制代碼
這個(gè)數(shù)據(jù)包繼續(xù)前進(jìn),當(dāng)它進(jìn)入NF_IP_POST_ROUTING Hook點(diǎn)時(shí),會(huì)進(jìn)入ip_conntrack_help函數(shù):
/*根據(jù)數(shù)據(jù)包,查找對應(yīng)的連接,如果此連接有關(guān)鏈的helper模塊,則調(diào)用help函數(shù)*/
- static unsigned int ip_conntrack_help(unsigned int hooknum,
- struct sk_buff **pskb,
- const struct net_device *in,
- const struct net_device *out,
- int (*okfn)(struct sk_buff *))
- {
- struct ip_conntrack *ct;
- enum ip_conntrack_info ctinfo;
- /* This is where we call the helper: as the packet goes out. */
- ct = ip_conntrack_get(*pskb, &ctinfo);
- if (ct && ct->helper) {
- unsigned int ret;
- ret = ct->helper->help(pskb, ct, ctinfo);
- if (ret != NF_ACCEPT)
- return ret;
- }
- return NF_ACCEPT;
- }
復(fù)制代碼
這個(gè)函數(shù)只有一件事,就是發(fā)現(xiàn)了tftp的這個(gè)連接(192.168.0.1:1106 192.168.1.1:69 udp),有相應(yīng)的helper模塊,于是,調(diào)用helper模塊的help函數(shù),于是,我們再回來看ip_conntrack_tftp.c中,這個(gè)help函數(shù)的實(shí)現(xiàn):
- static int tftp_help(struct sk_buff **pskb,
- struct ip_conntrack *ct,
- enum ip_conntrack_info ctinfo)
- {
- struct tftphdr _tftph, *tfh;
- struct ip_conntrack_expect *exp;
- unsigned int ret = NF_ACCEPT;
- tfh = skb_header_pointer(*pskb,
- (*pskb)->nh.iph->ihl*4+sizeof(struct udphdr),
- sizeof(_tftph), &_tftph);
- if (tfh == NULL)
- return NF_ACCEPT;
- switch (ntohs(tfh->opcode)) {
- /* RRQ and WRQ works the same way */
- case TFTP_OPCODE_READ:
- case TFTP_OPCODE_WRITE:
- DEBUGP("");
- DUMP_TUPLE(&ct->tuplehash[IP_CT_DIR_ORIGINAL].tuple);
- DUMP_TUPLE(&ct->tuplehash[IP_CT_DIR_REPLY].tuple);
- exp = ip_conntrack_expect_alloc();
- if (exp == NULL)
- return NF_DROP;
- exp->tuple = ct->tuplehash[IP_CT_DIR_REPLY].tuple;
- exp->mask.src.ip = 0xffffffff;
- exp->mask.dst.ip = 0xffffffff;
- exp->mask.dst.u.udp.port = 0xffff;
- exp->mask.dst.protonum = 0xff;
- exp->expectfn = NULL;
- exp->master = ct;
- DEBUGP("expect: ");
- DUMP_TUPLE(&exp->tuple);
- DUMP_TUPLE(&exp->mask);
- if (ip_nat_tftp_hook)
- ret = ip_nat_tftp_hook(pskb, ctinfo, exp);
- else if (ip_conntrack_expect_related(exp) != 0) {
- ip_conntrack_expect_free(exp);
- ret = NF_DROP;
- }
- break;
- case TFTP_OPCODE_DATA:
- case TFTP_OPCODE_ACK:
- DEBUGP("Data/ACK opcode\n");
- break;
- case TFTP_OPCODE_ERROR:
- DEBUGP("Error opcode\n");
- break;
- default:
- DEBUGP("Unknown opcode\n");
- }
- return NF_ACCEPT;
- }
復(fù)制代碼
這個(gè)函數(shù)很簡單,它只關(guān)注tftp操作碼的讀和寫,發(fā)現(xiàn),如果是這兩個(gè)操作碼的話,就先分配一個(gè)struct ip_conntrack_expect結(jié)構(gòu):
- exp = ip_conntrack_expect_alloc();
復(fù)制代碼
然后,初始化它:
- exp->tuple = ct->tuplehash[IP_CT_DIR_REPLY].tuple;
- exp->mask.src.ip = 0xffffffff;
- exp->mask.dst.ip = 0xffffffff;
- exp->mask.dst.u.udp.port = 0xffff;
- exp->mask.dst.protonum = 0xff;
- exp->expectfn = NULL;
- exp->master = ct;
復(fù)制代碼
最后,將它注冊:
- ip_conntrack_expect_related(exp) != 0
復(fù)制代碼
是到了解釋expect的時(shí)候了:
對于tftp來講,它的請求連接是:
192.168.0.1:1106 -> 192.168.1.1:69 udp
我們希望它同其它普通協(xié)議一樣,應(yīng)答包是:
192.168.1.1:69 -> 192.168.0.1:1106 udp
而不是:
192.168.1.1:1077 -> 192.168.0.1:1106 udp
所以,這個(gè)expect就用來存儲(chǔ),該連接所“期望”的應(yīng)答包,僅此而已,這也是給它的成員tuple初始化時(shí),初始化的是當(dāng)前連接的應(yīng)答的tuple的原因:
- exp->tuple = ct->tuplehash[IP_CT_DIR_REPLY].tuple;
復(fù)制代碼
后面的那些mask,用于比較用。master指針讓expect指向了當(dāng)前連接。
至于注冊,它與注冊helper一樣,是一個(gè)插入鏈表的過程:
- int ip_conntrack_expect_related(struct ip_conntrack_expect *expect)
- {
- struct ip_conntrack_expect *i;
- int ret;
- DEBUGP("ip_conntrack_expect_related %p\n", related_to);
- DEBUGP("tuple: "); DUMP_TUPLE(&expect->tuple);
- DEBUGP("mask: "); DUMP_TUPLE(&expect->mask);
- WRITE_LOCK(&ip_conntrack_lock);
- list_for_each_entry(i, &ip_conntrack_expect_list, list) {
- if (expect_matches(i, expect)) {
- /* Refresh timer: if it's dying, ignore.. */
- if (refresh_timer(i)) {
- ret = 0;
- /* We don't need the one they've given us. */
- ip_conntrack_expect_free(expect);
- goto out;
- }
- } else if (expect_clash(i, expect)) {
- ret = -EBUSY;
- goto out;
- }
- }
- /* Will be over limit? */
- if (expect->master->helper->max_expected &&
- expect->master->expecting >= expect->master->helper->max_expected)
- evict_oldest_expect(expect->master);
- ip_conntrack_expect_insert(expect);
- ret = 0;
- out:
- WRITE_UNLOCK(&ip_conntrack_lock);
- return ret;
- }
復(fù)制代碼
首先看是否已經(jīng)有相應(yīng)節(jié)點(diǎn),如沒有,則插入之,不同的是,這次的鏈表首部是ip_conntrack_expect_list。
OK,數(shù)據(jù)包
192.168.0.1:1106 -> 192.168.1.1:69 udp
接下來就進(jìn)入ip_confirm,然后離開本機(jī)。
當(dāng)回來的數(shù)據(jù)傳輸?shù)陌M(jìn)入Netfliter:
- 192.168.1.1:1077 -> 192.168.0.1:1106 udp
復(fù)制代碼
因?yàn)槎丝谝呀?jīng)變成了1077,而不是69,所以它不會(huì)同第一條連接的repl_tuple匹配(廢話,當(dāng)然不匹配了,否則還用搞這么復(fù)雜),所以,當(dāng)然沒有屬于它的連接,數(shù)據(jù)包也會(huì)進(jìn)入init_conntrack,初始化一個(gè)連接:
- 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_expect *exp;
-
- ……
- 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);
- }
- ……
- }
復(fù)制代碼
這一次,find_expectation函數(shù)根據(jù)當(dāng)前數(shù)據(jù)包的tuple,查找有沒有對應(yīng)的expect,很幸運(yùn),我們剛才注冊的expect被查到了:
- static struct ip_conntrack_expect *
- find_expectation(const struct ip_conntrack_tuple *tuple)
- {
- struct ip_conntrack_expect *i;
- list_for_each_entry(i, &ip_conntrack_expect_list, list) {
- /* If master is not in hash table yet (ie. packet hasn't left
- this machine yet), how can other end know about expected?
- Hence these are not the droids you are looking for (if
- master ct never got confirmed, we'd hold a reference to it
- and weird things would happen to future packets). */
- if (ip_ct_tuple_mask_cmp(tuple, &i->tuple, &i->mask)
- && is_confirmed(i->master)
- && del_timer(&i->timeout)) {
- unlink_expect(i);
- return i;
- }
- }
- return NULL;
- }
復(fù)制代碼
比較函數(shù)仍然是ip_ct_tuple_mask_cmp,再來看一遍它的代碼:
- static inline int ip_ct_tuple_mask_cmp(const struct ip_conntrack_tuple *t,
- const struct ip_conntrack_tuple *tuple,
- const struct ip_conntrack_tuple *mask)
- {
- return !(((t->src.ip ^ tuple->src.ip) & mask->src.ip)
- || ((t->dst.ip ^ tuple->dst.ip) & mask->dst.ip)
- || ((t->src.u.all ^ tuple->src.u.all) & mask->src.u.all)
- || ((t->dst.u.all ^ tuple->dst.u.all) & mask->dst.u.all)
- || ((t->dst.protonum ^ tuple->dst.protonum)
- & mask->dst.protonum));
- }
復(fù)制代碼
回憶初始化tftp的expect時(shí),作為比較用的mask的源端口并沒有被賦值:
- exp->mask.src.ip = 0xffffffff;
- exp->mask.dst.ip = 0xffffffff;
- exp->mask.dst.u.udp.port = 0xffff;
- exp->mask.dst.protonum = 0xff;
- exp->expectfn = NULL;
復(fù)制代碼
所以,對于這條應(yīng)答的包來講,盡管它的來源端口是1077,而不是我們希望的69,但
((t->src.u.all ^ tuple->src.u.all) & mask->src.u.all)仍然為0,所以,它仍然被查找出來了。
這樣,Netfilter發(fā)現(xiàn)該連接有對應(yīng)的expect,哈哈,終于找到你了,于是:
- __set_bit(IPS_EXPECTED_BIT, &conntrack->status);
復(fù)制代碼
設(shè)置該連接為“關(guān)連”標(biāo)志位(等回到resolve_normal_ct函數(shù)中,再將此連接設(shè)置為IP_CT_RELATED)
,這樣,關(guān)連的連接就被識(shí)別出來了。并且,該連接的master指針,指向了第一條連接:
conntrack->master = exp->master;
主要的流程就這么簡單!!
小結(jié)一下:
首先,特殊的協(xié)議注冊一個(gè)helper,helper模塊根據(jù)協(xié)議的某些特性,如(udp & dport==69),“幫助”我們發(fā)現(xiàn)一條連接是“特殊協(xié)議”,于是調(diào)用help函數(shù),初始化一個(gè)“期望”連接expect,事實(shí)上,這個(gè)expect主要的作用僅僅是比較。當(dāng)回來的應(yīng)答包穿過Netfilter時(shí),它被發(fā)現(xiàn)有一個(gè)expect,于是,它就被識(shí)別出來是一個(gè)“關(guān)聯(lián)”連接了!! |
|