注册 登录  
 加关注
   显示下一条  |  关闭
温馨提示!由于新浪微博认证机制调整,您的新浪微博帐号绑定已过期,请重新绑定!立即重新绑定新浪微博》  |  关闭

BCB-DG's Blog

...

 
 
 

日志

 
 

Linux Netfilter 源码分析二  

2013-10-04 15:31:13|  分类: Linux |  标签: |举报 |字号 订阅

  下载LOFTER 我的照片书  |
//轉

四、nf_hook_ops 钩子的注册

filter表的初始化函数static int __init init(void)中除了有一个nf_register_hook函数注册一个tables外,还由nf_register_hook函数注册了3hook

 

4.1        nf_hook_ops数据结构 netfilter.h

struct nf_hook_ops
{
        struct list_head list;                        //
链表成员
        /* User fills in from here down. */
        nf_hookfn *hook;                        //
钩子函数指针
        struct module *owner;
        int pf;                                        //
协议簇,对于ipv4而言,是PF_INET
        int hooknum;                                //hook
类型
        /* 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   
在数据包离开本地主机上线之前

再看看它的初始化,仍以filter表为例

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 }
};

 

 

4.2   int nf_register_hook函数   netfilter.c

注册实际上就是在一个nf_hook_ops链表中再插入一个nf_hook_ops结构

int nf_register_hook(struct nf_hook_ops *reg)

{

      struct list_head *i;

 

      spin_lock_bh(&nf_hook_lock);

      list_for_each(i, &nf_hooks[reg->pf][reg->hooknum]) {

           if (reg->priority < ((struct nf_hook_ops *)i)->priority)

                 break;

      }

      list_add_rcu(&reg->list, i->prev);

      spin_unlock_bh(&nf_hook_lock);

 

      synchronize_net();

      return 0;

}

list_for_each 函数遍历当前待注册的钩子的协议pfHook类型所对应的链表,其首地址是&nf_hooks[reg->pf][reg->hooknum],如果当前待注册钩子的优先级小于匹配的的节点的优先级,则找到了待插入的位置,也就是说,按优先级的升序排列。

list_add_rcu
把当前节点插入到查到找的适合的位置,这样,完成后,所有pf协议下的hooknum类型的钩子,都被注册到&nf_hooks[reg->pf][reg->hooknum]为首的链表当中了。

 

 

 

4.3  ipt_hook钩子函数   iptable_raw.c

注册nf_hook_ops,也就向内核注册了一个钩子函数,这些函数有ipt_hookipt_local_hookipt_route_hookipt_local_out_hook等。

前面在nf_iterate()里调用的钩子函数就是它了

下面是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 *))     /* 默认处理函数 */

{

  /* 参数&packet_filter是由注册该nf_hook_ops的表(filter)决定的,也有可能是&packet_raw */

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

}

实际上是直接调用ipt_do_table(ip_tables.c)函数

接下来就是根据table里面的entry来处理数据包了

一个table就是一组防火墙规则的集合

而一个entry就是一条规则,每个entry由一系列的matches和一个target组成

一旦数据包匹配了该某个entry的所有matches,就用target来处理它

Match又分为两部份,一部份为一些基本的元素,如来源/目的地址,进/出网口,协议等,对应了struct ipt_ip,我们常常将其称为标准的match,另一部份match则以插件的形式存在,是动态可选择,也允许第三方开发的,常常称为扩展的match,如字符串匹配,p2p匹配等。同样,规则的target也是可扩展的。这样,一条规则占用的空间,可以分为:struct ipt_ip+n*match+n*target,(n表示了其个数,这里的match指的是可扩展的match部份)。

五、 ipt_do_table()函数,数据包的过滤

 

5.1          ipt_entry 相关结构  ip_tables.h

ipt_entry结构前面有过了,再看一遍

struct ipt_entry
{
struct ipt_ip ip;
/*
所要匹配的报文的IP头信息 */
unsigned int nfcache;
/*
位向量,标示本规则关心报文的什么部分,暂未使用 */
u_int16_t target_offset;
/* target
区的偏移,通常target区位于match区之后,而match区则在ipt_entry的末尾;
初始化为sizeof(struct ipt_entry),即假定没有match */
u_int16_t next_offset;
/*
下一条规则相对于本规则的偏移,也即本规则所用空间的总和,
初始化为sizeof(struct ipt_entry)+sizeof(struct ipt_target),即没有match */
unsigned int comefrom;
/*
位向量,标记调用本规则的HOOK号,可用于检查规则的有效性 */
struct ipt_counters counters;
/*
记录该规则处理过的报文数和报文总字节数 */
unsigned char elems[0];
/*target
或者是match的起始位置 */
}

 

ipt_ip结构  ip_tables.h

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];

 

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

     

      u_int8_t flags;      /* 标志字段 */

      u_int8_t invflags;    /* 取反标志 */

};

 

 

