免费注册 查看新帖 |

Chinaunix

  平台 论坛 博客 文库
最近访问板块 发新帖
楼主: 独孤九贱

[原创]Netfilter源码分析-我来抛砖,望能引玉 [复制链接]

论坛徽章:
0
发表于 2005-12-19 18:46 |显示全部楼层
ipt_ttl.c

  1. /* IP tables module for matching the value of the TTL
  2. *
  3. * ipt_ttl.c,v 1.5 2000/11/13 11:16:08 laforge Exp
  4. *
  5. * (C) 2000,2001 by Harald Welte <laforge@gnumonks.org>
  6. *
  7. * This software is distributed under the terms  GNU GPL
  8. */

  9. #include <linux/module.h>
  10. #include <linux/skbuff.h>

  11. #include <linux/netfilter_ipv4/ipt_ttl.h>
  12. #include <linux/netfilter_ipv4/ip_tables.h>

  13. MODULE_AUTHOR("Harald Welte <laforge@gnumonks.org>");
  14. MODULE_DESCRIPTION("IP tables TTL matching module");
  15. MODULE_LICENSE("GPL");

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

  23.         switch (info->mode) {
  24.                 case IPT_TTL_EQ:
  25.                         return (iph->ttl == info->ttl);
  26.                         break;
  27.                 case IPT_TTL_NE:
  28.                         return (!(iph->ttl == info->ttl));
  29.                         break;
  30.                 case IPT_TTL_LT:
  31.                         return (iph->ttl < info->ttl);
  32.                         break;
  33.                 case IPT_TTL_GT:
  34.                         return (iph->ttl > info->ttl);
  35.                         break;
  36.                 default:
  37.                         printk(KERN_WARNING "ipt_ttl: unknown mode %d\n",
  38.                                 info->mode);
  39.                         return 0;
  40.         }

  41.         return 0;
  42. }

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

  46.                       void *matchinfo, unsigned int matchsize,
  47.                       unsigned int hook_mask)
  48. {
  49.         if (matchsize != IPT_ALIGN(sizeof(struct ipt_ttl_info)))
  50.                 return 0;

  51.         return 1;
  52. }

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

  55. static int __init init(void)
  56. {
  57.         return ipt_register_match(&ttl_match);
  58. }

  59. static void __exit fini(void)
  60. {
  61.         ipt_unregister_match(&ttl_match);

  62. }

  63. module_init(init);
  64. module_exit(fini);
复制代码

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

论坛徽章:
0
发表于 2005-12-20 13:26 |显示全部楼层
内核中的match

接下来的流程,似乎应该是分析扩展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名称来遍历一次链表,无疑是一个效率非常低的事情(事实上,起先我没有仔细地看代码,想当然地以为就是这样,多亏思一克给我指出来)。但是,如果规则的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 编辑 ]

论坛徽章:
0
发表于 2005-12-20 15:19 |显示全部楼层
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函数,这个以后用单独的内容来分析。”

论坛徽章:
0
发表于 2005-12-20 15:49 |显示全部楼层
原帖由 思一克 于 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 编辑 ]

论坛徽章:
0
发表于 2005-12-20 21:54 |显示全部楼层
我在看 ipt_mark.c 时,有一个 IPT_ALIGN,于是用 ctags 去跟,发现 ip_tables.h 里有如下定义

  1. #define IPT_ALIGN(s) (((s) + (__alignof__(struct ipt_entry)-1)) & ~(__alignof__(struct ipt_entry)-1))
复制代码

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

论坛徽章:
0
发表于 2005-12-21 08:55 |显示全部楼层
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__(结构)是找结构中的最大基本类型变量的对齐数值。具体我也没看。

论坛徽章:
0
发表于 2005-12-21 09:10 |显示全部楼层
原帖由 platinum 于 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 ...


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

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

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

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

论坛徽章:
0
发表于 2005-12-21 09:49 |显示全部楼层
谢谢一克和九贱,再菜菜地问一下,为什么一定要对齐啊,什么情况下一定非要对齐不可 ^_^

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

论坛徽章:
0
发表于 2005-12-21 09:56 |显示全部楼层
To platinum,

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

论坛徽章:
0
发表于 2005-12-21 09:58 |显示全部楼层
原帖由 platinum 于 2005-12-21 09:49 发表
谢谢一克和九贱,再菜菜地问一下,为什么一定要对齐啊,什么情况下一定非要对齐不可 ^_^

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


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

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

本版积分规则 发表回复

  

北京盛拓优讯信息技术有限公司. 版权所有 京ICP备16024965号-6 北京市公安局海淀分局网监中心备案编号:11010802020122 niuxiaotong@pcpop.com 17352615567
未成年举报专区
中国互联网协会会员  联系我们:huangweiwei@itpub.net
感谢所有关心和支持过ChinaUnix的朋友们 转载本站内容请注明原作者名及出处

清除 Cookies - ChinaUnix - Archiver - WAP - TOP