|
by Nicolas Bouliane <nib(at)cookinglinux!org> About the author: Nicolas is a young warrior in the free software community. He's a gnu/linux addict since the day he installed it on his computer in 1998. He spend his time studying the linux networking stack, writing free softwares and attending at linux related conference like the OLS. When he's not in front of his computer, he likes watching sci-fi movies, playing chess and listening Richard Stallman's talk. |
Writing your own netfilter matchAbstract:
The iptables/netfilter framework gives us the possibility to add features.
To do so, we write kernel modules that registers against this framework.
Also, depending on the feature's category, we write an iptables module.
By writing your new extension, you can match, mangle, give faith and track a
given packet. In fact, you can do almost everything you want in this filtering
world.
Beware that a little error in a kernel module can severly crash the computer.
|
/* 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' is the file name string of your library (ie: libipt_ipaddr).
.name = "ipaddr",The next field, 'version', is the iptables's version. Both next are used to keep corelation between the user-space and kernel-space shared structure's size.
.version = IPTABLES_VERSION, .size = IPT_ALIGN(sizeof(struct ipt_ipaddr_info)), .userspacesize = IPT_ALIGN(sizeof(struct ipt_ipaddr_info)),'Help' is called when a user enter 'iptables -m module -h'. 'Parse' is called when you enter a new rule, its duty is to validate the arguments. In the case of 'print', its called by 'iptables -L' to show previously enterd rules.
.help = &help, .init = &init, .parse = &parse, .final_check = &final_check, .print = &print, .save = &save, .extra_opts = opts };The iptables infrastructure can support multiple shared libraries. Each library must register to iptables by calling 'register_match()', defined into <iptables/iptables.c>. This function is called when the module is loaded by iptables. For more information about it: '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;We print out the source-addresse if it's part of the rule.
if (info->flags & IPADDR_SRC) { if (info->flags & IPADDR_SRC_INV) printf("! "); printf("--ipsrc "); print_ipaddr((u_int32_t *)&info->ipaddr.src); }We print out the destination-adresse if it's part of the rule.
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) {We use this special structure to keep informations we will share with the kernel-part. 'Match' pointer is passed to a couple of functions so we can work on the same data structure. Once the rule is loaded, this pointer is copied to the kernel-part. This way, the kernel module knows what the user asks to analyze (and that's the point, no?).
struct ipt_ipaddr_info *info = (struct ipt_ipaddr_info *)(*match)->data;Each arguments correspond to an single value, so we can do specific actions according to the inputed arguments. We will see later in this text how we map arguments to values.
switch(c) {First, we check if the argument has been used more than once. If it appears to be the case, we call 'exit_error()' defined in <iptables/iptables.c>, which will exit immediatly with the status flag 'PARAMETER_PROBLEM' defined in <iptables/include/iptables_common.h>. Else, we set 'flags' and 'info->flags' to the 'IPADDR_SRC' value defined in our header's file. We will see this header's file later.
case '1': if (*flags & IPADDR_SRC) exit_error(PARAMETER_PROBLEM, "ipt_ipaddr: Only use --ipsrc once!"); *flags |= IPADDR_SRC; info->flags |= IPADDR_SRC;We verify if the invert flag, '!', has been inputed and then set appropriate information into the 'info->flags'.
if (invert) info->flags |= IPADDR_SRC_INV; parse_ipaddr(argv[optind-1], &info->ipaddr.src); break;In the same thought, we check for multiple use and set appropriate flags.
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 <ip>\t\t The incoming ip addr matches.\n" "[!] --ipdst <ip>\t\t The outgoing ip addr matches.\n" "\n", IPTABLES_VERSION ); }
#ifndef _IPT_IPADDR_H #define _IPT_IPADDR_HWe have seen earlier that we set flags to some specific values.
#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 */The structure 'ipt_ipaddr_info' is the one who will be copied to the kernel-part.
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; };
We initialize the common fields in the 'ipt_match' structure.
static struct ipt_match ipaddr_match = {'Name' is the file name string of your module (ie: ipt_ipaddr).
.name = "ipaddr",Next fields are callbacks that the framework will use. 'Match' is called when a packet is passed to your module.
.match = match, .checkentry = checkentry, .me = THIS_MODULE, };Your kernel module's init function needs to call 'ipt_register_match()' with a pointer to a 'struct ipt_match' to register against the netfilter framework. This function is called on module loading.
static int __init init(void) { printk(KERN_INFO "ipt_ipaddr: init!\n"); return ipt_register_match(&ipaddr_match); }When unloading the module this function is called. It's here that we unregister our match.
static void __exit fini(void) { printk(KERN_INFO "ipt_ipaddr: exit!\n"); ipt_unregister_match(&ipaddr_match); }We hand them functions that will be called at the loading and unloading of the module.
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) {Hope you remember what we did in the user-part ! :). Now we map the user-space's copied structure into our own one.
const struct ipt_skeleton_info *info = matchinfo;The 'skb' contains the packet we want to look at. For more information about this powerfull structure used everywhere in the linux tcp/ip stack, Harald Welte wrote an excellent article (ftp://ftp.gnumonks.org/pub/doc/skb-doc.html) about it.
struct iphdr *iph = skb->nh.iph;Here, we are just printing some funny stuff to see what they look like. The macro 'NIPQUAD' is used to display an ip address in readable format, defined in <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) );If the '--ipsrc' argument has been inputed we look if the source address match with the one specified in the rule. We don't forget to take in consideration the invert flag: '!'. If we don't match: we return the verdict; 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; } }Here, we do the same, except that we look for the destination address if '--ipdst' has been inputed.
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; } }If both failed, we return the verdict 1, which means we matched the packet.
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_IPTABLESThen, edit <linux/Documentation/Configure.help> and add the text in bold. I copied some text to help you find where you add yours.
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.Finally, you have to add this bold line into <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>.
Webpages maintained by the LinuxFocus Editor team
© Nicolas Bouliane "some rights reserved" see linuxfocus.org/license/ http://www.LinuxFocus.org |
Translation information:
|
2005-07-22, generated by lfparser_pdf version 2.51