基于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。
下面我们开始分析这个函数。
该函数的功能为遍历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
}
下面我们分析一下标准匹配函数。
功能:标准匹配
这个函数通过自定义的宏,巧妙的处理了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;
}
该宏就是从(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;\
})
该函数主要就是调用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的整个分析过程,基本上熟悉了表的创建以及规则的添加后,对这个函数的执行流程就比较清楚了,剩下的就是函数的逻辑流程的设计问题了,这个函数的很多设计思想还是值得我们学习的,有些逻辑设计的很巧妙。