您的位置:首页 > 运维架构 > Linux

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

2015-10-25 01:10 721 查看

2012-05-31 21:00:14

分类: LINUX

自己开发一个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原理、网络编程等有较好的基础才行。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: