基于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的整个分析过程,基本上熟悉了表的创建以及规则的添加后,对这个函数的执行流程就比较清楚了,剩下的就是函数的逻辑流程的设计问题了,这个函数的很多设计思想还是值得我们学习的,有些逻辑设计的很巧妙。
别人失去了信心,他却下决心实现自己的目标。