(十四)洞悉linux下的Netfilter&iptables:開發一個match模塊【實戰】

自己開發一個match模塊

       今天我們來寫一個很簡單的match來和大家分享如何爲iptables開發擴展功能模塊。這個模塊是根據IP報文中有效載荷字段的長度來對其進行匹配,支持固定包大小,也支持一個區間範圍的的數據包,在用戶空間的用法是:

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

       這條規則在FORWARD鏈上對於大小爲XX(或大小介於XX和YY之間)的數據包進行匹配,數據包的長度不包括IP頭部的長度。爲了簡單起見,這個模塊沒有處理“!”情況,因爲只是闡述開發過程。

       OK,下面我們開始動手吧。我們這個模塊名字叫pktsize,所以內核中該模塊對應的文件是ipt_pktsize.h和ipt_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函數、print和save函數就已經可以滿足基本要求了。我們先把整體代碼框架搭起來:

#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模塊。

 我將libipt_pktsize.c拷貝到/usr/src/iptables-1.4.0/ extensions目錄下,並修改該目錄下的Makefile文:

 

 然後在/usr/src/iptables-1.4.0/目錄下單獨執行一次make命令,最後將extensions/目錄下編譯出來的libipt_pktsize.so拷貝到iptables的庫目錄裏,例如/lib/iptables-1.4.0/iptables。

       此時,當我們再在命令行執行一次iptables -m pktsize -h時,在末尾處可以看到如下的信息:

 

      就證明我們的模塊已經被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放到系統目錄中去。詳細參見博文十三,我係統執行了如下步驟:

 

 當我們的模塊已經被內核認親後,那感覺真的是無以言表啊。廢話不多說,我們趕緊執行一條規則看看:

 

       曾經有個哥們說他在使用owner模塊時出現了同樣的問題,這會不會是由於同樣的原因導致的呢?如果你是嚴格遵循我的教程來的,那麼這裏我要說一定就是:這個問題是我特意留出的。細心的童鞋回頭看代碼時應該很容易找出問題了。原因:

 

       這裏有一點要提醒大家注意,內核中的模塊名和用戶空間的模塊名必須一致。這裏我們將pktsize_mach.name改爲“pktsize”,重新編譯,然後將其拷貝。在重新執行insmod前,先執行rmmod ipt_pktsize將原來的模塊卸載掉,最後再次執行那條規則:

 

      今天通過這個簡單的例子,向大家示範一下爲Netfitler/iptables開發功能模塊的方法。整體來說還是比較簡單,當然要寫出更有意義,更高效的模塊需要對協議棧、TCP/IP原理、網絡編程等有較好的基礎才行。

      未完,待續…

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章