5.2  ipt_do_table函数   ip_tables.c

 

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]     \

                 __attribute__((aligned(sizeof(long))));

      u_int16_t offset;

      struct iphdr *ip;

      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;                /* 获取IP */

      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));

/*检验HOOKdebug用的*/

/*获取当前表的当前CPU的规则入口*/

      table_base = (void *)table->private->entries

           + TABLE_OFFSET(table->private, smp_processor_id());

/*获得当前表的当前Hook的规则的起始偏移量*/ 

      e = get_entry(table_base, table->private->hook_entry[hook]);

 

/*获得当前表的当前Hook的规则的上限偏移量*/

      /* For return from builtin chain */

      back = get_entry(table_base, table->private->underflow[hook]);

/*  do ……  while(!hotdrop

      进行规则的匹配   */

      do {

           IP_NF_ASSERT(e);

           IP_NF_ASSERT(back);

           (*pskb)->nfcache |= e->nfcache;

 

/*

   匹配IP包,成功则继续匹配下去,否则跳到下一个规则  

   ip_packet_match匹配标准match, 也就是ip报文中的一些基本的元素,如来源/目的地址,进/出网口,协议等,因为要匹配的内容是固定的,所以具体的函数实现也是固定的。

   IPT_MATCH_ITERATE (应该猜到实际是调用第二个参数do_match函数)匹配扩展的match,如字符串匹配,p2p匹配等,因为要匹配的内容不确定,所以函数的实现也是不一样的,所以do_match的实现就和具体的match模块有关了。 

   这里的&e->ip就是上面的ipt_ip结构

*/

           if (ip_packet_match(ip, indev, outdev, &e->ip, offset)) {

                 struct ipt_entry_target *t;

 

                 if (IPT_MATCH_ITERATE(e, do_match,

                                  *pskb, in, out,

                                  offset, &hotdrop) != 0)

                      goto no_match;    /*不匹配则跳到 no_match,往下一个规则*/

 

        /* 匹配则继续执行 */

      /* 这个宏用来分别处理字节计数器和分组计数器这两个计数器 */

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

 

      /*获取规则的target的偏移地址*/

                 t = ipt_get_target(e);

                 IP_NF_ASSERT(t->u.kernel.target);

 

      /* 下面开始匹备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 {

                      verdict = t->u.kernel.target->target(pskb,

                                                  in, out,

                                                  hook,

                                                  t->data,

                                                  userdata);

 

                      /* Target might have changed stuff. */

                      ip = (*pskb)->nh.iph;

                      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);

 

      read_unlock_bh(&table->lock);

 

#ifdef DEBUG_ALLOW_ALL

      return NF_ACCEPT;

#else

      if (hotdrop)

            return NF_DROP;

      else return verdict;

#endif

}

 

 

5.3   标准的match   ip_packet_match函数  ip_tables.c

 

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;

 

/*定义一个宏,当boolinvflg的是一真一假的情况时,返回真。注意这里使用两个的目的是使得这样计算后的值域只取01两个值*/

#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语句后,处理接口的取反问题:当接口不匹配并且接口不取反,或者接口匹配,但是接口取反,说明接口不匹配。*/

      /* Look for ifname matches; this should unroll nicely. */

 

/*输入接口*/

      for (i = 0, ret = 0; i < IFNAMSIZ/sizeof(unsigned long); i++) {

           ret |= (((const unsigned long *)indev)[i]

                 ^ ((const unsigned long *)ipinfo->iniface)[i])

                 & ((const unsigned long *)ipinfo->iniface_mask)[i];

      }

 

      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)[i]

                 ^ ((const unsigned long *)ipinfo->outiface)[i])

                 & ((const unsigned long *)ipinfo->outiface_mask)[i];

      }

 

      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;

      }

 

