Commit e368fdb6 authored by Vlad Buslov's avatar Vlad Buslov Committed by David S. Miller

net: sched: use Qdisc rcu API instead of relying on rtnl lock

As a preparation from removing rtnl lock dependency from rules update path,
use Qdisc rcu and reference counting capabilities instead of relying on
rtnl lock while working with Qdiscs. Create new tcf_block_release()
function, and use it to free resources taken by tcf_block_find().
Currently, this function only releases Qdisc and it is extended in next
patches in this series.
Signed-off-by: default avatarVlad Buslov <vladbu@mellanox.com>
Acked-by: default avatarJiri Pirko <jiri@mellanox.com>
Signed-off-by: default avatarDavid S. Miller <davem@davemloft.net>
parent 9d7e82ce
...@@ -537,6 +537,7 @@ static struct tcf_block *tcf_block_find(struct net *net, struct Qdisc **q, ...@@ -537,6 +537,7 @@ static struct tcf_block *tcf_block_find(struct net *net, struct Qdisc **q,
struct netlink_ext_ack *extack) struct netlink_ext_ack *extack)
{ {
struct tcf_block *block; struct tcf_block *block;
int err = 0;
if (ifindex == TCM_IFINDEX_MAGIC_BLOCK) { if (ifindex == TCM_IFINDEX_MAGIC_BLOCK) {
block = tcf_block_lookup(net, block_index); block = tcf_block_lookup(net, block_index);
...@@ -548,55 +549,93 @@ static struct tcf_block *tcf_block_find(struct net *net, struct Qdisc **q, ...@@ -548,55 +549,93 @@ static struct tcf_block *tcf_block_find(struct net *net, struct Qdisc **q,
const struct Qdisc_class_ops *cops; const struct Qdisc_class_ops *cops;
struct net_device *dev; struct net_device *dev;
rcu_read_lock();
/* Find link */ /* Find link */
dev = __dev_get_by_index(net, ifindex); dev = dev_get_by_index_rcu(net, ifindex);
if (!dev) if (!dev) {
rcu_read_unlock();
return ERR_PTR(-ENODEV); return ERR_PTR(-ENODEV);
}
/* Find qdisc */ /* Find qdisc */
if (!*parent) { if (!*parent) {
*q = dev->qdisc; *q = dev->qdisc;
*parent = (*q)->handle; *parent = (*q)->handle;
} else { } else {
*q = qdisc_lookup(dev, TC_H_MAJ(*parent)); *q = qdisc_lookup_rcu(dev, TC_H_MAJ(*parent));
if (!*q) { if (!*q) {
NL_SET_ERR_MSG(extack, "Parent Qdisc doesn't exists"); NL_SET_ERR_MSG(extack, "Parent Qdisc doesn't exists");
return ERR_PTR(-EINVAL); err = -EINVAL;
goto errout_rcu;
} }
} }
*q = qdisc_refcount_inc_nz(*q);
if (!*q) {
NL_SET_ERR_MSG(extack, "Parent Qdisc doesn't exists");
err = -EINVAL;
goto errout_rcu;
}
/* Is it classful? */ /* Is it classful? */
cops = (*q)->ops->cl_ops; cops = (*q)->ops->cl_ops;
if (!cops) { if (!cops) {
NL_SET_ERR_MSG(extack, "Qdisc not classful"); NL_SET_ERR_MSG(extack, "Qdisc not classful");
return ERR_PTR(-EINVAL); err = -EINVAL;
goto errout_rcu;
} }
if (!cops->tcf_block) { if (!cops->tcf_block) {
NL_SET_ERR_MSG(extack, "Class doesn't support blocks"); NL_SET_ERR_MSG(extack, "Class doesn't support blocks");
return ERR_PTR(-EOPNOTSUPP); err = -EOPNOTSUPP;
goto errout_rcu;
} }
/* At this point we know that qdisc is not noop_qdisc,
* which means that qdisc holds a reference to net_device
* and we hold a reference to qdisc, so it is safe to release
* rcu read lock.
*/
rcu_read_unlock();
/* Do we search for filter, attached to class? */ /* Do we search for filter, attached to class? */
if (TC_H_MIN(*parent)) { if (TC_H_MIN(*parent)) {
*cl = cops->find(*q, *parent); *cl = cops->find(*q, *parent);
if (*cl == 0) { if (*cl == 0) {
NL_SET_ERR_MSG(extack, "Specified class doesn't exist"); NL_SET_ERR_MSG(extack, "Specified class doesn't exist");
return ERR_PTR(-ENOENT); err = -ENOENT;
goto errout_qdisc;
} }
} }
/* And the last stroke */ /* And the last stroke */
block = cops->tcf_block(*q, *cl, extack); block = cops->tcf_block(*q, *cl, extack);
if (!block) if (!block) {
return ERR_PTR(-EINVAL); err = -EINVAL;
goto errout_qdisc;
}
if (tcf_block_shared(block)) { if (tcf_block_shared(block)) {
NL_SET_ERR_MSG(extack, "This filter block is shared. Please use the block index to manipulate the filters"); NL_SET_ERR_MSG(extack, "This filter block is shared. Please use the block index to manipulate the filters");
return ERR_PTR(-EOPNOTSUPP); err = -EOPNOTSUPP;
goto errout_qdisc;
} }
} }
return block; return block;
errout_rcu:
rcu_read_unlock();
errout_qdisc:
if (*q)
qdisc_put(*q);
return ERR_PTR(err);
}
static void tcf_block_release(struct Qdisc *q, struct tcf_block *block)
{
if (q)
qdisc_put(q);
} }
struct tcf_block_owner_item { struct tcf_block_owner_item {
...@@ -1332,6 +1371,7 @@ static int tc_new_tfilter(struct sk_buff *skb, struct nlmsghdr *n, ...@@ -1332,6 +1371,7 @@ static int tc_new_tfilter(struct sk_buff *skb, struct nlmsghdr *n,
errout: errout:
if (chain) if (chain)
tcf_chain_put(chain); tcf_chain_put(chain);
tcf_block_release(q, block);
if (err == -EAGAIN) if (err == -EAGAIN)
/* Replay the request. */ /* Replay the request. */
goto replay; goto replay;
...@@ -1453,6 +1493,7 @@ static int tc_del_tfilter(struct sk_buff *skb, struct nlmsghdr *n, ...@@ -1453,6 +1493,7 @@ static int tc_del_tfilter(struct sk_buff *skb, struct nlmsghdr *n,
errout: errout:
if (chain) if (chain)
tcf_chain_put(chain); tcf_chain_put(chain);
tcf_block_release(q, block);
return err; return err;
} }
...@@ -1538,6 +1579,7 @@ static int tc_get_tfilter(struct sk_buff *skb, struct nlmsghdr *n, ...@@ -1538,6 +1579,7 @@ static int tc_get_tfilter(struct sk_buff *skb, struct nlmsghdr *n,
errout: errout:
if (chain) if (chain)
tcf_chain_put(chain); tcf_chain_put(chain);
tcf_block_release(q, block);
return err; return err;
} }
...@@ -1854,7 +1896,8 @@ static int tc_ctl_chain(struct sk_buff *skb, struct nlmsghdr *n, ...@@ -1854,7 +1896,8 @@ static int tc_ctl_chain(struct sk_buff *skb, struct nlmsghdr *n,
chain_index = tca[TCA_CHAIN] ? nla_get_u32(tca[TCA_CHAIN]) : 0; chain_index = tca[TCA_CHAIN] ? nla_get_u32(tca[TCA_CHAIN]) : 0;
if (chain_index > TC_ACT_EXT_VAL_MASK) { if (chain_index > TC_ACT_EXT_VAL_MASK) {
NL_SET_ERR_MSG(extack, "Specified chain index exceeds upper limit"); NL_SET_ERR_MSG(extack, "Specified chain index exceeds upper limit");
return -EINVAL; err = -EINVAL;
goto errout_block;
} }
chain = tcf_chain_lookup(block, chain_index); chain = tcf_chain_lookup(block, chain_index);
if (n->nlmsg_type == RTM_NEWCHAIN) { if (n->nlmsg_type == RTM_NEWCHAIN) {
...@@ -1866,23 +1909,27 @@ static int tc_ctl_chain(struct sk_buff *skb, struct nlmsghdr *n, ...@@ -1866,23 +1909,27 @@ static int tc_ctl_chain(struct sk_buff *skb, struct nlmsghdr *n,
tcf_chain_hold(chain); tcf_chain_hold(chain);
} else { } else {
NL_SET_ERR_MSG(extack, "Filter chain already exists"); NL_SET_ERR_MSG(extack, "Filter chain already exists");
return -EEXIST; err = -EEXIST;
goto errout_block;
} }
} else { } else {
if (!(n->nlmsg_flags & NLM_F_CREATE)) { if (!(n->nlmsg_flags & NLM_F_CREATE)) {
NL_SET_ERR_MSG(extack, "Need both RTM_NEWCHAIN and NLM_F_CREATE to create a new chain"); NL_SET_ERR_MSG(extack, "Need both RTM_NEWCHAIN and NLM_F_CREATE to create a new chain");
return -ENOENT; err = -ENOENT;
goto errout_block;
} }
chain = tcf_chain_create(block, chain_index); chain = tcf_chain_create(block, chain_index);
if (!chain) { if (!chain) {
NL_SET_ERR_MSG(extack, "Failed to create filter chain"); NL_SET_ERR_MSG(extack, "Failed to create filter chain");
return -ENOMEM; err = -ENOMEM;
goto errout_block;
} }
} }
} else { } else {
if (!chain || tcf_chain_held_by_acts_only(chain)) { if (!chain || tcf_chain_held_by_acts_only(chain)) {
NL_SET_ERR_MSG(extack, "Cannot find specified filter chain"); NL_SET_ERR_MSG(extack, "Cannot find specified filter chain");
return -EINVAL; err = -EINVAL;
goto errout_block;
} }
tcf_chain_hold(chain); tcf_chain_hold(chain);
} }
...@@ -1926,6 +1973,8 @@ static int tc_ctl_chain(struct sk_buff *skb, struct nlmsghdr *n, ...@@ -1926,6 +1973,8 @@ static int tc_ctl_chain(struct sk_buff *skb, struct nlmsghdr *n,
errout: errout:
tcf_chain_put(chain); tcf_chain_put(chain);
errout_block:
tcf_block_release(q, block);
if (err == -EAGAIN) if (err == -EAGAIN)
/* Replay the request. */ /* Replay the request. */
goto replay; goto replay;
......
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