七、 target 匹配
7.1 ipt_target和ipt_entry_target结构 ip_tables.h
ipt_target和ipt_match结构类似:
struct ipt_target
{
struct list_head list;
const char name[IPT_FUNCTION_MAXNAMELEN];
/* 在使用本Match的规则注入表中之前调用,进行有效性检查,如果返回0,规则就不会加入iptables中 */
int (*checkentry)(const char *tablename,
const struct ipt_entry *e,
void *targinfo,
unsigned int targinfosize,
unsigned int hook_mask);
/* 在包含本Target的规则从表中删除时调用,与checkentry配合可用于动态内存分配和释放 */
void (*destroy)(void *targinfo, unsigned int targinfosize);
/* target的模块函数,如果需要继续处理则返回IPT_CONTINUE(-1),否则返回NF_ACCEPT、NF_DROP等值,它的调用者根据它的返回值来判断如何处理它处理过的报文*/
unsigned int (*target)(struct sk_buff **pskb,
const struct net_device *in,
const struct net_device *out,
unsigned int hooknum,
const void *targinfo,
void *userdata);
/* 表示当前Target是否为模块(NULL为否) */
struct module *me;
};
ipt_entry_target和ipt_entry_match也几乎一模一样:
struct ipt_entry_target
{
union {
struct {
u_int16_t target_size;
char name[IPT_FUNCTION_MAXNAMELEN];
} user;
struct {
u_int16_t target_size;
struct ipt_target *target;
} kernel;
u_int16_t target_size;
} u;
unsigned char data[0];
};
看上去target和match好像没有区别,但当然,一个是条件,一个是动作,接着往下看是不是真的一样
之前有两个地方出现了ipt_target,一次是在ipt_do_table()函数里,当匹配到match后开始匹配target,另一次是在check_entry()里,检查完match后开始检查target
先看前一个
7.2 ipt_standard_target结构 ip_tables.h
再看一次ipt_do_table这个函数,前面匹配match的部分略过,从匹配match成功的地方开始:
ipt_do_table( )
{
……… /* 略去 */
if (ip_packet_match(ip, indev, outdev, &e->ip, offset)) {
struct ipt_entry_target *t;
if (IPT_MATCH_ITERATE(e, do_match,
*pskb, in, out,
offset, &hotdrop) != 0)
goto no_match;
/* 这里开始说明匹配match成功了,开始匹配target */
ADD_COUNTER(e->counters, ntohs(ip->tot_len), 1);
/* ipt_get_target获取当前target,t是一个ipt_entry_target结构,这个函数就是简单的返回e+e->target_offset
每个entry只有一个target,所以不需要像match一样遍历,直接指针指过去了*/
t = ipt_get_target(e);
IP_NF_ASSERT(t->u.kernel.target);
/* 这里都还是和扩展的match的匹配很像,但是下面一句
有句注释:Standard target? 判断当前target是否标准的target?
而判断的条件是u.kernel.target->target,就是ipt_target结构里的target函数是否为空,而下面还出现了ipt_standard_target结构和verdict变量,好吧,先停下,看看ipt_standard_target结构再说 */
if (!t->u.kernel.target->target) {
int v;
v = ((struct ipt_standard_target *)t)->verdict;
if (v < 0) {
…… /* 略去 */
}
ipt_standard_target的定义:
struct ipt_standard_target
{
struct ipt_entry_target target;
int verdict;
};
也就比ipt_entry_target多了一个verdict(判断),请看前面的nf_hook_slow()函数,里面也有verdict变量,用来保存hook函数的返回值,常见的有这些
#define NF_DROP 0
#define NF_ACCEPT 1
#define NF_STOLEN 2
#define NF_QUEUE 3
#define NF_REPEAT 4
#define RETURN IPT_RETURN
#define IPT_RETURN (-NF_MAX_VERDICT - 1)
#define NF_MAX_VERDICT NF_REPEAT
我们知道chain(链)是某个检查点上检查的规则的集合。除了默认的chain外,用户还可以创建新的chain。在iptables中,同一个chain里的规则是连续存放的。默认的chain的最后一条规则的target是chain的policy。用户创建的chain的最后一条规则的target的调用返回值是NF_RETURN,遍历过程将返回原来的chain。规则中的target也可以指定跳转到某个用户创建的chain上,这时它的target是ipt_stardard_target,并且这个target的verdict值大于0。如果在用户创建的chain上没有找到匹配的规则,遍历过程将返回到原来chain的下一条规则上。
事实上,target也是分标准的和扩展的,但前面说了,毕竟一个是条件,一个是动作,target的标准和扩展的关系和match还是不太一样的,不能一概而论,而且在标准的target里还可以根据verdict的值再划分为内建的动作或者跳转到自定义链
简单的说,标准target就是内核内建的一些处理动作或其延伸
扩展的当然就是完全由用户定义的处理动作
再看if (!t->u.kernel.target->target) 就明白了,如果target函数是空的,就是标准target,因为它不需要用户再提供target函数了,而反之是就是扩展的target,那么再看ipt_do_table()吧,还是只看一部分,否则眼花。
if (!t->u.kernel.target->target) {
/* 如果target为空,是标准target */
int v;
v = ((struct ipt_standard_target *)t)->verdict;
if (v < 0) {
/*v小于0,动作是默认内建的动作,也可能是自定义链已经结束而返回return标志*/
if (v != IPT_RETURN) { /*如果不是Return,则是内建的动作*/
verdict = (unsigned)(-v) - 1;
break;
}
e = back;
/* e和back分别是当前表的当前Hook的规则的起始偏移量和上限偏移量,即entry的头和尾,e=back */
back = get_entry(table_base,back->comefrom);
continue;
}
/* v大于等于0,处理用户自定义链,如果当前链后还有规则,而要跳到自定义链去执行,那么需要保存一个back点,以指示程序在匹配完自定义链后,应当继续匹配的规则位置,自然地, back点应该为当前规则的下一条规则(如果存在的话)
至于为什么下一条规则的地址是table_base+v, 就要去看具体的规则是如何添加的了 */
if (table_base + v!= (void *)e + e->next_offset) {
/* 如果还有规则 */
/* 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,则调用target函数,返回值给verdict */
verdict = t->u.kernel.target->target(pskb,
in, out,
hook,
t->data,
userdata);
/*Target函数有可能已经改变了stuff,所以这里重新定位指针*/
ip = (*pskb)->nh.iph;
datalen = (*pskb)->len - ip->ihl * 4;
/*如果返回的动作是继续检查下一条规则,则设置当前规则为下一条规则,继续循环,否则,就跳出循环,因为在ipt_do_table函数末尾有return verdict;表明,则将target函数决定的返回值返回给调用函数nf_iterate,由它来根据verdict决定数据包的命运*/
if (verdict == IPT_CONTINUE)
e = (void *)e + e->next_offset;
else
/* Verdict */
break;
}