* [PATCH net-next 1/3] netns: Remove __peernet2id_alloc()
2020-01-13 21:39 [PATCH net-next 0/3] netns: Optimise netns ID lookups Guillaume Nault
@ 2020-01-13 21:39 ` Guillaume Nault
2020-01-13 21:39 ` [PATCH net-next 2/3] netns: protect netns ID lookups with RCU Guillaume Nault
` (2 subsequent siblings)
3 siblings, 0 replies; 5+ messages in thread
From: Guillaume Nault @ 2020-01-13 21:39 UTC (permalink / raw)
To: David Miller, Jakub Kicinski; +Cc: netdev, Nicolas Dichtel
__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.
Signed-off-by: Guillaume Nault <gnault@redhat.com>
---
net/core/net_namespace.c | 55 +++++++++++++++++-----------------------
1 file changed, 23 insertions(+), 32 deletions(-)
diff --git a/net/core/net_namespace.c b/net/core/net_namespace.c
index 39402840025e..05e07d24b45b 100644
--- a/net/core/net_namespace.c
+++ b/net/core/net_namespace.c
@@ -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);
--
2.21.1
^ permalink raw reply related [flat|nested] 5+ messages in thread
* [PATCH net-next 2/3] netns: protect netns ID lookups with RCU
2020-01-13 21:39 [PATCH net-next 0/3] netns: Optimise netns ID lookups Guillaume Nault
2020-01-13 21:39 ` [PATCH net-next 1/3] netns: Remove __peernet2id_alloc() Guillaume Nault
@ 2020-01-13 21:39 ` Guillaume Nault
2020-01-13 21:39 ` [PATCH net-next 3/3] netns: don't disable BHs when locking "nsid_lock" Guillaume Nault
2020-01-14 19:29 ` [PATCH net-next 0/3] netns: Optimise netns ID lookups David Miller
3 siblings, 0 replies; 5+ messages in thread
From: Guillaume Nault @ 2020-01-13 21:39 UTC (permalink / raw)
To: David Miller, Jakub Kicinski; +Cc: netdev, Nicolas Dichtel
__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.
Signed-off-by: Guillaume Nault <gnault@redhat.com>
---
net/core/net_namespace.c | 22 +++++++---------------
1 file changed, 7 insertions(+), 15 deletions(-)
diff --git a/net/core/net_namespace.c b/net/core/net_namespace.c
index 05e07d24b45b..e7a5ff4966c9 100644
--- a/net/core/net_namespace.c
+++ b/net/core/net_namespace.c
@@ -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:
--
2.21.1
^ permalink raw reply related [flat|nested] 5+ messages in thread
* [PATCH net-next 3/3] netns: don't disable BHs when locking "nsid_lock"
2020-01-13 21:39 [PATCH net-next 0/3] netns: Optimise netns ID lookups Guillaume Nault
2020-01-13 21:39 ` [PATCH net-next 1/3] netns: Remove __peernet2id_alloc() Guillaume Nault
2020-01-13 21:39 ` [PATCH net-next 2/3] netns: protect netns ID lookups with RCU Guillaume Nault
@ 2020-01-13 21:39 ` Guillaume Nault
2020-01-14 19:29 ` [PATCH net-next 0/3] netns: Optimise netns ID lookups David Miller
3 siblings, 0 replies; 5+ messages in thread
From: Guillaume Nault @ 2020-01-13 21:39 UTC (permalink / raw)
To: David Miller, Jakub Kicinski; +Cc: netdev, Nicolas Dichtel
When 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".
Signed-off-by: Guillaume Nault <gnault@redhat.com>
---
net/core/net_namespace.c | 22 +++++++++++-----------
1 file changed, 11 insertions(+), 11 deletions(-)
diff --git a/net/core/net_namespace.c b/net/core/net_namespace.c
index e7a5ff4966c9..6412c1fbfcb5 100644
--- a/net/core/net_namespace.c
+++ b/net/core/net_namespace.c
@@ -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);
--
2.21.1
^ permalink raw reply related [flat|nested] 5+ messages in thread
* Re: [PATCH net-next 0/3] netns: Optimise netns ID lookups
2020-01-13 21:39 [PATCH net-next 0/3] netns: Optimise netns ID lookups Guillaume Nault
` (2 preceding siblings ...)
2020-01-13 21:39 ` [PATCH net-next 3/3] netns: don't disable BHs when locking "nsid_lock" Guillaume Nault
@ 2020-01-14 19:29 ` David Miller
3 siblings, 0 replies; 5+ messages in thread
From: David Miller @ 2020-01-14 19:29 UTC (permalink / raw)
To: gnault; +Cc: jakub.kicinski, netdev, nicolas.dichtel
From: Guillaume Nault <gnault@redhat.com>
Date: Mon, 13 Jan 2020 22:39:19 +0100
> 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.
^ permalink raw reply [flat|nested] 5+ messages in thread