netns: Optimise netns ID lookups
Subsystems (2)
cover netns: Optimise netns ID lookups
Netns ID lookups can be easily protected by RCU, rather than by holding a spinlock.
Patch 1 prepares the code, patch 2 does the RCU conversion, and finally patch 3 stops disabling BHs on updates (patch 2 makes that unnecessary).
Guillaume Nault (3):
netns: Remove __peernet2id_alloc()
netns: protect netns ID lookups with RCU
netns: don't disable BHs when locking "nsid_lock"
net/core/net_namespace.c | 93 ++++++++++++++++------------------------ 1 file changed, 38 insertions(+), 55 deletions(-)
Signature · 1 line
2.21.1
PATCH 1/3 netns: Remove __peernet2id_alloc()
__peernet2id_alloc() was used for both plain lookups and for netns ID allocations (depending the value of '*alloc'). Let's separate lookups from allocations instead. That is, integrate the lookup code into __peernet2id() and make peernet2id_alloc() responsible for allocating new netns IDs when necessary.
This makes it clear that __peernet2id() doesn't modify the idr and prepares the code for lockless lookups.
Also, mark the 'net' argument of __peernet2id() as 'const', since we're modifying this line.
--- net/core/net_namespace.c | 55 +++++++++++++++++----------------------- 1 file changed, 23 insertions(+), 32 deletions(-)
net/core/net_namespace.c +23 -32
@@ -211,16 +211,10 @@ static int net_eq_idr(int id, void *net, void *peer)return 0 ; } -/* Should be called with nsid_lock held. If a new id is assigned, the bool alloc -* is set to true, thus the caller knows that the new id must be notified via -* rtnl. -*/ -static int __peernet2id_alloc (struct net *net,struct net *peer,bool *alloc) +/* Should be called with nsid_lock held. */ +static int __peernet2id (const struct net *net,struct net *peer) {int id =idr_for_each (&net->netns_ids, net_eq_idr, peer); -bool alloc_it = *alloc; - - *alloc = false;/* Magic value for id 0. */ if (id ==NET_ID_ZERO )
@@ -228,23 +222,9 @@ static int __peernet2id_alloc(struct net *net, struct net *peer, bool *alloc)if (id >0 )return id; -if (alloc_it) { - id =alloc_netid (net, peer,-1 ); - *alloc = true; -return id >=0 ? id :NETNSA_NSID_NOT_ASSIGNED ; - } -return NETNSA_NSID_NOT_ASSIGNED ; } -/* should be called with nsid_lock held */ -static int __peernet2id (struct net *net,struct net *peer) -{ -bool no = false; - -return __peernet2id_alloc (net, peer, &no); -} -static void rtnl_net_notifyid (struct net *net,int cmd,int id,u32 portid,struct nlmsghdr *nlh,gfp_t gfp);/* This function returns the id of a peer netns. If no id is assigned, one will
@@ -252,26 +232,37 @@ static void rtnl_net_notifyid(struct net *net, int cmd, int id, u32 portid,*/ int peernet2id_alloc (struct net *net,struct net *peer,gfp_t gfp) { -bool alloc = false, alive = false;int id;if (refcount_read (&net->count) ==0 )return NETNSA_NSID_NOT_ASSIGNED ; +spin_lock_bh (&net->nsid_lock); -/* -* When peer is obtained from RCU lists, we may race with + id =__peernet2id (net, peer); +if (id >=0 ) { +spin_unlock_bh (&net->nsid_lock); +return id; + } + +/* When peer is obtained from RCU lists, we may race with * its cleanup. Check whether it's alive, and this guarantees * we never hash a peer back to net->netns_ids, after it has * just been idr_remove()'d from there in cleanup_net(). */ -if (maybe_get_net (peer)) - alive = alloc = true; - id =__peernet2id_alloc (net, peer, &alloc); +if (!maybe_get_net (peer)) { +spin_unlock_bh (&net->nsid_lock); +return NETNSA_NSID_NOT_ASSIGNED ; + } + + id =alloc_netid (net, peer,-1 );spin_unlock_bh (&net->nsid_lock); -if (alloc && id >=0 ) -rtnl_net_notifyid (net,RTM_NEWNSID , id,0 ,NULL , gfp); -if (alive) -put_net (peer); + +put_net (peer); +if (id <0 ) +return NETNSA_NSID_NOT_ASSIGNED ; + +rtnl_net_notifyid (net,RTM_NEWNSID , id,0 ,NULL , gfp); +return id; }EXPORT_SYMBOL_GPL (peernet2id_alloc);
Signature · 1 line
2.21.1
PATCH 2/3 netns: protect netns ID lookups with RCU
In reply to Guillaume Nault__peernet2id() can be protected by RCU as it only calls idr_for_each(), which is RCU-safe, and never modifies the nsid table.
rtnl_net_dumpid() can also do lockless lookups. It does two nested idr_for_each() calls on nsid tables (one direct call and one indirect call because of rtnl_net_dumpid_one() calling __peernet2id()). The netnsid tables are never updated. Therefore it is safe to not take the nsid_lock and run within an RCU-critical section instead.
--- net/core/net_namespace.c | 22 +++++++--------------- 1 file changed, 7 insertions(+), 15 deletions(-)
net/core/net_namespace.c +7 -15
@@ -211,7 +211,7 @@ static int net_eq_idr(int id, void *net, void *peer)return 0 ; } -/* Should be called with nsid_lock held. */ +/* Must be called from RCU-critical section or with nsid_lock held */ static int __peernet2id (const struct net *net,struct net *peer) {int id =idr_for_each (&net->netns_ids, net_eq_idr, peer);
@@ -272,9 +272,10 @@ int peernet2id(struct net *net, struct net *peer) {int id; -spin_lock_bh (&net->nsid_lock); +rcu_read_lock (); id =__peernet2id (net, peer); -spin_unlock_bh (&net->nsid_lock); +rcu_read_unlock (); +return id; }EXPORT_SYMBOL (peernet2id);
@@ -941,6 +942,7 @@ struct rtnl_net_dump_cb {int s_idx; }; +/* Runs in RCU-critical section. */ static int rtnl_net_dumpid_one (int id,void *peer,void *data) {struct rtnl_net_dump_cb *net_cb = (struct rtnl_net_dump_cb *)data;
@@ -1025,19 +1027,9 @@ static int rtnl_net_dumpid(struct sk_buff *skb, struct netlink_callback *cb) goto end; } -spin_lock_bh (&net_cb.tgt_net->nsid_lock); -if (net_cb.fillargs.add_ref && - !net_eq (net_cb.ref_net, net_cb.tgt_net) && - !spin_trylock_bh (&net_cb.ref_net->nsid_lock)) { -spin_unlock_bh (&net_cb.tgt_net->nsid_lock); - err = -EAGAIN ; - goto end; - } +rcu_read_lock ();idr_for_each (&net_cb.tgt_net->netns_ids, rtnl_net_dumpid_one, &net_cb); -if (net_cb.fillargs.add_ref && - !net_eq (net_cb.ref_net, net_cb.tgt_net)) -spin_unlock_bh (&net_cb.ref_net->nsid_lock); -spin_unlock_bh (&net_cb.tgt_net->nsid_lock); +rcu_read_unlock (); cb->args[0 ] = net_cb.idx; end:
Signature · 1 line
2.21.1
PATCH 3/3 netns: don't disable BHs when locking "nsid_lock"
In reply to Guillaume NaultWhen peernet2id() had to lock "nsid_lock" before iterating through the nsid table, we had to disable BHs, because VXLAN can call peernet2id() from the xmit path:
vxlan_xmit() -> vxlan_fdb_miss() -> vxlan_fdb_notify()
-> __vxlan_fdb_notify() -> vxlan_fdb_info() -> peernet2id().
Now that peernet2id() uses RCU protection, "nsid_lock" isn't used in BH context anymore. Therefore, we can safely use plain spin_lock()/spin_unlock() and let BHs run when holding "nsid_lock".
--- net/core/net_namespace.c | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-)
net/core/net_namespace.c +11 -11
@@ -237,10 +237,10 @@ int peernet2id_alloc(struct net *net, struct net *peer, gfp_t gfp)if (refcount_read (&net->count) ==0 )return NETNSA_NSID_NOT_ASSIGNED ; -spin_lock_bh (&net->nsid_lock); +spin_lock (&net->nsid_lock); id =__peernet2id (net, peer);if (id >=0 ) { -spin_unlock_bh (&net->nsid_lock); +spin_unlock (&net->nsid_lock);return id; }
@@ -250,12 +250,12 @@ int peernet2id_alloc(struct net *net, struct net *peer, gfp_t gfp)* just been idr_remove()'d from there in cleanup_net(). */ if (!maybe_get_net (peer)) { -spin_unlock_bh (&net->nsid_lock); +spin_unlock (&net->nsid_lock);return NETNSA_NSID_NOT_ASSIGNED ; } id =alloc_netid (net, peer,-1 ); -spin_unlock_bh (&net->nsid_lock); +spin_unlock (&net->nsid_lock);put_net (peer);if (id <0 )
@@ -520,20 +520,20 @@ static void unhash_nsid(struct net *net, struct net *last)for_each_net (tmp) {int id; -spin_lock_bh (&tmp->nsid_lock); +spin_lock (&tmp->nsid_lock); id =__peernet2id (tmp, net);if (id >=0 )idr_remove (&tmp->netns_ids, id); -spin_unlock_bh (&tmp->nsid_lock); +spin_unlock (&tmp->nsid_lock);if (id >=0 )rtnl_net_notifyid (tmp,RTM_DELNSID , id,0 ,NULL ,GFP_KERNEL );if (tmp == last)break ; } -spin_lock_bh (&net->nsid_lock); +spin_lock (&net->nsid_lock);idr_destroy (&net->netns_ids); -spin_unlock_bh (&net->nsid_lock); +spin_unlock (&net->nsid_lock); }static LLIST_HEAD (cleanup_list);
@@ -746,9 +746,9 @@ static int rtnl_net_newid(struct sk_buff *skb, struct nlmsghdr *nlh,return PTR_ERR (peer); } -spin_lock_bh (&net->nsid_lock); +spin_lock (&net->nsid_lock);if (__peernet2id (net, peer) >=0 ) { -spin_unlock_bh (&net->nsid_lock); +spin_unlock (&net->nsid_lock); err = -EEXIST ;NL_SET_BAD_ATTR (extack, nla); NL_SET_ERR_MSG(extack,
@@ -757,7 +757,7 @@ static int rtnl_net_newid(struct sk_buff *skb, struct nlmsghdr *nlh, } err =alloc_netid (net, peer, nsid); -spin_unlock_bh (&net->nsid_lock); +spin_unlock (&net->nsid_lock);if (err >=0 ) {rtnl_net_notifyid (net,RTM_NEWNSID , err,NETLINK_CB (skb).portid, nlh,GFP_KERNEL );
Signature · 1 line
2.21.1
David Miller
maintainerFrom: Guillaume Nault <[email protected]>
Date: Mon, 13 Jan 2020 22:39:19 +0100
Quoted 5 lines
Netns ID lookups can be easily protected by RCU, rather than by holding a spinlock.
Patch 1 prepares the code, patch 2 does the RCU conversion, and finally patch 3 stops disabling BHs on updates (patch 2 makes that unnecessary).
Series applied, thanks.