(十四)洞悉linux下的Netfilter&iptables:开发一个match模块【实战】


                  2012-05-31 21:00:14

分类: LINUX

自己开发一个match模块

       今天我们来写一个很简单的match来和大家分享如何为iptables开发扩展功能模块。这个模块是根据IP报文中有效载荷字段的长度来对其进行匹配,支持固定包大小,也支持一个区间范围的的数据包,在用户空间的用法是:

         iptables -A FORWARD -m pktsize --size XX[:YY] -j DROP

       这条规则在FORWARD链上对于大小为XX(或大小介于XXYY之间)的数据包进行匹配,数据包的长度不包括IP头部的长度。为了简单起见,这个模块没有处理“!”情况,因为只是阐述开发过程。

       OK,下面我们开始动手吧。我们这个模块名字叫pktsize,所以内核中该模块对应的文件是ipt_pktsize.hipt_pktsize.c;用户空间的文件名为libipt_pktsize.c

       我们先来定义头文件,因为这个匹配模块功能很单一,所以设计它的数据结构主要包含两部分,如下:

#ifndef __IPT_PKTSIZE_H

#define __IPT_PKTSIZE_H

 

#define PKTSIZE_VERSION "0.1"

//我们自己定义的用户保存规则中指定的数据包大小的结构体

struct ipt_pktsize_info {

    u_int32_t min_pktsize,max_pktsize; //数据包的最小和最大字节数(不包括IP)

};

 

#endif //__IPT_EXLENGTH_H

 

一、用户空间的开发

  我们知道用户空间的match是用struct iptables_match{}结构表示的,所以我们需要去实例化一个该对象,然后对其关键成员进行初始化赋值。一般情况我们需要实现parse函数、help函数、final_check函数、printsave函数就已经可以满足基本要求了。我们先把整体代码框架搭起来:

#include

#include

#include

#include

#include

#include

#include

#include

 

static void help(void)

{

    //Todo:  your code

}

 

/*用于解析命令行参数的回调函数; 如果成功则返回true */

static int

parse(int c, char **argv, int invert, unsigned int *flags,

        const void *entry,

        struct ipt_entry_match **match)

{

        return 1;

}

 

static void final_check(unsigned int flags)

{

     //Todo:  your code

}

 

static void print(const void *ip, const struct ipt_entry_match *match, int numeric)

{

    //Todo:  your code

}

 

static void save(const void *ip, const struct ipt_entry_match *match)

{

    //Todo:  your code

}

 

static struct iptables_match pktsize=

{

    .next           = NULL,

    .name           = "pktsize",

    .version        = IPTABLES_VERSION,

    .size           = IPT_ALIGN(sizeof(struct ipt_pktsize_info)),

    .userspacesize  = IPT_ALIGN(sizeof(struct ipt_pktsize_info)),

    .help           = &help,

    .parse          = &parse,

    .final_check    = &final_check,

    .print          = &print,

    .save           = &save

};

 

void _init(void)

{

    register_match(&pktsize);

}

        下面我们分别来实现这些回调函数,并对其做简单解释:

 

  help()函数:当我们在命令输入iptables -m pktsize -h时用于显示该模块用法的帮助信息,所以很简单,你想怎么提示用户都可以:

static void help(void)

{

    printf(

    "pktsize v%s options:\n"

    " --size size[:size]        Match packet size against value or range\n"

    "\nExamples:\n"

    " iptables -A FORWARD -m pktsize --size 65 -j DROP\n"

    " iptables -A FORWARD -m pktsize --size 80:120 -j DROP\n"

    , PKTSIZE_VERSION);

}

        

  print()函数:该函数是用于打印用户的输入参数的,因为其他地方也有可能会输出规则参数,所以我们将其封装成一个子函数__print()供其他人来调用,如下:

static void __print(struct ipt_pktsize_info * info){

    if (info->max_pktsize == info->min_pktsize)

        printf("%u ", info->min_pktsize);

    else

        printf("%u:%u ", info->min_pktsize, info->max_pktsize);

}

 

