Linux netfilter 学习笔记 之五 ip层netfilter的table中规则的匹

基于linux2.6.21

通过上面一节的分析我们知道,我们通过iptables-A操作添加的规则,都会保存在一个xt_table->private->entries[]中,所以当数据到来后,协议栈执行NF_HOOK操作时,肯定需要遍历xt_table->private->entries中的规则,找到一个匹配的规则,然后对进来的数据包执行该规则的target操作。从xt_table的通用性,我们可以猜到,肯定会有一个通用的表规则匹配函数接口,被filter、nat、mangle表的hook回调函数调用。那这个函数是谁呢?这个函数就是ipt_do_table。

下面我们开始分析这个函数。

ipt_do_table

该函数的功能为遍历xt_table表中相应默认规则链里的所有规则,找到一个匹配的规则,并返回target结果。

功能:遍历xt_table的hook链上的所有规则,并进行标准match和扩展match(存在的话),并进行target操作。

unsignedint

ipt_do_table(structsk_buff**pskb,

unsignedinthook,

conststructnet_device*in,

conststructnet_device*out,

structipt_table*table,

void*userdata)

{

staticconstcharnulldevname[IFNAMSIZ]__attribute__((aligned(sizeof(long))));

u_int16_toffset;

structiphdr*ip;

u_int16_tdatalen;

inthotdrop=0;

/*InitializingverdicttoNF_DROPkeepsgcchappy.*/

unsignedintverdict=NF_DROP;/*默认值为NF_DROP*/

constchar*indev,*outdev;

void*table_base;

structipt_entry*e,*back;

structxt_table_info*private=table->private;

/*Initialization*/

ip=(*pskb)->nh.iph;

datalen=(*pskb)->len-ip->ihl*4;

indev=in?in->name:nulldevname;

outdev=out?out->name:nulldevname;

offset=ntohs(ip->frag_off)&IP_OFFSET;

read_lock_bh(&table->lock);

IP_NF_ASSERT(table->valid_hooks&(1<<hook));

/*获取表的首个规则的地址*/

table_base=(void*)private->entries[smp_processor_id()];

/*获取到相应规则链的首个规则*/

e=get_entry(table_base,private->hook_entry[hook]);

/*Forreturnfrombuiltinchain*/

/*获取到相应规则链的最后一个规则??

对于别人说这个underflow是规则链的最后一个规则的说法我持怀疑态度

我反而觉得其与hookentry[]中相对应位置的值是一样的。通过阅读代码,

我感觉就是根据其underflow指针获取到规则链的首个ipt_entry,然后在一 个rule的target为NF_REPEAT时,则从该underflow处的ipt_entry开始重 新进行rule规则检查,重新执行一次ipt_do_table而已。

*/

back=get_entry(table_base,private->underflow[hook]);

/*在下面的循环中,如果一直不匹配,岂不是要循环到其他的规则链的规则?

答案是否定的,因为每一条链都有默认规则,默认规则的标准match肯定是匹 配的,我们在系统初始化时,会通过iptables-ttable_name-PCHAIN为相应的链添加一条默认的规则,。

*/

do{

IP_NF_ASSERT(e);

IP_NF_ASSERT(back);

/*首先进行标准match,主要是进行源、目的ip地址的比较以及输入设备 和输出设备名称的比较,即标准match是必须的*/

if(ip_packet_match(ip,indev,outdev,&e->ip,offset)){

structipt_entry_target*t;

/*

当标准match通过后,若存在扩展match,则遍历所有的扩展match, 并通过函数do_match调用

ipt_entry_match->u.kernel.match->match,并返回调用结果,若有 一个match回调函数返回值为1,则说明数据包不匹配该rule,继续 下一条rule进行match操作。

*/

if(IPT_MATCH_ITERATE(e,do_match,

*pskb,in,out,

offset,&hotdrop)!=0)

gotono_match;

ADD_COUNTER(e->counters,ntohs(ip->tot_len),1);

/*当标准match与扩展match都匹配后,则数据包需要执行该条规则 的target操作。首先,调用函数ipt_get_target,获得该rule对应 的ipt_entry_target

*/

t=ipt_get_target(e);

IP_NF_ASSERT(t->u.kernel.target);

/*下面需要进行标准或者扩展target的操作了*/

/*

a)若ipt_entry_match->u.kernel.target->target==NULL,说明这 是一个标准的target,此时又分为如下两种情况

(这时候就需要根据ipt_standard_target->verdict,因为 ipt_entry_match与ipt_standard_target相比,

ipt_standard_target就多了一个verdict,所以他们之间的转发 也是非常容易)

i)如果ipt_standard_target->verdict小于0,说明这是一个标 准target,且不会跳转到用户自定义链,此时就直接返回 -verdict-1即可(此时还有一种情况需要注意,如果-verdict为 IPT_RETURN(即为NF_REPEAT)时,这就说明还需要继续对该规则 链上的下一条rule进行匹配操作。)

ii)如果ipt_standard_target->verdict大于0,说明这是一个需 要跳转到用户自定义链的标准target,此时就需要跳转到用户自 定义链了,但是为了跳转到用户自定义链后,还能返回到当前遍 历的rule的下一条rule,则需要借助back指针与e->comefrom, 实现用户链遍历完以后的跳转回来操作。

*/

if(!t->u.kernel.target->target){

intv;

v=((structipt_standard_target*)t)->verdict;

/*标准target*/

if(v<0){

/*不是NF_REPEAT时,则无须重新执行该hook函数,程序返回*/

if(v!=IPT_RETURN){

verdict=(unsigned)(-v)-1;

break;

}

/*当是NF_REPEAT时,则需要用到back指针了,即从back rule开始,重新进行一次rule规则检查。也就类似于重新执 行了一次ipt_do_table函数。

此处其实是包含两种情况的

a)当为标准target的NF_REPEAT时,则是从该规则链的首个 ipt_entry开始遍历每一个rule,进行rule检查与匹配

b)当为用户自定义链中的NF_REPEAT时,则跳转到符合如下 条件的一个rule,即这个rule是曾经跳转到用户链的那一条 rule的下一条ip_entry(这个是根据back指针与 back->comefrom实现的)。

当跳转到back之前,需要重新设置一下back指针,设置这 个back指针是有意义的

当为用户链的NF_REPEAT时,通过重新设置back指针,则使 back指针重新指向underflow[]的首个ipt_entry*/

e=back;

back=get_entry(table_base,

back->comefrom);

continue;

}

/*说明这是一个跳转到用户链的标准链

当需要跳转的用户链不是下一个ipt_entry时,执行以下操作

a)获取当前ipt_entry的下一个ipt_entry,即next

b)将next->comefrom的值设置为当前back相对于表的首个 ipt_entry的偏移值

c)将back设置为next,这主要为了使用户链的规则没有匹配时 能够返回到当前规则的下一个规则而已。

*/

if(table_base+v!=(void*)e+e->next_offset

&&!(e->ip.flags&IPT_F_GOTO)){

/*Saveoldbackptrinnextentry*/

structipt_entry*next

=(void*)e+e->next_offset;

next->comefrom

=(void*)back-table_base;

/*setbackpointertonextentry*/

back=next;

}

e=get_entry(table_base,v);

}else{

/*当时扩展target时,则需要调用t->u.kernel.target->target, 执行扩展的target操作,并返回结果。

当返回的结果为IPT_CONTINUE时,则需要获取下一条规则,继续进行

规则检查*/

#ifdefCONFIG_NETFILTER_DEBUG

((structipt_entry*)table_base)->comefrom

=0xeeeeeeec;

#endif

verdict=t->u.kernel.target->target(pskb,

in,out,

hook,

t->data,

userdata);

#ifdefCONFIG_NETFILTER_DEBUG

if(((structipt_entry*)table_base)->comefrom

!=0xeeeeeeec

&&verdict==IPT_CONTINUE){

printk("Target%sreentered!\n",

t->u.kernel.target->name);

verdict=NF_DROP;

}

((structipt_entry*)table_base)->comefrom

=0x57acc001;

#endif

/*Targetmighthavechangedstuff.*/

ip=(*pskb)->nh.iph;

datalen=(*pskb)->len-ip->ihl*4;

if(verdict==IPT_CONTINUE)

e=(void*)e+e->next_offset;

else

/*Verdict*/

break;

}

}else{

no_match:

e=(void*)e+e->next_offset;

}

}while(!hotdrop);

read_unlock_bh(&table->lock);

#ifdefDEBUG_ALLOW_ALL

returnNF_ACCEPT;

#else

if(hotdrop)

returnNF_DROP;

elsereturnverdict;

#endif

}

