Chinaunix首页 | 论坛 | 博客
  • 博客访问: 1684000
  • 博文数量: 511
  • 博客积分: 967
  • 博客等级: 准尉
  • 技术积分: 2560
  • 用 户 组: 普通用户
  • 注册时间: 2012-07-06 14:19
文章分类

全部博文(511)

文章存档

2016年(11)

2015年(61)

2014年(257)

2013年(63)

2012年(119)

分类: 网络与安全

2014-05-20 22:30:50

六、 扩展的match

 

6.1 do_match函数  ip_tables.c

do_match通过IPT_MATCH_ITERATE宏来调用,

IPT_MATCH_ITERATE是在ipt_do_table函数中调用的宏

IPT_MATCH_ITERATE(e, do_match,

                                  *pskb, in, out,

                                  offset, &hotdrop)

定义如下:

#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;                         \

})

 

下面就是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,这个东西应该就是调用后面解释

这里还出现了一个ipt_entry_match结构,它用来把match的内核态与用户态关连起来

 

6.2 ipt_xxx.c文件

我们在编译内核的netfilter选项时,有ahesplength……等一大堆的匹配选项,他们既可以是模块的形式注册,又可以是直接编译进内核,所以,他们应该是以单独的文件形式,以:
module_init(init);
module_exit(cleanup);
这样形式存在的,我们在源码目录下边,可以看到Ipt_ah.cIpt_esp.cIpt_length.c等许多文件,这些就是我们所要关心的了,另一方面,基本的TCP/UDP 的端口匹配,ICMP类型匹配不在此之列,所以,应该有初始化的地方,

我们注意到Ip_tables.cinit中,有如下语句:
        /* 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_targeterror_target两个targettcp_matchstruct等三个match。这两个地方,就是涉及到match在内核中的注册了,以Ipt_*.c为例,它们都是以下结构:
#include XXX

MODULE_AUTHOR
()
MODULE_DESCRIPTION
()
MODULE_LICENSE
()

static int match
()       /* ipt_match中的匹配函数 */
{
}

static int checkentry
()     /* 检查entry有效性 */
{
}

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进行注册,另外,有两个函数matchcheckentry

 

6.3 ipt_match,内核中的match结构    ip_tables.h

struct  ipt_match

{

      struct list_head list;             /* 可见ipt_match也由一个链表来维护 */

 

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

 

    /* 匹配函数,最重要的部分,返回非0表示匹配成功,如果返回0hotdrop设为1,则表示该报文应当立刻丢弃。 */

      /* Arguments changed since 2.4, as this must now handle

           non-linear skbs, using skb_copy_bits and

           skb_ip_make_writable. */

      int (*match)(const struct sk_buff *skb,

                const struct net_device *in,

                const struct net_device *out,

                const void *matchinfo,

                int offset,

                int *hotdrop);

 

      /*在使用本Match的规则注入表中之前调用,进行有效性检查,如果返回0,规则就不会加入iptables. */

      int (*checkentry)(const char *tablename,

                   const struct ipt_ip *ip,

                   void *matchinfo,

                   unsigned int matchinfosize,

                   unsigned int hook_mask);

 

      /* 删除包含本matchentry时调用,与checkentry配合可用于动态内存分配和释放 */

      void (*destroy)(void *matchinfo, unsigned int matchinfosize);

 

      /* 是否为模块 */

      struct module *me;

};

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

当然,对于分析filter的实现,每个match/target的匹配函数才是我们关心的重点,但是这里为了不中断分析系统框架,就不再一一分析每个matchmatch函数

 

6.4 iptables_match,用户态的match结构    ip_tables.h

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的命令行参数,正确接收时返回非0flags用于保存状态信息
*/
        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 optiongetopt(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
};

 

6.5 ipt_entry_match结构   ip_tables.h

ipt_entry_match将内核态与用户态关联起来,按我的理解,内核和用户在注册和维护match时使用的是各自的match结构ipt_matchiptables_match,但在具体应用到某个规则时则需要统一成ipt_entry_match结构。

前面说过,match区存储在ipt_entry的末尾,target在最后,结合ipt_entry_match的定义,可以知道一条具体的规则中存储的数据结构不是:

ipt_entry + ipt_match1 + ipt_match2 + ipt_match3 + … + target

而是:

ipt_entry + ipt_entry_match1 + ipt_entry_match2 + ipt_entry_match3 + … + target

struct ipt_entry_match
{
        union {
                struct {
                        u_int16_t match_size;

                  
      /* 用户态 */

                        char name[IPT_FUNCTION_MAXNAMELEN];
                } user;
                struct {
                        u_int16_t match_size;

                    
    /* 内核态 */
                        
struct ipt_match *match;
                } kernel;

         
       /* 总长度 */
                u_int16_t match_size;
        } u;

        unsigned char data[0];
};

里面定义了两个数据结构,userkernel,很明显,是分别为iptables_matchipt_match准备的

前面在do_match函数中出现的m->u.kernel.match->match()函数,也就是调用ipt_match里的match函数了,接下来要关心的就是如何将ipt_entry_matchipt_match关联起来。换句话说,注册时还是ipt_match结构的match是何时变成ipt_entry_match结构的?

 

