Chinaunix首页 | 论坛 | 博客
  • 博客访问: 70800
  • 博文数量: 12
  • 博客积分: 135
  • 博客等级: 入伍新兵
  • 技术积分: 105
  • 用 户 组: 普通用户
  • 注册时间: 2012-02-29 10:06
文章分类
文章存档

2013年(1)

2012年(11)

我的朋友

分类: 网络与安全

2012-02-29 10:23:19

[精华] [原创]Netfilter源码分析-我来抛砖,望能引玉
作者:  发表于:2009-03-19 15:32:21
【】 【】 【】【】

[color=Red][size=3]前段时间贴了一篇《iptables源码分析》,虽然后来没有写完,但是我发现有许多朋友都挺喜欢netfilter的,于是我就有一个想法,大家能不能把各自学习的心得贴下来,写成一篇完整的文章呢??
一来大家可以互相学习、交流、进步; 
二来大家一起来完成,也希望能成为CU上一篇经典之作,体现CUer们的互相协作精神!!! 

但是关键是要有条理,不要搞乱了,没了体系,就不太好了,对于这一点,还希望版主们如果支持我的意见,多费点神…… 

我先来抛砖,把自己的开头部份的笔记稍改了一下,贴出来,开个头 
欢迎大家指正(因为手头没有什么资料,都是一句句用sourceinsight跟的,错误难免了) 
希望有志同道合的朋友继续写下去,期盼中……[/size][/color]

[ 本帖最后由 独孤九贱 于 2005-12-16 14:26 编辑 ]


  回复于:2005-12-16 12:52:01

一、主函数 

init为初始化函数,主要完成表的注册,然后再注册与表相对应的HOOK 
//初始化函数为init: 
module_init(init); 

//init 函数负责注册filter表和默认的三个chain 
static int __init init(void) 

int ret; 

if (forward < 0 || forward > NF_MAX_VERDICT) { 
printk("iptables forward must be 0 or 1\n"); 
return -EINVAL; 


/* Entry 1 is the FORWARD hook */ 
initial_table.entries[1].target.verdict = -forward - 1; 

/* 注册filter表 */ 
ret = ipt_register_table(&packet_filter); 
if (ret < 0) 
return ret; 

/* 注册各个钩子函数 */ 
ret = nf_register_hook(&ipt_ops[0]); 
if (ret < 0) 
goto cleanup_table; 

ret = nf_register_hook(&ipt_ops[1]); 
if (ret < 0) 
goto cleanup_hook0; 

ret = nf_register_hook(&ipt_ops[2]); 
if (ret < 0) 
goto cleanup_hook1; 

return ret; 

//如果注册失败,将已注册的钩子清除掉 
 cleanup_hook1: 
nf_unregister_hook(&ipt_ops[1]); 
 cleanup_hook0: 
nf_unregister_hook(&ipt_ops[0]); 
 cleanup_table: 
ipt_unregister_table(&packet_filter); 

return ret; 
}

  回复于:2005-12-16 12:52:40

二、表的注册 
表的注册由函数ipt_register_table来完成, 
ipt_register_table(&packet_filter); 
其参数packet_filter包含了待注册表的各个参数: 
static struct ipt_table packet_filter 
= { { NULL, NULL }, "filter", &initial_table.repl, 
    FILTER_VALID_HOOKS, RW_LOCK_UNLOCKED, NULL, THIS_MODULE }; 
     
可见,内核中,表是以结构struct ipt_table来表示的: 
struct ipt_table 

struct list_head list; 
/* 用于构造,维护链表的结构 */ 
char name[IPT_TABLE_MAXNAMELEN]; 
/* 表名,如"filter"、"nat"等,为了满足自动模块加载的设计,包含该表的模块应命名为iptable_'name'.o */ 
struct ipt_replace *table; 
/* 表的初始化模板,初始为initial_table.repl */ 
unsigned int valid_hooks; 
/* 位向量,表示当前表所影响的HOOK */ 
rwlock_t lock; 
/* 读写锁,初始为打开状态 */ 
struct ipt_table_info *private; 
/* iptable的数据区*/ 
struct module *me; 
/* 是否在模块中定义,若否,则为NULL */ 
}; 

对照这一结构分析,filter表的初始化为: 
链表:{ NULL, NULL } 
表名:"filter" 
初始化模板:&initial_table.repl 
当前表所影响的Hook:FILTER_VALID_HOOKS /*#define FILTER_VALID_HOOKS ((1 << NF_IP_LOCAL_IN) | (1 << NF_IP_FORWARD) | (1 << NF_IP_LOCAL_OUT))*/ 
读写锁:RW_LOCK_UNLOCKED,即为打开状态 
数据区: NULL 
是否在模块中定义:THIS_MODULE,见如下宏定义: 

#ifndef THIS_MODULE 
#ifdef MODULE 
#define THIS_MODULE (&__this_module) 
#else 
#define THIS_MODULE (NULL) 
#endif 
#endif 

先来看维护表的链表的结构: 
struct list_head { 
struct list_head *next, *prev; 
}; 
很简单,它是一个双向链表。 

另一个重要的东东就是表的模板和数据区。表模板定义了一个初始化用的该表的所默认的HOOK所包含的规则等信息,它被初始化成了 
&initial_table.repl。而初始化的数据区struct ipt_table_info *private为空。这样,ipt_register_table()函数会用repl成员的 
内容去填充private成员. 

struct ipt_table_info是实际描述表的数据结构(net/ipv4/netfilter/ip_tables.c): 
struct ipt_table_info 

unsigned int size; 
/* 表大小 */ 
unsigned int number; 
/* 表中的规则数 */ 
unsigned int initial_entries; 
/* 初始的规则数,用于模块计数 */ 
unsigned int hook_entry[NF_IP_NUMHOOKS]; 
/* 记录所影响的HOOK的规则入口相对于下面的entries变量的偏移量 */ 
unsigned int underflow[NF_IP_NUMHOOKS]; 
/* 与hook_entry相对应的规则表上限偏移量,当无规则录入时,相应的hook_entry和underflow均为0 */ 
char entries[0] ____cacheline_aligned; 
/* 规则表入口 */ 
}; 

再来看模板的定义,这个结构很简单,不过长了点: 

static struct 

struct ipt_replace repl; 
struct ipt_standard entries[3]; 
struct ipt_error term; 
} initial_table __initdata 
= { { "filter", FILTER_VALID_HOOKS, 4, 
      sizeof(struct ipt_standard) * 3 + sizeof(struct ipt_error), 
      { [NF_IP_LOCAL_IN] 0, 
[NF_IP_FORWARD] sizeof(struct ipt_standard), 
[NF_IP_LOCAL_OUT] sizeof(struct ipt_standard) * 2 }, 
      { [NF_IP_LOCAL_IN] 0, 
[NF_IP_FORWARD] sizeof(struct ipt_standard), 
[NF_IP_LOCAL_OUT] sizeof(struct ipt_standard) * 2 }, 
      0, NULL, { } }, 
    { 
    /* LOCAL_IN */ 
    { { { { 0 }, { 0 }, { 0 }, { 0 }, "", "", { 0 }, { 0 }, 0, 0, 0 }, 
0, 
sizeof(struct ipt_entry), 
sizeof(struct ipt_standard), 
0, { 0, 0 }, { } }, 
      { { { { IPT_ALIGN(sizeof(struct ipt_standard_target)), "" } }, { } }, 
-NF_ACCEPT - 1 } }, 
    /* FORWARD */ 
    { { { { 0 }, { 0 }, { 0 }, { 0 }, "", "", { 0 }, { 0 }, 0, 0, 0 }, 
0, 
sizeof(struct ipt_entry), 
sizeof(struct ipt_standard), 
0, { 0, 0 }, { } }, 
      { { { { IPT_ALIGN(sizeof(struct ipt_standard_target)), "" } }, { } }, 
-NF_ACCEPT - 1 } }, 
    /* LOCAL_OUT */ 
    { { { { 0 }, { 0 }, { 0 }, { 0 }, "", "", { 0 }, { 0 }, 0, 0, 0 }, 
0, 
sizeof(struct ipt_entry), 
sizeof(struct ipt_standard), 
0, { 0, 0 }, { } }, 
      { { { { IPT_ALIGN(sizeof(struct ipt_standard_target)), "" } }, { } }, 
-NF_ACCEPT - 1 } } 
    }, 
    /* ERROR */ 
    { { { { 0 }, { 0 }, { 0 }, { 0 }, "", "", { 0 }, { 0 }, 0, 0, 0 }, 
0, 
sizeof(struct ipt_entry), 
sizeof(struct ipt_error), 
0, { 0, 0 }, { } }, 
      { { { { IPT_ALIGN(sizeof(struct ipt_error_target)), IPT_ERROR_TARGET } }, 
  { } }, 
"ERROR" 
      } 
    } 
}; 

结构长了点,我们先来关心注册表时的初始值: 
&initial_table.repl 
这是一个struct ipt_replace结构,该结构做为初始化模版被使用,同样用户通过系统调用更换 
表时也要用到这个结构。定义如下: 

/* The argument to IPT_SO_SET_REPLACE. */ 
struct ipt_replace 

/* 表名. */ 
char name[IPT_TABLE_MAXNAMELEN]; 

/* 该表所影响的Hook. */ 
unsigned int valid_hooks; 

/* Number of entries */ 
unsigned int num_entries; 

/* Total size of new entries */ 
unsigned int size; 

/* 记录所影响的HOOK的规则入口相对于下面的entries变量的偏移量 */ 
unsigned int hook_entry[NF_IP_NUMHOOKS]; 

/* 与hook_entry相对应的规则表上限偏移量,当无规则录入时,相应的hook_entry和underflow均为0 */ 
unsigned int underflow[NF_IP_NUMHOOKS]; 

/* Information about old entries: */ 
/* Number of counters (must be equal to current number of entries). */ 
unsigned int num_counters; 
/* The old entries' counters. */ 
struct ipt_counters *counters; 

/* The entries (hang off end: not really an array). */ 
struct ipt_entry entries[0]; 
}; 

对照结构,可以分析各个成员的初始化值了: 
char name[IPT_TABLE_MAXNAMELEN]="filter"; 
unsigned int valid_hooks=FILTER_VALID_HOOKS; 
unsigned int num_entries=4; 
unsigned int size=sizeof(struct ipt_standard) * 3 + sizeof(struct ipt_error); 
unsigned int hook_entry[NF_IP_NUMHOOKS]={ [NF_IP_LOCAL_IN] 0, 
[NF_IP_FORWARD] sizeof(struct ipt_standard), 
[NF_IP_LOCAL_OUT] sizeof(struct ipt_standard) * 2 }; 
unsigned int underflow={ [NF_IP_LOCAL_IN] 0, 
[NF_IP_FORWARD] sizeof(struct ipt_standard), 
[NF_IP_LOCAL_OUT] sizeof(struct ipt_standard) * 2 }; 
unsigned int num_counters=0; 
struct ipt_counters *counters=NULL; 
struct ipt_entry entries[0]={ }; 


了解了这些结构后,再来看表的注册函数: 

int ipt_register_table(struct ipt_table *table) 

int ret; 
struct ipt_table_info *newinfo; 
static struct ipt_table_info bootstrap 
= { 0, 0, 0, { 0 }, { 0 }, { } }; 

/*宏MOD_INC_USE_COUNT用于模块计数器累加,主要是为了防止模块异常删除,对应的 
宏MOD_DEC_USE_COUNT就是累减了*/ 
MOD_INC_USE_COUNT; 

/*为每个CPU分配规则空间*/ 
newinfo = vmalloc(sizeof(struct ipt_table_info) 
  + SMP_ALIGN(table->table->size) * smp_num_cpus); 