/* 检查协议是否匹配 */

      /* Check specific protocol */

      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;       /* 以上所有都匹配则返回1 */

}

六、 扩展的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的任务了。


七、 target 匹配

7.1 ipt_targetipt_entry_target结构      ip_tables.h

ipt_targetipt_match结构类似:

struct ipt_target

{

      struct list_head list;

 

      const char name[IPT_FUNCTION_MAXNAMELEN];

 

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

      int (*checkentry)(const char *tablename,

                   const struct ipt_entry *e,

                   void *targinfo,

                   unsigned int targinfosize,

                   unsigned int hook_mask);

 

/* 在包含本Target的规则从表中删除时调用,与checkentry配合可用于动态内存分配和释放 */

      void (*destroy)(void *targinfo, unsigned int targinfosize);

 

/* target的模块函数,如果需要继续处理则返回IPT_CONTINUE-1),否则返回NF_ACCEPTNF_DROP等值,它的调用者根据它的返回值来判断如何处理它处理过的报文*/

      unsigned int (*target)(struct sk_buff **pskb,

                        const struct net_device *in,

                        const struct net_device *out,

                        unsigned int hooknum,

                        const void *targinfo,

                        void *userdata);

 

/* 表示当前Target是否为模块(NULL为否) */

      struct module *me;

};

 

 

 

ipt_entry_targetipt_entry_match也几乎一模一样:

struct ipt_entry_target

{

      union {

           struct {

                 u_int16_t target_size;

                 char name[IPT_FUNCTION_MAXNAMELEN];

           } user;

 

           struct {

                 u_int16_t target_size;

                 struct ipt_target *target;

           } kernel;

 

           u_int16_t target_size;

      } u;

 

      unsigned char data[0];

};

 

看上去targetmatch好像没有区别,但当然,一个是条件,一个是动作,接着往下看是不是真的一样

 

之前有两个地方出现了ipt_target,一次是在ipt_do_table()函数里,当匹配到match后开始匹配target,另一次是在check_entry()里,检查完match后开始检查target

先看前一个

 

7.2  ipt_standard_target结构    ip_tables.h

再看一次ipt_do_table这个函数,前面匹配match的部分略过,从匹配match成功的地方开始:

ipt_do_table( )