还记得注册table时调用的translate_table()函数吗

IPT_ENTRY_ITERATE宏出现三次,分别调用了

check_entry_size_and_hookscheck_entry,  cleanup_entry,三个函数

check_entry_size_and_hooks用来做一些边界检查,检查数据结构的长度之类的,略过

cleanup_entry,很明显,释放空间用的

下面看看check_entry

 

6.6  check_entrycheck_match函数   ip_tables.c

顾名思义,对entry结构进行检查

check_entry(struct ipt_entry *e, const char *name, unsigned int size,

          unsigned int *i)

{

      struct ipt_entry_target *t;

      struct ipt_target *target;

      int ret;

      unsigned int j;

 

 /* 检查flaginvflag … */

      if (!ip_checkentry(&e->ip)) {

           duprintf("ip_tables: ip check failed %p %s.\n", e, name);

           return -EINVAL;

      }

 

/* 先别看后面,这里是重点,之前遍历时用了IPT_ENTRY_ITERATE宏,这里又出现了用来遍历matchIPT_MATCH_ITERATE宏,两个很像。

另外IPT_MATCH_ITERATE宏前面看到过一次,在调用钩子函数时的ipt_do_table()函数里出现过,那里是用来遍历match并调用do_match()函数的。怎么样,思路又回到开头扩展的match那里了吧,那里是调用阶段,而这里正好是之前的初始化阶段。应该说这里才是IPT_MATCH_ITERATEipt_entry_match的第一次出现。

遍历该entry里的所有match,并对每一个match调用检查函数check_match() */

      j = 0;

      ret = IPT_MATCH_ITERATE(e, check_match, name, &e->ip, e->comefrom, &j);

      if (ret != 0)

           goto cleanup_matches;

 

/* 下面是关于target的部分 */

      t = ipt_get_target(e);

      target = ipt_find_target_lock(t->u.user.name, &ret, &ipt_mutex);

      if (!target) {

           duprintf("check_entry: `%s' not found\n", t->u.user.name);

           goto cleanup_matches;

      }

      if (!try_module_get(target->me)) {

           up(&ipt_mutex);

           ret = -ENOENT;

           goto cleanup_matches;

      }

      t->u.kernel.target = target;

      up(&ipt_mutex);

 

      if (t->u.kernel.target == &ipt_standard_target) {

           if (!standard_check(t, size)) {

                 ret = -EINVAL;

                 goto cleanup_matches;

           }

      } else if (t->u.kernel.target->checkentry

              && !t->u.kernel.target->checkentry(name, e, t->data,

                                        t->u.target_size

                                        - sizeof(*t),

                                        e->comefrom)) {

           module_put(t->u.kernel.target->me);

           duprintf("ip_tables: check failed for `%s'.\n",

                  t->u.kernel.target->name);

           ret = -EINVAL;

           goto cleanup_matches;

      }

 

      (*i)++;

      return 0;

 

 cleanup_matches:

      IPT_MATCH_ITERATE(e, cleanup_match, &j);

      return ret;

}

 

 

再看一下IPT_MATCH_ITERATE宏的定义:

#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_entry_match结构出现了,就是说,到这里为止,entry结构中的match结构已经由ipt_match替换成了ipt_entry_match,当然这只是形式上,因为具体结构还是有区别,所以还要对新的ipt_entry_match做一些初始化,也就是把ipt_match里的实际内容关联过来

 

 

check_match()match结构进行检查:

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双向链表中查找对应结点

可能有一点疑问就是为什么用m->u.user.name作为名字来查找一个ipt_match,在定义ipt_entry_match的时候应该只是把它的指针指向了ipt_match的开头位置,并没有对里面的name变量赋值吧。

我猜想是这两个结构里第一个变量分别是一个list_head结构体和一个u_int16_t,它们都应该是一个(还是两个?)地址变量,所以占用同样的空间,那么两个作为结构里第二个参数的字符串name[IPT_FUNCTION_MAXNAMELEN] 就刚好重合了 */

      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 (!try_module_get(match->me)) {

           up(&ipt_mutex);

           return -ENOENT;

      }

 

/* 再回到开头的do_match()函数,这下全部联系起来了吧 */

      m->u.kernel.match = match;

      up(&ipt_mutex);

 

/* 调用match里的checkentry做一些检查 */

      if (m->u.kernel.match->checkentry

          && !m->u.kernel.match->checkentry(name, ip, m->data,

                                  m->u.match_size - sizeof(*m),

                                  hookmask)) {

           module_put(m->u.kernel.match->me);

           duprintf("ip_tables: check failed for `%s'.\n",

                  m->u.kernel.match->name);

           return -EINVAL;

      }

 

      (*i)++;

      return 0;

}

 

还有一点,这里并没有讲到具体的match的实现,包括每个match是如何放进entry里,entry又是如何放进table里的。也就是说,分析了半天,实际上我们的table里的entry部分根本就是空的,不过也对,内核在初始化netfilter时只是注册了3个表(filternatmangle),而里面的规则本来就是空的。至于具体的entrymatch是如何加入进来的,就是netfilter在用户空间的配置工具iptables的任务了。

阅读(934) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~