- 論壇徽章:
- 0
|
/*kendo 2006-4-2修正原來對(duì)target部份描述不完全的情況*/
filter表的內(nèi)容,還剩下三個(gè)內(nèi)容:
1、target的匹配;
2、每個(gè)模塊的target/match等函數(shù)的實(shí)現(xiàn);
3、內(nèi)核與用戶空間的交互;
這里,以target的匹配做為2005的的結(jié)尾吧(因?yàn)槊魈祜w貴州,估計(jì)2005年是沒有機(jī)會(huì)再發(fā)貼了)
注:這里說匹配,其實(shí)不太正確,因?yàn)榍懊鎚atch是條件,匹配條件是正常的,target是動(dòng)作,應(yīng)該用執(zhí)行更準(zhǔn)確些。
target的匹配
要理解target的匹配,還是需要先了解相關(guān)的數(shù)據(jù)結(jié)構(gòu)。
與match相似,內(nèi)核中每個(gè)target模塊,通過一個(gè)struct ipt_target來實(shí)現(xiàn):
/* Registration hooks for targets. */
struct ipt_target
{
struct list_head list; /*target鏈,初始為NULL*/
const char name[IPT_FUNCTION_MAXNAMELEN]; /*target名稱*/
/*target的模塊函數(shù),如果需要繼續(xù)處理則返回IPT_CONTINUE(-1),否則返回NF_ACCEPT、NF_DROP等值,
它的調(diào)用者根據(jù)它的返回值來判斷如何處理它處理過的報(bào)文*/
unsigned int (*target)(struct sk_buff **pskb,
unsigned int hooknum,
const struct net_device *in,
const struct net_device *out,
const void *targinfo,
void *userdata);
/* Called when user tries to insert an entry of this type:
hook_mask is a bitmask of hooks from which it can be
called. */
/* 在使用本Match的規(guī)則注入表中之前調(diào)用,進(jìn)行有效性檢查,如果返回0,規(guī)則就不會(huì)加入iptables中 */
int (*checkentry)(const char *tablename,
const struct ipt_entry *e,
void *targinfo,
unsigned int targinfosize,
unsigned int hook_mask);
/* 在包含本Target的規(guī)則從表中刪除時(shí)調(diào)用,與checkentry配合可用于動(dòng)態(tài)內(nèi)存分配和釋放 */
void (*destroy)(void *targinfo, unsigned int targinfosize);
/* 表示當(dāng)前Target是否為模塊(NULL為否) */
struct module *me;
};
這個(gè)結(jié)構(gòu)樣子與match的內(nèi)核模塊的描述幾乎是一模一樣了……
而內(nèi)核及用戶態(tài)中,具體地存儲(chǔ)描述一個(gè)target,是通過一個(gè)struct ipt_entry_target來實(shí)現(xiàn)的:
struct ipt_entry_target
{
union {
struct {
u_int16_t target_size;
/* Used by userspace */
char name[IPT_FUNCTION_MAXNAMELEN];
} user;
struct {
u_int16_t target_size;
/* Used inside the kernel */
struct ipt_target *target;
} kernel;
/* Total length */
u_int16_t target_size;
} u;
unsigned char data[0];
};
這個(gè)結(jié)構(gòu)也與match一模一樣,那么我們是不是就可以按照分析match的思路來分析match呢?“通過成員struct ipt_target *target;來與內(nèi)核中注冊(cè)的target的處理模塊建立關(guān)連,再來調(diào)用u.kernel.target->target()函數(shù)進(jìn)行匹配”???
先不急,Netfilter的target共分為三類:內(nèi)建動(dòng)作、擴(kuò)展target和用戶自定義鏈。而以上兩個(gè)結(jié)構(gòu)是不夠的,它們只能描述基于擴(kuò)展target的匹配函數(shù),沒有或可以講至少?zèng)]有顯著地描述一個(gè)內(nèi)建動(dòng)作或者是用戶自定義鏈!事實(shí)上,Netfilter描述一個(gè)完整的target,是通過以下結(jié)構(gòu)來實(shí)現(xiàn)的:
struct ipt_standard_target
{
struct ipt_entry_target target; /*模塊函數(shù)*/
int verdict; /*常數(shù)*/
};
其中成員verdict(判斷、判決)表明用來針對(duì)內(nèi)建動(dòng)作(ACCEPT、DROP)或者是用戶自定義鏈,如果是擴(kuò)展的target,則通過其target成員去定位最終的模塊處理函數(shù)。
那么,問題又接踵而至了,如果內(nèi)核中,模塊也是以類似注冊(cè)/維護(hù)雙向鏈表的形式儲(chǔ)備,那么在內(nèi)核中匹配的時(shí)候如何來區(qū)分這三類target?
事實(shí)上,考慮到程序的通用性、擴(kuò)展性,對(duì)于內(nèi)建動(dòng)作或者是用戶自定義鏈,內(nèi)核是采用了“假注冊(cè)”的方式來處理(這個(gè)名字是偶私人取的,或許不正確),也就是說,把內(nèi)建動(dòng)作或者是用戶自定義鏈和擴(kuò)展的target采用一樣的處冊(cè)方式,只是這個(gè)注冊(cè),只是一個(gè)樣子,不具備實(shí)質(zhì)意義:
在標(biāo)準(zhǔn)模塊初始化Ip_tables.c的init函數(shù)注冊(cè)target 的時(shí)候,可以看到:
static int __init init(void)
{
int ret;
/* Noone else will be downing sem now, so we won't sleep */
down(&ipt_mutex);
list_append(&ipt_target, &ipt_standard_target);
……
其注冊(cè)的成員ipt_standard_target表示“標(biāo)準(zhǔn)的target”,即前文所提到的內(nèi)建動(dòng)作和用戶自定義鏈。我們來看看它的初始化值:
/* The built-in targets: standard (NULL) and error. */
static struct ipt_target ipt_standard_target
= { { NULL, NULL }, IPT_STANDARD_TARGET, NULL, NULL, NULL };
同樣,它也是一個(gè)ipt_target結(jié)構(gòu),也就是說和其它擴(kuò)展的target模塊一樣,但是,它的處理函數(shù)全為空的,如target函數(shù)指針。所以,匹配的時(shí)候,如果要匹分的話,可以判斷此指針函數(shù)是否指向NULL即可。而至于在標(biāo)準(zhǔn)的target中區(qū)分內(nèi)建動(dòng)作還是用戶自定議鏈,前面提到過,它們都是以struct ipt_standard_target結(jié)構(gòu)的verdict成員描述。到時(shí)候來判斷verdict的值就可以了。我們可以推斷出這段匹配的算法應(yīng)該大致如下:
do {
…… /*前面為匹配match部份,前幾節(jié)已分析過了*/
假設(shè)e為當(dāng)前待匹配規(guī)則。
t=get_current_target(e); /*獲取當(dāng)前規(guī)則的當(dāng)前target*/
/*因?yàn)槿绻?cè)時(shí),如果是標(biāo)準(zhǔn)target,則t->u.kernel.target->target==NULL*/
if (!t->u.kernel.target->target) /*如果是標(biāo)準(zhǔn)target*/
{
/*進(jìn)入標(biāo)準(zhǔn)target的話,還要來區(qū)分究竟是內(nèi)建的動(dòng)作,還是用戶自定鏈,前面分析
struct ipt_standard_target時(shí)說過,它們都是以verdict成員來描述的,則*/
if(判斷verdict==內(nèi)建動(dòng)作)
{
/*相應(yīng)處理*/
}
else
{
/*相應(yīng)處理*/
}
}
else
{
/*如果是擴(kuò)展的target,調(diào)用target函數(shù)*/
verdict = t->u.kernel.target->target();
}
}while(……);
就是在規(guī)則的循環(huán)匹配中,先根據(jù)target函數(shù)指針的值判斷target,如果是標(biāo)準(zhǔn)的target,再根據(jù)的值匹別是內(nèi)建動(dòng)作還是自定義鏈。
程序?qū)嶋H的代碼與此已經(jīng)很相似了,唯一的區(qū)別在于程序在處理自定義鏈時(shí)有一些技巧。
回到struct ipt_standard_target的verdict成員上來,這是一個(gè)非常重要的東西。用戶空間表示一個(gè)接受動(dòng)作,使用ACCEPT,內(nèi)核不用能這個(gè)字符串來匹配,!strcmp(target.name,"ACCEPT"),這樣效率差了點(diǎn),一個(gè)自然的想法是,用一些整形數(shù)來代替,就像我們平常用1來代替true,0來代替false一樣。
鏈中還有一種規(guī)則的target,形如-j 自定義鏈名,內(nèi)核中的規(guī)則,并沒有直接的“鏈”的概念,是呈一維線性排例的,所以,需要跳轉(zhuǎn)至自定義鏈時(shí),就需要兩個(gè)東東:
1、待跳轉(zhuǎn)的鏈相對(duì)于這條-j 自定義鏈的偏移值;
2、回指針,跳完了,總要回來吧……并且,規(guī)則中-j RETURN這種規(guī)則,它同樣也需要回指針;
對(duì)于一條默認(rèn)鏈來講:
back = get_entry(table_base, table->private->underflow[hook]);
最初回指針是指向這條鏈的末尾處的。
OK,再回到偏移值的問題上來,內(nèi)核仍然以verdict這個(gè)成員來描述這個(gè)偏移值,剛才說過用verdict來描述ACCEPT等這些內(nèi)建動(dòng)作,難道它們不會(huì)沖突,答案是否定的。內(nèi)核約定:以負(fù)數(shù)來描述ACCEPT等內(nèi)建動(dòng)作,需要用到時(shí),再取其正值就行了。
例如:
#define NF_ACCEPT 1 /*內(nèi)核中定義NF_ACCEPT這種動(dòng)作,對(duì)應(yīng)常數(shù)為1*/
當(dāng)用戶在iptables中輸入是"ACCEPT"字符串時(shí),將其轉(zhuǎn)換成:
verdict=-NF_ACCEPT - 1
到了內(nèi)核中,要用到NETFILTER的動(dòng)作時(shí),只需要反過來:
(unsigned)(-verdict) - 1;
就OK了。
也就是說,用:
v=target.verdict;
if(v<0) /*內(nèi)核默認(rèn)動(dòng)作*/
{
if (v != IPT_RETURN)
{
return verdict = (unsigned)(-v) - 1; /*是默認(rèn)動(dòng)作,且不為RETURN,直接返回*/
}
//以下代碼處理RETURN的情況
……
}
else /*自定義鏈*/
{
}
就可以處理內(nèi)建動(dòng)作與自定義鏈或RETURN幾種情況了。讓我們來看內(nèi)核中實(shí)際的這塊的代碼處理:
/*獲取當(dāng)前規(guī)則的target*/
t = ipt_get_target(e);
/*如果不存在target模塊函數(shù),那么target應(yīng)為常數(shù),如ACCEPT,DROP等,或是自定義鏈*/
if (!t->u.kernel.target->target)
{
int v;
v = ((struct ipt_standard_target *)t)->verdict; /*取得target的verdict值*/
/*
#define NF_DROP 0
#define NF_ACCEPT 1
#define NF_STOLEN 2
#define NF_QUEUE 3
#define NF_REPEAT 4
#define NF_MAX_VERDICT NF_REPEAT
#define IPT_CONTINUE 0xFFFFFFFF
#define IPT_RETURN (-NF_MAX_VERDICT - 1)
*/
if (v < 0) /*動(dòng)作是默認(rèn)內(nèi)建的動(dòng)作*/
{
/* Pop from stack? */
if (v != IPT_RETURN) /*如果不是Return,返回相應(yīng)的動(dòng)作*/
{
verdict = (unsigned)(-v) - 1;
break;
}
/*back用來描述return 動(dòng)作,或者是自定義鏈執(zhí)行完了,若還需繼續(xù)匹配,那它指向那條應(yīng)繼續(xù)匹配的規(guī)則,所以,這里用e=back來取得下一條待匹配的規(guī)則*/
e = back;
/*重新設(shè)置back點(diǎn)*/
back = get_entry(table_base, back->comefrom);
continue;
}
/*v>=0的情況,v表示了一個(gè)偏移值——待跳轉(zhuǎn)的自定義鏈相對(duì)于規(guī)則起始地址的偏移,即如果是自定義鏈,那么應(yīng)該跳到哪條規(guī)則去繼續(xù)執(zhí)行匹配,這里這個(gè)判斷的意思是,如果下一條跳轉(zhuǎn)換規(guī)則剛好就是當(dāng)前規(guī)則的下一條規(guī)則,那就不用跳了……,否則,將當(dāng)前規(guī)則(形如-j 自定義鏈名這樣的規(guī)則)的下一條規(guī)則設(shè)置為back點(diǎn)*/
if (table_base + v!= (void *)e + e->next_offset) /*當(dāng)前鏈后還有規(guī)則*/
{
/* Save old back ptr in next entry */
struct ipt_entry *next = (void *)e + e->next_offset;
next->comefrom = (void *)back - table_base;
/* set back pointer to next entry */
back = next;
}
/*確定等匹配的下一條規(guī)則*/
e = get_entry(table_base, v);
}
else /*如果存在target模塊函數(shù)*/
{
/* 如果需要繼續(xù)處理則返回IPT_CONTINUE(-1),否則返回NF_ACCEPT、NF_DROP等值 */
verdict = t->u.kernel.target->target(pskb,
hook,
in, out,
t->data,
userdata);
/* Target might have changed stuff. */
/*Target函數(shù)有可能已經(jīng)改變了stuff,所以這里重新定位指針*/
ip = (*pskb)->nh.iph;
protohdr = (u_int32_t *)ip + ip->ihl;
datalen = (*pskb)->len - ip->ihl * 4;
/*如果返回的動(dòng)作是繼續(xù)檢查下一條規(guī)則,則設(shè)置當(dāng)前規(guī)則為下一條規(guī)則,繼續(xù)循環(huán),否則,
就跳出循環(huán),因?yàn)樵趇pt_do_table函數(shù)末尾有return verdict;表明,則將target函數(shù)決定的返回值返回給
調(diào)用函數(shù)nf_iterate,由它來根據(jù)verdict決定數(shù)據(jù)包的命運(yùn)*/
if (verdict == IPT_CONTINUE)
e = (void *)e + e->next_offset;
else
/* Verdict */
break;
}
[ 本帖最后由 獨(dú)孤九賤 于 2006-4-4 13:51 編輯 ] |
|