当前位置:资讯中心主页 >程序安全 >文章内容
- 自己写 Netfilter 匹配器
- 来源:作者: 发布时间:2007-11-21 21:26:00
-
编译:王旭
- 域名惊喜价格 cn域名1元注册
- com域名39.9元
域名注册
- 主机按月支付,低至19元/月
- 超大流量,可开子站点
虚拟主机
- 特惠VPS168元/月,4-8M独享带宽保证
- 独立操作系统,无限开站点
VPS主机
摘要:
iptables/netfilter 框架让我们能向其中添加功能。要添加功能,我们需要自己写一个内核模块并向这个框架注册。也就是说,依照要添加的功能的分类,写一个 iptables 模块。通过写自定义的扩展模块,你能匹配、修改、增加保障措施、跟踪一个给定的包。事实上,你几乎能在这个过滤的世界中做所有你想做的事情。单要小心,内核模块中的一点错误有可能导致系统即时崩溃。
简单起见,我将介绍一个我写的匹配器的骨架。通过这个骨架,我希望能让这个和 iptables/netfilter 框架的交互更加易于理解。这里,我假设你已对 iptables 有了一定了解并且会使用 C 语言。
这个例子将告诉你怎么根据源或是目的 ip 地址来匹配一个包。_________________ _________________ _________________ 概述
写一个 iptables/netfilter 匹配模块的大体步骤如下:- 找到你要匹配的具体情况。
- 写用于接受参数的用户空间部分程式。
- 写用于分析包信息,得出是否匹配结论的内核空间部分程式。
1.0 iptables 模块
iptables 库的用途基本上讲就是和用户交互,他捕捉用户要传送给核心态程式的参数。1.1 可用的数据结构和函数
首先是一些基本数据结构。 <iptables/include/iptables.h>
稍后文中就将能看到这些结构的用途了。/* Include file for additions: new matches and targets. */ struct iptables_match { struct iptables_match *next; ipt_chainlabel name; const char *version; /* Size of match data. */ size_t size; /* Size of match data relevent for userspace comparison purposes */ size_t userspacesize; /* Function which prints out usage message. */ void (*help)(void); /* Initialize the match. */ void (*init)(struct ipt_entry_match *m, unsigned int *nfcache); /* 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, unsigned int *nfcache, struct ipt_entry_match **match); /* Final check; exit if not ok. */ void (*final_check)(unsigned int flags); /* Prints out the match iff non-NULL: put space at end */ void (*print)(const struct ipt_ip *ip, const struct ipt_entry_match *match, int numeric); /* Saves the match info in parsable form to stdout. */ void (*save)(const struct ipt_ip *ip, const struct ipt_entry_match *match); /* Pointer to list of extra command-line options */ const struct option *extra_opts; /* Ignore these men behind the curtain: */ unsigned int option_offset; struct ipt_entry_match *m; unsigned int mflags; #ifdef NO_SHARED_LIBS unsigned int loaded; /* simulate loading so options are merged properly */ #endif };1.2 深入骨架程式
1.2.1 初始化
我们首先初始化 ’iptables_match’ 结构中的常用字段:static struct iptables_match ipaddr = {’Name’ 是你的函数库的文件名(也就是 libipt_ipaddr)。
你不能在这个位置放其他的东西,这是用来自动加载你的库的。.name = "ipaddr",
下一个字段 ’version’ 是 iptables 的版本。后面的两个字段都是用于保持用户态程式和核心态共享结构的大小一致性的。.version = IPTABLES_VERSION, .size = IPT_ALIGN(sizeof(struct ipt_ipaddr_info)), .userspacesize = IPT_ALIGN(sizeof(struct ipt_ipaddr_info)),’Help’ 是用户输入 ’iptables -m module -h’ 的时候要调用的函数。’Parse’ 是用户输入一条新规则的时候调用的,用于验证参数的合法性。’print’ 就是使用 ’iptables -L’ 的时候显示前面添加的规则的。.help = &help, .init = &init, .parse = &parse, .final_check = &final_check, .print = &print, .save = &save, .extra_opts = opts };iptables 架构能够支持多个共享库。每个共享库必须使用 <iptables/iptables.c> 中定义的 ’register_match()’ 向 iptables 注册。这个函数将在模块被 iptables 加载的时候调用。 更多信息请参考:’man dlopen’。void _init(void) { register_match(&ipaddr); }1.2.2 save 函数
如果我们有一个需要保存的规则集,能利用 iptables 提供的工具 ’iptables-save’,他能保存下所有的规则。显然你需要扩展这个工具来保存下来这些规则。这个扩展通过 save 函数完成。static void save(const struct ipt_ip *ip, const struct ipt_entry_match *match) { const struct ipt_ipaddr_info *info = (const struct ipt_ipaddr_info *)match->data;如果源地址是规则的一部分的话,打印他。if (info->flags & IPADDR_SRC) { if (info->flags & IPADDR_SRC_INV) printf("! "); printf("--ipsrc "); print_ipaddr((u_int32_t *)&info->ipaddr.src); }如果目的地址是规则的一部分的话就打印目的地址。if (info->flags & IPADDR_DST) { if (info->flags & IPADDR_DST_INV) printf("! "); printf("--ipdst "); print_ipaddr((u_int32_t *)&info->ipaddr.dst); } }1.2.3 print 函数
和上面的 save 所蕴含的哲学相同,也有一个 print 函数用于打印规则。他在 ’iptables -L’ 的时候被调用。我们将在下文里看到参数 ’ipt_entry_match *match’ 的用途,不过我们已对他有了一点概念了,是吧?static void print(const struct ipt_ip *ip, const struct ipt_entry_match *match, int numeric) { const struct ipt_ipaddr_info *info = (const struct ipt_ipaddr_info *)match->data; if (info->flags & IPADDR_SRC) { printf("src IP "); if (info->flags & IPADDR_SRC_INV) printf("! "); print_ipaddr((u_int32_t *)&info->ipaddr.src); } if (info->flags & IPADDR_DST) { printf("dst IP "); if (info->flags & IPADDR_DST_INV) printf("! "); print_ipaddr((u_int32_t *)&info->ipaddr.dst); } }1.2.4 final check 函数
这个函数是最后一次正确性检查的机会。他在用户输入完规则之后、参数解析刚刚完成的时候被调用。static void final_check(unsigned int flags) { if (!flags) exit_error(PARAMETER_PROBLEM, "ipt_ipaddr: Invalid parameters."); }1.2.5 parse 函数
parse 是最重要的一个函数,因为这里要检查参数的正确性,并写入我们将共享给核心态程式的信息。他在每次参数被发现的时候被调用,也就是说,如果用户输入了两个参数,这个函数就将被以不同的参数代码 c 调用两次。static int parse(int c, char **argv, int invert, unsigned int *flags, const struct ipt_entry *entry, unsigned int *nfcache, struct ipt_entry_match **match) {我们使用特别结构来保存我们要传递给核心态程式的信息。’match’ 指针被传递给多个函数,我们能每次使用同样的数据结构。一旦规则被加载了,这个指针就被复制到了核心态程式里。通过这个方式,内核模块能知道用户想要分析什么(这正是问题的关键,不是么?)。struct ipt_ipaddr_info *info = (struct ipt_ipaddr_info *)(*match)->data;
每个参数对应着一个独立的值,于是我们能根据进入的参数决定采取何种行动。下文中我们将看到我们怎么把参数变成数值。switch(c) {首先,我们检查参数是否被使用了多次。如果使用了多次的话,调用 <iptables/iptables.c> 中定义的 ’exit_error()’ 函数,这样程式会即时带着 <iptables/include/iptables_common.h> 中定义的 ’PARAMETER_PROBLEM’ 的错误状态推出。否则,我们在我们的头文件中定义的 ’IPADDR_SRC’ 中设置 ’flags’ 和 ’info->flags’。稍后我们将介绍这个头文件。
虽然这两个标志看起来差不多,不过是完全不同的。’flag’ 的作用域就是这个函数,而 ’info->flags’ 是我们用于和核心态程式共享信息的结构的一部分。case ’1’: if (*flags & IPADDR_SRC) exit_error(PARAMETER_PROBLEM, "ipt_ipaddr: Only use --ipsrc once!"); *flags |= IPADDR_SRC; info->flags |= IPADDR_SRC;检查如果取反标志 ’!’ 是否存在,如果有的话,在 ’info->flags’ 中写相应的值。
之后调用为这个骨架程式所写的内部函数 ’parse_ipaddr’ 来把 IP 地址从字符串转化为 32 位值。if (invert) info->flags |= IPADDR_SRC_INV; parse_ipaddr(argv[optind-1], &info->ipaddr.src); break;同样考虑,我们检查是否存在多次设置,置恰当的标志。case ’2’: if (*flags & IPADDR_DST) exit_error(PARAMETER_PROBLEM, "ipt_ipaddr: Only use --ipdst once!"); *flags |= IPADDR_DST; info->flags |= IPADDR_DST; if (invert) info->flags |= IPADDR_DST_INV; parse_ipaddr(argv[optind-1], &info->ipaddr.dst); break; default: return 0; } return 1; }1.2.6 options 结构
前文中,我们已谈到了要将每个参数映射到一个值。 ’struct option’ 就是个达到这个目的的好办法。要想得到关于这个结构的进一步信息,强烈建议阅读 ’man 3 getopt’。static struct option opts[] = { { .name = "ipsrc", .has_arg = 1, .flag = 0, .val = ’1’ }, { .name = "ipdst", .has_arg = 1, .flag = 0, .val = ’2’ }, { .name = 0 } };1.2.7 init 函数
init 函数用于初始化一些特定的东西,比如 netfilter 的 cache 系统。目前不必过多考虑这个函数的具体用途。static void init(struct ipt_entry_match *m, unsigned int *nfcache) { /* Can’t cache this */ *nfcache |= NFC_UNKNOWN; }1.2.7 help 函数
这个函数通过 ’iptables -m match_name -h’ 被调用,用于显示可用的参数。static void help(void) { printf ( "IPADDR v%s options:\n" "[!] --ipsrc\t\t The incoming ip addr matches.\n" "[!] --ipdst \t\t The outgoing ip addr matches.\n" "\n", IPTABLES_VERSION ); } 1.2.8 头文件 ’ipt_ipaddr.h’
这个文件定义了我们需要的一些东西。#ifndef _IPT_IPADDR_H #define _IPT_IPADDR_H
我们已在上文中使用了这些特定的值了。#define IPADDR_SRC 0x01 /* Match source IP addr */ #define IPADDR_DST 0x02 /* Match destination IP addr */ #define IPADDR_SRC_INV 0x10 /* Negate the condition */ #define IPADDR_DST_INV 0x20 /* Negate the condition */
结构 ’ipt_ipaddr_info’ 是将要被拷贝到核心态程式的那个数据结构。struct ipt_ipaddr { u_int32_t src, dst; }; struct ipt_ipaddr_info { struct ipt_ipaddr ipaddr; /* Flags from above */ u_int8_t flags; }; #endif1.3 第一章小结
第一部分中,我们讨论了 iptables 库的作用。我们记述了每个函数的内容和 ’ipt_ipaddr_info’ 这个用于保存信息的将要被拷贝到核心态程式来做进一步处理的重要结构。我们也看到了 iptables 结构和怎么注册一个新的库。
应该注意,这仅仅是个用于演示框架怎么工作的骨架程式。而且,’ipt_ipaddr_info’ 和其他类似的东西并不是 iptables/netfilter 的一部分,而仅仅是这个例子的一部分。2.0 netfilter 模块
一个匹配模块的工作就是察看每一个收到的包并决定是否符合某个判决准则。这个模块要做如下工作:- 接收每个包,并察看匹配模块相关的表
- 告知 netfilter,我们的模块是否匹配上了这个包
2.1 可用的函数和数据结构
首先是一些基本数据结构,这些数据结构定义在 <linux/netfilter_ipv4/ip_tables.h>。
如果你对这个结构及前面的 iptables 部分更有兴趣的话,你能看看 Rusty Russell 和 Harald Welte 写的 netfilter hacking howto 。struct ipt_match { struct list_head list; const char name[IPT_FUNCTION_MAXNAMELEN]; /* Return true or false: return FALSE and set *hotdrop = 1 to force immediate packet drop. */ /* Arguments changed since 2.4, as this must now handle non-linear skbs, using skb_copy_bits and skb_ip_make_writable. */ int (*match)(const struct sk_buff *skb, const struct net_device *in, const struct net_device *out, const void *matchinfo, int offset, int *hotdrop); /* Called when user tries to insert an entry of this type. */ /* Should return true or false. */ int (*checkentry)(const char *tablename, const struct ipt_ip *ip, void *matchinfo, unsigned int matchinfosize, unsigned int hook_mask); /* Called when entry of this type deleted. */ void (*destroy)(void *matchinfo, unsigned int matchinfosize); /* Set this to THIS_MODULE. */ struct module *me; };2.2 深入骨架程式
2.2.1 初始化
首先,我们初始化 ’ipt_match’ 数据结构中的常用域。
static struct ipt_match ipaddr_match = {’name’ 是你的模块的文件名字符串(也就是说 ipt_ipaddr)。.name = "ipaddr",
下面的字段是框架将要使用的回调函数.’match’是当一个包传送给你的模块的时候要调用的函数..match = match, .checkentry = checkentry, .me = THIS_MODULE, };
你的内核模块的 init 函数需要通过指向一个 ’struct ipt_match’ 的指针调用 ’ipt_register_match()’ 来向 netfilter 框架注册.这个函数在模块被加载的时候调用.static int __init init(void) { printk(KERN_INFO "ipt_ipaddr: init!\n"); return ipt_register_match(&ipaddr_match); }当把模块从内核中移出的时候这个函数会被调用.这里我们进行的工作是注销匹配器。static void __exit fini(void) { printk(KERN_INFO "ipt_ipaddr: exit!\n"); ipt_unregister_match(&ipaddr_match); }设置让这两个函数在模块装入和移出的时候被调用。module_init(init); module_exit(fini);
2.2.2 match 函数
Linux 的 TCP/IP 协议栈包括5个 netfilter 钩子。这样,一个包近来之后,协议栈把包送到相应的钩子,依次进入每个表,再依次叠带每条规则。当你的模块得到包的时候,你的模块就能进行他的工作了。static int match(const struct sk_buff *skb, const struct net_device *in, const struct net_device *out, const void *matchinfo, int offset, const void *hdr, u_int16_t datalen, int *hotdrop) {希望你还记着我们在用户态程式里面做了些什么! :)。目前把用户态程式拷贝过来的数据结构映射到我们这里const struct ipt_skeleton_info *info = matchinfo;
’skb’ 包含了我们想要处理的包。想要得到关于这个在 linux 的 TCP/IP 协议栈中到处都是功能强大的数据结构的信息,能看看 Harald Welte 写的一出色的文章 article (ftp://ftp.gnumonks.org/pub/doc/skb-doc.html) 。struct iphdr *iph = skb->nh.iph;
这里,我们就是打印一些有趣的东西来看看他们长成什么样子。宏 ’NIPQUAD’ 用于以可读的方式显示一个 IP 地址,他是在 <linux/include/linux/kernel.h> 中定义的。printk(KERN_INFO "ipt_ipaddr: IN=%s OUT=%s TOS=0x%02X " "TTL=%x SRC=%u.%u.%u.%u DST=%u.%u.%u.%u " "ID=%u IPSRC=%u.%u.%u.%u IPDST=%u.%u.%u.%u\n", in ? (char *)in : "", out ? (char *)out : "", iph->tos, iph->ttl, NIPQUAD(iph->saddr), NIPQUAD(iph->daddr), ntohs(iph->id), NIPQUAD(info->ipaddr.src), NIPQUAD(info->ipaddr.dst) );如果输入了 ’--ipsrc’ 参数,我们察看源地址是否和规则指定的地址相匹配。别忘了考虑反标志 ’!’。如果没有匹配,我们返回 0.if (info->flags & IPADDR_SRC) { if ( (ntohl(iph->saddr) != ntohl(info->ipaddr.src)) ^ !!(info->flags & IPADDR_SRC_INV) ) { printk(KERN_NOTICE "src IP %u.%u.%u.%u is not matching %s.\n", NIPQUAD(info->ipaddr.src), info->flags & IPADDR_SRC_INV ? " (INV)" : ""); return 0; } }这里,我们进行完全相同的工作,只是察看 ’--ipdst’ 参数。if (info->flags & IPADDR_DST) { if ( (ntohl(iph->daddr) != ntohl(info->ipaddr.dst)) ^ !!(info->flags & IPADDR_DST_INV) ) { printk(KERN_NOTICE "dst IP %u.%u.%u.%u is not matching%s.\n", NIPQUAD(info->ipaddr.dst), info->flags & IPADDR_DST_INV ? " (INV)" : ""); return 0; } }如果都不成功,返回 1,表明我们匹配了这个包。return 1; }
2.2.3 checkentry 函数
checkentry 通常是最后一次合法性检查的机会。关于他何时被调用有些难以理解。看看 post (http://www.mail-archive.com/netfilter-devel@lists.samba.org/msg00625.html) 作为一个解释吧。这篇文章也是一篇 netfilter hacking howto。static int checkentry(const char *tablename, const struct ipt_ip *ip, void *matchinfo, unsigned int matchsize, unsigned int hook_mask) { const struct ipt_skeleton_info *info = matchinfo; if (matchsize != IPT_ALIGN(sizeof(struct ipt_skeleton_info))) { printk(KERN_ERR "ipt_skeleton: matchsize differ, you may have forgotten to recompile me.\n"); return 0; } printk(KERN_INFO "ipt_skeleton: Registered in the %s table, hook=%x, proto=%u\n", tablename, hook_mask, ip->proto); return 1; }2.3 第二章小结
在第二部分,我们讲了 netfilter 模块及怎么使用特定结构注册他。另外我们还讨论了怎么根据用户空间部分给出的判据匹配特定的情况。3.0 运行 iptables/netfilter
我们已看到了怎么写一个新的 iptables/netfilter 匹配模块。目前我们将把他添加到内核中来运行他。这里,我假设你知道怎么编译内核。首先把骨架匹配文件从本文下载页面下载下来。3.1 iptables
目前,如果你还没有 iptables 的原始码的话,能从 ftp://ftp.netfilter.org/pub/iptables/ 下载。然后拷贝 ’libipt_ipaddr.c’ 到 <iptables/extensions/>。
这是 <iptables/extensions/Makefile> 中的一行,你应该加上 ’ipaddr’。PF_EXT_SLIB:=ah addrtype comment connlimit connmark conntrack dscp ecn esp hashlimit helper icmp iprange length limit ipaddr mac mark multiport owner physdev pkttype realm rpc sctp standard state tcp tcpmss tos ttl udp unclean CLASSIFY CONNMARK DNAT DSCP ECN LOG MARK MASQUERADE MIRROR NETMAP NOTRACK REDIRECT REJECT SAME SNAT TARPIT TCPMSS TOS TRACE TTL ULOG
3.2 内核
首先,你应该拷贝 ’ipt_ipaddr.c’ 到 <linux/net/ipv4/netfilter/>,拷贝 ’ ipt_ipaddr.h’ 到 <linux/net/ipv4/netfilter/>。有些读者可能还在使用 2.4 内核,所以我同时提供了 2.4和 2.6 的文件。
对于 2.4 内核,编辑 <linux/net/ipv4/netfilter/Config.in> ,加入下面加重的行。# The simple matches. dep_tristate ’ limit match support’ CONFIG_IP_NF_MATCH_LIMIT $CONFIG_IP_NF_IPTABLES dep_tristate ’ ipaddr match support’ CONFIG_IP_NF_MATCH_IPADDR $CONFIG_IP_NF_IPTABLES
然后,编辑 <linux/Documentation/Configure.help> 加入加重的行。我复制了一些文本来帮助你找到要加入内容的地方。limit match support CONFIG_IP_NF_MATCH_LIMIT limit matching allows you to control the rate at which a rule can be ... ipaddr match support CONFIG_IP_NF_MATCH_IPADDR ipaddr matching. etc etc.
最后,你必须把加重的行加入到 <linux/net/ipv4/netfilter/Makefile> 之中。# matches obj-$(CONFIG_IP_NF_MATCH_HELPER) += ipt_helper.o obj-$(CONFIG_IP_NF_MATCH_LIMIT) += ipt_limit.o obj-$(CONFIG_IP_NF_MATCH_IPADDR) += ipt_ipaddr.o
Now for 2.6, files to edit are <linux/net/ipv4/netfilter/Kconfig> and <linux/net/ipv4/netfilter/Makefile>. 对 2.6 内核,编辑的文件应该是 <linux/net/ipv4/netfilter/Kconfig>和 <linux/net/ipv4/netfilter/Makefile>。总结
下面剩下的的就是从新编译及我忘了说的了。
Happy hacking!!
感谢 Samuel Jean.
以上内容由 华夏名网 搜集整理,如转载请注明原文出处,并保留这一部分内容。
“华夏名网” http://www.sudu.cn 和 http://www.bigwww.com 是成都飞数科技有限公司的网络服务品牌,专业经营虚拟主机,域名注册,VPS,服务器租用业务。公司创建于2002年,经过6年的高速发展,“华夏名网”已经成为我国一家知名的互联网服务提供商,被国外权威机构webhosting.info评价为25大IDC服务商之一。 - (阅读次数:74)
- 上一篇: 使用AIDE做文件的完整性检测 下一篇: Logwatch:Linux日志情报官
- [
收藏] [
推荐] [
评论] [打印本页] [返回上一页][关闭窗口]


