aboutsummaryrefslogtreecommitdiffstats
path: root/target/linux/generic/backport-4.19/292-v4.16-netfilter-core-free-hooks-with-call_rcu.patch
diff options
context:
space:
mode:
Diffstat (limited to 'target/linux/generic/backport-4.19/292-v4.16-netfilter-core-free-hooks-with-call_rcu.patch')
-rw-r--r--target/linux/generic/backport-4.19/292-v4.16-netfilter-core-free-hooks-with-call_rcu.patch132
1 files changed, 132 insertions, 0 deletions
diff --git a/target/linux/generic/backport-4.19/292-v4.16-netfilter-core-free-hooks-with-call_rcu.patch b/target/linux/generic/backport-4.19/292-v4.16-netfilter-core-free-hooks-with-call_rcu.patch
new file mode 100644
index 0000000000..5eca73552b
--- /dev/null
+++ b/target/linux/generic/backport-4.19/292-v4.16-netfilter-core-free-hooks-with-call_rcu.patch
@@ -0,0 +1,132 @@
+From 8c873e2199700c2de7dbd5eedb9d90d5f109462b Mon Sep 17 00:00:00 2001
+From: Florian Westphal <fw@strlen.de>
+Date: Fri, 1 Dec 2017 00:21:04 +0100
+Subject: [PATCH 04/11] netfilter: core: free hooks with call_rcu
+
+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 | 19 +++++++++++++++----
+ net/netfilter/core.c | 34 ++++++++++++++++++++++++++++------
+ 2 files changed, 43 insertions(+), 10 deletions(-)
+
+--- a/include/linux/netfilter.h
++++ b/include/linux/netfilter.h
+@@ -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
+ */
+ };
+
+--- a/net/netfilter/core.c
++++ b/net/netfilter/core.c
+@@ -74,7 +74,8 @@ static struct nf_hook_entries *allocate_
+ 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_
+ 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
+ #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 *
+ if (!p)
+ return;
+
+- synchronize_net();
+-
+ nf_queue_nf_hook_drop(net);
+- kvfree(p);
++ nf_hook_entries_free(p);
+ }
+ EXPORT_SYMBOL(nf_unregister_net_hook);
+