netfilter: reduce netns create/delete cost
Subsystems (2)
Florian Westphal
maintainercover netfilter: reduce netns create/delete cost
This patch series removes all synchronize_net() calls from netfilter core to speed up net namespace create/delete rate.
Freeing of hooks is moved to call_rcu at the cost of additional 24 bytes at the end of each rule blob.
include/linux/netfilter.h | 19 +++++-- include/net/netfilter/nf_queue.h | 2 net/netfilter/core.c | 99 ++++++++++++--------------------------- net/netfilter/nf_internals.h | 2 net/netfilter/nf_queue.c | 7 -- net/netfilter/nfnetlink_queue.c | 9 --- 6 files changed, 53 insertions(+), 85 deletions(-)
Florian Westphal
maintainerPATCH 1/3 netfilter: core: make nf_unregister_net_hooks simple wrapper again
This reverts commit d3ad2c17b4047
("netfilter: core: batch nf_unregister_net_hooks synchronize_net calls").
Nothing wrong with it. However, followup patch will delay freeing of hooks with call_rcu, so all synchronize_net() calls become obsolete and there is no need anymore for this batching.
This revert causes a temporary performance degradation when destroying network namespace, but its resolved with the upcoming call_rcu conversion.
--- net/netfilter/core.c | 59 +++------------------------------------------------- 1 file changed, 3 insertions(+), 56 deletions(-)
net/netfilter/core.c +3 -56
@@ -395,63 +395,10 @@ EXPORT_SYMBOL(nf_register_net_hooks);void nf_unregister_net_hooks (struct net *net,const struct nf_hook_ops *reg,unsigned int hookcount) { -struct nf_hook_entries *to_free[16 ], *p; -struct nf_hook_entries __rcu **pp; -unsigned int i, j, n; - -mutex_lock (&nf_hook_mutex); -for (i =0 ; i < hookcount; i++) { - pp =nf_hook_entry_head (net, ®[i]); -if (!pp) -continue ; - - p =nf_entry_dereference (*pp); -if (WARN_ON_ONCE (!p)) -continue ; -__nf_unregister_net_hook (p, ®[i]); - } -mutex_unlock (&nf_hook_mutex); - -do { - n = min_t(unsigned int, hookcount, ARRAY_SIZE(to_free)); - -mutex_lock (&nf_hook_mutex); - -for (i =0 , j =0 ; i < hookcount && j < n; i++) { - pp =nf_hook_entry_head (net, ®[i]); -if (!pp) -continue ; - - p =nf_entry_dereference (*pp); -if (!p) -continue ; - - to_free[j] =__nf_hook_entries_try_shrink (pp); -if (to_free[j]) - ++j; - } - -mutex_unlock (&nf_hook_mutex); - -if (j) { -unsigned int nfq; - -synchronize_net (); - -/* need 2nd synchronize_net() if nfqueue is used, skb -* can get reinjected right before nf_queue_hook_drop() -*/ - nfq =nf_queue_nf_hook_drop (net); -if (nfq) -synchronize_net (); - -for (i =0 ; i < j; i++) -kvfree (to_free[i]); - } +unsigned int i; - reg += n; - hookcount -= n; - }while (hookcount >0 ); +for (i =0 ; i < hookcount; i++) +nf_unregister_net_hook (net, ®[i]); }EXPORT_SYMBOL (nf_unregister_net_hooks);
Signature · 1 line
2.13.6
Florian Westphal
maintainerPATCH 2/3 netfilter: core: remove synchronize_net call if nfqueue is used
In reply to Florian Westphalsince commit 960632ece6949b ("netfilter: convert hook list to an array") nfqueue no longer stores a pointer to the hook that caused the packet to be queued. Therefore no extra synchronize_net() call is needed after dropping the packets enqueued by the old rule blob.
--- include/net/netfilter/nf_queue.h | 2 +- net/netfilter/core.c | 6 +----- net/netfilter/nf_internals.h | 2 +- net/netfilter/nf_queue.c | 7 ++----- net/netfilter/nfnetlink_queue.c | 9 ++------- 5 files changed, 7 insertions(+), 19 deletions(-)
include/net/netfilter/nf_queue.h +1 -1
@@ -25,7 +25,7 @@ struct nf_queue_entry {struct nf_queue_handler {int (*outfn)(struct nf_queue_entry *entry,unsigned int queuenum); -unsigned int (*nf_hook_drop)(struct net *net); +void (*nf_hook_drop)(struct net *net); };void nf_register_queue_handler (struct net *net,const struct nf_queue_handler *qh);
net/netfilter/core.c +1 -5
@@ -341,7 +341,6 @@ void nf_unregister_net_hook(struct net *net, const struct nf_hook_ops *reg) {struct nf_hook_entries __rcu **pp;struct nf_hook_entries *p; -unsigned int nfq; pp =nf_hook_entry_head (net, reg);if (!pp)
@@ -364,10 +363,7 @@ void nf_unregister_net_hook(struct net *net, const struct nf_hook_ops *reg)synchronize_net (); -/* other cpu might still process nfqueue verdict that used reg */ - nfq =nf_queue_nf_hook_drop (net); -if (nfq) -synchronize_net (); +nf_queue_nf_hook_drop (net);kvfree (p); }EXPORT_SYMBOL (nf_unregister_net_hook);
net/netfilter/nf_internals.h +1 -1
@@ -10,7 +10,7 @@int nf_queue (struct sk_buff *skb,struct nf_hook_state *state,const struct nf_hook_entries *entries,unsigned int index,unsigned int verdict); -unsigned int nf_queue_nf_hook_drop (struct net *net); +void nf_queue_nf_hook_drop (struct net *net);/* nf_log.c */ int __init netfilter_log_init (void );
net/netfilter/nf_queue.c +2 -5
@@ -96,18 +96,15 @@ void nf_queue_entry_get_refs(struct nf_queue_entry *entry) }EXPORT_SYMBOL_GPL (nf_queue_entry_get_refs); -unsigned int nf_queue_nf_hook_drop (struct net *net) +void nf_queue_nf_hook_drop (struct net *net) {const struct nf_queue_handler *qh; -unsigned int count =0 ;rcu_read_lock (); qh =rcu_dereference (net->nf.queue_handler);if (qh) - count = qh->nf_hook_drop (net); + qh->nf_hook_drop (net);rcu_read_unlock (); - -return count; }EXPORT_SYMBOL_GPL (nf_queue_nf_hook_drop);
net/netfilter/nfnetlink_queue.c +2 -7
@@ -941,23 +941,18 @@ static struct notifier_block nfqnl_dev_notifier = { .notifier_call = nfqnl_rcv_dev_event, }; -static unsigned int nfqnl_nf_hook_drop (struct net *net) +static void nfqnl_nf_hook_drop (struct net *net) {struct nfnl_queue_net *q =nfnl_queue_pernet (net); -unsigned int instances =0 ;int i;for (i =0 ; i <INSTANCE_BUCKETS ; i++) {struct nfqnl_instance *inst;struct hlist_head *head = &q->instance_table[i]; -hlist_for_each_entry_rcu (inst, head, hlist) { +hlist_for_each_entry_rcu (inst, head, hlist)nfqnl_flush (inst,NULL ,0 ); - instances++; - } } - -return instances; } static int
Signature · 1 line
2.13.6
Florian Westphal
maintainerPATCH 3/3 netfilter: core: free hooks with call_rcu
In reply to Florian WestphalGiuseppe 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.
--- include/linux/netfilter.h | 19 +++++++++++++++---- net/netfilter/core.c | 34 ++++++++++++++++++++++++++++------ 2 files changed, 43 insertions(+), 10 deletions(-)
include/linux/netfilter.h +15 -4
@@ -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 */ };
net/netfilter/core.c +28 -6
@@ -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);
Signature · 1 line
2.13.6
Pablo Neira Ayuso
maintainerQuoted 5 lines · Florian Westphal
This patch series removes all
synchronize_net()calls from netfilter core to speed up net namespace create/delete rate.Freeing of hooks is moved to
call_rcuat the cost of additional 24 bytes at the end of each rule blob.
Series applied, thanks Florian.