![]() 编译:王旭
概述写一个 iptables/netfilter 匹配模块的大体步骤如下:
1.0 iptables 模块iptables 库的用途基本上讲就是和用户交互,他捕获用户要传送给核心态程式的参数。1.1 可用的数据结构和函数首先是一些基本数据结构。 <iptables/include/iptables.h>稍后文中就将能够看到这些结构的用途了。
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
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;
};
#endif
1.3 第一章小结第一部分中,我们讨论了 iptables 库的作用。我们记述了每个函数的内容和 ’ipt_ipaddr_info’ 这个用于保存信息的将要被拷贝到核心态程式来做进一步处理的重要结构。我们也看到了 iptables 结构和怎样注册一个新的库。应该注意,这仅仅是个用于演示框架怎样工作的骨架程式。而且,’ipt_ipaddr_info’ 和其他类似的东西并不是 iptables/netfilter 的一部分,而仅仅是这个例子的一部分。 2.0 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.oNow 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. |
喜欢本文,那就收藏到: |