netfilter: core: free hooks with call_rcu
authorFlorian Westphal <fw@strlen.de>
Thu, 30 Nov 2017 23:21:04 +0000 (00:21 +0100)
committerPablo Neira Ayuso <pablo@netfilter.org>
Mon, 8 Jan 2018 17:01:07 +0000 (18:01 +0100)
Giuseppe Scrivano says:
  "SELinux, if enabled, registers for each new network namespace 6
    netfilter hooks."

Cost for this is high.  With synchronize_net() removed:
   "The net benefit on an SMP machine with two cores is that creating a
   new network namespace takes -40% of the original time."

This patch replaces synchronize_net+kvfree with call_rcu().
We store rcu_head at the tail of a structure that has no fixed layout,
i.e. we cannot use offsetof() to compute the start of the original
allocation.  Thus store this information right after the rcu head.

We could simplify this by just placing the rcu_head at the start
of struct nf_hook_entries.  However, this structure is used in
packet processing hotpath, so only place what is needed for that
at the beginning of the struct.

Reported-by: Giuseppe Scrivano <gscrivan@redhat.com>
Signed-off-by: Florian Westphal <fw@strlen.de>
Signed-off-by: Pablo Neira Ayuso <pablo@netfilter.org>
include/linux/netfilter.h
net/netfilter/core.c

index b24e9b10165124121359d206b74c54b430a483d9..792f6d53570735544b8fa406d07c10de638b127b 100644 (file)
@@ -77,17 +77,28 @@ struct nf_hook_entry {
        void                            *priv;
 };
 
+struct nf_hook_entries_rcu_head {
+       struct rcu_head head;
+       void    *allocation;
+};
+
 struct nf_hook_entries {
        u16                             num_hook_entries;
        /* padding */
        struct nf_hook_entry            hooks[];
 
-       /* trailer: pointers to original orig_ops of each hook.
-        *
-        * This is not part of struct nf_hook_entry since its only
-        * needed in slow path (hook register/unregister).
+       /* trailer: pointers to original orig_ops of each hook,
+        * followed by rcu_head and scratch space used for freeing
+        * the structure via call_rcu.
         *
+        *   This is not part of struct nf_hook_entry since its only
+        *   needed in slow path (hook register/unregister):
         * const struct nf_hook_ops     *orig_ops[]
+        *
+        *   For the same reason, we store this at end -- its
+        *   only needed when a hook is deleted, not during
+        *   packet path processing:
+        * struct nf_hook_entries_rcu_head     head
         */
 };
 
index 9a84b6cb99e6770030c300330599c7f4ea773839..6921f9f1cc813c3aaeccd862e57211d90230772a 100644 (file)
@@ -74,7 +74,8 @@ static struct nf_hook_entries *allocate_hook_entries_size(u16 num)
        struct nf_hook_entries *e;
        size_t alloc = sizeof(*e) +
                       sizeof(struct nf_hook_entry) * num +
-                      sizeof(struct nf_hook_ops *) * num;
+                      sizeof(struct nf_hook_ops *) * num +
+                      sizeof(struct nf_hook_entries_rcu_head);
 
        if (num == 0)
                return NULL;
@@ -85,6 +86,30 @@ static struct nf_hook_entries *allocate_hook_entries_size(u16 num)
        return e;
 }
 
+static void __nf_hook_entries_free(struct rcu_head *h)
+{
+       struct nf_hook_entries_rcu_head *head;
+
+       head = container_of(h, struct nf_hook_entries_rcu_head, head);
+       kvfree(head->allocation);
+}
+
+static void nf_hook_entries_free(struct nf_hook_entries *e)
+{
+       struct nf_hook_entries_rcu_head *head;
+       struct nf_hook_ops **ops;
+       unsigned int num;
+
+       if (!e)
+               return;
+
+       num = e->num_hook_entries;
+       ops = nf_hook_entries_get_hook_ops(e);
+       head = (void *)&ops[num];
+       head->allocation = e;
+       call_rcu(&head->head, __nf_hook_entries_free);
+}
+
 static unsigned int accept_all(void *priv,
                               struct sk_buff *skb,
                               const struct nf_hook_state *state)
@@ -291,9 +316,8 @@ int nf_register_net_hook(struct net *net, const struct nf_hook_ops *reg)
 #ifdef HAVE_JUMP_LABEL
        static_key_slow_inc(&nf_hooks_needed[reg->pf][reg->hooknum]);
 #endif
-       synchronize_net();
        BUG_ON(p == new_hooks);
-       kvfree(p);
+       nf_hook_entries_free(p);
        return 0;
 }
 EXPORT_SYMBOL(nf_register_net_hook);
@@ -361,10 +385,8 @@ void nf_unregister_net_hook(struct net *net, const struct nf_hook_ops *reg)
        if (!p)
                return;
 
-       synchronize_net();
-
        nf_queue_nf_hook_drop(net);
-       kvfree(p);
+       nf_hook_entries_free(p);
 }
 EXPORT_SYMBOL(nf_unregister_net_hook);