/*分配失败*/ 
if (!newinfo) { 
ret = -ENOMEM; 
MOD_DEC_USE_COUNT; 
return ret; 


/*将规则项拷贝到新表项的第一个cpu空间里面*/ 
memcpy(newinfo->entries, table->table->entries, table->table->size); 

/*translate_table函数将newinfo表示的table的各个规则进行边界检查,然后对于newinfo所指的 
ipt_talbe_info结构中的hook_entries和underflows赋予正确的值,最后将表项向其他cpu拷贝*/ 
ret = translate_table(table->name, table->valid_hooks, 
      newinfo, table->table->size, 
      table->table->num_entries, 
      table->table->hook_entry, 
      table->table->underflow); 
if (ret != 0) { 
vfree(newinfo); 
MOD_DEC_USE_COUNT; 
return ret; 


ret = down_interruptible(&ipt_mutex); 
if (ret != 0) { 
vfree(newinfo); 
MOD_DEC_USE_COUNT; 
return ret; 


/* 如果注册的table已经存在,释放空间 并且递减模块计数 */ 
if (list_named_find(&ipt_tables, table->name)) { 
ret = -EEXIST; 
goto free_unlock; 


/* 替换table项. */ 
table->private = &bootstrap; 
if (!replace_table(table, 0, newinfo, &ret)) 
goto free_unlock; 

duprintf("table->private->number = %u\n", 
 table->private->number); 

/* 保存初始规则计数器 */ 
table->private->initial_entries = table->private->number; 

table->lock = RW_LOCK_UNLOCKED; 
/*将表添加进链表*/ 
list_prepend(&ipt_tables, table); 

 unlock: 
up(&ipt_mutex); 
return ret; 

 free_unlock: 
vfree(newinfo); 
MOD_DEC_USE_COUNT; 
goto unlock; 


呵呵,初次看table的注册,有点头大,因为它不光是netfilter,还涉及到linux内核中的内存管理、 
信号量设置等等,不过其实注册也就完成两件事:初始化表,将表添加进表的链表。

  回复于:2005-12-16 12:53:14

表的注册中涉及到的重要函数 

表注册函数中,主要涉及到的重要函数有: 
translate_table 
list_named_find 
list_prepend 

1、translate_table 
/* 
 * 函数:translate_table() 
 * 参数: 
 *  name:表名称; 
 * valid_hooks:当前表所影响的hook 
 * newinfo:包含当前表的所有信息的结构 
 * size:表的大小 
 * number:表中的规则数 
 * hook_entries:记录所影响的HOOK的规则入口相对于下面的entries变量的偏移量 
 * underflows:与hook_entry相对应的规则表上限偏移量 
 * 作用: 
 * translate_table函数将newinfo表示的table的各个规则进行边界检查,然后对于newinfo所指的 
 * ipt_talbe_info结构中的hook_entries和underflows赋予正确的值,最后将表项向其他cpu拷贝 
 * 返回值: 
 * int ret==0表示成功返回 
*/ 

static int 
translate_table(const char *name, 
unsigned int valid_hooks, 
struct ipt_table_info *newinfo, 
unsigned int size, 
unsigned int number, 
const unsigned int *hook_entries, 
const unsigned int *underflows) 

unsigned int i; 
int ret; 

newinfo->size = size; 
newinfo->number = number; 

/* 初始化所有Hooks为不可能的值. */ 
for (i = 0; i < NF_IP_NUMHOOKS; i++) { 
newinfo->hook_entry = 0xFFFFFFFF; 
newinfo->underflow = 0xFFFFFFFF; 


duprintf("translate_table: size %u\n", newinfo->size); 
i = 0; 
/* 遍历所有规则,检查所有偏量,检查的工作都是由IPT_ENTRY_ITERATE这个宏来完成,并且它 
的最后一个参数i,返回表的所有规则数. */ 
ret = IPT_ENTRY_ITERATE(newinfo->entries, newinfo->size, 
check_entry_size_and_hooks, 
newinfo, 
newinfo->entries, 
newinfo->entries + size, 
hook_entries, underflows, &i); 
if (ret != 0) 
return ret; 

/*实际计算得到的规则数与指定的不符*/ 
if (i != number) { 
duprintf("translate_table: %u not %u entries\n", 
 i, number); 
return -EINVAL; 


/* 因为函数一开始将HOOK的偏移地址全部初始成了不可能的值,而在上一个宏的遍历中设置了 
hook_entries和underflows的值,这里对它们进行检查 */ 
for (i = 0; i < NF_IP_NUMHOOKS; i++) { 
/* 只检查当前表所影响的hook */ 
if (!(valid_hooks & (1 << i))) 
continue; 
if (newinfo->hook_entry == 0xFFFFFFFF) { 
duprintf("Invalid hook entry %u %u\n", 
 i, hook_entries); 
return -EINVAL; 

if (newinfo->underflow == 0xFFFFFFFF) { 
duprintf("Invalid underflow %u %u\n", 
 i, underflows); 
return -EINVAL; 



/*确保新的table中不存在规则环*/ 
if (!mark_source_chains(newinfo, valid_hooks)) 
return -ELOOP; 

/* 对tables中的规则项进行完整性检查,保证每一个规则项在形式上是合法的*/ 
i = 0; 
ret = IPT_ENTRY_ITERATE(newinfo->entries, newinfo->size, 
check_entry, name, size, &i); 

/*检查失败,释放空间,返回*/ 
if (ret != 0) { 
IPT_ENTRY_ITERATE(newinfo->entries, newinfo->size, 
  cleanup_entry, &i); 
return ret; 


/* 为每个CPU复制一个完整的table项*/ 
for (i = 1; i < smp_num_cpus; i++) { 
memcpy(newinfo->entries + SMP_ALIGN(newinfo->size)*i, 
       newinfo->entries, 
       SMP_ALIGN(newinfo->size)); 


return ret; 


函数的核心处理,是调用了IPT_ENTRY_ITERATE,我在《iptables源码分析》中已提过,这个宏用来遍历每一个规则,然后 
调用其第三个参数(函数指针)进行处理,前两个参数分别表示规则的起始位置和规则总大小,后面的参数则视情况而定。 
再来看一次: 
/* fn returns 0 to continue iteration */ 
#define IPT_ENTRY_ITERATE(entries, size, fn, args...) \ 
({ \ 
unsigned int __i; \ 
int __ret = 0; \ 
struct ipt_entry *__entry; \ 

for (__i = 0; __i < (size); __i += __entry->next_offset) { \ 
__entry = (void *)(entries) + __i; \ 

__ret = fn(__entry , ## args); \ 
if (__ret != 0) \ 
break; \ 
} \ 
__ret; \ 
}) 

对应地,函数的第一次宏的调用, 
ret = IPT_ENTRY_ITERATE(newinfo->entries, newinfo->size, 
check_entry_size_and_hooks, 
newinfo, 
newinfo->entries, 
newinfo->entries + size, 
hook_entries, underflows, &i); 
遍历到每一项规则后,就调用check_entry_size_and_hooks继续处理。 

static inline int 
check_entry_size_and_hooks(struct ipt_entry *e, 
   struct ipt_table_info *newinfo, 
   unsigned char *base, 
   unsigned char *limit, 
   const unsigned int *hook_entries, 
   const unsigned int *underflows, 
   unsigned int *i) 

unsigned int h; 

/*(unsigned long)e % __alignof__(struct ipt_entry) != 0--不能整除,规则不完整 
(unsigned char *)e + sizeof(struct ipt_entry) >= limit--超过上限了*/ 

if ((unsigned long)e % __alignof__(struct ipt_entry) != 0 
    || (unsigned char *)e + sizeof(struct ipt_entry) >= limit) { 
duprintf("Bad offset %p\n", e); 
return -EINVAL; 


/*e->next_offset 
    < sizeof(struct ipt_entry) + sizeof(struct ipt_entry_target)--规则太"短"了,小于最基本的长度 
    */ 
if (e->next_offset 
    < sizeof(struct ipt_entry) + sizeof(struct ipt_entry_target)) { 
duprintf("checking: element %p size %u\n", 
 e, e->next_offset); 
return -EINVAL; 


/* 检查并设置正确的 hooks & underflows */ 
for (h = 0; h < NF_IP_NUMHOOKS; h++) { 
if ((unsigned char *)e - base == hook_entries[h]) 
newinfo->hook_entry[h] = hook_entries[h]; 
if ((unsigned char *)e - base == underflows[h]) 
newinfo->underflow[h] = underflows[h]; 


/* FIXME: underflows must be unconditional, standard verdicts 
           < 0 (not IPT_RETURN). --RR */ 

/* Clear counters and comefrom */ 
e->counters = ((struct ipt_counters) { 0, 0 }); /*包和字节的计数器清零*/ 
e->comefrom = 0; /*环路计数器清零*/ 

(*i)++; /*规则计数器累加*/ 
return 0; 


2、replace_table 
前面说过,表中以struct ipt_table_info *private;表示实际数据区。但是在初始化赋值的时候,被设为 
NULL,而表的初始变量都以模版的形式,放在struct ipt_replace *table;中。 
注册函数一开始,就声明了: 
struct ipt_table_info *newinfo; 
然后对其分配了空间,将模块中的初值拷贝了进来。所以replace_table要做的工作,主要就是把newinfo中的 
值传递给table结构中的private成员。 

其函数原型如下: 

static struct ipt_table_info * 
replace_table(struct ipt_table *table, 
      unsigned int num_counters, 
      struct ipt_table_info *newinfo, 
      int *error) 

struct ipt_table_info *oldinfo; 

#ifdef CONFIG_NETFILTER_DEBUG 

struct ipt_entry *table_base; 
unsigned int i; 

for (i = 0; i < smp_num_cpus; i++) { 
table_base = 
(void *)newinfo->entries 
+ TABLE_OFFSET(newinfo, i); 

table_base->comefrom = 0xdead57ac; 


#endif 

/* Do the substitution. */ 
write_lock_bh(&table->lock); 
/* Check inside lock: is the old number correct? */ 
if (num_counters != table->private->number) { 
duprintf("num_counters != table->private->number (%u/%u)\n", 
 num_counters, table->private->number); 
write_unlock_bh(&table->lock); 
*error = -EAGAIN; 
return NULL; 

oldinfo = table->private; 
table->private = newinfo; 
newinfo->initial_entries = oldinfo->initial_entries; 
write_unlock_bh(&table->lock); 

return oldinfo; 



3、list_named_find 

在注册函数中,调用 
/* Don't autoload: we'd eat our tail... */ 
if (list_named_find(&ipt_tables, table->name)) { 
ret = -EEXIST; 
goto free_unlock; 

来检查当前表是否已被注册过了。可见,第一个参数为链表首部,第二个参数为当前表名。 
其原型如下: 
/* Find this named element in the list. */ 
#define list_named_find(head, name) \ 
LIST_FIND(head, __list_cmp_name, void *, name) 

/* Return pointer to first true entry, if any, or NULL.  A macro 
   required to allow inlining of cmpfn. */ 
#define LIST_FIND(head, cmpfn, type, args...) \ 
({ \ 
const struct list_head *__i = (head); \ 

ASSERT_READ_LOCK(head); \ 
do { \ 
__i = __i->next; \ 
if (__i == (head)) { \ 
__i = NULL; \ 
break; \ 
} \ 
} while (!cmpfn((const type)__i , ## args)); \ 
(type)__i; \ 
}) 

前面提过,表是一个双向链表,在宏当中,以while进行循环,以__i = __i->next; 
进行遍历,然后调用比较函数进行比较,传递过来的比较函数是__list_cmp_name。 

比较函数很简单: 
static inline int __list_cmp_name(const void *i, const char *name) 

return strcmp(name, i+sizeof(struct list_head)) == 0; 


4、list_prepend 
当所有的初始化工作结束,就调用list_prepend来构建链表了。 
/* Prepend. */ 
static inline void 
list_prepend(struct list_head *head, void *new) 

ASSERT_WRITE_LOCK(head); /*设置写互斥*/ 
list_add(new, head); /*将当前表节点添加进链表*/ 

list_add就是一个构建双向链表的过程: 
static __inline__ void list_add(struct list_head *new, struct list_head *head) 

__list_add(new, head, head->next); 


static __inline__ void __list_add(struct list_head * new, 
struct list_head * prev, 
struct list_head * next) 

next->prev = new; 
new->next = next; 
new->prev = prev; 
prev->next = new; 
}

  回复于:2005-12-16 12:54:08

三、Hook的注册 

如果你对Netfilter的hook的注册还不了解的话,推荐先到网上搜搜《深入Linux网络核心堆栈》bioforge  
看看先。(本节中有部份文字引自该文) 

注册一个hook函数是围绕nf_hook_ops数据结构的一个非常简单的操作,nf_hook_ops数据结构在linux/netfilter.h中定义, 
 该数据结构的定义如下: 
struct nf_hook_ops 

struct list_head list; 

/* User fills in from here down. */ 
nf_hookfn *hook; 
int pf; 
int hooknum; 
/* Hooks are ordered in ascending priority. */ 
int priority; 
}; 

该数据结构中的list成员用于维护Netfilter hook的列表。 
hook成员是一个指向nf_hookfn类型的函数的指针,该函数是这个hook被调用时执行的函数。 
nf_hookfn同样在linux/netfilter.h中定义。 
pf这个成员用于指定协议族。有效的协议族在linux/socket.h中列出,但对于IPv4我们希望使用协议族PF_INET。 
hooknum这个成员用于指定安装的这个函数对应的具体的hook类型: 
NF_IP_PRE_ROUTING    在完整性校验之后,选路确定之前 
NF_IP_LOCAL_IN        在选路确定之后,且数据包的目的是本地主机 
NF_IP_FORWARD        目的地是其它主机地数据包 
NF_IP_LOCAL_OUT        来自本机进程的数据包在其离开本地主机的过程中 
NF_IP_POST_ROUTING    在数据包离开本地主机“上线”之前 

最后,priority这个成员用于指定在执行的顺序中,这个hook函数应当在被放在什么地方。 
对于IPv4,可用的值在linux/netfilter_ipv4.h的nf_ip_hook_priorities枚举中定义。 

针对HOOK的注册,在初始化函数中有: 
/* Register table */ 
ret = ipt_register_table(&packet_filter); 
if (ret < 0) 
return ret; 

/* Register hooks */ 
ret = nf_register_hook(&ipt_ops[0]); 
if (ret < 0) 
goto cleanup_table; 

ret = nf_register_hook(&ipt_ops[1]); 
if (ret < 0) 
goto cleanup_hook0; 

ret = nf_register_hook(&ipt_ops[2]); 
if (ret < 0) 
goto cleanup_hook1; 
可见,注册是通过nf_register_hook函数来完成,每一个Hook的相关信息,都在ipt_ops结构数组中,它的成员变量前面已做分析, 
来看看它的初始化值: 
static struct nf_hook_ops ipt_ops[] 
= { { { NULL, NULL }, ipt_hook, PF_INET, NF_IP_LOCAL_IN, NF_IP_PRI_FILTER }, 
    { { NULL, NULL }, ipt_hook, PF_INET, NF_IP_FORWARD, NF_IP_PRI_FILTER }, 
    { { NULL, NULL }, ipt_local_out_hook, PF_INET, NF_IP_LOCAL_OUT, 
NF_IP_PRI_FILTER } 
}; 

对应结构各成员变量的含义,可见,filter表上总共设置了NF_IP_LOCAL_IN,NF_IP_FORWARD,NF_IP_LOCAL_OUT,用熟了iptables三个 
链,对这三个东东应该是刻骨铭心了。协议簇是PF_INET,初始化链表为NULL,处理函数,前两个为ipt_hook,后一个为ipt_local_out_hook, 
优化级均为NF_IP_PRI_FILTER。 

hook的注册,是通过nf_register_hook来完成的,它也是一个维护双向链表的过程,值得注意的是,注册的钩子函数,全部是放在全局变量 
nf_hooks中,它是一个二维数组,函数一开始先遍历它,找到合适的地方,再将当前节点插入之。(我们可以想像,将来调用钩子函数时,就 
是一个查找nf_hooks数组成员的过程) 
int nf_register_hook(struct nf_hook_ops *reg) 

struct list_head *i; 

br_write_lock_bh(BR_NETPROTO_LOCK); 
/*寻找与当前待注册节点reg匹配的数组元素(按协议族和Hook来匹配)*/ 
for (i = nf_hooks[reg->pf][reg->hooknum].next;  
     i != &nf_hooks[reg->pf][reg->hooknum];  
     i = i->next) { 
if (reg->priority < ((struct nf_hook_ops *)i)->priority) 
break; 

/*添加节点*/ 
list_add(®->list, i->prev); 
br_write_unlock_bh(BR_NETPROTO_LOCK); 
return 0; 


能过表的注册,HOOK的注册,准备工作基本上就完成了,其它表的注册和Hook的注册,都是一样的,可以对照分析,没有必要再详述了。 
不过注册也只是准备工作。重要的事情是对数据包的处理,对于filter来说,就是包过滤,对于nat来讲,就是地址转换。 

四、数据包过滤 

1、钩子函数 
以中转包过滤为例(FORWARD),注册的时候,向内核注册了一个ipt_hook的钩子函数。 
static unsigned int 
ipt_hook(unsigned int hook, //Hook类型 
 struct sk_buff **pskb, //数据包 
 const struct net_device *in, //进入数据包接口 
 const struct net_device *out, //离开数据包接口 
 int (*okfn)(struct sk_buff *)) //默认处理函数 

return ipt_do_table(pskb, hook, in, out, &packet_filter, NULL); 


转向到了ipt_do_table。也就是说,如果向内核挂了钩,中转的数据,将进入ipt_do_table函数。 

2、钩子函数被调用 
钩子函数被注册了,但是内核是如何调用它的呢? 
在/src/net/ipv4下边,对应于input/output/forward,分别有Ip_forward.c,Ip_output.c,Ip_input.c。同样继续以forward为例, 
(关于linux堆栈处理数据包流程的各个函数的作用等,这里就不进一步详述,请参考其它相关资料)。 

对于转发的数据,将进入Ip_forward.c中的ip_forward函数,当处理完成后,在最后一句,可以看到: 
return NF_HOOK(PF_INET, NF_IP_FORWARD, skb, skb->dev, dev2, 
       ip_forward_finish); 
事实上,你在linux的每一个数据转发的"关节"的函数处,都可以发现这个宏的调用,它就是调用我们注册的钩子,其最后一个参数为 
下一步处理的函数,即,如果有钩子函数,则处理完所有的钩子函数后,调用这个函数继续处理,如果没有注册任何钩子,则直接调用 
此函数。 

/* This is gross, but inline doesn't cut it for avoiding the function 
   call in fast path: gcc doesn't inline (needs value tracking?). --RR */ 
#ifdef CONFIG_NETFILTER_DEBUG 
#define NF_HOOK nf_hook_slow 
#else 
#define NF_HOOK(pf, hook, skb, indev, outdev, okfn) \ 
(list_empty(&nf_hooks[(pf)][(hook)]) \ 
 ? (okfn)(skb) \ 
 : nf_hook_slow((pf), (hook), (skb), (indev), (outdev), (okfn))) 
#endif 


先初略看看这个宏,okfn,我们已讲过,它是下一步要处理的函数,这里先调用 
list_empty函数检查nf_hooks是否为空,为空则表示没有Hook注册,则直接调用 
okfn继续处理。如果不为空,则转入nf_hook_slow函数: 


int nf_hook_slow(int pf, unsigned int hook, struct sk_buff *skb, 
 struct net_device *indev, 
 struct net_device *outdev, 
 int (*okfn)(struct sk_buff *)) 

struct list_head *elem; 
unsigned int verdict; 
int ret = 0; 

/* This stopgap cannot be removed until all the hooks are audited. */ 
if (skb_is_nonlinear(skb) && skb_linearize(skb, GFP_ATOMIC) != 0) { 
kfree_skb(skb); 
return -ENOMEM; 

if (skb->ip_summed == CHECKSUM_HW) { 
if (outdev == NULL) { 
skb->ip_summed = CHECKSUM_NONE; 
} else { 
skb_checksum_help(skb); 



/* We may already have this, but read-locks nest anyway */ 
br_read_lock_bh(BR_NETPROTO_LOCK); 

#ifdef CONFIG_NETFILTER_DEBUG 
if (skb->nf_debug & (1 << hook)) { 
printk("nf_hook: hook %i already set.\n", hook); 
nf_dump_skb(pf, skb); 

skb->nf_debug |= (1 << hook); 
#endif 

/*因为在调用NF_HOOK宏时,已经指定了协议簇和钩子名称,所以要找到对应的Hook点,是很容易的 
elem即为我们要找的,记得struct nf_hook_ops结构么?双向链表中的每个elem->hook就是我们关心的终极目标*/ 
elem = &nf_hooks[pf][hook]; 
/*找到后,遍历双向链表,进一步处理,以调用Hook函数,并返回相应的动作*/ 
verdict = nf_iterate(&nf_hooks[pf][hook], &skb, hook, indev, 
     outdev, &elem, okfn); 
if (verdict == NF_QUEUE) { 
NFDEBUG("nf_hook: Verdict = QUEUE.\n"); 
nf_queue(skb, elem, pf, hook, indev, outdev, okfn); 

/*如果是接受,则调用okfn继续处理,否则丢度之*/ 
switch (verdict) { 
case NF_ACCEPT: 
ret = okfn(skb);
break; 

case NF_DROP: 
kfree_skb(skb); 
ret = -EPERM; 
break; 


br_read_unlock_bh(BR_NETPROTO_LOCK); 
return ret; 


再来看nf_iterate: 

static unsigned int nf_iterate(struct list_head *head, 
       struct sk_buff **skb, 
       int hook, 
       const struct net_device *indev, 
       const struct net_device *outdev, 
       struct list_head **i, 
       int (*okfn)(struct sk_buff *)) 

/*循环遍历所有注册的钩子函数,包括系统默认的三个,用户自定义的……*/ 
for (*i = (*i)->next; *i != head; *i = (*i)->next) { 
struct nf_hook_ops *elem = (struct nf_hook_ops *)*i; 
/*就在这里调用了*/ 
switch (elem->hook(hook, skb, indev, outdev, okfn)) { 
case NF_QUEUE: 
return NF_QUEUE; 

case NF_STOLEN: 
return NF_STOLEN; 

case NF_DROP: 
return NF_DROP; 

case NF_REPEAT: 
*i = (*i)->prev; 
break; 

#ifdef CONFIG_NETFILTER_DEBUG 
case NF_ACCEPT: 
break; 

default: 
NFDEBUG("Evil return from %p(%u).\n",  
elem->hook, hook); 
#endif 


return NF_ACCEPT; 
}    

解释一下各个返回值: 

NF_DROP                丢弃该数据包 
NF_ACCEPT            保留该数据包 
NF_STOLEN            忘掉该数据包 
NF_QUEUE            将该数据包插入到用户空间 
NF_REPEAT            再次调用该hook函数 

这样,最终关心的还是每一个注册的函数,这样又回到本节开头所说的ipt_do_table…… 

说了这么多,其实只是把最简单,最没有用的讲了,难的还在于Hook函数,呵呵命运的魔轮已经开启,每一个数据包的命运将会如何? 
那就要去分析每一个Hook函数了…… 

未完,待续……

  回复于:2005-12-16 15:05:49

里面有太多的结构体不了解,而对应的 include 文件又不好找,且 include 文件里面有可能又存在嵌套 include 
对于我这样的水平,该如何学啊 ^_^

  回复于:2005-12-16 15:22:13

引用:原帖由 platinum 于 2005-12-16 15:05 发表 
里面有太多的结构体不了解,而对应的 include 文件又不好找,且 include 文件里面有可能又存在嵌套 include 
对于我这样的水平,该如何学啊 ^_^ 



我是先找结构中重要的成员拿下来(网上找资料,源码中本来的注释),剩下的就在读代码的时候,再明白一些,最后个别的,到现在也没有明白。 

至于不好找的问题,应该不会吧,SourceInsight很好跟的。

  回复于:2005-12-16 15:26:39

引用:原帖由 platinum 于 2005-12-16 15:05 发表 
里面有太多的结构体不了解,而对应的 include 文件又不好找,且 include 文件里面有可能又存在嵌套 include 
对于我这样的水平,该如何学啊 ^_^ 



白金兄习惯在什么系统下工作? 
如果是linux,我用vim+ctags足够浏览整个kernel了。  

ctags -R arch/i386 fs include/asm-i386 include/asm-generic include/asm-net …… 
vim 
:ts the-symbol 
:tf ──第一个 
:tn ──下一个 
:tp ──上一个 
:tl ──最后一个 

如果在安装TagList的Vim插件,就更爽了:D

  回复于:2005-12-16 15:35:12

引用:原帖由 albcamus 于 2005-12-16 15:26 发表 


白金兄习惯在什么系统下工作? 
如果是linux,我用vim+ctags足够浏览整个kernel了。  

ctags -R arch/i386 fs include/asm-i386 include/asm-generic include/asm-net …… 
vim 
:ts the-symbol 
:tf ── ... 



albcamus:你不是也在看Netfilter?能不能把你看的分享出来,继续写下去呢……?

  回复于:2005-12-16 15:45:51

引用:原帖由 独孤九贱 于 2005-12-16 15:35 发表 


albcamus:你不是也在看Netfilter?能不能把你看的分享出来,继续写下去呢……? 



没看netfilter呢:) 
我学的比较杂,想起什么来学什么,现在想钻CPU和中断,以及协议栈, 得慢慢来:(

  回复于:2005-12-16 16:14:21

你们常说得 注册函数  或者 hook 这个是什么原理实现的呢? 

普通程序怎么做?  我想到的是函数指针  

给个简单的例子 ok?

  回复于:2005-12-16 16:34:42

引用:原帖由 benjiam 于 2005-12-16 16:14 发表 
你们常说得 注册函数  或者 hook 这个是什么原理实现的呢? 

普通程序怎么做?  我想到的是函数指针  

给个简单的例子 ok? 



方法是多种多样的,可以举几个例子: 
1、Netfilter 
#define NF_HOOK(pf, hook, skb, indev, outdev, okfn)                        \ 
(list_empty(&nf_hooks[(pf)][(hook)])                                        \ 
? (okfn)(skb)                                                                \ 
: nf_hook_slow((pf), (hook), (skb), (indev), (outdev), (okfn))) 
#endif 

先将钩子函数置于一个全局变量中,它是一个二维数组,当程序运行至某个节点,调用NF_HOOK,判断是否有注册的函数,有则转向执行注册的函数,再回来继续执行,否则,直接就继续运行了; 

当然,这种要框架支持,即源码本身提供接口。 


对于Windows来讲,系统也有一些Hook的接口,可以参考其相关API,另外两种常用的是: 
1、Hook动态链接库 
如dll,so(so没有试过,我想当然认为原理应是一样的),加载其,获得函数的空间,保存原来地址,然后把自己的函数挂上去,然后再…… 

2、Hook PE 
也是利用PE的export table,找到要被Hook的地址,然后替换就OK了。

  回复于:2005-12-16 16:37:58

引用:原帖由 albcamus 于 2005-12-16 15:26 发表 


白金兄习惯在什么系统下工作? 
如果是linux,我用vim+ctags足够浏览整个kernel了。  

ctags -R arch/i386 fs include/asm-i386 include/asm-generic include/asm-net …… 
vim 
:ts the-symbol 
:tf ── ... 


albcamus 兄,看来我和你们差的不是一清半点啊,我一定要赶上 
如果问了一些白痴问题的话,千万不要怪罪 :em02:

  回复于:2005-12-16 16:39:38

引用:原帖由 独孤九贱 于 2005-12-16 15:22 发表 


我是先找结构中重要的成员拿下来(网上找资料,源码中本来的注释),剩下的就在读代码的时候,再明白一些,最后个别的,到现在也没有明白。 

至于不好找的问题,应该不会吧,SourceInsight很好跟的。 


我也下了一个 SourceInsight,还是中文的,确实感觉不错 
如果一同读入了 .h 文件的话,还可以在另一个窗口里把那个 struct 的定义显示出来,和很方便 
但是,我如何能一次载入程序里涉及到的所有 .h 文件呢?

  回复于:2005-12-16 16:51:49

先将钩子函数置于一个全局变量中,它是一个二维数组,当程序运行至某个节点,调用NF_HOOK,判断是否有注册的函数,有则转向执行注册的函数,再回来继续执行,否则,直接就继续运行了; 

当然,这种要框架支持,即源码本身提供接口。 


你指的是源码实现了的 。  我如何去注册吧 

我现在是指  我自己如何写一个c 代码  提供接口给别人注册。 
这个和操作系统有关吧  unix 高级编程里面 没看到累世的概念

  回复于:2005-12-16 17:11:27

引用:原帖由 platinum 于 2005-12-16 16:37 发表 

albcamus 兄,看来我和你们差的不是一清半点啊,我一定要赶上 
如果问了一些白痴问题的话,千万不要怪罪 :em02: 



千万别这么说, 大家互有短长, 相互学习而已。 再说,好多时候“高水平”就是个海市蜃楼,刚入门时觉得好多高人呀,慢慢扎实的学习过程中,很快就会发现原来一色的高人也分出个三六九等,自己可以超越他们:本来就是这样啊。 

Solaris12兄对我说:光环效应谁到了那个时候都会有。 其实那就是没看清楚时的错觉.  

(p.s. 聚会你去吗?去的话我也去认识认识:mrgreen:)

  回复于:2005-12-16 17:13:54

引用:原帖由 benjiam 于 2005-12-16 16:51 发表 
先将钩子函数置于一个全局变量中,它是一个二维数组,当程序运行至某个节点,调用NF_HOOK,判断是否有注册的函数,有则转向执行注册的函数,再回来继续执行,否则,直接就继续运行了; 

当然,这种要框架支持, ... 



如果可能,你可以直接借鉴这种框架结构,上面贴子中的源码分析事实上已经揭示了其全过程。 

如果你在Win下边,直接让他来Hook你的dll/sys/exe……

  回复于:2005-12-17 00:50:26

大家可以根据自己的体会随便谈谈 
netfilter这个东西,有什么不足之处? 
批判的学习才有更大的收获呀!

  回复于:2005-12-19 17:53:15

引用:原帖由 caibird3rd 于 2005-12-17 00:50 发表 
大家可以根据自己的体会随便谈谈 
netfilter这个东西,有什么不足之处? 
批判的学习才有更大的收获呀! 



[size=3][color=Red]我写出来等着大家来批判,来继续写下去,来指正我的许多错误,不过好像没有人响应,牛人们都对这个没有兴趣……? 
哎,那我就接着写吧。 

前面说到,对于filter表来说,所有的一切,要靠ipt_do_table函数来进行包配备,前面提过,过滤规则分为三部份:标准mathc,扩展match,target。可以预想一想,ipt_do_table就是要针对这三部份来过滤,来看一下该函数:[/color][/size] 


/* Returns one of the generic firewall policies, like NF_ACCEPT. */ 
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] = { 0 }; 
u_int16_t offset; 
struct iphdr *ip; 
void *protohdr; 
u_int16_t datalen; 
int hotdrop = 0; 
/* Initializing verdict to NF_DROP keeps gcc happy. */ 
unsigned int verdict = NF_DROP; 
const char *indev, *outdev; 
void *table_base; 
struct ipt_entry *e, *back; 

/* Initialization */ 
ip = (*pskb)->nh.iph; 
protohdr = (u_int32_t *)ip + ip->ihl; 
datalen = (*pskb)->len - ip->ihl * 4; 
indev = in ? in->name : nulldevname; 
outdev = out ? out->name : nulldevname; 
/* We handle fragments by dealing with the first fragment as 
 * if it was a normal packet.  All other fragments are treated 
 * normally, except that they will NEVER match rules that ask 
 * things we don't know, ie. tcp syn flag or ports).  If the 
 * rule is also a fragment-specific rule, non-fragments won't 
 * match it. */ 
offset = ntohs(ip->frag_off) & IP_OFFSET; 

read_lock_bh(&table->lock); 
IP_NF_ASSERT(table->valid_hooks & (1 << hook)); 
table_base = (void *)table->private->entries 
+ TABLE_OFFSET(table->private, 
       cpu_number_map(smp_processor_id())); 
e = get_entry(table_base, table->private->hook_entry[hook]); 

#ifdef CONFIG_NETFILTER_DEBUG 
/* Check noone else using our table */ 
if (((struct ipt_entry *)table_base)->comefrom != 0xdead57ac 
    && ((struct ipt_entry *)table_base)->comefrom != 0xeeeeeeec) { 
printk("ASSERT: CPU #%u, %s comefrom(%p) = %X\n", 
       smp_processor_id(), 
       table->name, 
       &((struct ipt_entry *)table_base)->comefrom, 
       ((struct ipt_entry *)table_base)->comefrom); 

((struct ipt_entry *)table_base)->comefrom = 0x57acc001; 
#endif 

/* For return from builtin chain */ 
back = get_entry(table_base, table->private->underflow[hook]); 

do { 
IP_NF_ASSERT(e); 
IP_NF_ASSERT(back); 
(*pskb)->nfcache |= e->nfcache; 
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, protohdr, 
      datalen, &hotdrop) != 0) 
goto no_match; 

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

t = ipt_get_target(e); 
IP_NF_ASSERT(t->u.kernel.target); 
/* Standard target? */ 
if (!t->u.kernel.target->target) { 
int v; 

v = ((struct ipt_standard_target *)t)->verdict; 
if (v < 0) { 
/* Pop from stack? */ 
if (v != IPT_RETURN) { 
verdict = (unsigned)(-v) - 1; 
break; 

e = back; 
back = get_entry(table_base, 
 back->comefrom); 
continue; 

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 { 
/* Targets which reenter must return 
                                   abs. verdicts */ 
#ifdef CONFIG_NETFILTER_DEBUG 
((struct ipt_entry *)table_base)->comefrom 
= 0xeeeeeeec; 
#endif 
verdict = t->u.kernel.target->target(pskb, 
     hook, 
     in, out, 
     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; 
protohdr = (u_int32_t *)ip + ip->ihl; 
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); 

#ifdef CONFIG_NETFILTER_DEBUG 
((struct ipt_entry *)table_base)->comefrom = 0xdead57ac; 
#endif 
read_unlock_bh(&table->lock); 

#ifdef DEBUG_ALLOW_ALL 
return NF_ACCEPT; 
#else 
if (hotdrop) 
return NF_DROP; 
else return verdict; 
#endif 


再来一句句看这个函数吧: 
[color=Red][size=3]先是把该取的值取出来:[/size][/color] 


ip = (*pskb)->nh.iph; /*获取IP头*/ 
protohdr = (u_int32_t *)ip + ip->ihl; /*跳过IP头,搞不明白,为什么不用( u_int8_t * )ip + ip->ihl << 2^o^*/ 
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)); /*检验HOOK,debug用的*/ 

/*获取当前表的当前CPU的规则入口*/ 
table_base = (void *)table->private->entries 
+ TABLE_OFFSET(table->private, 
       cpu_number_map(smp_processor_id())); 

/*获得当前表的当前Hook的规则的起始偏移量*/         
e = get_entry(table_base, table->private->hook_entry[hook]); 

/*获得当前表的当前Hook的规则的上限偏移量*/ 
back = get_entry(table_base, table->private->underflow[hook]);


[color=Red][size=3]然后进行规则的匹配,是在一个do...while中实现的:[/size][/color]do { 
(*pskb)->nfcache |= e->nfcache; 

/*如果IP包匹配,就断续匹配下去,否则就跳到下一条规则*/ 
if (ip_packet_match(ip, indev, outdev, &e->ip, offset)) 


else 

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

}while (!hotdrop); 

[color=Red][size=3]标准的match匹配,即struct ipt_ip这部份,是通过调用函数ip_packet_match来实现的; 
当 ip_packet_match匹配,则继续匹配下去,否则就跳到下一条规则(e = (void *)e + e->next_offset;)[/size][/color] 

ip_packet_match放到一边,把来看看后面的情况:如果标准的match匹配了,则: 
接着匹配扩展match 
if (IPT_MATCH_ITERATE(e, do_match, 
      *pskb, in, out, 
      offset, protohdr, 
      datalen, &hotdrop) != 0) 
goto no_match; 
[color=Red][size=3]IPT_MATCH_ITERATE这个宏已经遇到很多次了,它的作用是遍历扩展的match,而实际执行的功能函数是 
do_match[/size][/color] 

OK,如果不匹配,则goto no_match;执行下条规则,否则: 
/*这个宏用来分别处理字节计数器和分组计数器这两个计数器*/ 
ADD_COUNTER(e->counters, ntohs(ip->tot_len), 1); 

/*获取规则的target的偏移地址*/ 
t = ipt_get_target(e); 

[size=3][color=Red]然后接着匹备target:[/color][/size] 
if (!t->u.kernel.target->target) { 
int v; 

v = ((struct ipt_standard_target *)t)->verdict; 
if (v < 0) { 
/* Pop from stack? */ 
if (v != IPT_RETURN) { 
verdict = (unsigned)(-v) - 1; 
break; 

e = back; 
back = get_entry(table_base, 
 back->comefrom); 
continue; 

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 { 
/* Targets which reenter must return 
                                   abs. verdicts */ 
#ifdef CONFIG_NETFILTER_DEBUG 
((struct ipt_entry *)table_base)->comefrom 
= 0xeeeeeeec; 
#endif 
verdict = t->u.kernel.target->target(pskb, 
     hook, 
     in, out, 
     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; 
protohdr = (u_int32_t *)ip + ip->ihl; 
datalen = (*pskb)->len - ip->ihl * 4; 

if (verdict == IPT_CONTINUE) 
e = (void *)e + e->next_offset; 
else 
/* Verdict */ 
break; 

因为到目前为止,我们谈match/target的数据结构,只接触到用户态,对于内核态的,几乎没有怎么接触,所以要把它说清楚,可不是一件容易的事情。(或者先分析用户态添加规则在内核中是如何实现的,了解了match和target的组织再来分析代码,更容易些)。不过,反正无论如何,还是一步步地来, 
先来看看标准match的匹配部份,即ip_packet_match函数

[ 本帖最后由 独孤九贱 于 2005-12-20 10:21 编辑 ]

  回复于:2005-12-19 17:56:07

[size=3][color=Red]ip_packet_match函数,标准match部份的匹配[/color][/size] 

先来看看相关的数据结构,在内核中,标准的match是以struct ipt_ip 结构来表示的,它包含了一条规则最基本的部份: 
/* Yes, Virginia, you have to zero the padding. */ 
struct ipt_ip { 
/* 来源/目的地址 */ 
struct in_addr src, dst; 
/* 来源/目的地址的掩码 */ 
struct in_addr smsk, dmsk; 
/*输入输出网络接口*/ 
char iniface[IFNAMSIZ], outiface[IFNAMSIZ]; 
unsigned char iniface_mask[IFNAMSIZ], outiface_mask[IFNAMSIZ]; 

/* 协议, 0 = ANY */ 
u_int16_t proto; 

/* 标志字段 */ 
u_int8_t flags; 
/* 取反标志 */ 
u_int8_t invflags; 
}; 

这样,再来看这部份的判断是一个很简单的事情了: 

/* Returns whether matches rule or not. */ 
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; 
/*定义一个宏,当bool和invflg的是一真一假的情况时,返回真。注意这里使用两个“!”的目的是使得这样计算后的值域只取0和1两个值*/ 
#define FWINV(bool,invflg) ((bool) ^ !!(ipinfo->invflags & invflg)) 

/*处理源和目标ip地址,这个if语句的意义是:到达分组的源ip地址经过掩码处理后与规则中的ip不匹配并且规则中 
没有包含对ip地址的取反,或者规则中包含了对匹配地址的取反,但到达分组的源ip与规则中的ip地址匹配,if的第 
一部分返回真,同样道理处理到达分组的目的ip地址。这两部分任意部分为真时,源或者目标地址不匹配。*/ 
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; 


/*接着处理输入和输出的接口,for语句处理接口是否与规则中的接口匹配,不匹配时,ret返回非零,离开for语句后, 
处理接口的取反问题:当接口不匹配并且接口不取反,或者接口匹配,但是接口取反,说明接口不匹配。*/ 

/*输入接口*/ 
for (i = 0, ret = 0; i < IFNAMSIZ/sizeof(unsigned long); i++) { 
ret |= (((const unsigned long *)indev) 
^ ((const unsigned long *)ipinfo->iniface)
& ((const unsigned long *)ipinfo->iniface_mask)


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) 
^ ((const unsigned long *)ipinfo->outiface)
& ((const unsigned long *)ipinfo->outiface_mask)


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; 


/* 检查协议是否匹配的情况 */ 
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; 
}

  回复于:2005-12-19 18:46:38

ipt_ttl.c 

/* IP tables module for matching the value of the TTL
 *
 * ipt_ttl.c,v 1.5 2000/11/13 11:16:08 laforge Exp
 *
 * (C) 2000,2001 by Harald Welte 
 *
 * This software is distributed under the terms  GNU GPL
 */

#include 
#include 

#include 
#include 

MODULE_AUTHOR("Harald Welte ");
MODULE_DESCRIPTION("IP tables TTL matching module");
MODULE_LICENSE("GPL");

static int match(const struct sk_buff *skb, const struct net_device *in,
                 const struct net_device *out, const void *matchinfo,
                 int offset, const void *hdr, u_int16_t datalen,
                 int *hotdrop)
{
        const struct ipt_ttl_info *info = matchinfo;
        const struct iphdr *iph = skb->nh.iph;

        switch (info->mode) {
                case IPT_TTL_EQ:
                        return (iph->ttl == info->ttl);
                        break;
                case IPT_TTL_NE:
                        return (!(iph->ttl == info->ttl));
                        break;
                case IPT_TTL_LT:
                        return (iph->ttl < info->ttl);
                        break;
                case IPT_TTL_GT:
                        return (iph->ttl > info->ttl);
                        break;
                default:
                        printk(KERN_WARNING "ipt_ttl: unknown mode %d\n",
                                info->mode);
                        return 0;
        }

        return 0;
}

static int checkentry(const char *tablename, const struct ipt_ip *ip,
                      void *matchinfo, unsigned int matchsize,
                      unsigned int hook_mask)

                      void *matchinfo, unsigned int matchsize,
                      unsigned int hook_mask)
{
        if (matchsize != IPT_ALIGN(sizeof(struct ipt_ttl_info)))
                return 0;

        return 1;
}

static struct ipt_match ttl_match = { { NULL, NULL }, "ttl", &match,
                &checkentry, NULL, THIS_MODULE };

static int __init init(void)
{
        return ipt_register_match(&ttl_match);
}

static void __exit fini(void)
{
        ipt_unregister_match(&ttl_match);

}

module_init(init);
module_exit(fini);

这个模块挺好理解的,连我都看懂了 :em02: 
ps:ctags -R 真好用,谢谢 albcamus 兄指点! 
不过,里面有太多的结构体需要学习了,TCP/IP 的知识也需要补习

  回复于:2005-12-20 13:26:42

[size=3][color=Red]内核中的match[/color][/size] 

接下来的流程,似乎应该是分析扩展match及target的匹配了,如继续分析do_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, 
     const void *hdr, 
     u_int16_t datalen, 
     int *hotdrop) 

/* Stop iteration if it doesn't match */ 
if (!m->u.kernel.match->match(skb, in, out, m->data, 
      offset, hdr, datalen, hotdrop)) 
return 1; 
else 
return 0; 


虽然函数只有一句话,但是m->u.kernel.match->match()这是什么东东?不明白。因为至目前为止,我们对于 
扩展的match和target在内核中的结构、组织、注册等东东都没有接触过,只是在分析iptables时接触过用户态
的那个基于插件形式的框架。所以,函数流程分析至此,要中断一下了。从内核中match的组织分析起。 

我们在编译内核的netfilter选项时,有ah、esp、length……等一大堆的匹配选项,他们既可以是模块的形式注册, 
又可以是直接编译进内核,所以,他们应该是以单独的文件形式,以: 
module_init(init); 
module_exit(cleanup); 
这样形式存在的,我们在源码目录下边,可以看到Ipt_ah.c、Ipt_esp.c、Ipt_length.c等许多文件,这些就是我们 
所要关心的了,另一方面,基本的TCP/UDP 的端口匹配,ICMP类型匹配不在此之列,所以,应该有初始化的地方, 
我们注意到Ip_tables.c的init中,有如下语句: 
/* Noone else will be downing sem now, so we won't sleep */ 
down(&ipt_mutex); 
list_append(&ipt_target, &ipt_standard_target); 
list_append(&ipt_target, &ipt_error_target); 
list_append(&ipt_match, &tcp_matchstruct); 
list_append(&ipt_match, &udp_matchstruct); 
list_append(&ipt_match, &icmp_matchstruct); 
up(&ipt_mutex); 

可以看到,这里注册了standard_target、error_target两个target和tcp_matchstruct等三个match。 

这两个地方,就是涉及到match在内核中的注册了,以Ipt_*.c为例,它们都是以下结构: 
#include XXX 

MODULE_AUTHOR() 
MODULE_DESCRIPTION() 
MODULE_LICENSE() 

static int match() 



static int checkentry() 



static struct ipt_match XXX_match = { { NULL, NULL }, "XXX", &match, 
&checkentry, NULL, THIS_MODULE }; 

static int __init init(void) 

return ipt_register_match(&XXX_match); 


static void __exit fini(void) 

ipt_unregister_match(&XXX_match); 


module_init(init); 
module_exit(fini); 

其中,init函数调用ipt_register_match对一个struct ipt_match结构的XXX_match进行注册, 
另外,有两个函数match和checkentry。 

先来看struct ipt_match结构: 


struct ipt_match 

struct list_head list; /* 组织链表的成员,前面提到过很多次了,通常初始化成{NULL,NULL}*/ 

const char name[IPT_FUNCTION_MAXNAMELEN]; /*match的名称*/ 
/*匹配函数,到时候进行该match的匹配,就要调用它了,这也是我们最为关心的实现 
返回非0表示匹配成功,如果返回0且hotdrop设为1,则表示该报文应当立刻丢弃*/ 
int (*match)(const struct sk_buff *skb, 
     const struct net_device *in, 
     const struct net_device *out, 
     const void *matchinfo, 
     int offset, 
     const void *hdr, 
     u_int16_t datalen, 
     int *hotdrop); 
/* 在使用本Match的规则注入表中之前调用,进行有效性检查,如果返回0,规则就不会加入iptables中 */ 
int (*checkentry)(const char *tablename, 
  const struct ipt_ip *ip, 
  void *matchinfo, 
  unsigned int matchinfosize, 
  unsigned int hook_mask); 

/* 在包含本Match的规则从表中删除时调用,与checkentry配合可用于动态内存分配和释放 */
void (*destroy)(void *matchinfo, unsigned int matchinfosize); 

/* 表示当前Match是否为模块(NULL为否)*/
struct module *me;
}; 

有了对这个结构的认识,就可以很容易地理解init函数了。我们也可以猜测,ipt_register_match的作用可能就是建立一个 
双向链表的过程,到时候要用某个match的某种功能,遍历链表,调用其成员函数即可。 

当然,对于分析filter的实现,每个match/target的匹配函数的确是我们关心的重点,但是这里为了不中断分析系统框架,就 
不再一一分析每个match的match函数,以后专门搞个章节来分析。 

接着来看ipt_register_match函数是如何建立双向链表的(猜一下,应该也是调用list_add函数吧……) 

int 
ipt_register_match(struct ipt_match *match) 

int ret; 

MOD_INC_USE_COUNT; 
ret = down_interruptible(&ipt_mutex); 
if (ret != 0) { 
MOD_DEC_USE_COUNT; 
return ret; 

if (!list_named_insert(&ipt_match, match)) { 
duprintf("ipt_register_match: `%s' already in list!\n", 
 match->name); 
MOD_DEC_USE_COUNT; 
ret = -EINVAL; 

up(&ipt_mutex); 

return ret; 


可以看到,是通过调用list_named_insert(&ipt_match, match)来实现,函数第一个参数是链表头,第二个参数 
是当前待插入接点,并没有如偶想像的直接调用list_add函数,list_named_insert,根据名称排序插入?继续看看先: 
/* Returns false if same name already in list, otherwise does insert. */ 
static inline int 
list_named_insert(struct list_head *head, void *new) 

if (LIST_FIND(head, __list_cmp_name, void *, 
      new + sizeof(struct list_head))) 
return 0; 
list_prepend(head, new); 
return 1; 


涉及到两点:先调用宏LIST_FIND,再调用list_prepend,list_prepend是一个建立链表的过程: 
/* Prepend. */ 
static inline void 
list_prepend(struct list_head *head, void *new) 

ASSERT_WRITE_LOCK(head); 
list_add(new, head); 

这个前面已提到很多次了,不再继续分析。 

那么LIST_FIND呢? 
/* Return pointer to first true entry, if any, or NULL.  A macro 
   required to allow inlining of cmpfn. */ 
#define LIST_FIND(head, cmpfn, type, args...) \ 
({ \ 
const struct list_head *__i = (head); \ 

ASSERT_READ_LOCK(head); \ 
do { \ 
__i = __i->next; \ 
if (__i == (head)) { \ 
__i = NULL; \ 
break; \ 
} \ 
} while (!cmpfn((const type)__i , ## args)); \ 
(type)__i; \ 
}) 

由于可知了,LIST_FIND的作用是遍历链表中的每一个节点,然后将每一个节点与当前待处理的节点通过 
第二个参数(函数指针)来处理,对于此次调用, 
LIST_FIND(head, __list_cmp_name, void *,new + sizeof(struct list_head)) 
cmpfn就是__list_cmp_name。 
而new + sizeof(struct list_head)表示的是struct ipt_match结构跳过一个struct list_head结构,刚好 
就是待处理节点的名称,即当然待插入的match的name。 
那么,__list_cmp_name函数就很一目了然,就是比较两个name是否相同。 
/* If the field after the list_head is a nul-terminated string, you 
   can use these functions. */ 
static inline int __list_cmp_name(const void *i, const char *name) 

return strcmp(name, i+sizeof(struct list_head)) == 0; 


回过头来,list_named_insert的作用就是先看链表中待插入match的名称是否已存在,不存在再进行链表节点的 
插入。 

同样的,对于第二种形式: 

list_append(&ipt_match, &tcp_matchstruct); 
list_append(&ipt_match, &udp_matchstruct); 
list_append(&ipt_match, &icmp_matchstruct); 
因为他们不是以可选插件的形式存在,不需要检查是否注册,查接建立链表即可: 

/* Append. */ 
static inline void 
list_append(struct list_head *head, void *new) 

ASSERT_WRITE_LOCK(head); 
list_add((new), (head)->prev); 


OK,了解了内核中match的注册、组织、链表的构建,那么我们要说的match的匹配就是一个很简单的事情了——遍历双向链表,调用每一个节点match封装好的match函数,就OK了。 
现在内核中的某条规则是match1+match2+match3+target……这样子, 
而检测对应的match节点的函数又被封装成一个双向链表: 
node1--node2--node3…… 

每检测一个包,匹配每一条规则的每一个match都根据match名称来遍历一次链表,无疑是一个效率非常低的事情[color=Red](事实上,起先我没有仔细地看代码,想当然地以为就是这样,多亏思一克给我指出来)。[/color]但是,如果规则的match中,在检测之前,根据match的名称,把其对应的检测函数与链表中的对应函数关连起来,那么,我们就可以直接使用IPT_MATCH_ITERATE宏来遍历规则中的每一个match,然后直接调用: 
match->match检测函数 
这样的形式来进行数据包的匹配,无疑效率就得到了极大的提升。也就是: 
do_match函数中的 
if (!m->u.kernel.match->match(skb, in, out, m->data,offset, hdr, datalen, hotdrop)) 
我将在下一节继续分析为什么通过m->u.kernel.match->match就可以直接定位到相应的match函数。 

不过,事实上重点应该是分析每一个match函数,这个以后用单独的内容来分析。

[ 本帖最后由 独孤九贱 于 2005-12-20 19:20 编辑 ]

  回复于:2005-12-20 15:19:17

To JIU JIAN 


你看的有问题吧。我看的是match的匹配过程根本没有遍历双向链表呀。否则IPTABLE的效率就太低了。也许我错了。 

你还是慢点写。IPT_MATCH_ITERATE 不遍历双向链表而是查数组。那个ipt_match结构中list_head不是为了match匹配时用的。 

iptables设计的十分精妙为了提高效率。因此许多程序让人费解。就说那个ipt_do_table吧,要全搞明白就十分费力(我也没有全明白,我觉得没有必要和压力了)。 





“ 
OK,了解了内核中match的注册、组织、链表的构建,那么我们要说的match的匹配就是一个很简单的事情了——遍历双向 
链表,调用每一个节点match封装好的match函数,就OK了。 

这样,本节一开头所说的do_match函数中的 
if (!m->u.kernel.match->match(skb, in, out, m->data,offset, hdr, datalen, hotdrop)) 
就很容易理解了…… 
重点是分析每一个match函数,这个以后用单独的内容来分析。”

  回复于:2005-12-20 15:49:57

引用:原帖由 思一克 于 2005-12-20 15:19 发表 
To JIU JIAN 


你看的有问题吧。我看的是match的匹配过程根本没有遍历双向链表呀。否则IPTABLE的效率就太低了。也许我错了。 

你还是慢点写。IPT_MATCH_ITERATE 不遍历双向链表而是查数组。那个ipt_match结构 ... 



终于有人回复了,谢谢! 
为了行文,思考,有些地方的确用语不当,或者不完善: 

1、“看的是match的匹配过程根本没有遍历双向链表呀”——我分析了match的注册,组织后,说“应该是遍历注册时建立的那个链表,然后调用封装好的match函数”,具体是不是这样,下一节正找算写; 

2、我写提那个宏是遍历链表么?sorry!笔误: 
/* fn returns 0 to continue iteration */ 
#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; \ 
}) 

内核中规则的match是顺序存储,靠偏移植来取的,不是链表,我马上修正贴子……

[ 本帖最后由 独孤九贱 于 2005-12-20 16:17 编辑 ]

  回复于:2005-12-20 21:54:05

我在看 ipt_mark.c 时,有一个 IPT_ALIGN,于是用 ctags 去跟,发现 ip_tables.h 里有如下定义 

#define IPT_ALIGN(s) (((s) + (__alignof__(struct ipt_entry)-1)) & ~(__alignof__(struct ipt_entry)-1))

我实在是看不懂这句的意思,能讲讲吗?

  回复于:2005-12-21 08:55:27

To Platnum, 

__alignof__(struct ipt_entry)   ====== 4 
(__alignof__(struct ipt_entry)-1) ======= 3 

~(__alignof__(struct ipt_entry)-1) ======== 0xfffffffc 

所以 
IPT_ALIGN( S ) 就是使 S 变为 4 对齐(取0,4,8,12,16等4的倍数)。 

比如 
IA(1) = 4 
IA(2) = IA(3) = IA(4) ==== 4 
IA(5,6,7,8) ===== 8 等。 

_alignof__(结构)是找结构中的最大基本类型变量的对齐数值。具体我也没看。

  回复于:2005-12-21 09:10:44

引用:原帖由 platinum 于 2005-12-20 21:54 发表 
我在看 ipt_mark.c 时,有一个 IPT_ALIGN,于是用 ctags 去跟,发现 ip_tables.h 里有如下定义 

#define IPT_ALIGN(s) (((s) + (__alignof__(struct ipt_entry)-1)) & ~(__alignof__(struct ipt_entr ... 



它应该是解决用户空间和内核空间架构不同上边的问题的:
当通过用户空间向内核这间传递规则时,需要用这个宏来对齐match和target,事实上,我认为,一般来讲,我们用户态和内核态都为32为,是用不着这个宏的,除非是64-32的情况!

在linux内核中,使用__alignof__对对像进行对齐,呵呵,记得一篇专门讲结构对齐的某人的blog,本来想找出来贴上来,找不着了。不过另一篇贴子还是有点用的:
http://www-128.ibm.com/developerworks/cn/linux/l-pow-inteltopwr/index.html?ca=dwcn-newsletter-linux
《Linux on x86 程序到 Linux on POWER 的移植指南》,你搜索其中关于__alignof__的部份!

[size=3][color=Red]PS:对于match的匹配问题,当注册时建立好双向链表,但是当匹配数据包时,是直接调用其match函数,我其先没有仔细看代码,想当然认为是遍历一个遍表的过程,谢谢思一克兄给小弟指出来,但是在匹备数据包之前,应该根据match的名称,将规则中的match相关结构与链表中的相关结构进行关连,才能直接调用match函数,我认为这种动作应该在添加规则的时候来完成比较合适,于是昨天通读了iptables/netfilter添加/插入规则的部份的代码,没有看到相关的代码,可能是我自己思路错了,现在正在重新梳理用户态和内核态的所有关于match的结构,哪位大哥知道内核中这部份是如何完成的,还望不吝指点一二,谢谢……[/color][/size]

[ 本帖最后由 独孤九贱 于 2005-12-21 09:15 编辑 ]

  回复于:2005-12-21 09:49:25

谢谢一克和九贱,再菜菜地问一下,为什么一定要对齐啊,什么情况下一定非要对齐不可 ^_^

还有,这些知识哪里有讲,如果我想学,应该看什么书?LDD 吗?还是 GNU/LINUX 编程指南,还是别的?
相对于答案,我更关心的是一些学习的方法,还请赐教 :em15:

  回复于:2005-12-21 09:56:26

To platinum,

对齐是CPU要求的(为了发挥起最高效能)。
比如对于32BIT的整数,应该是4 align的(在地址0,或4, 8, 12, 16。。。等存放)。如果不是,CPU劳累或拒绝

  回复于:2005-12-21 09:58:48

引用:原帖由 platinum 于 2005-12-21 09:49 发表
谢谢一克和九贱,再菜菜地问一下,为什么一定要对齐啊,什么情况下一定非要对齐不可 ^_^

还有,这些知识哪里有讲,如果我想学,应该看什么书?LDD 吗?还是 GNU/LINUX 编程指南,还是别的?
相对于答案,我更 ... 



个人意见:
首先,32位和64 位好像一定要对齐吧,只是看内核中的代码,个人没有64位机,没有办法测试了!(不过我个人觉得如果我前面贴的那篇IBM站的贴子上讲的是正确的,至少X86下边好像没得必要)

其次,如果同是32位,结构的对齐,是基于“现在内存已经比较大了,为了更好地提高效率,浪费一点也没有关系的”,因为计算机总是以2的n次方来处理数据,那么处理2的倍数的效率比一些奇数要更高一些。那么,如果一个结构是7字节的话,就让它等于8字节吧,^o^我只是说思路,表达方法可能有些错误,不过对齐方法,楼上的楼上思一克已经讲了。我没有找到以前我看见的那篇关于内存对齐的精彩贴子,不过找到自己很久以前的一篇日记:
!1pQT3u4I2CIgJRXeSH5R6uaQ!118.entry?owner=1
应该还是有点参考价值吧^o^

  回复于:2005-12-21 10:02:40

对齐,主要是为了告诉缓存的效率更高,内存访问的次数尽可能少。 

好象在一些RISC处理器上,不对齐的访问会引发CPU异常

  回复于:2005-12-21 10:09:46

各位老大,这些知识你们都是哪里知道的?
如果我想学,应该看什么书?LDD 吗?还是 GNU/LINUX 编程指南,还是别的?
相对于答案,我更关心的是一些学习的方法,还请赐教 :oops:

  回复于:2005-12-21 10:15:11

pinfo gcc 里边的C extensions一段(其实还包括很多章节)值得一看,pinfo ld值得一看……

个人觉得把linuxforum.net那个置顶贴看下来,基本就明白大部分类似层次的问题了--可惜现在不能访问:mrgreen:

  回复于:2005-12-21 11:54:55

[color=Red][size=3]前面关于match的那个问题解决了,自己来回复。搞了大半天,还是看代码不仔细……

本节前面大部份为解决问题的思路,和前面的贴子有重复,可以直接跳到后半截看^o^。

内核中的match:
[/size][/color]

1、表与规则
内核中,表用struct ipt_table表示,其成员struct ipt_table_info *private;表示表的数据区,而private的成员
char entries[0] __attribute__((aligned(SMP_CACHE_BYTES)));;表示每个CPU的规则的入口;

2、规则
struct ipt_entry
{
struct ipt_ip ip; /*标准的match部份,如地址,网络接口等*/

/* 规则所关心的数据包的位置的标志,有些match使用了,有些没有用 */
unsigned int nfcache;

/* target区的偏移,通常target区位于match区之后,而match区则在ipt_entry的末尾;
初始化为sizeof(struct ipt_entry),即假定没有match */
u_int16_t target_offset;
/* 下一条规则相对于本规则的偏移,也即本规则所用空间的总和,
初始化为sizeof(struct ipt_entry)+sizeof(struct ipt_target),即没有match */
u_int16_t next_offset;

/* 位向量,为发现规则中存在”环路“提供手段*/
unsigned int comefrom;

/* 包和字节计数器. */
struct ipt_counters counters;

/*target或者是match(如果存在)的起始位置 */
unsigned char elems[0];
};

3、match的表示

A、用户态
struct iptables_match
{
        /* Match链,初始为NULL */
struct iptables_match *next;

        /* Match名,和核心模块加载类似,作为动态链接库存在的Iptables Extension的命名规则为libipt_'name'.so */
        ipt_chainlabel name;

        /*版本信息,一般设为NETFILTER_VERSION */
        const char *version;

        /* Match数据的大小,必须用IPT_ALIGN()宏指定对界*/
        size_t size;

        /*由于内核可能修改某些域,因此size可能与确切的用户数据不同,这时就应该把不会被改变的数据放在数据区的前面部分,而这里就应该填写被改变的数据区大小;一般来说,这个值和size相同*/
        size_t userspacesize;

        /*当iptables要求显示当前match的信息时(比如iptables-m ip_ext -h),就会调用这个函数,输出在iptables程序的通用信息之后. */
        void (*help)(void);

        /*初始化,在parse之前调用. */
        void (*init)(struct ipt_entry_match *m, unsigned int *nfcache);

        /*扫描并接收本match的命令行参数,正确接收时返回非0,flags用于保存状态信息*/
        int (*parse)(int c, char **argv, int invert, unsigned int *flags,
                     const struct ipt_entry *entry,
                     unsigned int *nfcache,
                     struct ipt_entry_match **match);

        /* 前面提到过这个函数,当命令行参数全部处理完毕以后调用,如果不正确,应该
退出(exit_error())*/
        void (*final_check)(unsigned int flags);

        /*当查询当前表中的规则时,显示使用了当前match的规则*/
        void (*print)(const struct ipt_ip *ip,
                      const struct ipt_entry_match *match, int numeric);

        /*按照parse允许的格式将本match的命令行参数输出到标准输出,用于iptables-save命令. */
        void (*save)(const struct ipt_ip *ip,
                     const struct ipt_entry_match *match);

        /* NULL结尾的参数列表,struct option与getopt(3)使用的结构相同*/
        const struct option *extra_opts;

        /* Ignore these men behind the curtain: */
        unsigned int option_offset;
        struct ipt_entry_match *m;
        unsigned int mflags;
        unsigned int used;
#ifdef NO_SHARED_LIBS
        unsigned int loaded; /* simulate loading so options are merged properly */
#endif
};

成员指针m是一个struct ipt_entry_match类型,这个东东后面再分析。

B、内核中,核心用struct ipt_match表征一个Match数据结构:
struct ipt_match
{
/* 组织链表的成员,通常初始化成{NULL,NULL},由核心使用 */
struct list_head list;

/* Match的名字*/
const char name[IPT_FUNCTION_MAXNAMELEN];

/*指向该Match的匹配函数,返回非0表示匹配成功,如果返回0且hotdrop设为1,则表示该报文应当立刻丢弃*/
int (*match)(const struct sk_buff *skb,
     const struct net_device *in,
     const struct net_device *out,
     const void *matchinfo,
     int offset,
     const void *hdr,
     u_int16_t datalen,
     int *hotdrop);

/* 在使用本Match的规则注入表中之前调用,进行有效性检查,如果返回0,规则就不会加入iptables中 */
int (*checkentry)(const char *tablename,
  const struct ipt_ip *ip,
  void *matchinfo,
  unsigned int matchinfosize,
  unsigned int hook_mask);

/* 在包含本Match的规则从表中删除时调用,与checkentry配合可用于动态内存分配和释放 */
void (*destroy)(void *matchinfo, unsigned int matchinfosize);

/* 表示当前Match是否为模块(NULL为否) */
struct module *me;

};

因为Match都是以模块的形式存在,这两个结构分别在iptables/Netfilter的模块初始化函数注册时被使用。

struct ipt_entry_match结构非常重要,它把内核态与用户态关连起来。如果说前面两个关于match的结构用来做
match的抽像点的处理的话,那么struct ipt_entry_match则表示了规则中具体的每个match,即规则中存储的一条规则是:
ipt_entry+ipt_entry_match1+ipt_entry_match2+ipt_entry_match3……

struct ipt_entry_match
{
union {
struct {
u_int16_t match_size;

/* Used by userspace */
char name[IPT_FUNCTION_MAXNAMELEN];
} user;
struct {
u_int16_t match_size;

/* Used inside the kernel */
struct ipt_match *match;
} kernel;

/* Total length */
u_int16_t match_size;
} u;

unsigned char data[0];
};

而在匹配每一条规则时:
/* fn returns 0 to continue iteration */
#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; \
})
宏IPT_MATCH_ITERATE用来遍历规则中的每一个match,i用来做循环变量。
struct ipt_entry用来表示一条规则,最后一个成员unsigned char elems[0];用来紧跟
match和target,那么match的起始位置自然是:
__i = sizeof(struct ipt_entry);即跳过ipt_entry

target_offset表示规则中target的偏移位,即match的结束,所以i的结束自然为:
 __i < (e)->target_offset;
 
 而每次步增的空间大小为match的大小,结构struct ipt_entry_match中以成员u.match_size表示当前match的大小,即:
 __i += __match->u.match_size
 
 i是每个match的偏移量,那么(void *)(e) + __i;则为每个match的绝对地址。
 
 问题有又回到起始点上来了:规则匹配的时候,每条规则的__match.u.kernel.match 是如何与内核模块中每个match初始化
 注册时建立链表的struct ipt_match东东关连起来的?
 
 先来看看用户空间添加一条规则(虽然觉得极不可能是用户空间来做这件事情,报着侥幸的心理)
 用户空间调用(iptables1.2.7 源码,Iptables.c:1653):
  switch (command) {
case CMD_APPEND:
ret = append_entry(……);

append_entry
iptc_append_entry
insert_rules一路下来直接处理规则了,并没有单独的match的处理,看来不是用户态完成这一工作了。再来看看内核中:

发现在内核添加规则之前,会调用函数translate_table来进行检查和传递用户空间传递过来的规则:
其中有一句:
static int
translate_table(const char *name,
unsigned int valid_hooks,
struct ipt_table_info *newinfo,
unsigned int size,
unsigned int number,
const unsigned int *hook_entries,
const unsigned int *underflows)
{
/*linux2.4.20 Ip_tables.c 318行*/
ret = IPT_ENTRY_ITERATE(newinfo->entries, newinfo->size,
check_entry, name, size, &i);
}

IPT_ENTRY_ITERATE这个宏前面分析过,用来遍历每一条规则,而check_entry为处理函数,在
check_entry函数中,又有如下语句:
static inline int
check_entry(struct ipt_entry *e, const char *name, unsigned int size,
    unsigned int *i)
{
ret = IPT_MATCH_ITERATE(e, check_match, name, &e->ip, e->comefrom, &j);
}

IPT_MATCH_ITERATE宏用来遍历某一条规则中的各个Match,check_matck为遍历到后的进一步处理函数。
再来看check_matck:
static inline int
check_match(struct ipt_entry_match *m,
    const char *name,
    const struct ipt_ip *ip,
    unsigned int hookmask,
    unsigned int *i)
{
int ret;
struct ipt_match *match;

/*根据规则中Match的名称,在已注册好的ipt_match双向链表中查找对应接点——已经接近要找的目标了*/
match = find_match_lock(m->u.user.name, &ret, &ipt_mutex);
if (!match) {
duprintf("check_match: `%s' not found\n", m->u.user.name);
return ret;
}
if (match->me)
__MOD_INC_USE_COUNT(match->me);
[size=3][color=Red] /*晕,找的就是它了,找了大半天*/
m->u.kernel.match = match;[/color][/size]
up(&ipt_mutex);

if (m->u.kernel.match->checkentry
    && !m->u.kernel.match->checkentry(name, ip, m->data,
      m->u.match_size - sizeof(*m),
      hookmask)) {
if (m->u.kernel.match->me)
__MOD_DEC_USE_COUNT(m->u.kernel.match->me);
duprintf("ip_tables: check failed for `%s'.\n",
 m->u.kernel.match->name);
return -EINVAL;
}

(*i)++;
return 0;
}

知道了关连,理解do_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,
     const void *hdr,
     u_int16_t datalen,
     int *hotdrop)
{
/* Stop iteration if it doesn't match */
if (![color=Red][size=3]m->u.kernel.match->match[/size][/color](skb, in, out, m->data,
      offset, hdr, datalen, hotdrop))
return 1;
else
return 0;
}

[ 本帖最后由 独孤九贱 于 2005-12-21 11:57 编辑 ]

  回复于:2005-12-21 17:09:47

各位兄台,对于基于netfilter的防火墙,不同网段共用,不知能否按不同用户配置各自不同需要的规则来保护相应的网段?

  回复于:2005-12-22 19:01:55

楼主的几位版主真牛,,,都是从事什么工作的,是不是搞开发的。
真是佩服呀,,看来自己要加油了。。

  回复于:2005-12-22 20:43:10

最好整理一下做成一个PDF文档, 可以打印了看

  回复于:2005-12-23 11:16:42

源码分析的文章的中断一断时间了,本来打算年前把防火墙部份写完,春节前把nat表部份写完,不过要去贵州出差啦,完了还要为年终总终排练节目,无语……
不过过段时间我会把这个贴子继续贴下去的,谢谢大家的关注!

  回复于:2005-12-23 12:13:50

排练节目,哇咔咔 :m01:

  回复于:2005-12-23 13:21:40

引用:原帖由 platinum 于 2005-12-23 12:13 发表
排练节目,哇咔咔 :m01: 



没办法呀,不知会不会是涂成大花脸,然后上台跳几下就没了……反正手头的所有事情都中断了,一切以老板的命令为中心!

对了,platinum ,想请教一个Netfilter的问题:
我把Linux做网关,因为当连接过多的时候(如蠕虫或其它其击,甚至内网使用网络频率),它会报“连接已满”之类的信息,还是上次讨论的那个ip_conntrack文件的问题吧,一味地扩内存等不是办法,我的想法是把连接跟踪功能去掉,但是这样会不会影响filter表的状态检测以及nat表的目的地址转换、源地址转换等需要保持连接状态的功能(我源码还没有看到这里来^o^,平时也没有试过)?

  回复于:2005-12-23 16:01:02

千万不能修改代码去掉 ip_conntrack 功能
我虽然看不懂,但是我知道那个功能是必不可少的,因为 netfilter 是基于状态检测的,如果那个功能去掉了基本就没什么可用的了
新的内核如果打了 patch 之后会,载入 ip_conntrack 后会有 sysctl -a|grep estab 显示中那样的提示
你可以修改这个数(默认是保持一天),也可以增大 ip_conntrack_max 值,最好两者一起改

  回复于:2005-12-23 16:15:35

引用:原帖由 platinum 于 2005-12-23 16:01 发表
千万不能修改代码去掉 ip_conntrack 功能
我虽然看不懂,但是我知道那个功能是必不可少的,因为 netfilter 是基于状态检测的,如果那个功能去掉了基本就没什么可用的了
新的内核如果打了 patch 之后会,载入 ip ... 



谢谢,被这个问题困扰好久了,我试试!

  回复于:2005-12-23 16:26:58

代码方面还要多请九贱兄指点呢 ^_^

  回复于:2005-12-26 09:26:47

/*kendo 2006-4-2修正原来对target部份描述不完全的情况*/

filter表的内容,还剩下三个内容:
1、target的匹配;
2、每个模块的target/match等函数的实现;
3、内核与用户空间的交互;

这里,以target的匹配做为2005的的结尾吧(因为明天飞贵州,估计2005年是没有机会再发贴了)

注:这里说匹配,其实不太正确,因为前面match是条件,匹配条件是正常的,target是动作,应该用执行更准确些。

[color=Red]target的匹配[/color]
要理解target的匹配,还是需要先了解相关的数据结构。
与match相似,内核中每个target模块,通过一个struct ipt_target来实现:
/* Registration hooks for targets. */
struct ipt_target
{
struct list_head list; /*target链,初始为NULL*/

const char name[IPT_FUNCTION_MAXNAMELEN]; /*target名称*/

/*target的模块函数,如果需要继续处理则返回IPT_CONTINUE(-1),否则返回NF_ACCEPT、NF_DROP等值,
它的调用者根据它的返回值来判断如何处理它处理过的报文*/
unsigned int (*target)(struct sk_buff **pskb,
       unsigned int hooknum,
       const struct net_device *in,
       const struct net_device *out,
       const void *targinfo,
       void *userdata);

/* Called when user tries to insert an entry of this type:
           hook_mask is a bitmask of hooks from which it can be
           called. */
/* 在使用本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是否为模块(NULL为否) */
struct module *me;
};
这个结构样子与match的内核模块的描述几乎是一模一样了……

而内核及用户态中,具体地存储描述一个target,是通过一个struct ipt_entry_target来实现的:
struct ipt_entry_target
{
union {
struct {
u_int16_t target_size;

/* Used by userspace */
char name[IPT_FUNCTION_MAXNAMELEN];
} user;
struct {
u_int16_t target_size;

/* Used inside the kernel */
struct ipt_target *target;
} kernel;

/* Total length */
u_int16_t target_size;
} u;

unsigned char data[0];
};
这个结构也与match一模一样,那么我们是不是就可以按照分析match的思路来分析match呢?“通过成员struct ipt_target *target;来与内核中注册的target的处理模块建立关连,再来调用u.kernel.target->target()函数进行匹配”???
先不急,Netfilter的target共分为三类:内建动作、扩展target和用户自定义链。而以上两个结构是不够的,它们只能描述基于扩展target的匹配函数,没有或可以讲至少没有显著地描述一个内建动作或者是用户自定义链!事实上,Netfilter描述一个完整的target,是通过以下结构来实现的:

struct ipt_standard_target
{
struct ipt_entry_target target; /*模块函数*/
int verdict; /*常数*/
};

其中成员verdict(判断、判决)表明用来针对内建动作(ACCEPT、DROP)或者是用户自定义链,如果是扩展的target,则通过其target成员去定位最终的模块处理函数。

那么,问题又接踵而至了,如果内核中,模块也是以类似注册/维护双向链表的形式储备,那么在内核中匹配的时候如何来区分这三类target?

事实上,考虑到程序的通用性、扩展性,对于内建动作或者是用户自定义链,内核是采用了“假注册”的方式来处理(这个名字是偶私人取的,或许不正确),也就是说,把内建动作或者是用户自定义链和扩展的target采用一样的处册方式,只是这个注册,只是一个样子,不具备实质意义:
在标准模块初始化Ip_tables.c的init函数注册target 的时候,可以看到:
static int __init init(void)
{
int ret;

/* Noone else will be downing sem now, so we won't sleep */
down(&ipt_mutex);
list_append(&ipt_target, &ipt_standard_target);
……
其注册的成员ipt_standard_target表示“标准的target”,即前文所提到的内建动作和用户自定义链。我们来看看它的初始化值:
/* The built-in targets: standard (NULL) and error. */
static struct ipt_target ipt_standard_target
= { { NULL, NULL }, IPT_STANDARD_TARGET, NULL, NULL, NULL };

同样,它也是一个ipt_target结构,也就是说和其它扩展的target模块一样,但是,它的处理函数全为空的,如target函数指针。所以,匹配的时候,如果要匹分的话,可以判断此指针函数是否指向NULL即可。而至于在标准的target中区分内建动作还是用户自定议链,前面提到过,它们都是以struct ipt_standard_target结构的verdict成员描述。到时候来判断verdict的值就可以了。我们可以推断出这段匹配的算法应该大致如下:
do {
…… /*前面为匹配match部份,前几节已分析过了*/
假设e为当前待匹配规则。
t=get_current_target(e); /*获取当前规则的当前target*/

/*因为如果注册时,如果是标准target,则t->u.kernel.target->target==NULL*/
if (!t->u.kernel.target->target) /*如果是标准target*/
{
/*进入标准target的话,还要来区分究竟是内建的动作,还是用户自定链,前面分析
struct ipt_standard_target时说过,它们都是以verdict成员来描述的,则*/
if(判断verdict==内建动作)
{
/*相应处理*/
}
else
{
/*相应处理*/
}
}
else
{
/*如果是扩展的target,调用target函数*/
verdict = t->u.kernel.target->target();
}
}while(……);

就是在规则的循环匹配中,先根据target函数指针的值判断target,如果是标准的target,再根据的值匹别是内建动作还是自定义链。
程序实际的代码与此已经很相似了,唯一的区别在于程序在处理自定义链时有一些技巧。
回到struct ipt_standard_target的verdict成员上来,这是一个非常重要的东西。用户空间表示一个接受动作,使用ACCEPT,内核不用能这个字符串来匹配,!strcmp(target.name,"ACCEPT"),这样效率差了点,一个自然的想法是,用一些整形数来代替,就像我们平常用1来代替true,0来代替false一样。
链中还有一种规则的target,形如-j 自定义链名,内核中的规则,并没有直接的“链”的概念,是呈一维线性排例的,所以,需要跳转至自定义链时,就需要两个东东:
1、待跳转的链相对于这条-j 自定义链的偏移值;
2、回指针,跳完了,总要回来吧……并且,规则中-j RETURN这种规则,它同样也需要回指针;
对于一条默认链来讲:
back = get_entry(table_base, table->private->underflow[hook]);
最初回指针是指向这条链的末尾处的。

OK,再回到偏移值的问题上来,内核仍然以verdict这个成员来描述这个偏移值,刚才说过用verdict来描述ACCEPT等这些内建动作,难道它们不会冲突,答案是否定的。内核约定:以负数来描述ACCEPT等内建动作,需要用到时,再取其正值就行了。
例如:
#define NF_ACCEPT 1                     /*内核中定义NF_ACCEPT这种动作,对应常数为1*/
当用户在iptables中输入是"ACCEPT"字符串时,将其转换成:
verdict=-NF_ACCEPT - 1
到了内核中,要用到NETFILTER的动作时,只需要反过来:
(unsigned)(-verdict) - 1;
就OK了。

也就是说,用:
v=target.verdict;
if(v<0)            /*内核默认动作*/
{
    if (v != IPT_RETURN)
   {
            return verdict = (unsigned)(-v) - 1;     /*是默认动作,且不为RETURN,直接返回*/
    }
    //以下代码处理RETURN的情况
   ……
}
else                   /*自定义链*/
{
}
就可以处理内建动作与自定义链或RETURN几种情况了。让我们来看内核中实际的这块的代码处理:

/*获取当前规则的target*/
t = ipt_get_target(e);
/*如果不存在target模块函数,那么target应为常数,如ACCEPT,DROP等,或是自定义链*/
if (!t->u.kernel.target->target)
{
int v;

v = ((struct ipt_standard_target *)t)->verdict; /*取得target的verdict值*/
/*
#define NF_DROP 0
#define NF_ACCEPT 1
#define NF_STOLEN 2
#define NF_QUEUE 3
#define NF_REPEAT 4
#define NF_MAX_VERDICT NF_REPEAT

#define IPT_CONTINUE 0xFFFFFFFF

#define IPT_RETURN (-NF_MAX_VERDICT - 1)
*/

if (v < 0) /*动作是默认内建的动作*/
{
/* Pop from stack? */
if (v != IPT_RETURN) /*如果不是Return,返回相应的动作*/
{
verdict = (unsigned)(-v) - 1;
break;
}
/*back用来描述return 动作,或者是自定义链执行完了,若还需继续匹配,那它指向那条应继续匹配的规则,所以,这里用e=back来取得下一条待匹配的规则*/
e = back;
                               /*重新设置back点*/
back = get_entry(table_base, back->comefrom);
continue;
}
/*v>=0的情况,v表示了一个偏移值——待跳转的自定义链相对于规则起始地址的偏移,即如果是自定义链,那么应该跳到哪条规则去继续执行匹配,这里这个判断的意思是,如果下一条跳转换规则刚好就是当前规则的下一条规则,那就不用跳了……,否则,将当前规则(形如-j 自定义链名这样的规则)的下一条规则设置为back点*/
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模块函数*/
{
/* 如果需要继续处理则返回IPT_CONTINUE(-1),否则返回NF_ACCEPT、NF_DROP等值 */
verdict = t->u.kernel.target->target(pskb,
hook,
in, out,
t->data,
userdata);

/* Target might have changed stuff. */
/*Target函数有可能已经改变了stuff,所以这里重新定位指针*/
ip = (*pskb)->nh.iph;
protohdr = (u_int32_t *)ip + ip->ihl;
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;
}

[ 本帖最后由 独孤九贱 于 2006-4-4 13:51 编辑 ]

  回复于:2005-12-31 12:28:30

不错,兄台辛苦了
最近刚开始看 netfilter 的源码,不过我想第一步应该是了解它的框架是如何实现的,一下子钻到细节中可能效果反而不好。
还没仔细看你的文章,看完之后再交流

  回复于:2006-01-08 18:16:54

呵呵,内核的代码才精彩呢。

我只能看懂大概,一到加锁的细节就晕了!

  回复于:2006-01-08 18:39:12

我认为,一个数据报在netfilter处理的过程中,一直是在softirq中进行的,因此,不可能被其他softirq打断,

如果在一个netfilter的动作,也即target中,直接调用dev_queue_xmit()来发送自己构造的数据报,会出现内存泄漏吗?

  回复于:2006-01-09 11:25:50

引用:原帖由 guotie 于 2006-1-8 18:39 发表
我认为,一个数据报在netfilter处理的过程中,一直是在softirq中进行的,因此,不可能被其他softirq打断,

如果在一个netfilter的动作,也即target中,直接调用dev_queue_xmit()来发送自己构造的数据报,会出现 ... 



Why?不太同意你的说法
netfilter和softirq有关系吗???最多是受影响而已吧?
softirq更多的是和网卡收发数据有关系,而这个处理,从2.4.20有了较大的改变……使用了一个类似poll的动作……

  回复于:2006-02-22 10:13:11

我有一处不明白。还望各位多指点:
规则的组织方式是struct ipt_ip + struct ipt_entry_match + struct ipt_entry_match + ... + struct ipt_entry_target;

而规则在初始化的时候(建立的时候),需要分配它的内存,而match和target都要储存在这个分配给规则的内存空间中,也就是储存在unsigned char elems[0];的elems地址开始之处,那么给规则分配内存的时候岂不是要分配(sizeof(struct ipt_ip + struct ipt_entry_match + struct ipt_entry_match + ... + struct ipt_entry_target))这么大么??
这样的话每一条规则不是只能储存一定数目的match吗?太大会超过规则的空间,内存溢出??
如果match很少,不是浪费资源吗??

如果我有理解不对的地方,各位多多包涵,耐心指正,本人十分感激。

  回复于:2006-02-22 12:24:32

引用:原帖由 wwwspirit 于 2006-2-22 10:13 发表
我有一处不明白。还望各位多指点:
规则的组织方式是struct ipt_ip + struct ipt_entry_match + struct ipt_entry_match + ... + struct ipt_entry_target;

而规则在初始化的时候(建立的时候),需要分 ... 



想法是对了的……只是想得太过简单了……

我们以规则的添加为例来描述规则的构造,
看先iptables的源码的do_command()函数:
其中,分析一些标准的规则参数,如地址等等,存储于struct ipt_entry fw这个结构中,这个略去不表,重点在match和target.

每添加一个mathc,都对应一个-m,我们来看选项分析的case 'm':
case 'm': {
……
m->m = fw_calloc(1, size);
……
}
这样,就为每个match动态分析了一段地址!!target类似,略去不表。然后是调用每个match的parse函数分析,填充相应的值。

好,最后来看规则的整合了:
e = generate_entry(&fw, iptables_matches, target->t);

有是你所说的规则,fw是标准规则部份,每二个是match的全局结构变量,过会会遍历它,后一个是target:
只需看这个函数的前面开头部份就可以回答你的问题了:

static struct ipt_entry *
generate_entry(const struct ipt_entry *fw,
       struct iptables_match *matches,
       struct ipt_entry_target *target)
{
unsigned int size;
struct iptables_match *m;
struct ipt_entry *e;

size = sizeof(struct ipt_entry);
for (m = matches; m; m = m->next) {
if (!m->used)
continue;

size += m->m->u.match_size;
}

e = fw_malloc(size + target->u.target_size);
……
其中size用来计算规是空间,其实就是分别计算三个部份的长度,然后一起分配,所以即不会浪费,也不会有溢出等等……

而到了内核中,只需把用户空间的内容直接拷贝到对应地址空间即可!!!

  回复于:2006-02-22 13:00:53

那我修改要修改ipt_entry,或者修改ipt_entry_match怎么办呢?
如果我只需要添加一条match,那是不是就必须添加一条规则,而无法在已有的规则中添加match?

  回复于:2006-02-22 13:09:24

引用:原帖由 wwwspirit 于 2006-2-22 13:00 发表
那我修改要修改ipt_entry,或者修改ipt_entry_match怎么办呢?
如果我只需要添加一条match,那是不是就必须添加一条规则,而无法在已有的规则中添加match? 



根据match名,查到到对应的空间,替换之……不过iptables的相应动作是如何的,好久不看代码了,记不清了,你可以看看代码就一目了然了……

[ 本帖最后由 独孤九贱 于 2006-2-22 13:10 编辑 ]

  回复于:2006-02-22 13:11:17

如果我需要增加一条match呢?
是不是就要增加一条新的规则??

  回复于:2006-02-22 13:14:23

引用:原帖由 wwwspirit 于 2006-2-22 13:11 发表
如果我需要增加一条match呢?
是不是就要增加一条新的规则?? 



当然……

  回复于:2006-02-22 13:24:53

谢谢独孤大侠指点。
我最近比较忙,过几天再认真拜读一下netfilter代码。到时候如有问题再请教:D

  回复于:2006-02-22 13:31:38

引用:原帖由 wwwspirit 于 2006-2-22 13:24 发表
谢谢独孤大侠指点。
我最近比较忙,过几天再认真拜读一下netfilter代码。到时候如有问题再请教:D 



呵呵,九贱非大侠也,大家一起讨论,共同进步而已!
我也想抽个时间,将Netfilter和iptables的源码分析更正、补充完整,无奈时间太少,每天早出晚归的,没有时间啊没有时间!

  回复于:2006-02-23 09:01:46

在问一个问题:
用户用iptables添加规则,用户命令的解析由iptables完成,iptables是把用户的命令组织为规则,然后通过socket传递给netfilter的吗?如果是这样的话,那iptables是不是也必须知道每一条match如何构成,也就是说在netfilter中每注册一种扩展Match,对应的也要在iptables中增加内容呢?如果不改动iptables,iptables可能没有足够的信息来完成规则的构建。

  回复于:2006-02-23 09:28:37

引用:原帖由 wwwspirit 于 2006-2-23 09:01 发表
在问一个问题:
用户用iptables添加规则,用户命令的解析由iptables完成,iptables是把用户的命令组织为规则,然后通过socket传递给netfilter的吗?如果是这样的话,那iptables是不是也必须知道每一条match如何构 ... 



目前这种架构, Netfilter与iptables的模块结构是一致的,拿添加规则来讲,上面已经讲了它的全部流程,它是在用户态进行解析、分配空间,初始化,然后再得到这条规则的空间总得,再分配一块内存给这条规则,然后将这个内存地址传递给核心态,内核得到这个地址后,调用copy_from_user将用户态的该地址内容拷贝至内核来(从这个解度上讲,内核在添加规则的时候,如果不进行必要的检查的话,它根本不知道这块内存中是什么东西,也就是说是透明的)。

如果你想自己重写iptables,事实上,在数据结构上,当然得参照一下iptables 的实现了。你想直接写,而不遵循其结构的定义,无疑是不可能的!

  回复于:2006-02-23 09:51:39

我不是要重写iptables,我在写一个类似netfilter/iptables的东西,也就是这两个部分的功能都要实现,现在在做框架设计。所以要参考netfilter/iptables,但也不能太受它们的限制。等我自己的框架写完了,我在来认真拜读netfilter/iptables,来改进我自己的设计。现在有几个地方还弄不明白,比方内核与用户态通信,如果要在用户态就把规则/match封装好,必然要在内核和用户两个模块维护两份关键数据结构,这样影响模块化,且也不易于日后扩展和维护。
考虑的另外一个方法就是在用户态做半解析,然后将结果传递给内核模块,内核模块完成规则的维护。这样用户态和内核态就完全可以分开开发/维护,日后扩展也容易,但这样也不易实现。

  回复于:2006-02-23 09:52:25

我水平比较菜,刚学内核编程没几天,这只是个尝试,不过希望大家鼎立相助。

  回复于:2006-02-23 10:55:09

引用:原帖由 wwwspirit 于 2006-2-23 09:51 发表
如果要在用户态就把规则/match封装好,必然要在内核和用户两个模块维护两份关键数据结构,这样影响模块化,且也不易于日后扩展和维护。



所以,现在比如你升级了新的模块,你就得使用与之对应的iptables管理工具……一起维护,一起升级,它们就像是一个整体,一个程序的两个部份,没有问题的呀!

  回复于:2006-03-28 21:44:20

我看了你的iptables的分析
然后接着看netfilter的分析
写得好详细,这是强啊~~~~
限于水平,这个地方不懂
unsigned int hook_entry[NF_IP_NUMHOOKS]={ [NF_IP_LOCAL_IN] 0,
        [NF_IP_FORWARD] sizeof(struct ipt_standard),
        [NF_IP_LOCAL_OUT] sizeof(struct ipt_standard) * 2 };
[NF_IP_LOCAL_IN] 0表示什么意思?

  回复于:2006-03-29 09:14:31

引用:原帖由 tomorrow0530 于 2006-3-28 21:44 发表
我看了你的iptables的分析
然后接着看netfilter的分析
写得好详细,这是强啊~~~~
限于水平,这个地方不懂
unsigned int hook_entry[NF_IP_NUMHOOKS]={ [NF_IP_LOCAL_IN] 0,
        [NF_IP_FORWARD] sizeof( ... 



不好意思,iptables/netfilter源码分析,只是第一遍看的时候,边看边写的笔记,现在回想其来,其中错误连连,一直想找个时间修正后再发再CU上面来,只是近来很忙,没有时间,很久不搞了,包括你说的这段代码,我也完全没有印像了……

  回复于:2006-03-29 12:00:35

那没关系啊,我先继续往下看
等你整理好了,估计我也看完了
到时再温故知新!!吼吼

  回复于:2008-06-09 12:51:00

又读了你一篇非常不错的文章,收藏

  回复于:2008-06-23 14:24:53

好文章,最近准备看netfilter。mark一下。

  回复于:2009-03-19 15:32:21

这个是GCC编译器的新特性之一:标号元素。
[NF_IP_LOCAL_IN] 0  表示数组的索引 NF_IP_LOCAL_IN 初始化为 0,这样可以初始化数组的指定元素。



阅读(3680) | 评论(0) | 转发(2) |
0

上一篇:没有了

下一篇:独孤九贱 之 Netfilter 地址转换的实现

给主人留下些什么吧!~~