iptables工作在user space,netfilter工作在kernel space,二者合作完成对网络数据包的处理。
在user space使用iptables命令创建各种规则,iptables将这些规则通知内核,内核中通过netfilter实施这些规则。
如果iptables提供的现有模块无法满足需求,可以自己开发一些模块,主要包括两个方面:
1、user space
最好下载一份iptables的源码,找到extensions目录,模块都在这里,我们自己开发的程序也要放到这个目录进行编译。文件名称类似libipt_xxx.c,其中xxx是模块名称,比如有REJECT等,每个文件结构类似,都是先定义一个iptables_target结构体,并实现其中的一些函数,然后register_target()注册之前定义的iptable_target到iptables。
主要结构体
struct iptables_target
{
struct iptables_target *next;
ipt_chainlabel name;
const char *version;
size_t size; /* Size of target data. */
size_t userspacesize; /* Size of target data relevent for userspace comparison purposes */
void (*help)(void); /* Function which prints out usage message. */
void (*init)(struct ipt_entry_target *t, unsigned int *nfcache); /* Initialize the target. */
/* Function which parses command options; returns true if it ate an option */
int (*parse)(int c, char **argv, int invert, unsigned int *flags, const struct ipt_entry *entry, struct ipt_entry_target **target);
void (*final_check)(unsigned int flags); /* Final check; exit if not ok. */
/* Prints out the target iff non-NULL: put space at end */
void (*print)(const struct ipt_ip *ip, const struct ipt_entry_target *target, int numeric);
/* Saves the targinfo in parsable form to stdout. */
void (*save)(const struct ipt_ip *ip, const struct ipt_entry_target *target);
/* Pointer to list of extra command-line options */
struct option *extra_opts;
/* Ignore these men behind the curtain: */
unsigned int option_offset;
struct ipt_entry_target *t;
unsigned int tflags;
unsigned int used;
};
next: 目标链表的下一个,目标链表是一个单向链表;
name:目标的名称,必须是唯一的;
version:iptables的版本;
size:目标结构的数据长度;
userspacesize:用于目标部分的数据长度,通常此值等于size,但某些情况可能会小于size;
help函数:打印帮助信息,当 "-j xxx -h"时调用;
init函数:初始化函数,可对目标结构赋初值;
parse函数:解析用户输入参数,这是最主要的处理函数;
final_check函数:对用户数据进行最后的检查;
print函数:打印目标信息,iptables -L时调用
save函数:保存当前iptables规则时打印目标格式,被iptables-save程序调用;
extra_opts:选项信息,选项格式是标准的UNIX选项格式,通过getopt函数识别;
option_offset:选项偏移;
t:指向iptables规则;
tflags:规则相关标志
used: 模块使用计数;
下面是代码框架
先定义各回调函数
void help (void){//调用printf打印一些帮助信息,主要是使用说明。运行命令 时显示}
void init (struct ipt_entry_target *t, unsigned int *nfcache){}
int parse (int c, char **argv, int invert, unsigned int *flags, const struct ipt_entry *entry, struct ipt_entry_target **target)
{//解析命令参数}
void final_check (unsigned int flags){ }
void print (const struct ipt_ip *ip, const struct ipt_entry_target *target, int numeric){}
void save (const struct ipt_ip *ip, const struct ipt_entry_target *target){}
这些回调可以选择实现一部分,都不实现也可以,建议函数体中添加一些打印,可以知道该函数在何时被调用。
struct option opts[] = {{0},{0}};
struct iptables_target mymod = {
.next = NULL,
.name = "MYMOD",
.version = IPTABLES_VERSION,
.size = IPT_ALIGN(0),
.userspacesize = IPT_ALIGN(0),
.help = &help,
.init = &init,
.parse = &parse,
.final_check = &final_check,
.print = &print,
.save = &save,
.extra_opts = opts
};
void _init(void)
{
register_target(&mymod);
}
将代码保存到libipt_MYMOD.c文件中,放到extensions目录下,修改该目录下的Makefile,在PF_EXT_SLIB变量后添加MYMOD,make后就会生成libipt_MYMOD.so,将其拷贝到/lib/iptables目录
当使用类似 iptables -A input -p udp -j MYMOD 命令是,libipt_MYMOD.so就会被iptables调用
2、kernel space
主要用到
struct ipt_target
{
struct list_head list;
const char name[IPT_FUNCTION_MAXNAMELEN];
/* Called when user tries to insert an entry of this type: hook_mask is a bitmask of hooks from which it can be called. */
/* Should return true or false. */
int (*checkentry)(const char *tablename,const struct ipt_entry *e, void *targinfo, unsigned int targinfosize, unsigned int hook_mask);
/* Called when entry of this type deleted. */
void (*destroy)(void *targinfo, unsigned int targinfosize);
/* Returns verdict. Argument order changed since 2.4, as this must now handle non-linear skbs, using skb_copy_bits and skb_ip_make_writable. */
unsigned int (*target)(struct sk_buff **pskb, const struct net_device *in,
const struct net_device *out, unsigned int hooknum, const void *targinfo, void *userdata);
struct module *me; /* Set this to THIS_MODULE. */
};
struct list_head list:用来挂接到目标链表,必须初始化为{NULL, NULL};
name: 该目标的名称,必须是唯一的,且与iptables_target中的相同。
checkentry:用于对用户层传入的数据进行合法性检查,如目标数据长度是否正确,是否是在正确的表中使用等;
destroy:用于释放该目标中动态分配的资源,规则删除时会调用;
target:该函数是最主要函数,完成对数据包的处理,返回结果可能是NF_ACCEPT(接受)/NF_DROP(丢弃)/NF_STOLEN(偷窃,指该包由该目标接管,不再由系统网络栈处理)/IPT_CONTINUE(继续,继续按后面的规则进行检查)等;
struct module *me:指向模块本身。
target函数是目标的核心函数,返回1表示数据符合目标条件,0表示不目标,函数定义为:
unsigned int (*target)(struct sk_buff **pskb, const struct net_device *in, const struct net_device *out,
unsigned int hooknum, const void *targinfo, void *userdata);
skb:数据包,主要基于此变量进行处理
in:数据进入的网卡
out:数据发出的网卡
hooknum:hook号,取值为:NF_IP_PRE_ROUTING/NF_IP_LOCAL_IN/NF_IP_FORWARD/
NF_IP_LOCAL_OUT/NF_IP_POST_ROUTING之一
targetinfo:目标条件信息的指针
offset:碎片数据偏移量
userdata:特殊专用数据,目前内核中基本没有用到,通常都是NULL;
checkentry函数对数据进行检查,返回1表示数据合法,0表示非法,函数定义为:
int (*checkentry)(const char *tablename, const struct ipt_entry *e, void *targinfo,
unsigned int targinfosize, unsigned int hook_mask);
tablename:表名,如“filter”,“nat”,“mangle”等,可用来限制目标只能在指定的表中处理;
struct ipt_entry *e:指向规则的指针;
targetinfo:用户空间传入的目标条件信息的指针;
targetinfosize:用户空间传入目标条件信息的长度;
hook_mask:挂接点(PREROUTING/INPUT/FORWARD/OUTPUT/POSTROUTING)掩码,限制目标只能在指定的挂接点中处理;
destroy函数释放该目标中动态分配的资源,无返回值,函数定义为:
void (*destroy)(void *targetinfo, unsigned int targetinfosize);
各函数实现后,调用ipt_register_target()函数将ipt_target挂接到系统目标链表中,
调用ipt_unregister_target()函数从目标链表中去除。
static struct ipt_target ipt_mymod_reg = {
.name = "MYMOD",
.target = target,
.checkentry = checkentry,
.me = THIS_MODULE,
};
static int __init init(void){
return ipt_register_target(&ipt_mymod_reg );
}
static void __exit fini(void){
ipt_unregister_target(&ipt_mymod_reg );
}
module_init(init);
module_exit(fini);
使用编译内核的makefile编译,并insmod到内核。
3、使用
iptables -A input -p udp -j MYMOD
添加该规则后,系统会针对所有进入的udp数据包调用上面的target()函数进行处理。具体的处理在target()自行编写。