X-Git-Url: http://git.onelab.eu/?a=blobdiff_plain;f=net%2Fipv4%2Fnetfilter%2Fip_tables.c;h=fc1f153c86ba1f33a261a038fa2da3e87b38531f;hb=refs%2Fheads%2Fvserver;hp=f24f17b8e03ebe07ed9d989b9217251445834fd7;hpb=9bf4aaab3e101692164d49b7ca357651eb691cb6;p=linux-2.6.git diff --git a/net/ipv4/netfilter/ip_tables.c b/net/ipv4/netfilter/ip_tables.c index f24f17b8e..fc1f153c8 100644 --- a/net/ipv4/netfilter/ip_tables.c +++ b/net/ipv4/netfilter/ip_tables.c @@ -2,7 +2,7 @@ * Packet matching code. * * Copyright (C) 1999 Paul `Rusty' Russell & Michael J. Neuling - * Copyright (C) 2000-2004 Netfilter Core Team + * Copyright (C) 2000-2005 Netfilter Core Team * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 as @@ -11,22 +11,26 @@ * 19 Jan 2002 Harald Welte * - increase module usage count as soon as we have rules inside * a table + * 08 Oct 2005 Harald Welte + * - Generalize into "x_tables" layer and "{ip,ip6,arp}_tables" */ -#include #include +#include #include #include #include #include #include -#include -#include #include #include +#include #include -#include +#include #include +#include +#include +#include #include MODULE_LICENSE("GPL"); @@ -59,13 +63,6 @@ do { \ #else #define IP_NF_ASSERT(x) #endif -#define SMP_ALIGN(x) (((x) + SMP_CACHE_BYTES-1) & ~(SMP_CACHE_BYTES-1)) - -/* Must have mutex */ -#define ASSERT_READ_LOCK(x) IP_NF_ASSERT(down_trylock(&ipt_mutex) != 0) -#define ASSERT_WRITE_LOCK(x) IP_NF_ASSERT(down_trylock(&ipt_mutex) != 0) -#include -#include #if 0 /* All the better to debug you with... */ @@ -80,48 +77,8 @@ do { \ context stops packets coming through and allows user context to read the counters or update the rules. - To be cache friendly on SMP, we arrange them like so: - [ n-entries ] - ... cache-align padding ... - [ n-entries ] - Hence the start of any table is given by get_table() below. */ -/* The table itself */ -struct ipt_table_info -{ - /* Size per table */ - unsigned int size; - /* Number of entries: FIXME. --RR */ - unsigned int number; - /* Initial number of entries. Needed for module usage count */ - unsigned int initial_entries; - - /* Entry points and underflows */ - unsigned int hook_entry[NF_IP_NUMHOOKS]; - unsigned int underflow[NF_IP_NUMHOOKS]; - - /* ipt_entry tables: one per CPU */ - char entries[0] ____cacheline_aligned; -}; - -static LIST_HEAD(ipt_target); -static LIST_HEAD(ipt_match); -static LIST_HEAD(ipt_tables); -#define ADD_COUNTER(c,b,p) do { (c).bcnt += (b); (c).pcnt += (p); } while(0) - -#ifdef CONFIG_SMP -#define TABLE_OFFSET(t,p) (SMP_ALIGN((t)->size)*(p)) -#else -#define TABLE_OFFSET(t,p) 0 -#endif - -#if 0 -#define down(x) do { printk("DOWN:%u:" #x "\n", __LINE__); down(x); } while(0) -#define down_interruptible(x) ({ int __r; printk("DOWNi:%u:" #x "\n", __LINE__); __r = down_interruptible(x); if (__r != 0) printk("ABORT-DOWNi:%u\n", __LINE__); __r; }) -#define up(x) do { printk("UP:%u:" #x "\n", __LINE__); up(x); } while(0) -#endif - /* Returns whether matches rule or not. */ static inline int ip_packet_match(const struct iphdr *ip, @@ -222,8 +179,8 @@ ipt_error(struct sk_buff **pskb, const struct net_device *in, const struct net_device *out, unsigned int hooknum, - const void *targinfo, - void *userinfo) + const struct xt_target *target, + const void *targinfo) { if (net_ratelimit()) printk("ip_tables: error: `%s'\n", (char *)targinfo); @@ -240,7 +197,8 @@ int do_match(struct ipt_entry_match *m, int *hotdrop) { /* Stop iteration if it doesn't match */ - if (!m->u.kernel.match->match(skb, in, out, m->data, offset, hotdrop)) + if (!m->u.kernel.match->match(skb, in, out, m->u.kernel.match, m->data, + offset, skb->nh.iph->ihl*4, hotdrop)) return 1; else return 0; @@ -258,8 +216,7 @@ ipt_do_table(struct sk_buff **pskb, unsigned int hook, const struct net_device *in, const struct net_device *out, - struct ipt_table *table, - void *userdata) + struct ipt_table *table) { static const char nulldevname[IFNAMSIZ] __attribute__((aligned(sizeof(long)))); u_int16_t offset; @@ -271,6 +228,7 @@ ipt_do_table(struct sk_buff **pskb, const char *indev, *outdev; void *table_base; struct ipt_entry *e, *back; + struct xt_table_info *private; /* Initialization */ ip = (*pskb)->nh.iph; @@ -287,30 +245,16 @@ ipt_do_table(struct sk_buff **pskb, read_lock_bh(&table->lock); IP_NF_ASSERT(table->valid_hooks & (1 << hook)); - table_base = (void *)table->private->entries - + TABLE_OFFSET(table->private, smp_processor_id()); - e = get_entry(table_base, table->private->hook_entry[hook]); - -#ifdef CONFIG_NETFILTER_DEBUG - /* Check noone else using our table */ - if (((struct ipt_entry *)table_base)->comefrom != 0xdead57ac - && ((struct ipt_entry *)table_base)->comefrom != 0xeeeeeeec) { - printk("ASSERT: CPU #%u, %s comefrom(%p) = %X\n", - smp_processor_id(), - table->name, - &((struct ipt_entry *)table_base)->comefrom, - ((struct ipt_entry *)table_base)->comefrom); - } - ((struct ipt_entry *)table_base)->comefrom = 0x57acc001; -#endif + private = table->private; + table_base = (void *)private->entries[smp_processor_id()]; + e = get_entry(table_base, private->hook_entry[hook]); /* For return from builtin chain */ - back = get_entry(table_base, table->private->underflow[hook]); + back = get_entry(table_base, private->underflow[hook]); do { IP_NF_ASSERT(e); IP_NF_ASSERT(back); - (*pskb)->nfcache |= e->nfcache; if (ip_packet_match(ip, indev, outdev, &e->ip, offset)) { struct ipt_entry_target *t; @@ -339,8 +283,8 @@ ipt_do_table(struct sk_buff **pskb, back->comefrom); continue; } - if (table_base + v - != (void *)e + e->next_offset) { + if (table_base + v != (void *)e + e->next_offset + && !(e->ip.flags & IPT_F_GOTO)) { /* Save old back ptr in next entry */ struct ipt_entry *next = (void *)e + e->next_offset; @@ -361,8 +305,8 @@ ipt_do_table(struct sk_buff **pskb, verdict = t->u.kernel.target->target(pskb, in, out, hook, - t->data, - userdata); + t->u.kernel.target, + t->data); #ifdef CONFIG_NETFILTER_DEBUG if (((struct ipt_entry *)table_base)->comefrom @@ -392,9 +336,6 @@ ipt_do_table(struct sk_buff **pskb, } } while (!hotdrop); -#ifdef CONFIG_NETFILTER_DEBUG - ((struct ipt_entry *)table_base)->comefrom = 0xdead57ac; -#endif read_unlock_bh(&table->lock); #ifdef DEBUG_ALLOW_ALL @@ -406,75 +347,6 @@ ipt_do_table(struct sk_buff **pskb, #endif } -/* If it succeeds, returns element and locks mutex */ -static inline void * -find_inlist_lock_noload(struct list_head *head, - const char *name, - int *error, - struct semaphore *mutex) -{ - void *ret; - -#if 0 - duprintf("find_inlist: searching for `%s' in %s.\n", - name, head == &ipt_target ? "ipt_target" - : head == &ipt_match ? "ipt_match" - : head == &ipt_tables ? "ipt_tables" : "UNKNOWN"); -#endif - - *error = down_interruptible(mutex); - if (*error != 0) - return NULL; - - ret = list_named_find(head, name); - if (!ret) { - *error = -ENOENT; - up(mutex); - } - return ret; -} - -#ifndef CONFIG_KMOD -#define find_inlist_lock(h,n,p,e,m) find_inlist_lock_noload((h),(n),(e),(m)) -#else -static void * -find_inlist_lock(struct list_head *head, - const char *name, - const char *prefix, - int *error, - struct semaphore *mutex) -{ - void *ret; - - ret = find_inlist_lock_noload(head, name, error, mutex); - if (!ret) { - duprintf("find_inlist: loading `%s%s'.\n", prefix, name); - request_module("%s%s", prefix, name); - ret = find_inlist_lock_noload(head, name, error, mutex); - } - - return ret; -} -#endif - -static inline struct ipt_table * -ipt_find_table_lock(const char *name, int *error, struct semaphore *mutex) -{ - return find_inlist_lock(&ipt_tables, name, "iptable_", error, mutex); -} - -static inline struct ipt_match * -find_match_lock(const char *name, int *error, struct semaphore *mutex) -{ - return find_inlist_lock(&ipt_match, name, "ipt_", error, mutex); -} - -struct ipt_target * -ipt_find_target_lock(const char *name, int *error, struct semaphore *mutex) -{ - return find_inlist_lock(&ipt_target, name, "ipt_", error, mutex); -} - /* All zeroes == unconditional rule. */ static inline int unconditional(const struct ipt_ip *ip) @@ -491,7 +363,8 @@ unconditional(const struct ipt_ip *ip) /* Figures out from what hook each rule can be called: returns 0 if there are loops. Puts hook bitmask in comefrom. */ static int -mark_source_chains(struct ipt_table_info *newinfo, unsigned int valid_hooks) +mark_source_chains(struct xt_table_info *newinfo, + unsigned int valid_hooks, void *entry0) { unsigned int hook; @@ -500,7 +373,7 @@ mark_source_chains(struct ipt_table_info *newinfo, unsigned int valid_hooks) for (hook = 0; hook < NF_IP_NUMHOOKS; hook++) { unsigned int pos = newinfo->hook_entry[hook]; struct ipt_entry *e - = (struct ipt_entry *)(newinfo->entries + pos); + = (struct ipt_entry *)(entry0 + pos); if (!(valid_hooks & (1 << hook))) continue; @@ -511,6 +384,7 @@ mark_source_chains(struct ipt_table_info *newinfo, unsigned int valid_hooks) for (;;) { struct ipt_standard_target *t = (void *)ipt_get_target(e); + int visited = e->comefrom & (1 << hook); if (e->comefrom & (1 << NF_IP_NUMHOOKS)) { printk("iptables: loop hook %u pos %u %08X.\n", @@ -521,13 +395,20 @@ mark_source_chains(struct ipt_table_info *newinfo, unsigned int valid_hooks) |= ((1 << hook) | (1 << NF_IP_NUMHOOKS)); /* Unconditional return/END. */ - if (e->target_offset == sizeof(struct ipt_entry) + if ((e->target_offset == sizeof(struct ipt_entry) && (strcmp(t->target.u.user.name, IPT_STANDARD_TARGET) == 0) && t->verdict < 0 - && unconditional(&e->ip)) { + && unconditional(&e->ip)) || visited) { unsigned int oldpos, size; + if (t->verdict < -NF_MAX_VERDICT - 1) { + duprintf("mark_source_chains: bad " + "negative verdict (%i)\n", + t->verdict); + return 0; + } + /* Return: backtrack through the last big jump. */ do { @@ -550,13 +431,13 @@ mark_source_chains(struct ipt_table_info *newinfo, unsigned int valid_hooks) goto next; e = (struct ipt_entry *) - (newinfo->entries + pos); + (entry0 + pos); } while (oldpos == pos + e->next_offset); /* Move along one */ size = e->next_offset; e = (struct ipt_entry *) - (newinfo->entries + pos + size); + (entry0 + pos + size); e->counters.pcnt = pos; pos += size; } else { @@ -565,6 +446,13 @@ mark_source_chains(struct ipt_table_info *newinfo, unsigned int valid_hooks) if (strcmp(t->target.u.user.name, IPT_STANDARD_TARGET) == 0 && newpos >= 0) { + if (newpos > newinfo->size - + sizeof(struct ipt_entry)) { + duprintf("mark_source_chains: " + "bad verdict (%i)\n", + newpos); + return 0; + } /* This a jump; chase it. */ duprintf("Jump rule %u -> %u\n", pos, newpos); @@ -573,7 +461,7 @@ mark_source_chains(struct ipt_table_info *newinfo, unsigned int valid_hooks) newpos = pos + e->next_offset; } e = (struct ipt_entry *) - (newinfo->entries + newpos); + (entry0 + newpos); e->counters.pcnt = pos; pos = newpos; } @@ -591,82 +479,104 @@ cleanup_match(struct ipt_entry_match *m, unsigned int *i) return 1; if (m->u.kernel.match->destroy) - m->u.kernel.match->destroy(m->data, - m->u.match_size - sizeof(*m)); + m->u.kernel.match->destroy(m->u.kernel.match, m->data); module_put(m->u.kernel.match->me); return 0; } static inline int -standard_check(const struct ipt_entry_target *t, - unsigned int max_offset) +check_entry(struct ipt_entry *e, const char *name) { - struct ipt_standard_target *targ = (void *)t; - - /* Check standard info. */ - if (t->u.target_size - != IPT_ALIGN(sizeof(struct ipt_standard_target))) { - duprintf("standard_check: target size %u != %u\n", - t->u.target_size, - IPT_ALIGN(sizeof(struct ipt_standard_target))); - return 0; - } + struct ipt_entry_target *t; - if (targ->verdict >= 0 - && targ->verdict > max_offset - sizeof(struct ipt_entry)) { - duprintf("ipt_standard_check: bad verdict (%i)\n", - targ->verdict); - return 0; + if (!ip_checkentry(&e->ip)) { + duprintf("ip_tables: ip check failed %p %s.\n", e, name); + return -EINVAL; } - if (targ->verdict < -NF_MAX_VERDICT - 1) { - duprintf("ipt_standard_check: bad negative verdict (%i)\n", - targ->verdict); - return 0; + if (e->target_offset + sizeof(struct ipt_entry_target) > e->next_offset) + return -EINVAL; + + t = ipt_get_target(e); + if (e->target_offset + t->u.target_size > e->next_offset) + return -EINVAL; + + return 0; +} + +static inline int check_match(struct ipt_entry_match *m, const char *name, + const struct ipt_ip *ip, unsigned int hookmask) +{ + struct ipt_match *match; + int ret; + + match = m->u.kernel.match; + ret = xt_check_match(match, AF_INET, m->u.match_size - sizeof(*m), + name, hookmask, ip->proto, + ip->invflags & IPT_INV_PROTO); + if (!ret && m->u.kernel.match->checkentry + && !m->u.kernel.match->checkentry(name, ip, match, m->data, + hookmask)) { + duprintf("ip_tables: check failed for `%s'.\n", + m->u.kernel.match->name); + ret = -EINVAL; } - return 1; + return ret; } static inline int -check_match(struct ipt_entry_match *m, +find_check_match(struct ipt_entry_match *m, const char *name, const struct ipt_ip *ip, unsigned int hookmask, unsigned int *i) { - int ret; struct ipt_match *match; + int ret; - match = find_match_lock(m->u.user.name, &ret, &ipt_mutex); - if (!match) { - duprintf("check_match: `%s' not found\n", m->u.user.name); - return ret; - } - if (!try_module_get(match->me)) { - up(&ipt_mutex); - return -ENOENT; + match = try_then_request_module(xt_find_match(AF_INET, m->u.user.name, + m->u.user.revision), + "ipt_%s", m->u.user.name); + if (IS_ERR(match) || !match) { + duprintf("find_check_match: `%s' not found\n", m->u.user.name); + return match ? PTR_ERR(match) : -ENOENT; } m->u.kernel.match = match; - up(&ipt_mutex); - if (m->u.kernel.match->checkentry - && !m->u.kernel.match->checkentry(name, ip, m->data, - m->u.match_size - sizeof(*m), - hookmask)) { - module_put(m->u.kernel.match->me); - duprintf("ip_tables: check failed for `%s'.\n", - m->u.kernel.match->name); - return -EINVAL; - } + ret = check_match(m, name, ip, hookmask); + if (ret) + goto err; (*i)++; return 0; +err: + module_put(m->u.kernel.match->me); + return ret; } -static struct ipt_target ipt_standard_target; +static inline int check_target(struct ipt_entry *e, const char *name) +{ + struct ipt_entry_target *t; + struct ipt_target *target; + int ret; + + t = ipt_get_target(e); + target = t->u.kernel.target; + ret = xt_check_target(target, AF_INET, t->u.target_size - sizeof(*t), + name, e->comefrom, e->ip.proto, + e->ip.invflags & IPT_INV_PROTO); + if (!ret && t->u.kernel.target->checkentry + && !t->u.kernel.target->checkentry(name, e, target, + t->data, e->comefrom)) { + duprintf("ip_tables: check failed for `%s'.\n", + t->u.kernel.target->name); + ret = -EINVAL; + } + return ret; +} static inline int -check_entry(struct ipt_entry *e, const char *name, unsigned int size, +find_check_entry(struct ipt_entry *e, const char *name, unsigned int size, unsigned int *i) { struct ipt_entry_target *t; @@ -674,50 +584,36 @@ check_entry(struct ipt_entry *e, const char *name, unsigned int size, int ret; unsigned int j; - if (!ip_checkentry(&e->ip)) { - duprintf("ip_tables: ip check failed %p %s.\n", e, name); - return -EINVAL; - } + ret = check_entry(e, name); + if (ret) + return ret; j = 0; - ret = IPT_MATCH_ITERATE(e, check_match, name, &e->ip, e->comefrom, &j); + ret = IPT_MATCH_ITERATE(e, find_check_match, name, &e->ip, + e->comefrom, &j); if (ret != 0) goto cleanup_matches; t = ipt_get_target(e); - target = ipt_find_target_lock(t->u.user.name, &ret, &ipt_mutex); - if (!target) { - duprintf("check_entry: `%s' not found\n", t->u.user.name); - goto cleanup_matches; - } - if (!try_module_get(target->me)) { - up(&ipt_mutex); - ret = -ENOENT; + target = try_then_request_module(xt_find_target(AF_INET, + t->u.user.name, + t->u.user.revision), + "ipt_%s", t->u.user.name); + if (IS_ERR(target) || !target) { + duprintf("find_check_entry: `%s' not found\n", t->u.user.name); + ret = target ? PTR_ERR(target) : -ENOENT; goto cleanup_matches; } t->u.kernel.target = target; - up(&ipt_mutex); - if (t->u.kernel.target == &ipt_standard_target) { - if (!standard_check(t, size)) { - ret = -EINVAL; - goto cleanup_matches; - } - } else if (t->u.kernel.target->checkentry - && !t->u.kernel.target->checkentry(name, e, t->data, - t->u.target_size - - sizeof(*t), - e->comefrom)) { - module_put(t->u.kernel.target->me); - duprintf("ip_tables: check failed for `%s'.\n", - t->u.kernel.target->name); - ret = -EINVAL; - goto cleanup_matches; - } + ret = check_target(e, name); + if (ret) + goto err; (*i)++; return 0; - + err: + module_put(t->u.kernel.target->me); cleanup_matches: IPT_MATCH_ITERATE(e, cleanup_match, &j); return ret; @@ -725,7 +621,7 @@ check_entry(struct ipt_entry *e, const char *name, unsigned int size, static inline int check_entry_size_and_hooks(struct ipt_entry *e, - struct ipt_table_info *newinfo, + struct xt_table_info *newinfo, unsigned char *base, unsigned char *limit, const unsigned int *hook_entries, @@ -759,7 +655,7 @@ check_entry_size_and_hooks(struct ipt_entry *e, < 0 (not IPT_RETURN). --RR */ /* Clear counters and comefrom */ - e->counters = ((struct ipt_counters) { 0, 0 }); + e->counters = ((struct xt_counters) { 0, 0 }); e->comefrom = 0; (*i)++; @@ -778,8 +674,7 @@ cleanup_entry(struct ipt_entry *e, unsigned int *i) IPT_MATCH_ITERATE(e, cleanup_match, NULL); t = ipt_get_target(e); if (t->u.kernel.target->destroy) - t->u.kernel.target->destroy(t->data, - t->u.target_size - sizeof(*t)); + t->u.kernel.target->destroy(t->u.kernel.target, t->data); module_put(t->u.kernel.target->me); return 0; } @@ -789,7 +684,8 @@ cleanup_entry(struct ipt_entry *e, unsigned int *i) static int translate_table(const char *name, unsigned int valid_hooks, - struct ipt_table_info *newinfo, + struct xt_table_info *newinfo, + void *entry0, unsigned int size, unsigned int number, const unsigned int *hook_entries, @@ -810,11 +706,11 @@ translate_table(const char *name, duprintf("translate_table: size %u\n", newinfo->size); i = 0; /* Walk through entries, checking offsets. */ - ret = IPT_ENTRY_ITERATE(newinfo->entries, newinfo->size, + ret = IPT_ENTRY_ITERATE(entry0, newinfo->size, check_entry_size_and_hooks, newinfo, - newinfo->entries, - newinfo->entries + size, + entry0, + entry0 + size, hook_entries, underflows, &i); if (ret != 0) return ret; @@ -842,93 +738,79 @@ translate_table(const char *name, } } - if (!mark_source_chains(newinfo, valid_hooks)) + if (!mark_source_chains(newinfo, valid_hooks, entry0)) return -ELOOP; /* Finally, each sanity check must pass */ i = 0; - ret = IPT_ENTRY_ITERATE(newinfo->entries, newinfo->size, - check_entry, name, size, &i); + ret = IPT_ENTRY_ITERATE(entry0, newinfo->size, + find_check_entry, name, size, &i); if (ret != 0) { - IPT_ENTRY_ITERATE(newinfo->entries, newinfo->size, - cleanup_entry, &i); + IPT_ENTRY_ITERATE(entry0, newinfo->size, + cleanup_entry, &i); return ret; } /* And one copy for every other CPU */ - for (i = 1; i < NR_CPUS; i++) { - memcpy(newinfo->entries + SMP_ALIGN(newinfo->size)*i, - newinfo->entries, - SMP_ALIGN(newinfo->size)); + for_each_possible_cpu(i) { + if (newinfo->entries[i] && newinfo->entries[i] != entry0) + memcpy(newinfo->entries[i], entry0, newinfo->size); } return ret; } -static struct ipt_table_info * -replace_table(struct ipt_table *table, - unsigned int num_counters, - struct ipt_table_info *newinfo, - int *error) +/* Gets counters. */ +static inline int +add_entry_to_counter(const struct ipt_entry *e, + struct xt_counters total[], + unsigned int *i) { - struct ipt_table_info *oldinfo; - -#ifdef CONFIG_NETFILTER_DEBUG - { - struct ipt_entry *table_base; - unsigned int i; - - for (i = 0; i < NR_CPUS; i++) { - table_base = - (void *)newinfo->entries - + TABLE_OFFSET(newinfo, i); - - table_base->comefrom = 0xdead57ac; - } - } -#endif - - /* Do the substitution. */ - write_lock_bh(&table->lock); - /* Check inside lock: is the old number correct? */ - if (num_counters != table->private->number) { - duprintf("num_counters != table->private->number (%u/%u)\n", - num_counters, table->private->number); - write_unlock_bh(&table->lock); - *error = -EAGAIN; - return NULL; - } - oldinfo = table->private; - table->private = newinfo; - newinfo->initial_entries = oldinfo->initial_entries; - write_unlock_bh(&table->lock); + ADD_COUNTER(total[*i], e->counters.bcnt, e->counters.pcnt); - return oldinfo; + (*i)++; + return 0; } -/* Gets counters. */ static inline int -add_entry_to_counter(const struct ipt_entry *e, +set_entry_to_counter(const struct ipt_entry *e, struct ipt_counters total[], unsigned int *i) { - ADD_COUNTER(total[*i], e->counters.bcnt, e->counters.pcnt); + SET_COUNTER(total[*i], e->counters.bcnt, e->counters.pcnt); (*i)++; return 0; } static void -get_counters(const struct ipt_table_info *t, - struct ipt_counters counters[]) +get_counters(const struct xt_table_info *t, + struct xt_counters counters[]) { unsigned int cpu; unsigned int i; + unsigned int curcpu; + + /* Instead of clearing (by a previous call to memset()) + * the counters and using adds, we set the counters + * with data used by 'current' CPU + * We dont care about preemption here. + */ + curcpu = raw_smp_processor_id(); - for (cpu = 0; cpu < NR_CPUS; cpu++) { + i = 0; + IPT_ENTRY_ITERATE(t->entries[curcpu], + t->size, + set_entry_to_counter, + counters, + &i); + + for_each_possible_cpu(cpu) { + if (cpu == curcpu) + continue; i = 0; - IPT_ENTRY_ITERATE(t->entries + TABLE_OFFSET(t, cpu), + IPT_ENTRY_ITERATE(t->entries[cpu], t->size, add_entry_to_counter, counters, @@ -936,33 +818,52 @@ get_counters(const struct ipt_table_info *t, } } -static int -copy_entries_to_user(unsigned int total_size, - struct ipt_table *table, - void __user *userptr) +static inline struct xt_counters * alloc_counters(struct ipt_table *table) { - unsigned int off, num, countersize; - struct ipt_entry *e; - struct ipt_counters *counters; - int ret = 0; + unsigned int countersize; + struct xt_counters *counters; + struct xt_table_info *private = table->private; /* We need atomic snapshot of counters: rest doesn't change (other than comefrom, which userspace doesn't care about). */ - countersize = sizeof(struct ipt_counters) * table->private->number; - counters = vmalloc(countersize); + countersize = sizeof(struct xt_counters) * private->number; + counters = vmalloc_node(countersize, numa_node_id()); if (counters == NULL) - return -ENOMEM; + return ERR_PTR(-ENOMEM); /* First, sum counters... */ - memset(counters, 0, countersize); write_lock_bh(&table->lock); - get_counters(table->private, counters); + get_counters(private, counters); write_unlock_bh(&table->lock); - /* ... then copy entire thing from CPU 0... */ - if (copy_to_user(userptr, table->private->entries, total_size) != 0) { + return counters; +} + +static int +copy_entries_to_user(unsigned int total_size, + struct ipt_table *table, + void __user *userptr) +{ + unsigned int off, num; + struct ipt_entry *e; + struct xt_counters *counters; + struct xt_table_info *private = table->private; + int ret = 0; + void *loc_cpu_entry; + + counters = alloc_counters(table); + if (IS_ERR(counters)) + return PTR_ERR(counters); + + /* choose the copy that is on our node/cpu, ... + * This choice is lazy (because current thread is + * allowed to migrate to another cpu) + */ + loc_cpu_entry = private->entries[raw_smp_processor_id()]; + /* ... then copy entire thing ... */ + if (copy_to_user(userptr, loc_cpu_entry, total_size) != 0) { ret = -EFAULT; goto free_counters; } @@ -974,7 +875,7 @@ copy_entries_to_user(unsigned int total_size, struct ipt_entry_match *m; struct ipt_entry_target *t; - e = (struct ipt_entry *)(table->private->entries + off); + e = (struct ipt_entry *)(loc_cpu_entry + off); if (copy_to_user(userptr + off + offsetof(struct ipt_entry, counters), &counters[num], @@ -1015,135 +916,357 @@ copy_entries_to_user(unsigned int total_size, return ret; } -static int -get_entries(const struct ipt_get_entries *entries, - struct ipt_get_entries __user *uptr) +#ifdef CONFIG_COMPAT +struct compat_delta { + struct compat_delta *next; + unsigned int offset; + short delta; +}; + +static struct compat_delta *compat_offsets = NULL; + +static int compat_add_offset(unsigned int offset, short delta) { - int ret; - struct ipt_table *t; + struct compat_delta *tmp; - t = ipt_find_table_lock(entries->name, &ret, &ipt_mutex); - if (t) { - duprintf("t->private->number = %u\n", - t->private->number); - if (entries->size == t->private->size) - ret = copy_entries_to_user(t->private->size, - t, uptr->entrytable); - else { - duprintf("get_entries: I've got %u not %u!\n", - t->private->size, - entries->size); - ret = -EINVAL; + tmp = kmalloc(sizeof(struct compat_delta), GFP_KERNEL); + if (!tmp) + return -ENOMEM; + tmp->offset = offset; + tmp->delta = delta; + if (compat_offsets) { + tmp->next = compat_offsets->next; + compat_offsets->next = tmp; + } else { + compat_offsets = tmp; + tmp->next = NULL; + } + return 0; +} + +static void compat_flush_offsets(void) +{ + struct compat_delta *tmp, *next; + + if (compat_offsets) { + for(tmp = compat_offsets; tmp; tmp = next) { + next = tmp->next; + kfree(tmp); } - up(&ipt_mutex); - } else - duprintf("get_entries: Can't find %s!\n", - entries->name); + compat_offsets = NULL; + } +} - return ret; +static short compat_calc_jump(unsigned int offset) +{ + struct compat_delta *tmp; + short delta; + + for(tmp = compat_offsets, delta = 0; tmp; tmp = tmp->next) + if (tmp->offset < offset) + delta += tmp->delta; + return delta; } -static int -do_replace(void __user *user, unsigned int len) +static void compat_standard_from_user(void *dst, void *src) { - int ret; - struct ipt_replace tmp; - struct ipt_table *t; - struct ipt_table_info *newinfo, *oldinfo; - struct ipt_counters *counters; + int v = *(compat_int_t *)src; - if (copy_from_user(&tmp, user, sizeof(tmp)) != 0) - return -EFAULT; + if (v > 0) + v += compat_calc_jump(v); + memcpy(dst, &v, sizeof(v)); +} - /* Hack: Causes ipchains to give correct error msg --RR */ - if (len != sizeof(tmp) + tmp.size) - return -ENOPROTOOPT; +static int compat_standard_to_user(void __user *dst, void *src) +{ + compat_int_t cv = *(int *)src; - /* Pedantry: prevent them from hitting BUG() in vmalloc.c --RR */ - if ((SMP_ALIGN(tmp.size) >> PAGE_SHIFT) + 2 > num_physpages) - return -ENOMEM; + if (cv > 0) + cv -= compat_calc_jump(cv); + return copy_to_user(dst, &cv, sizeof(cv)) ? -EFAULT : 0; +} - newinfo = vmalloc(sizeof(struct ipt_table_info) - + SMP_ALIGN(tmp.size) * NR_CPUS); - if (!newinfo) - return -ENOMEM; +static inline int +compat_calc_match(struct ipt_entry_match *m, int * size) +{ + *size += xt_compat_match_offset(m->u.kernel.match); + return 0; +} - if (copy_from_user(newinfo->entries, user + sizeof(tmp), - tmp.size) != 0) { - ret = -EFAULT; - goto free_newinfo; - } +static int compat_calc_entry(struct ipt_entry *e, struct xt_table_info *info, + void *base, struct xt_table_info *newinfo) +{ + struct ipt_entry_target *t; + unsigned int entry_offset; + int off, i, ret; - counters = vmalloc(tmp.num_counters * sizeof(struct ipt_counters)); - if (!counters) { - ret = -ENOMEM; - goto free_newinfo; - } - memset(counters, 0, tmp.num_counters * sizeof(struct ipt_counters)); + off = 0; + entry_offset = (void *)e - base; + IPT_MATCH_ITERATE(e, compat_calc_match, &off); + t = ipt_get_target(e); + off += xt_compat_target_offset(t->u.kernel.target); + newinfo->size -= off; + ret = compat_add_offset(entry_offset, off); + if (ret) + return ret; - ret = translate_table(tmp.name, tmp.valid_hooks, - newinfo, tmp.size, tmp.num_entries, - tmp.hook_entry, tmp.underflow); - if (ret != 0) - goto free_newinfo_counters; + for (i = 0; i< NF_IP_NUMHOOKS; i++) { + if (info->hook_entry[i] && (e < (struct ipt_entry *) + (base + info->hook_entry[i]))) + newinfo->hook_entry[i] -= off; + if (info->underflow[i] && (e < (struct ipt_entry *) + (base + info->underflow[i]))) + newinfo->underflow[i] -= off; + } + return 0; +} - duprintf("ip_tables: Translated table\n"); +static int compat_table_info(struct xt_table_info *info, + struct xt_table_info *newinfo) +{ + void *loc_cpu_entry; + int i; - t = ipt_find_table_lock(tmp.name, &ret, &ipt_mutex); - if (!t) - goto free_newinfo_counters_untrans; + if (!newinfo || !info) + return -EINVAL; - /* You lied! */ - if (tmp.valid_hooks != t->valid_hooks) { - duprintf("Valid hook crap: %08X vs %08X\n", - tmp.valid_hooks, t->valid_hooks); - ret = -EINVAL; - goto free_newinfo_counters_untrans_unlock; + memset(newinfo, 0, sizeof(struct xt_table_info)); + newinfo->size = info->size; + newinfo->number = info->number; + for (i = 0; i < NF_IP_NUMHOOKS; i++) { + newinfo->hook_entry[i] = info->hook_entry[i]; + newinfo->underflow[i] = info->underflow[i]; } + loc_cpu_entry = info->entries[raw_smp_processor_id()]; + return IPT_ENTRY_ITERATE(loc_cpu_entry, info->size, + compat_calc_entry, info, loc_cpu_entry, newinfo); +} +#endif + +static int get_info(void __user *user, int *len, int compat) +{ + char name[IPT_TABLE_MAXNAMELEN]; + struct ipt_table *t; + int ret; - /* Get a reference in advance, we're not allowed fail later */ - if (!try_module_get(t->me)) { - ret = -EBUSY; - goto free_newinfo_counters_untrans_unlock; + if (*len != sizeof(struct ipt_getinfo)) { + duprintf("length %u != %u\n", *len, + (unsigned int)sizeof(struct ipt_getinfo)); + return -EINVAL; } + if (copy_from_user(name, user, sizeof(name)) != 0) + return -EFAULT; - oldinfo = replace_table(t, tmp.num_counters, newinfo, &ret); - if (!oldinfo) - goto put_module; + name[IPT_TABLE_MAXNAMELEN-1] = '\0'; +#ifdef CONFIG_COMPAT + if (compat) + xt_compat_lock(AF_INET); +#endif + t = try_then_request_module(xt_find_table_lock(AF_INET, name), + "iptable_%s", name); + if (t && !IS_ERR(t)) { + struct ipt_getinfo info; + struct xt_table_info *private = t->private; + +#ifdef CONFIG_COMPAT + if (compat) { + struct xt_table_info tmp; + ret = compat_table_info(private, &tmp); + compat_flush_offsets(); + private = &tmp; + } +#endif + info.valid_hooks = t->valid_hooks; + memcpy(info.hook_entry, private->hook_entry, + sizeof(info.hook_entry)); + memcpy(info.underflow, private->underflow, + sizeof(info.underflow)); + info.num_entries = private->number; + info.size = private->size; + strcpy(info.name, name); + + if (copy_to_user(user, &info, *len) != 0) + ret = -EFAULT; + else + ret = 0; - /* Update module usage count based on number of rules */ - duprintf("do_replace: oldnum=%u, initnum=%u, newnum=%u\n", - oldinfo->number, oldinfo->initial_entries, newinfo->number); - if ((oldinfo->number > oldinfo->initial_entries) || - (newinfo->number <= oldinfo->initial_entries)) - module_put(t->me); - if ((oldinfo->number > oldinfo->initial_entries) && - (newinfo->number <= oldinfo->initial_entries)) + xt_table_unlock(t); module_put(t->me); + } else + ret = t ? PTR_ERR(t) : -ENOENT; +#ifdef CONFIG_COMPAT + if (compat) + xt_compat_unlock(AF_INET); +#endif + return ret; +} - /* Get the old counters. */ - get_counters(oldinfo, counters); - /* Decrease module usage counts and free resource */ - IPT_ENTRY_ITERATE(oldinfo->entries, oldinfo->size, cleanup_entry,NULL); - vfree(oldinfo); - /* Silent error: too late now. */ - copy_to_user(tmp.counters, counters, - sizeof(struct ipt_counters) * tmp.num_counters); - vfree(counters); - up(&ipt_mutex); - return 0; +static int +get_entries(struct ipt_get_entries __user *uptr, int *len) +{ + int ret; + struct ipt_get_entries get; + struct ipt_table *t; + + if (*len < sizeof(get)) { + duprintf("get_entries: %u < %d\n", *len, + (unsigned int)sizeof(get)); + return -EINVAL; + } + if (copy_from_user(&get, uptr, sizeof(get)) != 0) + return -EFAULT; + if (*len != sizeof(struct ipt_get_entries) + get.size) { + duprintf("get_entries: %u != %u\n", *len, + (unsigned int)(sizeof(struct ipt_get_entries) + + get.size)); + return -EINVAL; + } + + t = xt_find_table_lock(AF_INET, get.name); + if (t && !IS_ERR(t)) { + struct xt_table_info *private = t->private; + duprintf("t->private->number = %u\n", + private->number); + if (get.size == private->size) + ret = copy_entries_to_user(private->size, + t, uptr->entrytable); + else { + duprintf("get_entries: I've got %u not %u!\n", + private->size, + get.size); + ret = -EINVAL; + } + module_put(t->me); + xt_table_unlock(t); + } else + ret = t ? PTR_ERR(t) : -ENOENT; + + return ret; +} + +static int +__do_replace(const char *name, unsigned int valid_hooks, + struct xt_table_info *newinfo, unsigned int num_counters, + void __user *counters_ptr) +{ + int ret; + struct ipt_table *t; + struct xt_table_info *oldinfo; + struct xt_counters *counters; + void *loc_cpu_old_entry; + + ret = 0; + counters = vmalloc(num_counters * sizeof(struct xt_counters)); + if (!counters) { + ret = -ENOMEM; + goto out; + } + + t = try_then_request_module(xt_find_table_lock(AF_INET, name), + "iptable_%s", name); + if (!t || IS_ERR(t)) { + ret = t ? PTR_ERR(t) : -ENOENT; + goto free_newinfo_counters_untrans; + } + + /* You lied! */ + if (valid_hooks != t->valid_hooks) { + duprintf("Valid hook crap: %08X vs %08X\n", + valid_hooks, t->valid_hooks); + ret = -EINVAL; + goto put_module; + } + + oldinfo = xt_replace_table(t, num_counters, newinfo, &ret); + if (!oldinfo) + goto put_module; + + /* Update module usage count based on number of rules */ + duprintf("do_replace: oldnum=%u, initnum=%u, newnum=%u\n", + oldinfo->number, oldinfo->initial_entries, newinfo->number); + if ((oldinfo->number > oldinfo->initial_entries) || + (newinfo->number <= oldinfo->initial_entries)) + module_put(t->me); + if ((oldinfo->number > oldinfo->initial_entries) && + (newinfo->number <= oldinfo->initial_entries)) + module_put(t->me); + + /* Get the old counters. */ + get_counters(oldinfo, counters); + /* Decrease module usage counts and free resource */ + loc_cpu_old_entry = oldinfo->entries[raw_smp_processor_id()]; + IPT_ENTRY_ITERATE(loc_cpu_old_entry, oldinfo->size, cleanup_entry,NULL); + xt_free_table_info(oldinfo); + if (copy_to_user(counters_ptr, counters, + sizeof(struct xt_counters) * num_counters) != 0) + ret = -EFAULT; + vfree(counters); + xt_table_unlock(t); + return ret; put_module: module_put(t->me); - free_newinfo_counters_untrans_unlock: - up(&ipt_mutex); + xt_table_unlock(t); free_newinfo_counters_untrans: - IPT_ENTRY_ITERATE(newinfo->entries, newinfo->size, cleanup_entry,NULL); - free_newinfo_counters: vfree(counters); + out: + return ret; +} + +static int +do_replace(void __user *user, unsigned int len) +{ + int ret; + struct ipt_replace tmp; + struct xt_table_info *newinfo; + void *loc_cpu_entry; + + if (copy_from_user(&tmp, user, sizeof(tmp)) != 0) + return -EFAULT; + + /* Hack: Causes ipchains to give correct error msg --RR */ + if (len != sizeof(tmp) + tmp.size) + return -ENOPROTOOPT; + + /* overflow check */ + if (tmp.size >= (INT_MAX - sizeof(struct xt_table_info)) / NR_CPUS - + SMP_CACHE_BYTES) + return -ENOMEM; + if (tmp.num_counters >= INT_MAX / sizeof(struct xt_counters)) + return -ENOMEM; + + newinfo = xt_alloc_table_info(tmp.size); + if (!newinfo) + return -ENOMEM; + + /* choose the copy that is our node/cpu */ + loc_cpu_entry = newinfo->entries[raw_smp_processor_id()]; + if (copy_from_user(loc_cpu_entry, user + sizeof(tmp), + tmp.size) != 0) { + ret = -EFAULT; + goto free_newinfo; + } + + ret = translate_table(tmp.name, tmp.valid_hooks, + newinfo, loc_cpu_entry, tmp.size, tmp.num_entries, + tmp.hook_entry, tmp.underflow); + if (ret != 0) + goto free_newinfo; + + duprintf("ip_tables: Translated table\n"); + + ret = __do_replace(tmp.name, tmp.valid_hooks, + newinfo, tmp.num_counters, + tmp.counters); + if (ret) + goto free_newinfo_untrans; + return 0; + + free_newinfo_untrans: + IPT_ENTRY_ITERATE(loc_cpu_entry, newinfo->size, cleanup_entry,NULL); free_newinfo: - vfree(newinfo); + xt_free_table_info(newinfo); return ret; } @@ -1151,7 +1274,7 @@ do_replace(void __user *user, unsigned int len) * and everything is OK. */ static inline int add_counter_to_entry(struct ipt_entry *e, - const struct ipt_counters addme[], + const struct xt_counters addme[], unsigned int *i) { #if 0 @@ -1170,451 +1293,776 @@ add_counter_to_entry(struct ipt_entry *e, } static int -do_add_counters(void __user *user, unsigned int len) +do_add_counters(void __user *user, unsigned int len, int compat) { unsigned int i; - struct ipt_counters_info tmp, *paddc; + struct xt_counters_info tmp; + struct xt_counters *paddc; + unsigned int num_counters; + char *name; + int size; + void *ptmp; struct ipt_table *t; - int ret; + struct xt_table_info *private; + int ret = 0; + void *loc_cpu_entry; +#ifdef CONFIG_COMPAT + struct compat_xt_counters_info compat_tmp; - if (copy_from_user(&tmp, user, sizeof(tmp)) != 0) + if (compat) { + ptmp = &compat_tmp; + size = sizeof(struct compat_xt_counters_info); + } else +#endif + { + ptmp = &tmp; + size = sizeof(struct xt_counters_info); + } + + if (copy_from_user(ptmp, user, size) != 0) return -EFAULT; - if (len != sizeof(tmp) + tmp.num_counters*sizeof(struct ipt_counters)) +#ifdef CONFIG_COMPAT + if (compat) { + num_counters = compat_tmp.num_counters; + name = compat_tmp.name; + } else +#endif + { + num_counters = tmp.num_counters; + name = tmp.name; + } + + if (len != size + num_counters * sizeof(struct xt_counters)) return -EINVAL; - paddc = vmalloc(len); + paddc = vmalloc_node(len - size, numa_node_id()); if (!paddc) return -ENOMEM; - if (copy_from_user(paddc, user, len) != 0) { + if (copy_from_user(paddc, user + size, len - size) != 0) { ret = -EFAULT; goto free; } - t = ipt_find_table_lock(tmp.name, &ret, &ipt_mutex); - if (!t) + t = xt_find_table_lock(AF_INET, name); + if (!t || IS_ERR(t)) { + ret = t ? PTR_ERR(t) : -ENOENT; goto free; + } write_lock_bh(&t->lock); - if (t->private->number != paddc->num_counters) { + private = t->private; + if (private->number != num_counters) { ret = -EINVAL; goto unlock_up_free; } i = 0; - IPT_ENTRY_ITERATE(t->private->entries, - t->private->size, + /* Choose the copy that is on our node */ + loc_cpu_entry = private->entries[raw_smp_processor_id()]; + IPT_ENTRY_ITERATE(loc_cpu_entry, + private->size, add_counter_to_entry, - paddc->counters, + paddc, &i); unlock_up_free: write_unlock_bh(&t->lock); - up(&ipt_mutex); + xt_table_unlock(t); + module_put(t->me); free: vfree(paddc); return ret; } -static int -do_ipt_set_ctl(struct sock *sk, int cmd, void __user *user, unsigned int len) +#ifdef CONFIG_COMPAT +struct compat_ipt_replace { + char name[IPT_TABLE_MAXNAMELEN]; + u32 valid_hooks; + u32 num_entries; + u32 size; + u32 hook_entry[NF_IP_NUMHOOKS]; + u32 underflow[NF_IP_NUMHOOKS]; + u32 num_counters; + compat_uptr_t counters; /* struct ipt_counters * */ + struct compat_ipt_entry entries[0]; +}; + +static inline int compat_copy_match_to_user(struct ipt_entry_match *m, + void __user **dstptr, compat_uint_t *size) { - int ret; + return xt_compat_match_to_user(m, dstptr, size); +} - if (!capable(CAP_NET_ADMIN)) - return -EPERM; +static int compat_copy_entry_to_user(struct ipt_entry *e, + void __user **dstptr, compat_uint_t *size) +{ + struct ipt_entry_target *t; + struct compat_ipt_entry __user *ce; + u_int16_t target_offset, next_offset; + compat_uint_t origsize; + int ret; - switch (cmd) { - case IPT_SO_SET_REPLACE: - ret = do_replace(user, len); - break; + ret = -EFAULT; + origsize = *size; + ce = (struct compat_ipt_entry __user *)*dstptr; + if (copy_to_user(ce, e, sizeof(struct ipt_entry))) + goto out; + + *dstptr += sizeof(struct compat_ipt_entry); + ret = IPT_MATCH_ITERATE(e, compat_copy_match_to_user, dstptr, size); + target_offset = e->target_offset - (origsize - *size); + if (ret) + goto out; + t = ipt_get_target(e); + ret = xt_compat_target_to_user(t, dstptr, size); + if (ret) + goto out; + ret = -EFAULT; + next_offset = e->next_offset - (origsize - *size); + if (put_user(target_offset, &ce->target_offset)) + goto out; + if (put_user(next_offset, &ce->next_offset)) + goto out; + return 0; +out: + return ret; +} - case IPT_SO_SET_ADD_COUNTERS: - ret = do_add_counters(user, len); - break; +static inline int +compat_check_calc_match(struct ipt_entry_match *m, + const char *name, + const struct ipt_ip *ip, + unsigned int hookmask, + int *size, int *i) +{ + struct ipt_match *match; - default: - duprintf("do_ipt_set_ctl: unknown request %i\n", cmd); - ret = -EINVAL; + match = try_then_request_module(xt_find_match(AF_INET, m->u.user.name, + m->u.user.revision), + "ipt_%s", m->u.user.name); + if (IS_ERR(match) || !match) { + duprintf("compat_check_calc_match: `%s' not found\n", + m->u.user.name); + return match ? PTR_ERR(match) : -ENOENT; } + m->u.kernel.match = match; + *size += xt_compat_match_offset(match); - return ret; + (*i)++; + return 0; } -static int -do_ipt_get_ctl(struct sock *sk, int cmd, void __user *user, int *len) +static inline int +check_compat_entry_size_and_hooks(struct ipt_entry *e, + struct xt_table_info *newinfo, + unsigned int *size, + unsigned char *base, + unsigned char *limit, + unsigned int *hook_entries, + unsigned int *underflows, + unsigned int *i, + const char *name) { - int ret; + struct ipt_entry_target *t; + struct ipt_target *target; + unsigned int entry_offset; + int ret, off, h, j; - if (!capable(CAP_NET_ADMIN)) - return -EPERM; + duprintf("check_compat_entry_size_and_hooks %p\n", e); + if ((unsigned long)e % __alignof__(struct compat_ipt_entry) != 0 + || (unsigned char *)e + sizeof(struct compat_ipt_entry) >= limit) { + duprintf("Bad offset %p, limit = %p\n", e, limit); + return -EINVAL; + } - switch (cmd) { - case IPT_SO_GET_INFO: { - char name[IPT_TABLE_MAXNAMELEN]; - struct ipt_table *t; + if (e->next_offset < sizeof(struct compat_ipt_entry) + + sizeof(struct compat_xt_entry_target)) { + duprintf("checking: element %p size %u\n", + e, e->next_offset); + return -EINVAL; + } - if (*len != sizeof(struct ipt_getinfo)) { - duprintf("length %u != %u\n", *len, - sizeof(struct ipt_getinfo)); - ret = -EINVAL; - break; - } + ret = check_entry(e, name); + if (ret) + return ret; - if (copy_from_user(name, user, sizeof(name)) != 0) { - ret = -EFAULT; - break; - } - name[IPT_TABLE_MAXNAMELEN-1] = '\0'; - t = ipt_find_table_lock(name, &ret, &ipt_mutex); - if (t) { - struct ipt_getinfo info; - - info.valid_hooks = t->valid_hooks; - memcpy(info.hook_entry, t->private->hook_entry, - sizeof(info.hook_entry)); - memcpy(info.underflow, t->private->underflow, - sizeof(info.underflow)); - info.num_entries = t->private->number; - info.size = t->private->size; - strcpy(info.name, name); - - if (copy_to_user(user, &info, *len) != 0) - ret = -EFAULT; - else - ret = 0; + off = 0; + entry_offset = (void *)e - (void *)base; + j = 0; + ret = IPT_MATCH_ITERATE(e, compat_check_calc_match, name, &e->ip, + e->comefrom, &off, &j); + if (ret != 0) + goto cleanup_matches; - up(&ipt_mutex); - } + t = ipt_get_target(e); + target = try_then_request_module(xt_find_target(AF_INET, + t->u.user.name, + t->u.user.revision), + "ipt_%s", t->u.user.name); + if (IS_ERR(target) || !target) { + duprintf("check_compat_entry_size_and_hooks: `%s' not found\n", + t->u.user.name); + ret = target ? PTR_ERR(target) : -ENOENT; + goto cleanup_matches; } - break; + t->u.kernel.target = target; - case IPT_SO_GET_ENTRIES: { - struct ipt_get_entries get; + off += xt_compat_target_offset(target); + *size += off; + ret = compat_add_offset(entry_offset, off); + if (ret) + goto out; - if (*len < sizeof(get)) { - duprintf("get_entries: %u < %u\n", *len, sizeof(get)); - ret = -EINVAL; - } else if (copy_from_user(&get, user, sizeof(get)) != 0) { - ret = -EFAULT; - } else if (*len != sizeof(struct ipt_get_entries) + get.size) { - duprintf("get_entries: %u != %u\n", *len, - sizeof(struct ipt_get_entries) + get.size); - ret = -EINVAL; - } else - ret = get_entries(&get, user); - break; + /* Check hooks & underflows */ + for (h = 0; h < NF_IP_NUMHOOKS; h++) { + if ((unsigned char *)e - base == hook_entries[h]) + newinfo->hook_entry[h] = hook_entries[h]; + if ((unsigned char *)e - base == underflows[h]) + newinfo->underflow[h] = underflows[h]; } - default: - duprintf("do_ipt_get_ctl: unknown request %i\n", cmd); - ret = -EINVAL; - } + /* Clear counters and comefrom */ + e->counters = ((struct ipt_counters) { 0, 0 }); + e->comefrom = 0; + + (*i)++; + return 0; +out: + module_put(t->u.kernel.target->me); +cleanup_matches: + IPT_MATCH_ITERATE(e, cleanup_match, &j); return ret; } -/* Registration hooks for targets. */ -int -ipt_register_target(struct ipt_target *target) +static inline int compat_copy_match_from_user(struct ipt_entry_match *m, + void **dstptr, compat_uint_t *size, const char *name, + const struct ipt_ip *ip, unsigned int hookmask) { - int ret; + xt_compat_match_from_user(m, dstptr, size); + return 0; +} - ret = down_interruptible(&ipt_mutex); - if (ret != 0) +static int compat_copy_entry_from_user(struct ipt_entry *e, void **dstptr, + unsigned int *size, const char *name, + struct xt_table_info *newinfo, unsigned char *base) +{ + struct ipt_entry_target *t; + struct ipt_target *target; + struct ipt_entry *de; + unsigned int origsize; + int ret, h; + + ret = 0; + origsize = *size; + de = (struct ipt_entry *)*dstptr; + memcpy(de, e, sizeof(struct ipt_entry)); + + *dstptr += sizeof(struct compat_ipt_entry); + ret = IPT_MATCH_ITERATE(e, compat_copy_match_from_user, dstptr, size, + name, &de->ip, de->comefrom); + if (ret) return ret; + de->target_offset = e->target_offset - (origsize - *size); + t = ipt_get_target(e); + target = t->u.kernel.target; + xt_compat_target_from_user(t, dstptr, size); - if (!list_named_insert(&ipt_target, target)) { - duprintf("ipt_register_target: `%s' already in list!\n", - target->name); - ret = -EINVAL; + de->next_offset = e->next_offset - (origsize - *size); + for (h = 0; h < NF_IP_NUMHOOKS; h++) { + if ((unsigned char *)de - base < newinfo->hook_entry[h]) + newinfo->hook_entry[h] -= origsize - *size; + if ((unsigned char *)de - base < newinfo->underflow[h]) + newinfo->underflow[h] -= origsize - *size; } - up(&ipt_mutex); return ret; } -void -ipt_unregister_target(struct ipt_target *target) +static inline int compat_check_entry(struct ipt_entry *e, const char *name) { - down(&ipt_mutex); - LIST_DELETE(&ipt_target, target); - up(&ipt_mutex); + int ret; + + ret = IPT_MATCH_ITERATE(e, check_match, name, &e->ip, e->comefrom); + if (ret) + return ret; + + return check_target(e, name); } -int -ipt_register_match(struct ipt_match *match) +static int +translate_compat_table(const char *name, + unsigned int valid_hooks, + struct xt_table_info **pinfo, + void **pentry0, + unsigned int total_size, + unsigned int number, + unsigned int *hook_entries, + unsigned int *underflows) { + unsigned int i, j; + struct xt_table_info *newinfo, *info; + void *pos, *entry0, *entry1; + unsigned int size; int ret; - ret = down_interruptible(&ipt_mutex); + info = *pinfo; + entry0 = *pentry0; + size = total_size; + info->number = number; + + /* Init all hooks to impossible value. */ + for (i = 0; i < NF_IP_NUMHOOKS; i++) { + info->hook_entry[i] = 0xFFFFFFFF; + info->underflow[i] = 0xFFFFFFFF; + } + + duprintf("translate_compat_table: size %u\n", info->size); + j = 0; + xt_compat_lock(AF_INET); + /* Walk through entries, checking offsets. */ + ret = IPT_ENTRY_ITERATE(entry0, total_size, + check_compat_entry_size_and_hooks, + info, &size, entry0, + entry0 + total_size, + hook_entries, underflows, &j, name); if (ret != 0) - return ret; + goto out_unlock; - if (!list_named_insert(&ipt_match, match)) { - duprintf("ipt_register_match: `%s' already in list!\n", - match->name); - ret = -EINVAL; + ret = -EINVAL; + if (j != number) { + duprintf("translate_compat_table: %u not %u entries\n", + j, number); + goto out_unlock; } - up(&ipt_mutex); - return ret; -} + /* Check hooks all assigned */ + for (i = 0; i < NF_IP_NUMHOOKS; i++) { + /* Only hooks which are valid */ + if (!(valid_hooks & (1 << i))) + continue; + if (info->hook_entry[i] == 0xFFFFFFFF) { + duprintf("Invalid hook entry %u %u\n", + i, hook_entries[i]); + goto out_unlock; + } + if (info->underflow[i] == 0xFFFFFFFF) { + duprintf("Invalid underflow %u %u\n", + i, underflows[i]); + goto out_unlock; + } + } -void -ipt_unregister_match(struct ipt_match *match) -{ - down(&ipt_mutex); - LIST_DELETE(&ipt_match, match); - up(&ipt_mutex); + ret = -ENOMEM; + newinfo = xt_alloc_table_info(size); + if (!newinfo) + goto out_unlock; + + newinfo->number = number; + for (i = 0; i < NF_IP_NUMHOOKS; i++) { + newinfo->hook_entry[i] = info->hook_entry[i]; + newinfo->underflow[i] = info->underflow[i]; + } + entry1 = newinfo->entries[raw_smp_processor_id()]; + pos = entry1; + size = total_size; + ret = IPT_ENTRY_ITERATE(entry0, total_size, + compat_copy_entry_from_user, &pos, &size, + name, newinfo, entry1); + compat_flush_offsets(); + xt_compat_unlock(AF_INET); + if (ret) + goto free_newinfo; + + ret = -ELOOP; + if (!mark_source_chains(newinfo, valid_hooks, entry1)) + goto free_newinfo; + + ret = IPT_ENTRY_ITERATE(entry1, newinfo->size, compat_check_entry, + name); + if (ret) + goto free_newinfo; + + /* And one copy for every other CPU */ + for_each_possible_cpu(i) + if (newinfo->entries[i] && newinfo->entries[i] != entry1) + memcpy(newinfo->entries[i], entry1, newinfo->size); + + *pinfo = newinfo; + *pentry0 = entry1; + xt_free_table_info(info); + return 0; + +free_newinfo: + xt_free_table_info(newinfo); +out: + IPT_ENTRY_ITERATE(entry0, total_size, cleanup_entry, &j); + return ret; +out_unlock: + compat_flush_offsets(); + xt_compat_unlock(AF_INET); + goto out; } -int ipt_register_table(struct ipt_table *table) +static int +compat_do_replace(void __user *user, unsigned int len) { int ret; - struct ipt_table_info *newinfo; - static struct ipt_table_info bootstrap - = { 0, 0, 0, { 0 }, { 0 }, { } }; + struct compat_ipt_replace tmp; + struct xt_table_info *newinfo; + void *loc_cpu_entry; - newinfo = vmalloc(sizeof(struct ipt_table_info) - + SMP_ALIGN(table->table->size) * NR_CPUS); - if (!newinfo) - return -ENOMEM; + if (copy_from_user(&tmp, user, sizeof(tmp)) != 0) + return -EFAULT; - memcpy(newinfo->entries, table->table->entries, table->table->size); + /* Hack: Causes ipchains to give correct error msg --RR */ + if (len != sizeof(tmp) + tmp.size) + return -ENOPROTOOPT; - ret = translate_table(table->name, table->valid_hooks, - newinfo, table->table->size, - table->table->num_entries, - table->table->hook_entry, - table->table->underflow); - if (ret != 0) { - vfree(newinfo); - return ret; - } + /* overflow check */ + if (tmp.size >= (INT_MAX - sizeof(struct xt_table_info)) / NR_CPUS - + SMP_CACHE_BYTES) + return -ENOMEM; + if (tmp.num_counters >= INT_MAX / sizeof(struct xt_counters)) + return -ENOMEM; - ret = down_interruptible(&ipt_mutex); - if (ret != 0) { - vfree(newinfo); - return ret; - } + newinfo = xt_alloc_table_info(tmp.size); + if (!newinfo) + return -ENOMEM; - /* Don't autoload: we'd eat our tail... */ - if (list_named_find(&ipt_tables, table->name)) { - ret = -EEXIST; - goto free_unlock; + /* choose the copy that is our node/cpu */ + loc_cpu_entry = newinfo->entries[raw_smp_processor_id()]; + if (copy_from_user(loc_cpu_entry, user + sizeof(tmp), + tmp.size) != 0) { + ret = -EFAULT; + goto free_newinfo; } - /* Simplifies replace_table code. */ - table->private = &bootstrap; - if (!replace_table(table, 0, newinfo, &ret)) - goto free_unlock; + ret = translate_compat_table(tmp.name, tmp.valid_hooks, + &newinfo, &loc_cpu_entry, tmp.size, + tmp.num_entries, tmp.hook_entry, tmp.underflow); + if (ret != 0) + goto free_newinfo; - duprintf("table->private->number = %u\n", - table->private->number); - - /* save number of initial entries */ - table->private->initial_entries = table->private->number; + duprintf("compat_do_replace: Translated table\n"); - table->lock = RW_LOCK_UNLOCKED; - list_prepend(&ipt_tables, table); + ret = __do_replace(tmp.name, tmp.valid_hooks, + newinfo, tmp.num_counters, + compat_ptr(tmp.counters)); + if (ret) + goto free_newinfo_untrans; + return 0; - unlock: - up(&ipt_mutex); + free_newinfo_untrans: + IPT_ENTRY_ITERATE(loc_cpu_entry, newinfo->size, cleanup_entry,NULL); + free_newinfo: + xt_free_table_info(newinfo); return ret; - - free_unlock: - vfree(newinfo); - goto unlock; } -void ipt_unregister_table(struct ipt_table *table) +static int +compat_do_ipt_set_ctl(struct sock *sk, int cmd, void __user *user, + unsigned int len) { - down(&ipt_mutex); - LIST_DELETE(&ipt_tables, table); - up(&ipt_mutex); + int ret; - /* Decrease module usage counts and free resources */ - IPT_ENTRY_ITERATE(table->private->entries, table->private->size, - cleanup_entry, NULL); - vfree(table->private); -} + if (!capable(CAP_NET_ADMIN)) + return -EPERM; -/* Returns 1 if the port is matched by the range, 0 otherwise */ -static inline int -port_match(u_int16_t min, u_int16_t max, u_int16_t port, int invert) -{ - int ret; + switch (cmd) { + case IPT_SO_SET_REPLACE: + ret = compat_do_replace(user, len); + break; + + case IPT_SO_SET_ADD_COUNTERS: + ret = do_add_counters(user, len, 1); + break; + + default: + duprintf("do_ipt_set_ctl: unknown request %i\n", cmd); + ret = -EINVAL; + } - ret = (port >= min && port <= max) ^ invert; return ret; } -static int -tcp_find_option(u_int8_t option, - const struct sk_buff *skb, - unsigned int optlen, - int invert, - int *hotdrop) +struct compat_ipt_get_entries { - /* tcp.doff is only 4 bits, ie. max 15 * 4 bytes */ - u_int8_t opt[60 - sizeof(struct tcphdr)]; - unsigned int i; + char name[IPT_TABLE_MAXNAMELEN]; + compat_uint_t size; + struct compat_ipt_entry entrytable[0]; +}; - duprintf("tcp_match: finding option\n"); - /* If we don't have the whole header, drop packet. */ - if (skb_copy_bits(skb, skb->nh.iph->ihl*4 + sizeof(struct tcphdr), - opt, optlen) < 0) { - *hotdrop = 1; - return 0; - } +static int compat_copy_entries_to_user(unsigned int total_size, + struct ipt_table *table, void __user *userptr) +{ + unsigned int off, num; + struct compat_ipt_entry e; + struct xt_counters *counters; + struct xt_table_info *private = table->private; + void __user *pos; + unsigned int size; + int ret = 0; + void *loc_cpu_entry; + + counters = alloc_counters(table); + if (IS_ERR(counters)) + return PTR_ERR(counters); + + /* choose the copy that is on our node/cpu, ... + * This choice is lazy (because current thread is + * allowed to migrate to another cpu) + */ + loc_cpu_entry = private->entries[raw_smp_processor_id()]; + pos = userptr; + size = total_size; + ret = IPT_ENTRY_ITERATE(loc_cpu_entry, total_size, + compat_copy_entry_to_user, &pos, &size); + if (ret) + goto free_counters; - for (i = 0; i < optlen; ) { - if (opt[i] == option) return !invert; - if (opt[i] < 2) i++; - else i += opt[i+1]?:1; - } + /* ... then go back and fix counters and names */ + for (off = 0, num = 0; off < size; off += e.next_offset, num++) { + unsigned int i; + struct ipt_entry_match m; + struct ipt_entry_target t; + + ret = -EFAULT; + if (copy_from_user(&e, userptr + off, + sizeof(struct compat_ipt_entry))) + goto free_counters; + if (copy_to_user(userptr + off + + offsetof(struct compat_ipt_entry, counters), + &counters[num], sizeof(counters[num]))) + goto free_counters; + + for (i = sizeof(struct compat_ipt_entry); + i < e.target_offset; i += m.u.match_size) { + if (copy_from_user(&m, userptr + off + i, + sizeof(struct ipt_entry_match))) + goto free_counters; + if (copy_to_user(userptr + off + i + + offsetof(struct ipt_entry_match, u.user.name), + m.u.kernel.match->name, + strlen(m.u.kernel.match->name) + 1)) + goto free_counters; + } - return invert; + if (copy_from_user(&t, userptr + off + e.target_offset, + sizeof(struct ipt_entry_target))) + goto free_counters; + if (copy_to_user(userptr + off + e.target_offset + + offsetof(struct ipt_entry_target, u.user.name), + t.u.kernel.target->name, + strlen(t.u.kernel.target->name) + 1)) + goto free_counters; + } + ret = 0; +free_counters: + vfree(counters); + return ret; } static int -tcp_match(const struct sk_buff *skb, - const struct net_device *in, - const struct net_device *out, - const void *matchinfo, - int offset, - int *hotdrop) +compat_get_entries(struct compat_ipt_get_entries __user *uptr, int *len) { - struct tcphdr tcph; - const struct ipt_tcp *tcpinfo = matchinfo; - - if (offset) { - /* To quote Alan: - - Don't allow a fragment of TCP 8 bytes in. Nobody normal - causes this. Its a cracker trying to break in by doing a - flag overwrite to pass the direction checks. - */ - if (offset == 1) { - duprintf("Dropping evil TCP offset=1 frag.\n"); - *hotdrop = 1; - } - /* Must not be a fragment. */ - return 0; + int ret; + struct compat_ipt_get_entries get; + struct ipt_table *t; + + + if (*len < sizeof(get)) { + duprintf("compat_get_entries: %u < %u\n", + *len, (unsigned int)sizeof(get)); + return -EINVAL; } -#define FWINVTCP(bool,invflg) ((bool) ^ !!(tcpinfo->invflags & invflg)) + if (copy_from_user(&get, uptr, sizeof(get)) != 0) + return -EFAULT; - if (skb_copy_bits(skb, skb->nh.iph->ihl*4, &tcph, sizeof(tcph)) < 0) { - /* We've been asked to examine this packet, and we - can't. Hence, no choice but to drop. */ - duprintf("Dropping evil TCP offset=0 tinygram.\n"); - *hotdrop = 1; - return 0; + if (*len != sizeof(struct compat_ipt_get_entries) + get.size) { + duprintf("compat_get_entries: %u != %u\n", *len, + (unsigned int)(sizeof(struct compat_ipt_get_entries) + + get.size)); + return -EINVAL; } - if (!port_match(tcpinfo->spts[0], tcpinfo->spts[1], - ntohs(tcph.source), - !!(tcpinfo->invflags & IPT_TCP_INV_SRCPT))) - return 0; - if (!port_match(tcpinfo->dpts[0], tcpinfo->dpts[1], - ntohs(tcph.dest), - !!(tcpinfo->invflags & IPT_TCP_INV_DSTPT))) - return 0; - if (!FWINVTCP((((unsigned char *)&tcph)[13] & tcpinfo->flg_mask) - == tcpinfo->flg_cmp, - IPT_TCP_INV_FLAGS)) - return 0; - if (tcpinfo->option) { - if (tcph.doff * 4 < sizeof(tcph)) { - *hotdrop = 1; - return 0; + xt_compat_lock(AF_INET); + t = xt_find_table_lock(AF_INET, get.name); + if (t && !IS_ERR(t)) { + struct xt_table_info *private = t->private; + struct xt_table_info info; + duprintf("t->private->number = %u\n", + private->number); + ret = compat_table_info(private, &info); + if (!ret && get.size == info.size) { + ret = compat_copy_entries_to_user(private->size, + t, uptr->entrytable); + } else if (!ret) { + duprintf("compat_get_entries: I've got %u not %u!\n", + private->size, + get.size); + ret = -EINVAL; } - if (!tcp_find_option(tcpinfo->option, skb, tcph.doff*4 - sizeof(tcph), - tcpinfo->invflags & IPT_TCP_INV_OPTION, - hotdrop)) - return 0; - } - return 1; + compat_flush_offsets(); + module_put(t->me); + xt_table_unlock(t); + } else + ret = t ? PTR_ERR(t) : -ENOENT; + + xt_compat_unlock(AF_INET); + return ret; } -/* Called when user tries to insert an entry of this type. */ +static int do_ipt_get_ctl(struct sock *, int, void __user *, int *); + static int -tcp_checkentry(const char *tablename, - const struct ipt_ip *ip, - void *matchinfo, - unsigned int matchsize, - unsigned int hook_mask) +compat_do_ipt_get_ctl(struct sock *sk, int cmd, void __user *user, int *len) { - const struct ipt_tcp *tcpinfo = matchinfo; + int ret; + + if (!capable(CAP_NET_ADMIN)) + return -EPERM; - /* Must specify proto == TCP, and no unknown invflags */ - return ip->proto == IPPROTO_TCP - && !(ip->invflags & IPT_INV_PROTO) - && matchsize == IPT_ALIGN(sizeof(struct ipt_tcp)) - && !(tcpinfo->invflags & ~IPT_TCP_INV_MASK); + switch (cmd) { + case IPT_SO_GET_INFO: + ret = get_info(user, len, 1); + break; + case IPT_SO_GET_ENTRIES: + ret = compat_get_entries(user, len); + break; + default: + ret = do_ipt_get_ctl(sk, cmd, user, len); + } + return ret; } +#endif static int -udp_match(const struct sk_buff *skb, - const struct net_device *in, - const struct net_device *out, - const void *matchinfo, - int offset, - int *hotdrop) +do_ipt_set_ctl(struct sock *sk, int cmd, void __user *user, unsigned int len) { - struct udphdr udph; - const struct ipt_udp *udpinfo = matchinfo; + int ret; - /* Must not be a fragment. */ - if (offset) - return 0; + if (!capable(CAP_NET_ADMIN)) + return -EPERM; - if (skb_copy_bits(skb, skb->nh.iph->ihl*4, &udph, sizeof(udph)) < 0) { - /* We've been asked to examine this packet, and we - can't. Hence, no choice but to drop. */ - duprintf("Dropping evil UDP tinygram.\n"); - *hotdrop = 1; - return 0; + switch (cmd) { + case IPT_SO_SET_REPLACE: + ret = do_replace(user, len); + break; + + case IPT_SO_SET_ADD_COUNTERS: + ret = do_add_counters(user, len, 0); + break; + + default: + duprintf("do_ipt_set_ctl: unknown request %i\n", cmd); + ret = -EINVAL; } - return port_match(udpinfo->spts[0], udpinfo->spts[1], - ntohs(udph.source), - !!(udpinfo->invflags & IPT_UDP_INV_SRCPT)) - && port_match(udpinfo->dpts[0], udpinfo->dpts[1], - ntohs(udph.dest), - !!(udpinfo->invflags & IPT_UDP_INV_DSTPT)); + return ret; } -/* Called when user tries to insert an entry of this type. */ static int -udp_checkentry(const char *tablename, - const struct ipt_ip *ip, - void *matchinfo, - unsigned int matchinfosize, - unsigned int hook_mask) +do_ipt_get_ctl(struct sock *sk, int cmd, void __user *user, int *len) { - const struct ipt_udp *udpinfo = matchinfo; + int ret; - /* Must specify proto == UDP, and no unknown invflags */ - if (ip->proto != IPPROTO_UDP || (ip->invflags & IPT_INV_PROTO)) { - duprintf("ipt_udp: Protocol %u != %u\n", ip->proto, - IPPROTO_UDP); - return 0; + if (!capable(CAP_NET_ADMIN)) + return -EPERM; + + switch (cmd) { + case IPT_SO_GET_INFO: + ret = get_info(user, len, 0); + break; + + case IPT_SO_GET_ENTRIES: + ret = get_entries(user, len); + break; + + case IPT_SO_GET_REVISION_MATCH: + case IPT_SO_GET_REVISION_TARGET: { + struct ipt_get_revision rev; + int target; + + if (*len != sizeof(rev)) { + ret = -EINVAL; + break; + } + if (copy_from_user(&rev, user, sizeof(rev)) != 0) { + ret = -EFAULT; + break; + } + + if (cmd == IPT_SO_GET_REVISION_TARGET) + target = 1; + else + target = 0; + + try_then_request_module(xt_find_revision(AF_INET, rev.name, + rev.revision, + target, &ret), + "ipt_%s", rev.name); + break; } - if (matchinfosize != IPT_ALIGN(sizeof(struct ipt_udp))) { - duprintf("ipt_udp: matchsize %u != %u\n", - matchinfosize, IPT_ALIGN(sizeof(struct ipt_udp))); - return 0; + + default: + duprintf("do_ipt_get_ctl: unknown request %i\n", cmd); + ret = -EINVAL; } - if (udpinfo->invflags & ~IPT_UDP_INV_MASK) { - duprintf("ipt_udp: unknown flags %X\n", - udpinfo->invflags); - return 0; + + return ret; +} + +int ipt_register_table(struct xt_table *table, const struct ipt_replace *repl) +{ + int ret; + struct xt_table_info *newinfo; + static struct xt_table_info bootstrap + = { 0, 0, 0, { 0 }, { 0 }, { } }; + void *loc_cpu_entry; + + newinfo = xt_alloc_table_info(repl->size); + if (!newinfo) + return -ENOMEM; + + /* choose the copy on our node/cpu + * but dont care of preemption + */ + loc_cpu_entry = newinfo->entries[raw_smp_processor_id()]; + memcpy(loc_cpu_entry, repl->entries, repl->size); + + ret = translate_table(table->name, table->valid_hooks, + newinfo, loc_cpu_entry, repl->size, + repl->num_entries, + repl->hook_entry, + repl->underflow); + if (ret != 0) { + xt_free_table_info(newinfo); + return ret; } - return 1; + ret = xt_register_table(table, &bootstrap, newinfo); + if (ret != 0) { + xt_free_table_info(newinfo); + return ret; + } + + return 0; +} + +void ipt_unregister_table(struct ipt_table *table) +{ + struct xt_table_info *private; + void *loc_cpu_entry; + + private = xt_unregister_table(table); + + /* Decrease module usage counts and free resources */ + loc_cpu_entry = private->entries[raw_smp_processor_id()]; + IPT_ENTRY_ITERATE(loc_cpu_entry, private->size, cleanup_entry, NULL); + xt_free_table_info(private); } /* Returns 1 if the type and code is matched by the range, 0 otherwise */ @@ -1631,20 +2079,24 @@ static int icmp_match(const struct sk_buff *skb, const struct net_device *in, const struct net_device *out, + const struct xt_match *match, const void *matchinfo, int offset, + unsigned int protoff, int *hotdrop) { - struct icmphdr icmph; + struct icmphdr _icmph, *ic; const struct ipt_icmp *icmpinfo = matchinfo; /* Must not be a fragment. */ if (offset) return 0; - if (skb_copy_bits(skb, skb->nh.iph->ihl*4, &icmph, sizeof(icmph)) < 0){ + ic = skb_header_pointer(skb, protoff, sizeof(_icmph), &_icmph); + if (ic == NULL) { /* We've been asked to examine this packet, and we - can't. Hence, no choice but to drop. */ + * can't. Hence, no choice but to drop. + */ duprintf("Dropping evil ICMP tinygram.\n"); *hotdrop = 1; return 0; @@ -1653,35 +2105,41 @@ icmp_match(const struct sk_buff *skb, return icmp_type_code_match(icmpinfo->type, icmpinfo->code[0], icmpinfo->code[1], - icmph.type, icmph.code, + ic->type, ic->code, !!(icmpinfo->invflags&IPT_ICMP_INV)); } /* Called when user tries to insert an entry of this type. */ static int icmp_checkentry(const char *tablename, - const struct ipt_ip *ip, + const void *info, + const struct xt_match *match, void *matchinfo, - unsigned int matchsize, unsigned int hook_mask) { const struct ipt_icmp *icmpinfo = matchinfo; - /* Must specify proto == ICMP, and no unknown invflags */ - return ip->proto == IPPROTO_ICMP - && !(ip->invflags & IPT_INV_PROTO) - && matchsize == IPT_ALIGN(sizeof(struct ipt_icmp)) - && !(icmpinfo->invflags & ~IPT_ICMP_INV); + /* Must specify no unknown invflags */ + return !(icmpinfo->invflags & ~IPT_ICMP_INV); } /* The built-in targets: standard (NULL) and error. */ static struct ipt_target ipt_standard_target = { .name = IPT_STANDARD_TARGET, + .targetsize = sizeof(int), + .family = AF_INET, +#ifdef CONFIG_COMPAT + .compatsize = sizeof(compat_int_t), + .compat_from_user = compat_standard_from_user, + .compat_to_user = compat_standard_to_user, +#endif }; static struct ipt_target ipt_error_target = { .name = IPT_ERROR_TARGET, .target = ipt_error, + .targetsize = IPT_FUNCTION_MAXNAMELEN, + .family = AF_INET, }; static struct nf_sockopt_ops ipt_sockopts = { @@ -1689,179 +2147,78 @@ static struct nf_sockopt_ops ipt_sockopts = { .set_optmin = IPT_BASE_CTL, .set_optmax = IPT_SO_SET_MAX+1, .set = do_ipt_set_ctl, +#ifdef CONFIG_COMPAT + .compat_set = compat_do_ipt_set_ctl, +#endif .get_optmin = IPT_BASE_CTL, .get_optmax = IPT_SO_GET_MAX+1, .get = do_ipt_get_ctl, -}; - -static struct ipt_match tcp_matchstruct = { - .name = "tcp", - .match = &tcp_match, - .checkentry = &tcp_checkentry, -}; - -static struct ipt_match udp_matchstruct = { - .name = "udp", - .match = &udp_match, - .checkentry = &udp_checkentry, +#ifdef CONFIG_COMPAT + .compat_get = compat_do_ipt_get_ctl, +#endif }; static struct ipt_match icmp_matchstruct = { .name = "icmp", - .match = &icmp_match, - .checkentry = &icmp_checkentry, + .match = icmp_match, + .matchsize = sizeof(struct ipt_icmp), + .proto = IPPROTO_ICMP, + .family = AF_INET, + .checkentry = icmp_checkentry, }; -#ifdef CONFIG_PROC_FS -static inline int print_name(const char *i, - off_t start_offset, char *buffer, int length, - off_t *pos, unsigned int *count) -{ - if ((*count)++ >= start_offset) { - unsigned int namelen; - - namelen = sprintf(buffer + *pos, "%s\n", - i + sizeof(struct list_head)); - if (*pos + namelen > length) { - /* Stop iterating */ - return 1; - } - *pos += namelen; - } - return 0; -} - -static inline int print_target(const struct ipt_target *t, - off_t start_offset, char *buffer, int length, - off_t *pos, unsigned int *count) -{ - if (t == &ipt_standard_target || t == &ipt_error_target) - return 0; - return print_name((char *)t, start_offset, buffer, length, pos, count); -} - -static int ipt_get_tables(char *buffer, char **start, off_t offset, int length) -{ - off_t pos = 0; - unsigned int count = 0; - - if (down_interruptible(&ipt_mutex) != 0) - return 0; - - LIST_FIND(&ipt_tables, print_name, void *, - offset, buffer, length, &pos, &count); - - up(&ipt_mutex); - - /* `start' hack - see fs/proc/generic.c line ~105 */ - *start=(char *)((unsigned long)count-offset); - return pos; -} - -static int ipt_get_targets(char *buffer, char **start, off_t offset, int length) -{ - off_t pos = 0; - unsigned int count = 0; - - if (down_interruptible(&ipt_mutex) != 0) - return 0; - - LIST_FIND(&ipt_target, print_target, struct ipt_target *, - offset, buffer, length, &pos, &count); - - up(&ipt_mutex); - - *start = (char *)((unsigned long)count - offset); - return pos; -} - -static int ipt_get_matches(char *buffer, char **start, off_t offset, int length) -{ - off_t pos = 0; - unsigned int count = 0; - - if (down_interruptible(&ipt_mutex) != 0) - return 0; - - LIST_FIND(&ipt_match, print_name, void *, - offset, buffer, length, &pos, &count); - - up(&ipt_mutex); - - *start = (char *)((unsigned long)count - offset); - return pos; -} - -static struct { char *name; get_info_t *get_info; } ipt_proc_entry[] = -{ { "ip_tables_names", ipt_get_tables }, - { "ip_tables_targets", ipt_get_targets }, - { "ip_tables_matches", ipt_get_matches }, - { NULL, NULL} }; -#endif /*CONFIG_PROC_FS*/ - -static int __init init(void) +static int __init ip_tables_init(void) { int ret; + ret = xt_proto_init(AF_INET); + if (ret < 0) + goto err1; + /* Noone else will be downing sem now, so we won't sleep */ - down(&ipt_mutex); - list_append(&ipt_target, &ipt_standard_target); - list_append(&ipt_target, &ipt_error_target); - list_append(&ipt_match, &tcp_matchstruct); - list_append(&ipt_match, &udp_matchstruct); - list_append(&ipt_match, &icmp_matchstruct); - up(&ipt_mutex); + ret = xt_register_target(&ipt_standard_target); + if (ret < 0) + goto err2; + ret = xt_register_target(&ipt_error_target); + if (ret < 0) + goto err3; + ret = xt_register_match(&icmp_matchstruct); + if (ret < 0) + goto err4; /* Register setsockopt */ ret = nf_register_sockopt(&ipt_sockopts); - if (ret < 0) { - duprintf("Unable to register sockopts.\n"); - return ret; - } + if (ret < 0) + goto err5; -#ifdef CONFIG_PROC_FS - { - struct proc_dir_entry *proc; - int i; - - for (i = 0; ipt_proc_entry[i].name; i++) { - proc = proc_net_create(ipt_proc_entry[i].name, 0, - ipt_proc_entry[i].get_info); - if (!proc) { - while (--i >= 0) - proc_net_remove(ipt_proc_entry[i].name); - nf_unregister_sockopt(&ipt_sockopts); - return -ENOMEM; - } - proc->owner = THIS_MODULE; - } - } -#endif - - printk("ip_tables: (C) 2000-2002 Netfilter core team\n"); + printk("ip_tables: (C) 2000-2006 Netfilter Core Team\n"); return 0; + +err5: + xt_unregister_match(&icmp_matchstruct); +err4: + xt_unregister_target(&ipt_error_target); +err3: + xt_unregister_target(&ipt_standard_target); +err2: + xt_proto_fini(AF_INET); +err1: + return ret; } -static void __exit fini(void) +static void __exit ip_tables_fini(void) { nf_unregister_sockopt(&ipt_sockopts); -#ifdef CONFIG_PROC_FS - { - int i; - for (i = 0; ipt_proc_entry[i].name; i++) - proc_net_remove(ipt_proc_entry[i].name); - } -#endif + + xt_unregister_match(&icmp_matchstruct); + xt_unregister_target(&ipt_error_target); + xt_unregister_target(&ipt_standard_target); + + xt_proto_fini(AF_INET); } EXPORT_SYMBOL(ipt_register_table); EXPORT_SYMBOL(ipt_unregister_table); -EXPORT_SYMBOL(ipt_register_match); -EXPORT_SYMBOL(ipt_unregister_match); EXPORT_SYMBOL(ipt_do_table); -EXPORT_SYMBOL(ipt_register_target); -EXPORT_SYMBOL(ipt_unregister_target); -EXPORT_SYMBOL(ipt_find_target_lock); - -module_init(init); -module_exit(fini); +module_init(ip_tables_init); +module_exit(ip_tables_fini);