{

………   /* 略去 */

           if (ip_packet_match(ip, indev, outdev, &e->ip, offset)) {

                 struct ipt_entry_target *t;

 

                 if (IPT_MATCH_ITERATE(e, do_match,

                                  *pskb, in, out,

                                  offset, &hotdrop) != 0)

                      goto no_match;

/* 这里开始说明匹配match成功了,开始匹配target */

 

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

 

/* ipt_get_target获取当前targett是一个ipt_entry_target结构,这个函数就是简单的返回e+e->target_offset

每个entry只有一个target,所以不需要像match一样遍历,直接指针指过去了*/

                 t = ipt_get_target(e);

                 IP_NF_ASSERT(t->u.kernel.target);

/* 这里都还是和扩展的match的匹配很像,但是下面一句

有句注释:Standard target? 判断当前target是否标准的target

而判断的条件是u.kernel.target->target,就是ipt_target结构里的target函数是否为空,而下面还出现了ipt_standard_target结构和verdict变量,好吧,先停下,看看ipt_standard_target结构再说 */

                 if (!t->u.kernel.target->target) {

                      int v;

 

                      v = ((struct ipt_standard_target *)t)->verdict;

                      if (v < 0) {

      ……      /* 略去 */

}

 

 

ipt_standard_target的定义:

struct ipt_standard_target

{

      struct ipt_entry_target target;

      int verdict;

};

也就比ipt_entry_target多了一个verdict(判断),请看前面的nf_hook_slow()函数,里面也有verdict变量,用来保存hook函数的返回值,常见的有这些

#define NF_DROP 0

#define NF_ACCEPT 1

#define NF_STOLEN 2

#define NF_QUEUE 3

#define NF_REPEAT 4

#define RETURN     IPT_RETURN

#define IPT_RETURN     (-NF_MAX_VERDICT - 1)

#define NF_MAX_VERDICT NF_REPEAT 

我们知道chain(链)是某个检查点上检查的规则的集合。除了默认的chain外,用户还可以创建新的chain。在iptables中,同一个chain里的规则是连续存放的。默认的chain的最后一条规则的targetchainpolicy。用户创建的chain的最后一条规则的target的调用返回值是NF_RETURN,遍历过程将返回原来的chain。规则中的target也可以指定跳转到某个用户创建的chain上,这时它的targetipt_stardard_target,并且这个targetverdict值大于0。如果在用户创建的chain上没有找到匹配的规则,遍历过程将返回到原来chain的下一条规则上。

 

事实上,target也是分标准的和扩展的,但前面说了,毕竟一个是条件,一个是动作,target的标准和扩展的关系和match还是不太一样的,不能一概而论,而且在标准的target里还可以根据verdict的值再划分为内建的动作或者跳转到自定义链

简单的说,标准target就是内核内建的一些处理动作或其延伸

扩展的当然就是完全由用户定义的处理动作

再看if (!t->u.kernel.target->target) 就明白了,如果target函数是空的,就是标准target,因为它不需要用户再提供target函数了,而反之是就是扩展的target,那么再看ipt_do_table()吧,还是只看一部分,否则眼花。

 

if (!t->u.kernel.target->target) {

           /* 如果target为空,是标准target */

                 int v;

                 v = ((struct ipt_standard_target *)t)->verdict;

                 if (v < 0) {

/*v小于0,动作是默认内建的动作,也可能是自定义链已经结束而返回return标志*/

                      if (v != IPT_RETURN) {    /*如果不是Return,则是内建的动作*/

                            verdict = (unsigned)(-v) - 1;

                            break;

                      }

                      e = back; 

/* eback分别是当前表的当前Hook的规则的起始偏移量和上限偏移量,即entry的头和尾,e=back */

 

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

                      continue;

                 }

/* v大于等于0,处理用户自定义链,如果当前链后还有规则,而要跳到自定义链去执行,那么需要保存一个back点,以指示程序在匹配完自定义链后,应当继续匹配的规则位置,自然地, back点应该为当前规则的下一条规则(如果存在的话)

至于为什么下一条规则的地址是table_base+v, 就要去看具体的规则是如何添加的了 */

                 if (table_base + v!= (void *)e + e->next_offset) {

           /* 如果还有规则 */

                            /* Save old back ptr in next entry */

                            struct ipt_entry *next= (void *)e + e->next_offset;

                            next->comefrom= (void *)back - table_base;

                            /* set back pointer to next entry */

                            back = next;

                      }

 

                      e = get_entry(table_base, v);

                 } else {

           /* 如果是扩展的target,则调用target函数,返回值给verdict */

                      verdict = t->u.kernel.target->target(pskb,

                                                  in, out,

                                                  hook,

                                                  t->data,

                                                  userdata);

 

           /*Target函数有可能已经改变了stuff,所以这里重新定位指针*/

                      ip = (*pskb)->nh.iph;

                      datalen = (*pskb)->len - ip->ihl * 4;

        /*如果返回的动作是继续检查下一条规则,则设置当前规则为下一条规则,继续循环,否则,就跳出循环,因为在ipt_do_table函数末尾有return verdict;表明,则将target函数决定的返回值返回给调用函数nf_iterate,由它来根据verdict决定数据包的命运*/

                      if (verdict == IPT_CONTINUE)

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

                      else

                            /* Verdict */

                            break;

                 }

  评论这张
 
阅读(1273)| 评论(0)
推荐 转载

历史上的今天

评论

<#--最新日志,群博日志--> <#--推荐日志--> <#--引用记录--> <#--博主推荐--> <#--随机阅读--> <#--首页推荐--> <#--历史上的今天--> <#--被推荐日志--> <#--上一篇,下一篇--> <#-- 热度 --> <#-- 网易新闻广告 --> <#--右边模块结构--> <#--评论模块结构--> <#--引用模块结构--> <#--博主发起的投票-->
 
 
 
 
 
 
 
 
 
 
 
 
 
 

页脚

网易公司版权所有 ©1997-2017