static void print(const void *ip, const struct ipt_entry_match *match, int numeric)

{

    printf("size ");

    __print((struct ipt_pktsize_info *)match->data);

}

        从命令行终端输入的数据包大小的规则参数“XX:YY”其实最终是在ipt_entry_match结构体的data成员里保存着的,关于该结构体参见博文三的图解。


        save()函数:该函数和print类似:

static void save(const void *ip, const struct ipt_entry_match *match)

{

        printf("--size ");

        __print((struct ipt_pktsize_info *)match->data);

}

        

       final_check()函数:如果你的模块有些长参数格式是必须的,那么当用户调用了你的模块但又没进一步制定必须参数时,一般在这个函数里做校验限制。如,我的模块带了一个必须按参数--size ,而且后面必须跟数值,所以该函数内容如下:

static void final_check(unsigned int flags)

{

    if (!flags)

       exit_error(PARAMETER_PROBLEM,

        "\npktsize-parameter problem: for pktsize usage type: iptables -m pktsize --help\n");

}

 

       parse()函数:该函数是我们的核心,参数的解析最终是在该函数中完成的。因为我们用到长参数格式,所以必须引入一个结构体struct option{},我们在博文十三中已经见过,不清楚原理和用法的童鞋可以回头复习一下。

       这里我们的模块只有一个扩展参数,所以该结构非常简单,如果你有多个,则必须一一处理:

static struct option opts[] = {

        { "size", 1, NULL, '1' },

        {0}

};

//并且还要将该结构体对象赋给:pktsize.extra_opts= opts;

//解析参数的具体函数单独出来,会使得parse()函数的结构很优美

/* 我们的输入参数的可能格式如下:

         xx         指定数据包大小 XX

         :XX       范围是0~XX   

         YY:       范围是YY~65535

         xx:YY    范围是XX~YY

*/

static void parse_pkts(const char* s,struct ipt_pktsize_info *info){

   char* buff,*cp;

   buff = strdup(s);

 

   if(NULL == (cp=strchr(buff,':'))){

      info->min_pktsize = info->max_pktsize = strtol(buff,NULL,0);

   }else{

      *cp = '\0';

      cp++;

 

      info->min_pktsize = strtol(buff,NULL,0);

      info->max_pktsize = (cp[0]? strtol(cp,NULL,0):0xFFFF);

   }

 

   free(buff);

 

   if (info->min_pktsize > info->max_pktsize)

       exit_error(PARAMETER_PROBLEM,

                "pktsize min. range value `%u' greater than max. "

                "range value `%u'", info->min_pktsize, info->max_pktsize);

}

 

static int

parse(int c, char **argv, int invert, unsigned int *flags,

        const void *entry,

        struct ipt_entry_match **match)

{

   struct ipt_pktsize_info *info = (struct ipt_pktsize_info *)(*match)->data;

   switch(c){

      case '1':

        if (*flags)

           exit_error(PARAMETER_PROBLEM,

                      "size: `--size' may only be "

                       "specified once");

        parse_pkts(argv[optind-1], info);

        *flags = 1;

      break;

      default:

        return 0;

   }

   return 1;

}

      该文件的最终版本从“ libipt_pktsize.zip   ”下载。

 用户空间要用的libipt_pktsize.so的源代码我们就算编写完成了,迫不及待的去试一下吧。当前,我的iptables确实不认识pktsize模块。23069658_133847009195P1.jpg

 我将libipt_pktsize.c拷贝到/usr/src/iptables-1.4.0/ extensions目录下,并修改该目录下的Makefile文:23069658_1338470096ZhoL.jpg

 然后在/usr/src/iptables-1.4.0/目录下单独执行一次make命令,最后将extensions/目录下编译出来的libipt_pktsize.so拷贝到iptables的库目录里,例如/lib/iptables-1.4.0/iptables

       此时,当我们再在命令行执行一次iptables -m pktsize -h时,在末尾处可以看到如下的信息: 23069658_1338470101QTLi.jpg

      就证明我们的模块已经被iptables正确识别并成功加载了。

 

