分类:
2012-02-13 11:16:37
原文地址:自己写 Netfilter 匹配器(zz) 作者:newcch
摘要:
iptables/netfilter 框架让我们可以向其中添加功能。要添加功能,我们需要自己写一个内核模块并向这个框架注册。也就是说,依照要添加的功能的分类,写一个 iptables 模块。通过写自定义的扩展模块,你可以匹配、修改、增加保障措施、跟踪一个给定的包。事实上,你几乎可以在这个过滤的世界中做任何你想做的事情。 单要小心,内核模块中的一点错误有可能导致系统立刻崩溃。
简单起见,我将介绍一个我写的匹配器的骨架。通过这个骨架,我希望可以让这个和 iptables/netfilter 框架的交互更加易于理解。这里,我假设你已经对 iptables 有了一定了解并且会使用 C 语言。
这个例子将告诉你如何根据源或是目的 ip 地址来匹配一个包。
/* 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 }; |
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); }
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); } }
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); } }
static void final_check(unsigned int flags) { if (!flags) exit_error(PARAMETER_PROBLEM, "ipt_ipaddr: Invalid parameters."); }
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'。稍后我们将介绍这个头文件。
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' 中写相应的值。
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; }
static struct option opts[] = { { .name = "ipsrc", .has_arg = 1, .flag = 0, .val = '1' }, { .name = "ipdst", .has_arg = 1, .flag = 0, .val = '2' }, { .name = 0 } };
static void init(struct ipt_entry_match *m, unsigned int *nfcache) { /* Can't cache this */ *nfcache |= NFC_UNKNOWN; }
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 ); }
#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
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; };
首先,我们初始化 '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);
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 写的一出色的文章 。
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; }
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; }
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
# 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>。