解析Linux下Netfilter & iptables:开发一个match模块
来源:互联网 发布:淘宝上的好店铺推荐 编辑:程序博客网 时间:2024/05/22 23:20
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。
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,开始动手,我们先搭框架:
#include <stdio.h>
#include <netdb.h>
#include <string.h>
#include <stdlib.h>
#include <getopt.h>
#include <ctype.h>
#include <xtables.h>
#include <linux/netfilter_ipv4/ipt_pktsize.h>
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时 用于显示该模块用法的帮助信息。
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() 供其他人调用。
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类似 。
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,而且后面必须跟数值。
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{}。
static
struct
option PKTSIZE_opts[] =
{
{
"size"
, 1, NULL,
'1'
},
{0}
};
// 并且还要将结构体对象赋值给
// pktsize.extra_opts = opts;
// 解析参数的具体函数单独出来,会使parse()函数结构很优美
// 我们的输入参数可能格式如下:
// xx 指定数据包大小xx
// <img src="http://www.bairimeng.net/wp-includes/images/smilies/icon_mad.gif" alt=":x" class="wp-smiley"> 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,用户态的所有功能完成,完整代码如下:
#include <stdio.h>
#include <netdb.h>
#include <string.h>
#include <stdlib.h>
#include <getopt.h>
#include <ctype.h>
#include <xtables.h>
#include <linux/netfilter_ipv4/ipt_pktsize.h>
// 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
// <img src="http://www.bairimeng.net/wp-includes/images/smilies/icon_mad.gif" alt=":x" class="wp-smiley"> 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,
.final_check = PKTSIZE_final_check,
.print = PKTSIZE_print,
.save = PKTSIZE_save,
.extra_opts = PKTSIZE_opts
};
void
_init(
void
)
{
xtables_register_match(&pktsize);
}
现在我们将要将它编译成so库,现在如果我们使用iptables -m pktsize -h会提示说不能加载名为’pktsize’的match,缺少了/lib/xtables/libipt_pktsize.so,所以我们要把这个so编译出来,并放到那个目录下。
我们可以从Netfilter官网上下载到iptables-1.4.3.2的源码包,然后解压缩,1.4.3.2和1.4.0的Make不一样,所以make过程也有所不同,网上的资料大多是1.4.0的,所以我只说下1.4.3.2的。
我们在iptables的源码目录下能找到一个configure文件,这个文件会进行Makefile配置,这一点跟1.4.0不一样,运行它。
可以看到不停的有东西刷出来,别管他,一般配置过程结束之后,会在源码目录下产生一些Makefile。
我们将我们的libipt_pktsize.c复制到extensions/下,1.4.0需要修改extensions下的Makefile文件,但是1.4.3.2不需要,却多了上面那个配置过程。
然后我们返回iptables源码目录,执行一次make。
同样是一堆东西刷出来,它会编译整个iptables,因为本身比较小,所以我们也不改动Makefile了,干脆等它全部编译完成,然后我们只需要extensions/libipt_pktsize.so这个库文件。将它复制到iptables的库中,比如我的是lib/xtables/。
这时候我们执行iptables -m pktsize -h就应该可以看到help()函数输出的信息了。
在末尾看到了我们的模块的帮助信息,可喜可贺。
到这里我们用户态的模块已经开发完毕,iptables已经成功加载了我们的模块,但是我们还需要开发一个内核态的模块,使得Netfilter能调用它。
四、内核态开发
内核中,我们使用xt_match{}结构体来表示一个match,我们也要实例化一个这玩意儿,然后使用xt_register_match()来将它注册到xt[AF_INET].match链上面就行了,就这么简单。。
#include <linux/module.h>
#include <linux/skbuff.h>
#include <linux/ip.h>
#include <linux/version.h>
#include <linux/netfilter_ipv4/ip_tables.h>
#include <linux/netfilter/x_tables.h>
#include <linux/netfilter_ipv4/ipt_pktsize.h>
MODULE_AUTHOR(
"Sekai <sekaiamber@lolicon.me>"
);
MODULE_DESCRIPTION(
"Iptables pkt size range match module."
);
MODULE_LICENSE(
"GPL"
);
static
bool
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,
bool
* hotdrop)
{
// >>>TODO<<<
return
false
;
}
static
struct
xt_match pktsize_match __read_mostly =
{
.name =
"pktsize"
,
// >>>>TODO<<<<
.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);
我们在这里只需实现一个match函数,来告诉Netfilter数据包经过这个match时将要干嘛。
static
bool
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,
bool
* hotdrop)
{
const
struct
ipt_pktsize_info* info = matchinfo;
const
struct
iphdr* iph = ip_hdr(skb);
int
pkttruesize = ntohs(iph->tot_len) - (iph->ihl * 4);
if
(pkttruesize >= info->min_pktsize && pkttruesize <= info->max_pktsize)
{
return
true
;
}
else
{
return
false
;
}
}
这里要注意skb这个结构,是sk_buff,这个结构的完整定义在Linux内核源码的include/linux/skbuff.h中,在Linux内核2.4.23之前,这个结构和后面不太一样。
sk_buff代表了收到的数据包的一系列指针集合,在2.4.23内核之前,它的成员中含有3个共同体union,分别名为h,nh,mac,这三者分别代表传输层(L4)的头,网络层(L3)的头,数据链路层(L2)的头,而在之后版本的内核中,这三个字段被改成了类型为sk_buff_data_t的transport_header,network_header,mac_header,sk_buff_data_t的详细定义也在skbuff.h中,它是一个宏:
#ifdef NET_SKBUFF_DATA_USES_OFFSET
typedef
unsigned
int
sk_buff_data_t;
// 使用偏移来表示数据头位置
#else
typedef
unsigned
char
*sk_buff_data_t;
// 使用指针来表示数据头位置
#endif
所以事实上sk_buff_data_t是一个标志而已,所以我们要使用内核提供的函数来获得iphdr,这表示一个数据包的信息,详细定义在内核源码include/linux/ip.h中。
OK,编码完成,完整代码如下:
#include <linux/module.h>
#include <linux/skbuff.h>
#include <linux/ip.h>
#include <linux/version.h>
#include <linux/netfilter_ipv4/ip_tables.h>
#include <linux/netfilter/x_tables.h>
#include <linux/netfilter_ipv4/ipt_pktsize.h>
MODULE_AUTHOR(
"Sekai <sekaiamber@lolicon.me>"
);
MODULE_DESCRIPTION(
"Iptables pkt size range match module."
);
MODULE_LICENSE(
"GPL"
);
// TODO
static
bool
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,
bool
* hotdrop)
{
const
struct
ipt_pktsize_info* info = matchinfo;
const
struct
iphdr* iph = ip_hdr(skb);
int
pkttruesize = ntohs(iph->tot_len) - (iph->ihl * 4);
if
(pkttruesize >= info->min_pktsize && pkttruesize <= info->max_pktsize)
{
return
true
;
}
else
{
return
false
;
}
}
static
struct
xt_match pktsize_match __read_mostly =
{
.name =
"pktsize"
,
// >>>>TODO<<<<
.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);
内核态的模块已经编码完成,所以我们要对他进行编译成ko库。
我们复制ipt_pktsize.h头文件到系统引用路径中,比如我的是/usr/include/linux/netfilter_ipv4/。当然这个遵循你ipt_pktsize.c中引用头文件的位置,相对位置一般是/usr/include/。
然后复制ipt_pktsize.c源文件到Linux内核源代码中比如我是~/下载/linux-2.6.27.41/net/ipv4/netfilter/中。
然后将目录下的Makefile文件备份,新建一个Makefile,我们只需要编译一个我们的ipt_pktsize.ko就可以了,不需要全部编译。
新建Makefile,输入下列内容:
保存,然后执行make。
编译成功,我们就将ipt_pktsize.ko复制到系统模块目录中去,比如我的是/lib/modules/2.6.27.XXXXXXXXXXXXX(懒得打)/kernel/net/ipv4/netfilter/。然后安装模块即可。
上图第一行,我们将ko文件设置为全部可执行,第二行看一下有没有模块,这时候pktsize还没被安装,第三行复制到系统模块目录,第四个命令安装模块,手滑没打 sudo,权限不够,第五个命令继续,
第六个命令lsmod,发现模块已经成功被识别了。
然后我们来使用我们的模块配置一下iptables。
可以看到Chain Forward链上已经绑上了我们编写的模块了!大功告成!
- 解析Linux下Netfilter & iptables:开发一个match模块
- (十四)洞悉linux下的Netfilter&iptables:开发一个match模块【实战】
- (十四)洞悉linux下的Netfilter&iptables:开发一个match模块【实战】
- (十四)洞悉linux下的Netfilter&iptables:开发一个match模块【实战】
- 洞悉linux下的Netfilter&iptables:开发一个match模块【实战】
- (十四)洞悉linux下的Netfilter&iptables:开发一个match模块【实战】
- (十四)洞悉linux下的Netfilter&iptables:开发一个match模块【实战】
- (十四)洞悉linux下的Netfilter&iptables:开发一个match模块【实战】
- (十四)洞悉linux下的Netfilter&iptables:开发一个match模块【实战】
- 解析Linux下Netfilter & iptables:开发一个Hook函数
- netfilter:开发一个match模块
- Linux netfilter 学习笔记 之十五 netfilter模块添加一个match
- Linux netfilter 学习笔记 之十五 netfilter模块添加一个match
- linux下的Netfilter&iptables
- (三)洞悉linux下的Netfilter&iptables:内核中的rule,match和target
- (三)洞悉linux下的Netfilter&iptables:内核中的rule,match和target
- (三)洞悉linux下的Netfilter&iptables:内核中的rule,match和target
- (三)洞悉linux下的Netfilter&iptables:内核中的rule,match和target
- SpringMVC入门教程
- linux存储管理 MMU
- IT民工的2013的升迁
- 某张图片围绕自身旋转的动画
- 学习C#数组(1)
- 解析Linux下Netfilter & iptables:开发一个match模块
- eclipse + Blazeds 独立项目框架搭建
- Android基础学习之目录结构
- Android 开发XML解析,从服务器下载
- git的使用
- Fermat’s Chirstmas Theorem
- Android自定义对话框(Dialog)位置,大小
- TCP/IP协议头部结构体(转)
- Why Stored Procedure and Why not...