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操作。

unsigned int

ipt_do_table(struct sk_buff **pskb,

     unsigned int hook,

     const struct net_device *in,

     const struct net_device *out,

     struct ipt_table *table,

     void *userdata)

{

static const char nulldevname[IFNAMSIZ] __attribute__((aligned(sizeof(long))));

u_int16_t offset;

struct iphdr *ip;

u_int16_t datalen;

int hotdrop = 0;

/* Initializing verdict to NF_DROP keeps gcc happy. */

unsigned int verdict = NF_DROP;/*默认值为NF_DROP*/

const char *indev, *outdev;

void *table_base;

struct ipt_entry *e, *back;

struct xt_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]);

 

/* For return from builtin chain */

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

对于别人说这个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 -t table_name -P CHAIN为相应的链添加一条默认的规则,。

*/

 

do {

IP_NF_ASSERT(e);

IP_NF_ASSERT(back);

 

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

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

struct ipt_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)

goto no_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) {

int v;

 

v = ((struct ipt_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)) {

/* 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;

}

 

e = get_entry(table_base, v);

} else {

 

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

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

规则检查*/

#ifdef CONFIG_NETFILTER_DEBUG

((struct ipt_entry *)table_base)->comefrom

= 0xeeeeeeec;

#endif

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

     in, out,

     hook,

     t->data,

     userdata);

 

#ifdef CONFIG_NETFILTER_DEBUG

if (((struct ipt_entry *)table_base)->comefrom

    != 0xeeeeeeec

    && verdict == IPT_CONTINUE) {

printk("Target %s reentered!\n",

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

verdict = NF_DROP;

}

((struct ipt_entry *)table_base)->comefrom

= 0x57acc001;

#endif

/* Target might have changed stuff. */

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);

 

#ifdef DEBUG_ALLOW_ALL

return NF_ACCEPT;

#else

if (hotdrop)

return NF_DROP;

else return verdict;

#endif

}

 

1.1 ip_packet_match

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

功能:标准匹配

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

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

*/

static inline int

ip_packet_match(const struct iphdr *ip,

const char *indev,

const char *outdev,

const struct ipt_ip *ipinfo,

int isfrag)

{

size_t i;

unsigned long ret;

/*

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

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

*/

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

 

/*

下面这句话的意思是:

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

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

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

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

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

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

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

其有对dst ip地址进行取反操作后,说明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("Source or dest mismatch.\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.%u Mask: %u.%u.%u.%u Target: %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)" : "");

return 0;

}

 

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

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

匹配,函数返回0。

*/

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

ret |= (((const unsigned long *)indev)[i]

^ ((const unsigned long *)ipinfo->iniface)[i])

& ((const unsigned long *)ipinfo->iniface_mask)[i];

}

 

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

dprintf("VIA in mismatch (%s vs %s).%s\n",

indev, ipinfo->iniface,

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

return 0;

}

 

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

ret |= (((const unsigned long *)outdev)[i]

^ ((const unsigned long *)ipinfo->outiface)[i])

& ((const unsigned long *)ipinfo->outiface_mask)[i];

}

 

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

dprintf("VIA out mismatch (%s vs %s).%s\n",

outdev, ipinfo->outiface,

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

return 0;

}

 

/* Check specific protocol */

if (ipinfo->proto

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

dprintf("Packet protocol %hi does not match %hi.%s\n",

ip->protocol, ipinfo->proto,

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

return 0;

}

 

/* If we have a fragment rule but the packet is not a fragment

 * then we return zero */

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

dprintf("Fragment rule but not fragment.%s\n",

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

return 0;

}

 

return 1;

}

 

 

1.2 IPT_MATCH_ITERATE

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

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

#define IPT_MATCH_ITERATE(e, fn, args...)\

({\

unsigned int __i;\

int __ret = 0;\

struct ipt_entry_match *__match;\

\

for (__i = sizeof(struct ipt_entry);\

     __i < (e)->target_offset;\

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

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

\

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

if (__ret != 0)\

break;\

}\

__ret;\

})

 

1.3 do_match

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

static inline

int do_match(struct ipt_entry_match *m,

     const struct sk_buff *skb,

     const struct net_device *in,

     const struct net_device *out,

     int offset,

     int *hotdrop)

{

/* Stop iteration if it doesn't match */

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

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

return 1;

else

return 0;

}

 

 

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

你可能感兴趣的:(linux,网络)