1.1ip_packet_match

下面我们分析一下标准匹配函数。

功能:标准匹配

这个函数通过自定义的宏,巧妙的处理了ip地址匹配、协议号匹配、接口名称匹配

与取反匹配双重判断后的匹配结果。这个FWINV宏设计的很巧妙。

*/

staticinlineint

ip_packet_match(conststructiphdr*ip,

constchar*indev,

constchar*outdev,

conststructipt_ip*ipinfo,

intisfrag)

{

size_ti;

unsignedlongret;

/*

定义宏FWINV,当bool与!!(ipinfo->invflags&invflg)的值不是同时为真也不是同时为假时,返回真

后面的!!(ipinfo->invflags&invflg),其中使用!!,主要是使返回的值要么为真要么为假

*/

#defineFWINV(bool,invflg)((bool)^!!(ipinfo->invflags&invflg))

/*

下面这句话的意思是:

当到达分组的srcip地址经过掩码处理后与规则的srcip不等时,

且没有对srcip进行取反操作后,说明match不匹配,返回0

当到达分组的srcip地址经过掩码处理后与规则的src相等时,

其有对srcip地址进行取反操作后,说明match不匹配,返回0

当到达分组的dstip地址经过掩码处理后与规则的dstip不等时,

且没有对dstip进行取反操作后,说明match不匹配,返回0

当到达分组的srcip地址经过掩码处理后与规则的dstip相等时,

其有对dstip地址进行取反操作后,说明match不匹配,返回0

*/

if(FWINV((ip->saddr&ipinfo->smsk.s_addr)!=ipinfo->src.s_addr,

IPT_INV_SRCIP)

||FWINV((ip->daddr&ipinfo->dmsk.s_addr)!=ipinfo->dst.s_addr,

IPT_INV_DSTIP)){

dprintf("Sourceordestmismatch.\n");

dprintf("SRC:%u.%u.%u.%u.Mask:%u.%u.%u.%u.Target:%u.%u.%u.%u.%s\n",

NIPQUAD(ip->saddr),

NIPQUAD(ipinfo->smsk.s_addr),

NIPQUAD(ipinfo->src.s_addr),

ipinfo->invflags&IPT_INV_SRCIP?"(INV)":"");

dprintf("DST:%u.%u.%u.%uMask:%u.%u.%u.%uTarget:%u.%u.%u.%u.%s\n",

NIPQUAD(ip->daddr),

NIPQUAD(ipinfo->dmsk.s_addr),

NIPQUAD(ipinfo->dst.s_addr),

ipinfo->invflags&IPT_INV_DSTIP?"(INV)":"");

return0;

}

/*下面的判断与第一个判断是一样的,先判断入口名称是否相同

然后将判断结果与取反标志位传递给宏FWINV,若宏返回true则说明不

匹配,函数返回0。

*/

for(i=0,ret=0;i<IFNAMSIZ/sizeof(unsignedlong);i++){

ret|=(((constunsignedlong*)indev)[i]

^((constunsignedlong*)ipinfo->iniface)[i])

&((constunsignedlong*)ipinfo->iniface_mask)[i];

}

if(FWINV(ret!=0,IPT_INV_VIA_IN)){

dprintf("VIAinmismatch(%svs%s).%s\n",

indev,ipinfo->iniface,

ipinfo->invflags&IPT_INV_VIA_IN?"(INV)":"");

return0;

}

for(i=0,ret=0;i<IFNAMSIZ/sizeof(unsignedlong);i++){

ret|=(((constunsignedlong*)outdev)[i]

^((constunsignedlong*)ipinfo->outiface)[i])

&((constunsignedlong*)ipinfo->outiface_mask)[i];

}

if(FWINV(ret!=0,IPT_INV_VIA_OUT)){

dprintf("VIAoutmismatch(%svs%s).%s\n",

outdev,ipinfo->outiface,

ipinfo->invflags&IPT_INV_VIA_OUT?"(INV)":"");

return0;

}

/*Checkspecificprotocol*/

if(ipinfo->proto

&&FWINV(ip->protocol!=ipinfo->proto,IPT_INV_PROTO)){

dprintf("Packetprotocol%hidoesnotmatch%hi.%s\n",

ip->protocol,ipinfo->proto,

ipinfo->invflags&IPT_INV_PROTO?"(INV)":"");

return0;

}

/*Ifwehaveafragmentrulebutthepacketisnotafragment

*thenwereturnzero*/

if(FWINV((ipinfo->flags&IPT_F_FRAG)&&!isfrag,IPT_INV_FRAG)){

dprintf("Fragmentrulebutnotfragment.%s\n",

ipinfo->invflags&IPT_INV_FRAG?"(INV)":"");

return0;

}

return1;

}

