纸上得来终觉浅,绝知此事要躬行。 生命不息,奋斗不止。
分类: LINUX
2020-04-06 15:38:11
自己开发一个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原理、网络编程等有较好的基础才行。
未完,待续…