rtnetlink: Protect struct rtnl_af_ops with SRCU.
Once RTNL is replaced with rtnl_net_lock(), we need a mechanism to guarantee that rtnl_af_ops is alive during inflight RTM_SETLINK even when its module is being unloaded. Let's use SRCU to protect ops. rtnl_af_lookup() now iterates rtnl_af_ops under RCU and returns SRCU-protected ops pointer. The caller must call rtnl_af_put() to release the pointer after the use. Also, rtnl_af_unregister() unlinks the ops first and calls synchronize_srcu() to wait for inflight RTM_SETLINK requests to complete. Note that rtnl_af_ops needs to be protected by its dedicated lock when RTNL is removed. Note also that BUG_ON() in do_setlink() is changed to the normal error handling as a different af_ops might be found after validate_linkmsg(). Signed-off-by: Kuniyuki Iwashima <kuniyu@amazon.com> Signed-off-by: Paolo Abeni <pabeni@redhat.com>
This commit is contained in:
committed by
Paolo Abeni
parent
26eebdc4b0
commit
6ab0f86694
@@ -172,7 +172,8 @@ void rtnl_link_unregister(struct rtnl_link_ops *ops);
|
||||
/**
|
||||
* struct rtnl_af_ops - rtnetlink address family operations
|
||||
*
|
||||
* @list: Used internally
|
||||
* @list: Used internally, protected by RTNL and SRCU
|
||||
* @srcu: Used internally
|
||||
* @family: Address family
|
||||
* @fill_link_af: Function to fill IFLA_AF_SPEC with address family
|
||||
* specific netlink attributes.
|
||||
@@ -185,6 +186,8 @@ void rtnl_link_unregister(struct rtnl_link_ops *ops);
|
||||
*/
|
||||
struct rtnl_af_ops {
|
||||
struct list_head list;
|
||||
struct srcu_struct srcu;
|
||||
|
||||
int family;
|
||||
|
||||
int (*fill_link_af)(struct sk_buff *skb,
|
||||
|
||||
@@ -666,18 +666,31 @@ static size_t rtnl_link_get_size(const struct net_device *dev)
|
||||
|
||||
static LIST_HEAD(rtnl_af_ops);
|
||||
|
||||
static const struct rtnl_af_ops *rtnl_af_lookup(const int family)
|
||||
static struct rtnl_af_ops *rtnl_af_lookup(const int family, int *srcu_index)
|
||||
{
|
||||
const struct rtnl_af_ops *ops;
|
||||
struct rtnl_af_ops *ops;
|
||||
|
||||
ASSERT_RTNL();
|
||||
|
||||
list_for_each_entry(ops, &rtnl_af_ops, list) {
|
||||
if (ops->family == family)
|
||||
return ops;
|
||||
rcu_read_lock();
|
||||
|
||||
list_for_each_entry_rcu(ops, &rtnl_af_ops, list) {
|
||||
if (ops->family == family) {
|
||||
*srcu_index = srcu_read_lock(&ops->srcu);
|
||||
goto unlock;
|
||||
}
|
||||
}
|
||||
|
||||
return NULL;
|
||||
ops = NULL;
|
||||
unlock:
|
||||
rcu_read_unlock();
|
||||
|
||||
return ops;
|
||||
}
|
||||
|
||||
static void rtnl_af_put(struct rtnl_af_ops *ops, int srcu_index)
|
||||
{
|
||||
srcu_read_unlock(&ops->srcu, srcu_index);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -688,6 +701,11 @@ static const struct rtnl_af_ops *rtnl_af_lookup(const int family)
|
||||
*/
|
||||
int rtnl_af_register(struct rtnl_af_ops *ops)
|
||||
{
|
||||
int err = init_srcu_struct(&ops->srcu);
|
||||
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
rtnl_lock();
|
||||
list_add_tail_rcu(&ops->list, &rtnl_af_ops);
|
||||
rtnl_unlock();
|
||||
@@ -707,6 +725,8 @@ void rtnl_af_unregister(struct rtnl_af_ops *ops)
|
||||
rtnl_unlock();
|
||||
|
||||
synchronize_rcu();
|
||||
synchronize_srcu(&ops->srcu);
|
||||
cleanup_srcu_struct(&ops->srcu);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(rtnl_af_unregister);
|
||||
|
||||
@@ -2579,20 +2599,24 @@ static int validate_linkmsg(struct net_device *dev, struct nlattr *tb[],
|
||||
int rem, err;
|
||||
|
||||
nla_for_each_nested(af, tb[IFLA_AF_SPEC], rem) {
|
||||
const struct rtnl_af_ops *af_ops;
|
||||
struct rtnl_af_ops *af_ops;
|
||||
int af_ops_srcu_index;
|
||||
|
||||
af_ops = rtnl_af_lookup(nla_type(af));
|
||||
af_ops = rtnl_af_lookup(nla_type(af), &af_ops_srcu_index);
|
||||
if (!af_ops)
|
||||
return -EAFNOSUPPORT;
|
||||
|
||||
if (!af_ops->set_link_af)
|
||||
return -EOPNOTSUPP;
|
||||
|
||||
if (af_ops->validate_link_af) {
|
||||
err = -EOPNOTSUPP;
|
||||
else if (af_ops->validate_link_af)
|
||||
err = af_ops->validate_link_af(dev, af, extack);
|
||||
if (err < 0)
|
||||
return err;
|
||||
}
|
||||
else
|
||||
err = 0;
|
||||
|
||||
rtnl_af_put(af_ops, af_ops_srcu_index);
|
||||
|
||||
if (err < 0)
|
||||
return err;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3172,11 +3196,18 @@ static int do_setlink(const struct sk_buff *skb, struct net_device *dev,
|
||||
int rem;
|
||||
|
||||
nla_for_each_nested(af, tb[IFLA_AF_SPEC], rem) {
|
||||
const struct rtnl_af_ops *af_ops;
|
||||
struct rtnl_af_ops *af_ops;
|
||||
int af_ops_srcu_index;
|
||||
|
||||
BUG_ON(!(af_ops = rtnl_af_lookup(nla_type(af))));
|
||||
af_ops = rtnl_af_lookup(nla_type(af), &af_ops_srcu_index);
|
||||
if (!af_ops) {
|
||||
err = -EAFNOSUPPORT;
|
||||
goto errout;
|
||||
}
|
||||
|
||||
err = af_ops->set_link_af(dev, af, extack);
|
||||
rtnl_af_put(af_ops, af_ops_srcu_index);
|
||||
|
||||
if (err < 0)
|
||||
goto errout;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user