1.2IPT_MATCH_ITERATE

该宏就是从(char*)ipt_entry+1,

到(char*)ipt_entry+ipt_entry.target_offset-1为止,遍历这个内存区间存在的ipt_entry_match变量。

#defineIPT_MATCH_ITERATE(e,fn,args…)\

({\

unsignedint__i;\

int__ret=0;\

structipt_entry_match*__match;\

\

for(__i=sizeof(structipt_entry);\

__i<(e)->target_offset;\

__i+=__match->u.match_size){\

__match=(void*)(e)+__i;\

\

__ret=fn(__match,##args);\

if(__ret!=0)\

break;\

}\

__ret;\

})

1.3do_match

该函数主要就是调用m->u.kernel.match->match扩展match函数,并返回match结果

staticinline

intdo_match(structipt_entry_match*m,

conststructsk_buff*skb,

conststructnet_device*in,

conststructnet_device*out,

intoffset,

int*hotdrop)

{

/*Stopiterationifitdoesn’tmatch*/

if(!m->u.kernel.match->match(skb,in,out,m->data,offset,

skb->nh.iph->ihl*4,hotdrop))

return1;

else

return0;

}

以上就是ipt_do_table的整个分析过程,基本上熟悉了表的创建以及规则的添加后,对这个函数的执行流程就比较清楚了,剩下的就是函数的逻辑流程的设计问题了,这个函数的很多设计思想还是值得我们学习的,有些逻辑设计的很巧妙。

别人失去了信心,他却下决心实现自己的目标。

Linux netfilter 学习笔记 之五 ip层netfilter的table中规则的匹

相关文章:

你感兴趣的文章:

标签云: