解析Linux下Netfilter & iptables:开发一个match模块

http://www.bairimeng.net/2012/11/27/netfilter_iptables_matc/

一、说明
最近的项目需要软件组基于Netfilter和iptables开发Linux内核模块,以完成一系列防火墙功能,说白了防火墙就是过滤规则。
为了熟悉Netfilter和iptables的开发,于是开发过程中写下这篇笔记,以达到温故知新的作用。

(盗图自重=///=)
上图说明了Netfilter模块是如何运行的,它指出我们需要开发两个东西,一个是用户态的共享库so,一个是内核态的内核库ko。
命名规则有讲究,如果模块叫Mymodules,那么内核态源代码一般命名为ipt_Mymodules.c,头文件为ipt_Mymodules.h,用户态源代码为libipt_Mymodules.c。
我的Linux系统内核是2.6.27.41,iptables版本是1.4.3.2,因为Linux网路部分有些许头文件和2.6.24之前有些许差异,而iptables也有些概念和低版本有差异,所以将逐步记录。

二、模块描述
为了练习开发过程,和我看的资料一样,我们来设计一个最简单的模块,这个模块能匹配IP报文中有效荷载字段。用法如下:

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

关于iptables的资料请自行百度~上述规则说明,在FORWARD挂载点上对于大小在XX[到YY,可省略]的数据包进行匹配,数据包长度不包括IP头。
从规则可以看到我们的模块名为pktsize,所以我们要建立3个新文件,分别是ipt_pktsize.c,libipt_pktsize.c,ipt_pktsize.h。
因为头文件两边均要用到,所以我们先来定义头文件ipt_pktsize.h。

?
1
2
3
4
5
6
7
8
9
10
ifndef __IPT_PKTSIZE_H
#define __IPT_PKTSIZE_H
 
#define PKTSIZE_VERSION "0.1"
// 我们自己定义的用户保存规则中指定档数据包大小的结构体
struct ipt_pktsize_info {
     // 数据包的最小和最大字节数,不包括IP头
     u_int32_t min_pktsize, max_pktsize;
};
#endif // __IPT_EXLENGTH_H

我们定义了一个结构体ipt_pktsize_info,内含2个成员,代表什么一目了然。我们还定义了这个模块的版本号。

三、用户态开发
在netfilter/iptables体系中,我们使用struct xtables_match{}结构来表示用户态的match,所以我们要实例化一个这个结构,并赋上必要的初值,这个结构的详细定义在iptables的源码include/xtables.h中。
在我们开发用户态模块中,一般要实现以下几个函数:
1>help()
2>parse()
3>final_check()
4>print()
5>save()
详细解释将在代码中给出,本身已经到了猫叫喵喵的程度了,相信都知道是大致干嘛的。

OK,开始动手,我们先搭框架:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
#include
#include
#include
#include
#include
#include
#include
#include
   
static void PKTSIZE_help( void )
{
     // >>>TODO<<<
}
  
static int PKTSIZE_parse( int c, char ** argv, int invert, 
     unsigned int * flags, const void * entry,
     struct xt_entry_match** match)
{
     // >>>TODO<<<
     return 1;
}
  
static void PKTSIZE_final_check(unsigned int flags)
{
     // >>>TODO<<<
}
   
static void __print( struct ipt_pktsize_info* info)
{
     // >>>TODO<<<
}
  
static void PKTSIZE_print( const void * ip,
     const struct xt_entry_match* match, int numeric)
{
     // >>>TODO<<<
}
   
static void PKTSIZE_save( const void * ip,
      const struct xt_entry_match* match)
{
     // >>>TODO<<<
}
  
static struct xtables_match pktsize =
{
     .next       = NULL,
     .name       = "pktsize" ,
     .version    = XTABLES_VERSION,
     .family     = NFPROTO_IPV4,
     .size       = XT_ALIGN( sizeof ( struct ipt_pktsize_info)),
     .userspacesize  = XT_ALIGN( sizeof ( struct ipt_pktsize_info)),
     .help       = PKTSIZE_help,
     .parse      = PKTSIZE_parse,
     .final_check    = PKTSIZE_final_check,
     .print      = PKTSIZE_print,
     .save       = PKTSIZE_save,
     // .extra_opts  = PKTSIZE_opts    这一句以后将被添上
};
  
void _init( void )
{
     xtables_register_match(&pktsize);
}

下面我们来填充所有要实现的函数,并做解释。

help():当我们在命令行输入iptables -m pktsize -h时 用于显示该模块用法的帮助信息。

?
1
2
3
4
5
6
7
8
9
10
11
static void PKTSIZE_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() 供其他人调用。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
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 PKTSIZE_print( const void * ip,
     const struct xt_entry_match* match, int numeric)
{
     printf ( "size " );
     __print(( struct ipt_pktsize_info *)match->data);
}

可以注意到print()函数传入一个xt_entry_match结构体,事实上我们的ipt_pktsize_info数据存在这个结构体的data成员中。
xt_entry_match的详细定义在iptables源码的include/linux/netfilter/x_tables.h中。

save():该函数跟print类似 。

?
1
2
3
4
5
6
static void PKTSIZE_save( const void * ip,
      const struct xt_entry_match* match)
{
     printf ( "--size " );
     __print(( struct ipt_pktsize_info*)match->data);
}

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

?
1
2
3
4
5
6
static void PKTSIZE_final_check(unsigned int flags)
{
     if (!flags)
         xtables_error(PARAMETER_PROBLEM, 
             "\npktsize-parameter problem:for pktsize usage type:iptables -m pktsize --help\n" );
}

parse():这是我们的核心,用于解析命令行参数的回调函数,成功则返回true 该函数是核心,参数的解析最终是在该函数中完成的,因为我们 用到长参数格式,所以必须引入一个结构体struct option{}。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
static struct option PKTSIZE_opts[] =
{
     { "size" , 1, NULL, '1' },
     {0}
};
  
// 并且还要将结构体对象赋值给
// pktsize.extra_opts = opts;
  
// 解析参数的具体函数单独出来,会使parse()函数结构很优美
// 我们的输入参数可能格式如下:
//  xx  指定数据包大小xx
//  :x x   范围是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)
         xtables_error(PARAMETER_PROBLEM,
             "pktsize min.range value '%u' greater than max.range value '%u'" ,
             info->min_pktsize,
             info->max_pktsize
             );
}
  
static int PKTSIZE_parse( int c, char ** argv, int invert, 
     unsigned int * flags, const void * entry,
     struct xt_entry_match** match)
{
     struct ipt_pktsize_info* info = ( struct ipt_pktsize_info*)(*match)->data;
     switch (c)
     {
         case '1' :
             if (*flags)
                 xtables_error(PARAMETER_PROBLEM,
                 "size: '--size' may only be specified once"
                 );
             parse_pkts(argv[optind-1], info);
             *flags = 1;
             break ;
         default :
             return 0;
     }
     return 1;
}

OK,用户态的所有功能完成,完整代码如下:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
#include
#include
#include
#include
#include
#include
#include
#include
  
// help()TODO: 当我们在命令行输入iptables -m pktsize -h时
// 用于显示该模块用法的帮助信息。
static void PKTSIZE_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
     );
}
  
// parse()TODO:用于解析命令行参数的回调函数,成功则返回true
// 该函数是核心,参数的解析最终是在该函数中完成的,因为我们
// 用到长参数格式,所以必须引入一个结构体struct option{}。 
  
// 这里只有一个扩展参数,所以结构简单,有多个则必须一一处理
static struct option PKTSIZE_opts[] =
{
     { "size" , 1, NULL, '1' },
     {0}
};
  
// 并且还要将结构体对象赋值给
// pktsize.extra_opts = opts;
  
// 解析参数的具体函数单独出来,会使parse()函数结构很优美
// 我们的输入参数可能格式如下:
//  xx  指定数据包大小xx
//  :x x   范围是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)
         xtables_error(PARAMETER_PROBLEM,
             "pktsize min.range value '%u' greater than max.range value '%u'" ,
             info->min_pktsize,
             info->max_pktsize
             );
}
  
  
  
static int PKTSIZE_parse( int c, char ** argv, int invert, 
     unsigned int * flags, const void * entry,
     struct xt_entry_match** match)
{
     struct ipt_pktsize_info* info = ( struct ipt_pktsize_info*)(*match)->data;
     switch (c)
     {
         case '1' :
             if (*flags)
                 xtables_error(PARAMETER_PROBLEM,
                 "size: '--size' may only be specified once"
                 );
             parse_pkts(argv[optind-1], info);
             *flags = 1;
             break ;
         default :
             return 0;
     }
     return 1;
}
// final_check()TODO:如果你的模块有些长参数时必须的,
// 那么当用户调用了你的模块但又没有进一步制定必须参数时,
// 一般在这个函数里做校验限制。
// 如,我的模块带一个必须参数--size,而且后面必须跟数值
static void PKTSIZE_final_check(unsigned int flags)
{
     if (!flags)
         xtables_error(PARAMETER_PROBLEM, 
             "\npktsize-parameter problem:for pktsize usage type:iptables -m pktsize --help\n" );
}
  
// print()TODO:该函数用于打印用户输入参数的,因为其他人
// 可能也会需要输出规则参数,所以封装成一个子函数__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 PKTSIZE_print( const void * ip,
     const struct xt_entry_match* match, int numeric)
{
     printf ( "size " );
     __print(( struct ipt_pktsize_info *)match->data);
}
  
// save()TODO:该函数跟print类似
static void PKTSIZE_save( const void * ip,
      const struct xt_entry_match* match)
{
     printf ( "--size " );
     __print(( struct ipt_pktsize_info*)match->data);
}
  
static struct xtables_match pktsize =
{
     .next       = NULL,
     .name       = "pktsize" ,
     .version    = XTABLES_VERSION,
     .family     = NFPROTO_IPV4,
     .size       = XT_ALIGN( sizeof ( struct ipt_pktsize_info)),
     .userspacesize  = XT_ALIGN( sizeof ( struct ipt_pktsize_info)),
     .help       = PKTSIZE_help,
     .parse      = PKTSIZE_parse,

你可能感兴趣的:(linux,c,firewall)