- 論壇徽章:
- 0
|
之所以叫“淺析”,主要是分析其流程,很多細節(jié)的地方?jīng)]有一一注解出來,之所以以tftp為范本來剖析,主要是因為它簡單,呵呵,這篇貼子,作為舊貼
http://72891.cn/viewthread.php?tid=815129&extra=page%3D1%26filter%3Ddigest
的一個補充,好為對Netfliter的狀態(tài)跟蹤分析的結(jié)束……也希望,下一步“Netfliter的地址轉(zhuǎn)換的實現(xiàn)”能早點寫出來……
注:這些貼子,包括iptables,Netfilter的包過濾,Netfliter的狀態(tài)檢測,都只是筆記性質(zhì)的貼子,供有共同興趣的朋友一起討論,其中有不少錯誤的地方,希望大家指正,(并不是謙虛,我自己也在不斷地改正和完善 )!另,照舊,源碼版本是2.6.12
1、模塊的注冊
源碼在ip_conntrack_tftp.c中:
init函數(shù)中定義了
- static struct ip_conntrack_helper tftp[MAX_PORTS];
復制代碼 并初始化它,并注冊它:
- memset(&tftp[i], 0, sizeof(struct ip_conntrack_helper));
- ……
- ret=ip_conntrack_helper_register(&tftp[i]);
復制代碼
tftp是一個數(shù)組,最大允許MAX_PORTS個,并且變量ports_c決定其個數(shù),因為它做為注冊時for循環(huán)的終值,目前,只注冊了一個tftp。
tftp是一個ip_conntrack_helper類型,我在后文中,會把它叫做“helper”模塊,也就是說,初始化函數(shù)中,調(diào)用ip_conntrack_helper_register函數(shù)注冊了一個tftp的helper模塊。
在tftp的成員的賦初始化值的時候,我們可以對照理解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]); //目標端口,即69,這樣,UDP:69成為認識tftp的唯一標志
- tftp[i].mask.dst.protonum = 0xFF; //目標地址掩碼,以及下面一個源端口掩碼,以做比較之用
- tftp[i].mask.src.u.udp.port = 0xFFFF;
- tftp[i].max_expected = 1; //最大expect,這是什么東東?后面會詳解
- tftp[i].timeout = 5 * 60; /* 5 minutes */ //超時時間
- tftp[i].me = THIS_MODULE;
- tftp[i].help = tftp_help; //這個函數(shù)指針是最重要的東東了,后面再來分析它的具體作用
復制代碼
ip_conntrack_helper_register函數(shù)實質(zhì)上是把該模塊添加進以全局變量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;
- }
復制代碼
OK,tftp的helper模塊被注冊了,它什么時候被調(diào)用?以及它有什么用呢??
回憶在連接跟蹤的初時化時,注冊的兩個鉤子:
/*連接跟蹤初始化時,注冊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)心第一個鉤子,它注冊在NF_IP_POST_ROUTING Hook上,并且,比我們講過的ip_confirm優(yōu)先級要高。
這樣,也就是數(shù)據(jù)包經(jīng)過這個Hook點時,ip_conntrack_help 函數(shù)將被調(diào)用。
2.我的例子
結(jié)合一個實際的tftp傳輸來分析代碼,先來看這個例子(該例取自《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
復制代碼
第1行,是192.168.0.1發(fā)出了一個“讀請求”,文件名是test1.c;
第2行是,192.168.1.1 回應了讀請求,將文件的數(shù)據(jù),共516字節(jié)發(fā)送給請求者,注意,這里的來源端口不是69,而變成了1077;
第3行是一個回應包
第4,5行類似;
對于第1行,即新請求一個連接,回憶我前一次的描述,連接跟蹤模塊會執(zhí)行以下函數(shù):
在NF_IP_PRE_ROUTING Hook處調(diào)用鉤子函數(shù)ip_conntrack_in,接著進入resolve_normal_ct函數(shù),由于這是個新連接,所以,找不
到與之對應的tuple,于是進入了init_conntrack,初始化一個連接。
- 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);
- }
- ……
- }
復制代碼
exp是一個struct ip_conntrack_expect類型,find_expectation看樣子應該是根據(jù)該數(shù)據(jù)包對應的tuple,查找一個struct ip_conntrack_expect類型的節(jié)點,expect是什么東東?暫時不管它,因為我們目前還沒有提到它,所以,find_expectation什么也查不到,那么接下來那個if...else...則會進入else判斷:
- else
- {
- conntrack->helper = ip_ct_find_helper(&repl_tuple);
- CONNTRACK_STAT_INC(new);
- }
復制代碼
ip_ct_find_helper函數(shù)根據(jù)當前數(shù)據(jù)包對應的repl_tuple,在helpers鏈表中查找是否有相應的helper模塊:
PS:當前數(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);
- }
復制代碼
比較函數(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);
- }
復制代碼
實際轉(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));
- }
復制代碼
對照一下tftp模塊初始化時的helper的各成員值和當前數(shù)據(jù)包repl_tuple(192.168.1.1:69 192.168.0.1:1106 udp),可以發(fā)現(xiàn),最終tftp注冊的helper模塊將被正確地查找出來!
這樣,當前tftp的連接conntrack的helper指針就指向了tftp模塊。這一點非常重要。
- conntrack->helper = ip_ct_find_helper(&repl_tuple);
復制代碼
這個數(shù)據(jù)包繼續(xù)前進,當它進入NF_IP_POST_ROUTING Hook點時,會進入ip_conntrack_help函數(shù):
/*根據(jù)數(shù)據(jù)包,查找對應的連接,如果此連接有關(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;
- }
復制代碼
這個函數(shù)只有一件事,就是發(fā)現(xiàn)了tftp的這個連接(192.168.0.1:1106 192.168.1.1:69 udp),有相應的helper模塊,于是,調(diào)用helper模塊的help函數(shù),于是,我們再回來看ip_conntrack_tftp.c中,這個help函數(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;
- }
復制代碼
這個函數(shù)很簡單,它只關(guān)注tftp操作碼的讀和寫,發(fā)現(xiàn),如果是這兩個操作碼的話,就先分配一個struct ip_conntrack_expect結(jié)構(gòu):
- exp = ip_conntrack_expect_alloc();
復制代碼
然后,初始化它:
- 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;
復制代碼
最后,將它注冊:
- ip_conntrack_expect_related(exp) != 0
復制代碼
是到了解釋expect的時候了:
對于tftp來講,它的請求連接是:
192.168.0.1:1106 -> 192.168.1.1:69 udp
我們希望它同其它普通協(xié)議一樣,應答包是:
192.168.1.1:69 -> 192.168.0.1:1106 udp
而不是:
192.168.1.1:1077 -> 192.168.0.1:1106 udp
所以,這個expect就用來存儲,該連接所“期望”的應答包,僅此而已,這也是給它的成員tuple初始化時,初始化的是當前連接的應答的tuple的原因:
- exp->tuple = ct->tuplehash[IP_CT_DIR_REPLY].tuple;
復制代碼
后面的那些mask,用于比較用。master指針讓expect指向了當前連接。
至于注冊,它與注冊helper一樣,是一個插入鏈表的過程:
- 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;
- }
復制代碼
首先看是否已經(jīng)有相應節(jié)點,如沒有,則插入之,不同的是,這次的鏈表首部是ip_conntrack_expect_list。
OK,數(shù)據(jù)包
192.168.0.1:1106 -> 192.168.1.1:69 udp
接下來就進入ip_confirm,然后離開本機。
當回來的數(shù)據(jù)傳輸?shù)陌M入Netfliter:
- 192.168.1.1:1077 -> 192.168.0.1:1106 udp
復制代碼
因為端口已經(jīng)變成了1077,而不是69,所以它不會同第一條連接的repl_tuple匹配(廢話,當然不匹配了,否則還用搞這么復雜),所以,當然沒有屬于它的連接,數(shù)據(jù)包也會進入init_conntrack,初始化一個連接:
- 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);
- }
- ……
- }
復制代碼
這一次,find_expectation函數(shù)根據(jù)當前數(shù)據(jù)包的tuple,查找有沒有對應的expect,很幸運,我們剛才注冊的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;
- }
復制代碼
比較函數(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));
- }
復制代碼
回憶初始化tftp的expect時,作為比較用的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;
復制代碼
所以,對于這條應答的包來講,盡管它的來源端口是1077,而不是我們希望的69,但
((t->src.u.all ^ tuple->src.u.all) & mask->src.u.all)仍然為0,所以,它仍然被查找出來了。
這樣,Netfilter發(fā)現(xiàn)該連接有對應的expect,哈哈,終于找到你了,于是:
- __set_bit(IPS_EXPECTED_BIT, &conntrack->status);
復制代碼
設(shè)置該連接為“關(guān)連”標志位(等回到resolve_normal_ct函數(shù)中,再將此連接設(shè)置為IP_CT_RELATED)
,這樣,關(guān)連的連接就被識別出來了。并且,該連接的master指針,指向了第一條連接:
conntrack->master = exp->master;
主要的流程就這么簡單!!
小結(jié)一下:
首先,特殊的協(xié)議注冊一個helper,helper模塊根據(jù)協(xié)議的某些特性,如(udp & dport==69),“幫助”我們發(fā)現(xiàn)一條連接是“特殊協(xié)議”,于是調(diào)用help函數(shù),初始化一個“期望”連接expect,事實上,這個expect主要的作用僅僅是比較。當回來的應答包穿過Netfilter時,它被發(fā)現(xiàn)有一個expect,于是,它就被識別出來是一個“關(guān)聯(lián)”連接了! |
|