一、内核空间的开发

同样的,开发内核的Netfilter模块时,我们还是先搭其框架:

#include

#include

#include

#include

#include

#include

 

MODULE_AUTHOR("Koorey Wung ");

MODULE_DESCRIPTION("iptables pkt size range match module.");

MODULE_LICENSE("GPL");

 

static int

match(const struct sk_buff *skb,

      const struct net_device *in,

      const struct net_device *out,

      const struct xt_match *match,

      const void *matchinfo,

      int offset,

      unsigned int protoff,

      int *hotdrop)

{

    return 1;

}

 

 

static struct ipt_match pktsize_match = {

        .name           = "test",

        .family          = AF_INET,

        .match          = match,

        .matchsize       = sizeof(struct ipt_pktsize_info),

        .destroy         = NULL,

        .me             = THIS_MODULE,

};

 

static int __init init(void)

{

   return xt_register_match(&pktsize_match);

}

 

static void __exit fini(void)

{

    xt_unregister_match(&pktsize_match);

}

 

module_init(init);

module_exit(fini);

 

  通过前面几篇博文我们已经知道,内核中用struct ipt_match{}结构来表示一个match模块。我们要开发match的内核部分时,也必须去实例化一个struct ipt_match{}对象,然后对其进行必要的初始化设置,最后通过xt_register_match()将其注册到xt[AF_INET].match全局链表中就OK了,就这么简单。

  我们这里例子非常简单,只实现最关键的核心函数:match()函数。不过这已经满足我们需求了,我们的match函数做的事情也很simple,就是计算数据包的有效载荷:

static int

match(const struct sk_buff *skb,

      const struct net_device *in,

      const struct net_device *out,

      const struct xt_match *match,

      const void *matchinfo,

      int offset,

      unsigned int protoff,

      int *hotdrop)

{

        const struct ipt_pktsize_info *info = matchinfo;

        const struct iphdr *iph = skb->nh.iph;

 

        int pkttruesize = ntohs(iph->tot_len)-(iph->ihl*4);

 

        if(pkttruesize>=info->min_pktsize && pkttruesize <=info->max_pktsize){

                return 1;

        }

        else{

                return 0;

        }

    return 1;

}

 但有一点需要明确,如果数据包匹配了match函数返回1;否则返回0.

 该文件的最终版本从“ ipt_pktsize.zip   ”下载。

       至此,我们的pktsize模块的内核部分就算开发完了,接下将其编译成ipt_pktsize.ko放到系统目录中去。详细参见博文十三,我系统执行了如下步骤: 23069658_133847010876Kb.jpg

 当我们的模块已经被内核认亲后,那感觉真的是无以言表啊。废话不多说,我们赶紧执行一条规则看看:23069658_1338470111P0fY.jpg

       曾经有个哥们说他在使用owner模块时出现了同样的问题,这会不会是由于同样的原因导致的呢?如果你是严格遵循我的教程来的,那么这里我要说一定就是:这个问题是我特意留出的。细心的童鞋回头看代码时应该很容易找出问题了。原因:23069658_1338470122wz0w.jpg

       这里有一点要提醒大家注意,内核中的模块名和用户空间的模块名必须一致。这里我们将pktsize_mach.name改为“pktsize”,重新编译,然后将其拷贝。在重新执行insmod前,先执行rmmod ipt_pktsize将原来的模块卸载掉,最后再次执行那条规则:

23069658_1338470129zp05.jpg

      今天通过这个简单的例子,向大家示范一下为Netfitler/iptables开发功能模块的方法。整体来说还是比较简单,当然要写出更有意义,更高效的模块需要对协议栈、TCP/IP原理、网络编程等有较好的基础才行。

你可能感兴趣的:(netfilter,iptables,iptables,netfilter)