netfilter: iptables: lock free counters
[sfrench/cifs-2.6.git] / net / ipv4 / netfilter / arp_tables.c
index b5db46342614a901f0b02c56a99b269590a4cc65..64a7c6ce0b98a8d6dc57f6625c1c001d6dea923d 100644 (file)
@@ -261,9 +261,10 @@ unsigned int arpt_do_table(struct sk_buff *skb,
        indev = in ? in->name : nulldevname;
        outdev = out ? out->name : nulldevname;
 
-       read_lock_bh(&table->lock);
-       private = table->private;
-       table_base = (void *)private->entries[smp_processor_id()];
+       rcu_read_lock();
+       private = rcu_dereference(table->private);
+       table_base = rcu_dereference(private->entries[smp_processor_id()]);
+
        e = get_entry(table_base, private->hook_entry[hook]);
        back = get_entry(table_base, private->underflow[hook]);
 
@@ -335,7 +336,8 @@ unsigned int arpt_do_table(struct sk_buff *skb,
                        e = (void *)e + e->next_offset;
                }
        } while (!hotdrop);
-       read_unlock_bh(&table->lock);
+
+       rcu_read_unlock();
 
        if (hotdrop)
                return NF_DROP;
@@ -738,11 +740,65 @@ static void get_counters(const struct xt_table_info *t,
        }
 }
 
-static inline struct xt_counters *alloc_counters(struct xt_table *table)
+
+/* We're lazy, and add to the first CPU; overflow works its fey magic
+ * and everything is OK. */
+static int
+add_counter_to_entry(struct arpt_entry *e,
+                    const struct xt_counters addme[],
+                    unsigned int *i)
+{
+       ADD_COUNTER(e->counters, addme[*i].bcnt, addme[*i].pcnt);
+
+       (*i)++;
+       return 0;
+}
+
+/* Take values from counters and add them back onto the current cpu */
+static void put_counters(struct xt_table_info *t,
+                        const struct xt_counters counters[])
+{
+       unsigned int i, cpu;
+
+       local_bh_disable();
+       cpu = smp_processor_id();
+       i = 0;
+       ARPT_ENTRY_ITERATE(t->entries[cpu],
+                         t->size,
+                         add_counter_to_entry,
+                         counters,
+                         &i);
+       local_bh_enable();
+}
+
+static inline int
+zero_entry_counter(struct arpt_entry *e, void *arg)
+{
+       e->counters.bcnt = 0;
+       e->counters.pcnt = 0;
+       return 0;
+}
+
+static void
+clone_counters(struct xt_table_info *newinfo, const struct xt_table_info *info)
+{
+       unsigned int cpu;
+       const void *loc_cpu_entry = info->entries[raw_smp_processor_id()];
+
+       memcpy(newinfo, info, offsetof(struct xt_table_info, entries));
+       for_each_possible_cpu(cpu) {
+               memcpy(newinfo->entries[cpu], loc_cpu_entry, info->size);
+               ARPT_ENTRY_ITERATE(newinfo->entries[cpu], newinfo->size,
+                                 zero_entry_counter, NULL);
+       }
+}
+
+static struct xt_counters *alloc_counters(struct xt_table *table)
 {
        unsigned int countersize;
        struct xt_counters *counters;
-       const struct xt_table_info *private = table->private;
+       struct xt_table_info *private = table->private;
+       struct xt_table_info *info;
 
        /* We need atomic snapshot of counters: rest doesn't change
         * (other than comefrom, which userspace doesn't care
@@ -752,14 +808,30 @@ static inline struct xt_counters *alloc_counters(struct xt_table *table)
        counters = vmalloc_node(countersize, numa_node_id());
 
        if (counters == NULL)
-               return ERR_PTR(-ENOMEM);
+               goto nomem;
+
+       info = xt_alloc_table_info(private->size);
+       if (!info)
+               goto free_counters;
 
-       /* First, sum counters... */
-       write_lock_bh(&table->lock);
-       get_counters(private, counters);
-       write_unlock_bh(&table->lock);
+       clone_counters(info, private);
+
+       mutex_lock(&table->lock);
+       xt_table_entry_swap_rcu(private, info);
+       synchronize_net();      /* Wait until smoke has cleared */
+
+       get_counters(info, counters);
+       put_counters(private, counters);
+       mutex_unlock(&table->lock);
+
+       xt_free_table_info(info);
 
        return counters;
+
+ free_counters:
+       vfree(counters);
+ nomem:
+       return ERR_PTR(-ENOMEM);
 }
 
 static int copy_entries_to_user(unsigned int total_size,
@@ -1099,20 +1171,6 @@ static int do_replace(struct net *net, void __user *user, unsigned int len)
        return ret;
 }
 
-/* We're lazy, and add to the first CPU; overflow works its fey magic
- * and everything is OK.
- */
-static inline int add_counter_to_entry(struct arpt_entry *e,
-                                      const struct xt_counters addme[],
-                                      unsigned int *i)
-{
-
-       ADD_COUNTER(e->counters, addme[*i].bcnt, addme[*i].pcnt);
-
-       (*i)++;
-       return 0;
-}
-
 static int do_add_counters(struct net *net, void __user *user, unsigned int len,
                           int compat)
 {
@@ -1172,13 +1230,14 @@ static int do_add_counters(struct net *net, void __user *user, unsigned int len,
                goto free;
        }
 
-       write_lock_bh(&t->lock);
+       mutex_lock(&t->lock);
        private = t->private;
        if (private->number != num_counters) {
                ret = -EINVAL;
                goto unlock_up_free;
        }
 
+       preempt_disable();
        i = 0;
        /* Choose the copy that is on our node */
        loc_cpu_entry = private->entries[smp_processor_id()];
@@ -1187,8 +1246,10 @@ static int do_add_counters(struct net *net, void __user *user, unsigned int len,
                           add_counter_to_entry,
                           paddc,
                           &i);
+       preempt_enable();
  unlock_up_free:
-       write_unlock_bh(&t->lock);
+       mutex_unlock(&t->lock);
+
        xt_table_unlock(t);
        module_put(t->me);
  free: