分类:
2006-08-10 10:08:19
ipt_ttl.c [code] /* 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); [/code] 这个模块挺好理解的,连我都看懂了 :em02: ps:ctags -R 真好用,谢谢 albcamus 兄指点! 不过,里面有太多的结构体需要学习了,TCP/IP 的知识也需要补习 |
| ||
[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函数,这个以后用单独的内容来分析。 [[i] 本帖最后由 独孤九贱 于 2005-12-20 19:20 编辑 [/i]] |
| ||
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函数,这个以后用单独的内容来分析。” |
| ||
[quote]原帖由 [i]思一克[/i] 于 2005-12-20 15:19 发表 To JIU JIAN 你看的有问题吧。我看的是match的匹配过程根本没有遍历双向链表呀。否则IPTABLE的效率就太低了。也许我错了。 你还是慢点写。IPT_MATCH_ITERATE 不遍历双向链表而是查数组。那个ipt_match结构 ... [/quote] 终于有人回复了,谢谢! 为了行文,思考,有些地方的确用语不当,或者不完善: 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是顺序存储,靠偏移植来取的,不是链表,我马上修正贴子…… [[i] 本帖最后由 独孤九贱 于 2005-12-20 16:17 编辑 [/i]] |
| ||
我在看 ipt_mark.c 时,有一个 IPT_ALIGN,于是用 ctags 去跟,发现 ip_tables.h 里有如下定义 [code] #define IPT_ALIGN(s) (((s) + (__alignof__(struct ipt_entry)-1)) & ~(__alignof__(struct ipt_entry)-1)) [/code] 我实在是看不懂这句的意思,能讲讲吗? |
| ||
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__(结构)是找结构中的最大基本类型变量的对齐数值。具体我也没看。 |
| ||
[quote]原帖由 [i]platinum[/i] 于 2005-12-20 21:54 发表 我在看 ipt_mark.c 时,有一个 IPT_ALIGN,于是用 ctags 去跟,发现 ip_tables.h 里有如下定义 [code] #define IPT_ALIGN(s) (((s) + (__alignof__(struct ipt_entry)-1)) & ~(__alignof__(struct ipt_entr ... [/quote] 它应该是解决用户空间和内核空间架构不同上边的问题的: 当通过用户空间向内核这间传递规则时,需要用这个宏来对齐match和target,事实上,我认为,一般来讲,我们用户态和内核态都为32为,是用不着这个宏的,除非是64-32的情况! 在linux内核中,使用__alignof__对对像进行对齐,呵呵,记得一篇专门讲结构对齐的某人的blog,本来想找出来贴上来,找不着了。不过另一篇贴子还是有点用的: [url]http://www-128.ibm.com/developerworks/cn/linux/l-pow-inteltopwr/index.html?ca=dwcn-newsletter-linux[/url] 《Linux on x86 程序到 Linux on POWER 的移植指南》,你搜索其中关于__alignof__的部份! [size=3][color=Red]PS:对于match的匹配问题,当注册时建立好双向链表,但是当匹配数据包时,是直接调用其match函数,我其先没有仔细看代码,想当然认为是遍历一个遍表的过程,谢谢思一克兄给小弟指出来,但是在匹备数据包之前,应该根据match的名称,将规则中的match相关结构与链表中的相关结构进行关连,才能直接调用match函数,我认为这种动作应该在添加规则的时候来完成比较合适,于是昨天通读了iptables/netfilter添加/插入规则的部份的代码,没有看到相关的代码,可能是我自己思路错了,现在正在重新梳理用户态和内核态的所有关于match的结构,哪位大哥知道内核中这部份是如何完成的,还望不吝指点一二,谢谢……[/color][/size] [[i] 本帖最后由 独孤九贱 于 2005-12-21 09:15 编辑 [/i]] |
| ||
谢谢一克和九贱,再菜菜地问一下,为什么一定要对齐啊,什么情况下一定非要对齐不可 ^_^ 还有,这些知识哪里有讲,如果我想学,应该看什么书?LDD 吗?还是 GNU/LINUX 编程指南,还是别的? 相对于答案,我更关心的是一些学习的方法,还请赐教 :em15: |
| ||
To platinum, 对齐是CPU要求的(为了发挥起最高效能)。 比如对于32BIT的整数,应该是4 align的(在地址0,或4, 8, 12, 16。。。等存放)。如果不是,CPU劳累或拒绝 |
| ||
[quote]原帖由 [i]platinum[/i] 于 2005-12-21 09:49 发表 谢谢一克和九贱,再菜菜地问一下,为什么一定要对齐啊,什么情况下一定非要对齐不可 ^_^ 还有,这些知识哪里有讲,如果我想学,应该看什么书?LDD 吗?还是 GNU/LINUX 编程指南,还是别的? 相对于答案,我更 ... [/quote] 个人意见: 首先,32位和64 位好像一定要对齐吧,只是看内核中的代码,个人没有64位机,没有办法测试了!(不过我个人觉得如果我前面贴的那篇IBM站的贴子上讲的是正确的,至少X86下边好像没得必要) 其次,如果同是32位,结构的对齐,是基于“现在内存已经比较大了,为了更好地提高效率,浪费一点也没有关系的”,因为计算机总是以2的n次方来处理数据,那么处理2的倍数的效率比一些奇数要更高一些。那么,如果一个结构是7字节的话,就让它等于8字节吧,^o^我只是说思路,表达方法可能有些错误,不过对齐方法,楼上的楼上思一克已经讲了。我没有找到以前我看见的那篇关于内存对齐的精彩贴子,不过找到自己很久以前的一篇日记: [url]!1pQT3u4I2CIgJRXeSH5R6uaQ!118.entry?owner=1[/url] 应该还是有点参考价值吧^o^ |