Commit 169af346 authored by David S. Miller's avatar David S. Miller

Merge branch 'netns-Optimise-netns-ID-lookups'

Guillaume Nault says:

====================
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).
====================
Signed-off-by: default avatarDavid S. Miller <davem@davemloft.net>
parents 62140036 8d7e5dee
...@@ -211,16 +211,10 @@ static int net_eq_idr(int id, void *net, void *peer) ...@@ -211,16 +211,10 @@ static int net_eq_idr(int id, void *net, void *peer)
return 0; return 0;
} }
/* Should be called with nsid_lock held. If a new id is assigned, the bool alloc /* Must be called from RCU-critical section or with nsid_lock held */
* is set to true, thus the caller knows that the new id must be notified via static int __peernet2id(const struct net *net, struct net *peer)
* rtnl.
*/
static int __peernet2id_alloc(struct net *net, struct net *peer, bool *alloc)
{ {
int id = idr_for_each(&net->netns_ids, net_eq_idr, peer); int id = idr_for_each(&net->netns_ids, net_eq_idr, peer);
bool alloc_it = *alloc;
*alloc = false;
/* Magic value for id 0. */ /* Magic value for id 0. */
if (id == NET_ID_ZERO) if (id == NET_ID_ZERO)
...@@ -228,23 +222,9 @@ static int __peernet2id_alloc(struct net *net, struct net *peer, bool *alloc) ...@@ -228,23 +222,9 @@ static int __peernet2id_alloc(struct net *net, struct net *peer, bool *alloc)
if (id > 0) if (id > 0)
return id; 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; 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, static void rtnl_net_notifyid(struct net *net, int cmd, int id, u32 portid,
struct nlmsghdr *nlh, gfp_t gfp); struct nlmsghdr *nlh, gfp_t gfp);
/* This function returns the id of a peer netns. If no id is assigned, one will /* 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, ...@@ -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) int peernet2id_alloc(struct net *net, struct net *peer, gfp_t gfp)
{ {
bool alloc = false, alive = false;
int id; int id;
if (refcount_read(&net->count) == 0) if (refcount_read(&net->count) == 0)
return NETNSA_NSID_NOT_ASSIGNED; return NETNSA_NSID_NOT_ASSIGNED;
spin_lock_bh(&net->nsid_lock);
/* spin_lock(&net->nsid_lock);
* When peer is obtained from RCU lists, we may race with id = __peernet2id(net, peer);
if (id >= 0) {
spin_unlock(&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 * its cleanup. Check whether it's alive, and this guarantees
* we never hash a peer back to net->netns_ids, after it has * we never hash a peer back to net->netns_ids, after it has
* just been idr_remove()'d from there in cleanup_net(). * just been idr_remove()'d from there in cleanup_net().
*/ */
if (maybe_get_net(peer)) if (!maybe_get_net(peer)) {
alive = alloc = true; spin_unlock(&net->nsid_lock);
id = __peernet2id_alloc(net, peer, &alloc); return NETNSA_NSID_NOT_ASSIGNED;
spin_unlock_bh(&net->nsid_lock); }
if (alloc && id >= 0)
rtnl_net_notifyid(net, RTM_NEWNSID, id, 0, NULL, gfp); id = alloc_netid(net, peer, -1);
if (alive) spin_unlock(&net->nsid_lock);
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; return id;
} }
EXPORT_SYMBOL_GPL(peernet2id_alloc); EXPORT_SYMBOL_GPL(peernet2id_alloc);
...@@ -281,9 +272,10 @@ int peernet2id(struct net *net, struct net *peer) ...@@ -281,9 +272,10 @@ int peernet2id(struct net *net, struct net *peer)
{ {
int id; int id;
spin_lock_bh(&net->nsid_lock); rcu_read_lock();
id = __peernet2id(net, peer); id = __peernet2id(net, peer);
spin_unlock_bh(&net->nsid_lock); rcu_read_unlock();
return id; return id;
} }
EXPORT_SYMBOL(peernet2id); EXPORT_SYMBOL(peernet2id);
...@@ -528,20 +520,20 @@ static void unhash_nsid(struct net *net, struct net *last) ...@@ -528,20 +520,20 @@ static void unhash_nsid(struct net *net, struct net *last)
for_each_net(tmp) { for_each_net(tmp) {
int id; int id;
spin_lock_bh(&tmp->nsid_lock); spin_lock(&tmp->nsid_lock);
id = __peernet2id(tmp, net); id = __peernet2id(tmp, net);
if (id >= 0) if (id >= 0)
idr_remove(&tmp->netns_ids, id); idr_remove(&tmp->netns_ids, id);
spin_unlock_bh(&tmp->nsid_lock); spin_unlock(&tmp->nsid_lock);
if (id >= 0) if (id >= 0)
rtnl_net_notifyid(tmp, RTM_DELNSID, id, 0, NULL, rtnl_net_notifyid(tmp, RTM_DELNSID, id, 0, NULL,
GFP_KERNEL); GFP_KERNEL);
if (tmp == last) if (tmp == last)
break; break;
} }
spin_lock_bh(&net->nsid_lock); spin_lock(&net->nsid_lock);
idr_destroy(&net->netns_ids); idr_destroy(&net->netns_ids);
spin_unlock_bh(&net->nsid_lock); spin_unlock(&net->nsid_lock);
} }
static LLIST_HEAD(cleanup_list); static LLIST_HEAD(cleanup_list);
...@@ -754,9 +746,9 @@ static int rtnl_net_newid(struct sk_buff *skb, struct nlmsghdr *nlh, ...@@ -754,9 +746,9 @@ static int rtnl_net_newid(struct sk_buff *skb, struct nlmsghdr *nlh,
return PTR_ERR(peer); return PTR_ERR(peer);
} }
spin_lock_bh(&net->nsid_lock); spin_lock(&net->nsid_lock);
if (__peernet2id(net, peer) >= 0) { if (__peernet2id(net, peer) >= 0) {
spin_unlock_bh(&net->nsid_lock); spin_unlock(&net->nsid_lock);
err = -EEXIST; err = -EEXIST;
NL_SET_BAD_ATTR(extack, nla); NL_SET_BAD_ATTR(extack, nla);
NL_SET_ERR_MSG(extack, NL_SET_ERR_MSG(extack,
...@@ -765,7 +757,7 @@ static int rtnl_net_newid(struct sk_buff *skb, struct nlmsghdr *nlh, ...@@ -765,7 +757,7 @@ static int rtnl_net_newid(struct sk_buff *skb, struct nlmsghdr *nlh,
} }
err = alloc_netid(net, peer, nsid); err = alloc_netid(net, peer, nsid);
spin_unlock_bh(&net->nsid_lock); spin_unlock(&net->nsid_lock);
if (err >= 0) { if (err >= 0) {
rtnl_net_notifyid(net, RTM_NEWNSID, err, NETLINK_CB(skb).portid, rtnl_net_notifyid(net, RTM_NEWNSID, err, NETLINK_CB(skb).portid,
nlh, GFP_KERNEL); nlh, GFP_KERNEL);
...@@ -950,6 +942,7 @@ struct rtnl_net_dump_cb { ...@@ -950,6 +942,7 @@ struct rtnl_net_dump_cb {
int s_idx; int s_idx;
}; };
/* Runs in RCU-critical section. */
static int rtnl_net_dumpid_one(int id, void *peer, void *data) 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; struct rtnl_net_dump_cb *net_cb = (struct rtnl_net_dump_cb *)data;
...@@ -1034,19 +1027,9 @@ static int rtnl_net_dumpid(struct sk_buff *skb, struct netlink_callback *cb) ...@@ -1034,19 +1027,9 @@ static int rtnl_net_dumpid(struct sk_buff *skb, struct netlink_callback *cb)
goto end; goto end;
} }
spin_lock_bh(&net_cb.tgt_net->nsid_lock); rcu_read_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;
}
idr_for_each(&net_cb.tgt_net->netns_ids, rtnl_net_dumpid_one, &net_cb); idr_for_each(&net_cb.tgt_net->netns_ids, rtnl_net_dumpid_one, &net_cb);
if (net_cb.fillargs.add_ref && rcu_read_unlock();
!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);
cb->args[0] = net_cb.idx; cb->args[0] = net_cb.idx;
end: end:
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment