Commit 09215598 authored by Alexander Aring's avatar Alexander Aring Committed by David S. Miller

net: sched: sch_api: handle generic qdisc errors

This patch adds extack support for generic qdisc handling. The extack
will be set deeper to each called function which is not part of netdev
core api.

Cc: David Ahern <dsahern@gmail.com>
Acked-by: default avatarJamal Hadi Salim <jhs@mojatatu.com>
Signed-off-by: default avatarAlexander Aring <aring@mojatatu.com>
Signed-off-by: default avatarDavid S. Miller <davem@davemloft.net>
parent ac8ef4ab
...@@ -449,7 +449,8 @@ static const struct nla_policy stab_policy[TCA_STAB_MAX + 1] = { ...@@ -449,7 +449,8 @@ static const struct nla_policy stab_policy[TCA_STAB_MAX + 1] = {
[TCA_STAB_DATA] = { .type = NLA_BINARY }, [TCA_STAB_DATA] = { .type = NLA_BINARY },
}; };
static struct qdisc_size_table *qdisc_get_stab(struct nlattr *opt) static struct qdisc_size_table *qdisc_get_stab(struct nlattr *opt,
struct netlink_ext_ack *extack)
{ {
struct nlattr *tb[TCA_STAB_MAX + 1]; struct nlattr *tb[TCA_STAB_MAX + 1];
struct qdisc_size_table *stab; struct qdisc_size_table *stab;
...@@ -458,23 +459,29 @@ static struct qdisc_size_table *qdisc_get_stab(struct nlattr *opt) ...@@ -458,23 +459,29 @@ static struct qdisc_size_table *qdisc_get_stab(struct nlattr *opt)
u16 *tab = NULL; u16 *tab = NULL;
int err; int err;
err = nla_parse_nested(tb, TCA_STAB_MAX, opt, stab_policy, NULL); err = nla_parse_nested(tb, TCA_STAB_MAX, opt, stab_policy, extack);
if (err < 0) if (err < 0)
return ERR_PTR(err); return ERR_PTR(err);
if (!tb[TCA_STAB_BASE]) if (!tb[TCA_STAB_BASE]) {
NL_SET_ERR_MSG(extack, "Size table base attribute is missing");
return ERR_PTR(-EINVAL); return ERR_PTR(-EINVAL);
}
s = nla_data(tb[TCA_STAB_BASE]); s = nla_data(tb[TCA_STAB_BASE]);
if (s->tsize > 0) { if (s->tsize > 0) {
if (!tb[TCA_STAB_DATA]) if (!tb[TCA_STAB_DATA]) {
NL_SET_ERR_MSG(extack, "Size table data attribute is missing");
return ERR_PTR(-EINVAL); return ERR_PTR(-EINVAL);
}
tab = nla_data(tb[TCA_STAB_DATA]); tab = nla_data(tb[TCA_STAB_DATA]);
tsize = nla_len(tb[TCA_STAB_DATA]) / sizeof(u16); tsize = nla_len(tb[TCA_STAB_DATA]) / sizeof(u16);
} }
if (tsize != s->tsize || (!tab && tsize > 0)) if (tsize != s->tsize || (!tab && tsize > 0)) {
NL_SET_ERR_MSG(extack, "Invalid size of size table");
return ERR_PTR(-EINVAL); return ERR_PTR(-EINVAL);
}
list_for_each_entry(stab, &qdisc_stab_list, list) { list_for_each_entry(stab, &qdisc_stab_list, list) {
if (memcmp(&stab->szopts, s, sizeof(*s))) if (memcmp(&stab->szopts, s, sizeof(*s)))
...@@ -899,7 +906,8 @@ static void notify_and_destroy(struct net *net, struct sk_buff *skb, ...@@ -899,7 +906,8 @@ static void notify_and_destroy(struct net *net, struct sk_buff *skb,
static int qdisc_graft(struct net_device *dev, struct Qdisc *parent, static int qdisc_graft(struct net_device *dev, struct Qdisc *parent,
struct sk_buff *skb, struct nlmsghdr *n, u32 classid, struct sk_buff *skb, struct nlmsghdr *n, u32 classid,
struct Qdisc *new, struct Qdisc *old) struct Qdisc *new, struct Qdisc *old,
struct netlink_ext_ack *extack)
{ {
struct Qdisc *q = old; struct Qdisc *q = old;
struct net *net = dev_net(dev); struct net *net = dev_net(dev);
...@@ -914,8 +922,10 @@ static int qdisc_graft(struct net_device *dev, struct Qdisc *parent, ...@@ -914,8 +922,10 @@ static int qdisc_graft(struct net_device *dev, struct Qdisc *parent,
(new && new->flags & TCQ_F_INGRESS)) { (new && new->flags & TCQ_F_INGRESS)) {
num_q = 1; num_q = 1;
ingress = 1; ingress = 1;
if (!dev_ingress_queue(dev)) if (!dev_ingress_queue(dev)) {
NL_SET_ERR_MSG(extack, "Device does not have an ingress queue");
return -ENOENT; return -ENOENT;
}
} }
if (dev->flags & IFF_UP) if (dev->flags & IFF_UP)
...@@ -966,10 +976,12 @@ static int qdisc_graft(struct net_device *dev, struct Qdisc *parent, ...@@ -966,10 +976,12 @@ static int qdisc_graft(struct net_device *dev, struct Qdisc *parent,
if (cops && cops->graft) { if (cops && cops->graft) {
unsigned long cl = cops->find(parent, classid); unsigned long cl = cops->find(parent, classid);
if (cl) if (cl) {
err = cops->graft(parent, cl, new, &old); err = cops->graft(parent, cl, new, &old);
else } else {
NL_SET_ERR_MSG(extack, "Specified class not found");
err = -ENOENT; err = -ENOENT;
}
} }
if (!err) if (!err)
notify_and_destroy(net, skb, n, classid, old, new); notify_and_destroy(net, skb, n, classid, old, new);
...@@ -990,7 +1002,8 @@ static struct lock_class_key qdisc_rx_lock; ...@@ -990,7 +1002,8 @@ static struct lock_class_key qdisc_rx_lock;
static struct Qdisc *qdisc_create(struct net_device *dev, static struct Qdisc *qdisc_create(struct net_device *dev,
struct netdev_queue *dev_queue, struct netdev_queue *dev_queue,
struct Qdisc *p, u32 parent, u32 handle, struct Qdisc *p, u32 parent, u32 handle,
struct nlattr **tca, int *errp) struct nlattr **tca, int *errp,
struct netlink_ext_ack *extack)
{ {
int err; int err;
struct nlattr *kind = tca[TCA_KIND]; struct nlattr *kind = tca[TCA_KIND];
...@@ -1028,8 +1041,10 @@ static struct Qdisc *qdisc_create(struct net_device *dev, ...@@ -1028,8 +1041,10 @@ static struct Qdisc *qdisc_create(struct net_device *dev,
#endif #endif
err = -ENOENT; err = -ENOENT;
if (!ops) if (!ops) {
NL_SET_ERR_MSG(extack, "Specified qdisc not found");
goto err_out; goto err_out;
}
sch = qdisc_alloc(dev_queue, ops); sch = qdisc_alloc(dev_queue, ops);
if (IS_ERR(sch)) { if (IS_ERR(sch)) {
...@@ -1086,7 +1101,7 @@ static struct Qdisc *qdisc_create(struct net_device *dev, ...@@ -1086,7 +1101,7 @@ static struct Qdisc *qdisc_create(struct net_device *dev,
} }
if (tca[TCA_STAB]) { if (tca[TCA_STAB]) {
stab = qdisc_get_stab(tca[TCA_STAB]); stab = qdisc_get_stab(tca[TCA_STAB], extack);
if (IS_ERR(stab)) { if (IS_ERR(stab)) {
err = PTR_ERR(stab); err = PTR_ERR(stab);
goto err_out4; goto err_out4;
...@@ -1097,8 +1112,10 @@ static struct Qdisc *qdisc_create(struct net_device *dev, ...@@ -1097,8 +1112,10 @@ static struct Qdisc *qdisc_create(struct net_device *dev,
seqcount_t *running; seqcount_t *running;
err = -EOPNOTSUPP; err = -EOPNOTSUPP;
if (sch->flags & TCQ_F_MQROOT) if (sch->flags & TCQ_F_MQROOT) {
NL_SET_ERR_MSG(extack, "Cannot attach rate estimator to a multi-queue root qdisc");
goto err_out4; goto err_out4;
}
if (sch->parent != TC_H_ROOT && if (sch->parent != TC_H_ROOT &&
!(sch->flags & TCQ_F_INGRESS) && !(sch->flags & TCQ_F_INGRESS) &&
...@@ -1113,8 +1130,10 @@ static struct Qdisc *qdisc_create(struct net_device *dev, ...@@ -1113,8 +1130,10 @@ static struct Qdisc *qdisc_create(struct net_device *dev,
NULL, NULL,
running, running,
tca[TCA_RATE]); tca[TCA_RATE]);
if (err) if (err) {
NL_SET_ERR_MSG(extack, "Failed to generate new estimator");
goto err_out4; goto err_out4;
}
} }
qdisc_hash_add(sch, false); qdisc_hash_add(sch, false);
...@@ -1147,21 +1166,24 @@ static struct Qdisc *qdisc_create(struct net_device *dev, ...@@ -1147,21 +1166,24 @@ static struct Qdisc *qdisc_create(struct net_device *dev,
goto err_out3; goto err_out3;
} }
static int qdisc_change(struct Qdisc *sch, struct nlattr **tca) static int qdisc_change(struct Qdisc *sch, struct nlattr **tca,
struct netlink_ext_ack *extack)
{ {
struct qdisc_size_table *ostab, *stab = NULL; struct qdisc_size_table *ostab, *stab = NULL;
int err = 0; int err = 0;
if (tca[TCA_OPTIONS]) { if (tca[TCA_OPTIONS]) {
if (!sch->ops->change) if (!sch->ops->change) {
NL_SET_ERR_MSG(extack, "Change operation not supported by specified qdisc");
return -EINVAL; return -EINVAL;
}
err = sch->ops->change(sch, tca[TCA_OPTIONS]); err = sch->ops->change(sch, tca[TCA_OPTIONS]);
if (err) if (err)
return err; return err;
} }
if (tca[TCA_STAB]) { if (tca[TCA_STAB]) {
stab = qdisc_get_stab(tca[TCA_STAB]); stab = qdisc_get_stab(tca[TCA_STAB], extack);
if (IS_ERR(stab)) if (IS_ERR(stab))
return PTR_ERR(stab); return PTR_ERR(stab);
} }
...@@ -1259,8 +1281,10 @@ static int tc_get_qdisc(struct sk_buff *skb, struct nlmsghdr *n, ...@@ -1259,8 +1281,10 @@ static int tc_get_qdisc(struct sk_buff *skb, struct nlmsghdr *n,
if (clid != TC_H_ROOT) { if (clid != TC_H_ROOT) {
if (TC_H_MAJ(clid) != TC_H_MAJ(TC_H_INGRESS)) { if (TC_H_MAJ(clid) != TC_H_MAJ(TC_H_INGRESS)) {
p = qdisc_lookup(dev, TC_H_MAJ(clid)); p = qdisc_lookup(dev, TC_H_MAJ(clid));
if (!p) if (!p) {
NL_SET_ERR_MSG(extack, "Failed to find qdisc with specified classid");
return -ENOENT; return -ENOENT;
}
q = qdisc_leaf(p, clid); q = qdisc_leaf(p, clid);
} else if (dev_ingress_queue(dev)) { } else if (dev_ingress_queue(dev)) {
q = dev_ingress_queue(dev)->qdisc_sleeping; q = dev_ingress_queue(dev)->qdisc_sleeping;
...@@ -1268,26 +1292,38 @@ static int tc_get_qdisc(struct sk_buff *skb, struct nlmsghdr *n, ...@@ -1268,26 +1292,38 @@ static int tc_get_qdisc(struct sk_buff *skb, struct nlmsghdr *n,
} else { } else {
q = dev->qdisc; q = dev->qdisc;
} }
if (!q) if (!q) {
NL_SET_ERR_MSG(extack, "Cannot find specified qdisc on specified device");
return -ENOENT; return -ENOENT;
}
if (tcm->tcm_handle && q->handle != tcm->tcm_handle) if (tcm->tcm_handle && q->handle != tcm->tcm_handle) {
NL_SET_ERR_MSG(extack, "Invalid handle");
return -EINVAL; return -EINVAL;
}
} else { } else {
q = qdisc_lookup(dev, tcm->tcm_handle); q = qdisc_lookup(dev, tcm->tcm_handle);
if (!q) if (!q) {
NL_SET_ERR_MSG(extack, "Failed to find qdisc with specified handle");
return -ENOENT; return -ENOENT;
}
} }
if (tca[TCA_KIND] && nla_strcmp(tca[TCA_KIND], q->ops->id)) if (tca[TCA_KIND] && nla_strcmp(tca[TCA_KIND], q->ops->id)) {
NL_SET_ERR_MSG(extack, "Invalid qdisc name");
return -EINVAL; return -EINVAL;
}
if (n->nlmsg_type == RTM_DELQDISC) { if (n->nlmsg_type == RTM_DELQDISC) {
if (!clid) if (!clid) {
NL_SET_ERR_MSG(extack, "Classid cannot be zero");
return -EINVAL; return -EINVAL;
if (q->handle == 0) }
if (q->handle == 0) {
NL_SET_ERR_MSG(extack, "Cannot delete qdisc with handle of zero");
return -ENOENT; return -ENOENT;
err = qdisc_graft(dev, p, skb, n, clid, NULL, q); }
err = qdisc_graft(dev, p, skb, n, clid, NULL, q, extack);
if (err != 0) if (err != 0)
return err; return err;
} else { } else {
...@@ -1333,8 +1369,10 @@ static int tc_modify_qdisc(struct sk_buff *skb, struct nlmsghdr *n, ...@@ -1333,8 +1369,10 @@ static int tc_modify_qdisc(struct sk_buff *skb, struct nlmsghdr *n,
if (clid != TC_H_ROOT) { if (clid != TC_H_ROOT) {
if (clid != TC_H_INGRESS) { if (clid != TC_H_INGRESS) {
p = qdisc_lookup(dev, TC_H_MAJ(clid)); p = qdisc_lookup(dev, TC_H_MAJ(clid));
if (!p) if (!p) {
NL_SET_ERR_MSG(extack, "Failed to find specified qdisc");
return -ENOENT; return -ENOENT;
}
q = qdisc_leaf(p, clid); q = qdisc_leaf(p, clid);
} else if (dev_ingress_queue_create(dev)) { } else if (dev_ingress_queue_create(dev)) {
q = dev_ingress_queue(dev)->qdisc_sleeping; q = dev_ingress_queue(dev)->qdisc_sleeping;
...@@ -1349,21 +1387,33 @@ static int tc_modify_qdisc(struct sk_buff *skb, struct nlmsghdr *n, ...@@ -1349,21 +1387,33 @@ static int tc_modify_qdisc(struct sk_buff *skb, struct nlmsghdr *n,
if (!q || !tcm->tcm_handle || q->handle != tcm->tcm_handle) { if (!q || !tcm->tcm_handle || q->handle != tcm->tcm_handle) {
if (tcm->tcm_handle) { if (tcm->tcm_handle) {
if (q && !(n->nlmsg_flags & NLM_F_REPLACE)) if (q && !(n->nlmsg_flags & NLM_F_REPLACE)) {
NL_SET_ERR_MSG(extack, "NLM_F_REPLACE needed to override");
return -EEXIST; return -EEXIST;
if (TC_H_MIN(tcm->tcm_handle)) }
if (TC_H_MIN(tcm->tcm_handle)) {
NL_SET_ERR_MSG(extack, "Invalid minor handle");
return -EINVAL; return -EINVAL;
}
q = qdisc_lookup(dev, tcm->tcm_handle); q = qdisc_lookup(dev, tcm->tcm_handle);
if (!q) if (!q) {
NL_SET_ERR_MSG(extack, "No qdisc found for specified handle");
goto create_n_graft; goto create_n_graft;
if (n->nlmsg_flags & NLM_F_EXCL) }
if (n->nlmsg_flags & NLM_F_EXCL) {
NL_SET_ERR_MSG(extack, "Exclusivity flag on, cannot override");
return -EEXIST; return -EEXIST;
}
if (tca[TCA_KIND] && if (tca[TCA_KIND] &&
nla_strcmp(tca[TCA_KIND], q->ops->id)) nla_strcmp(tca[TCA_KIND], q->ops->id)) {
NL_SET_ERR_MSG(extack, "Invalid qdisc name");
return -EINVAL; return -EINVAL;
}
if (q == p || if (q == p ||
(p && check_loop(q, p, 0))) (p && check_loop(q, p, 0))) {
NL_SET_ERR_MSG(extack, "Qdisc parent/child loop detected");
return -ELOOP; return -ELOOP;
}
qdisc_refcount_inc(q); qdisc_refcount_inc(q);
goto graft; goto graft;
} else { } else {
...@@ -1398,33 +1448,45 @@ static int tc_modify_qdisc(struct sk_buff *skb, struct nlmsghdr *n, ...@@ -1398,33 +1448,45 @@ static int tc_modify_qdisc(struct sk_buff *skb, struct nlmsghdr *n,
} }
} }
} else { } else {
if (!tcm->tcm_handle) if (!tcm->tcm_handle) {
NL_SET_ERR_MSG(extack, "Handle cannot be zero");
return -EINVAL; return -EINVAL;
}
q = qdisc_lookup(dev, tcm->tcm_handle); q = qdisc_lookup(dev, tcm->tcm_handle);
} }
/* Change qdisc parameters */ /* Change qdisc parameters */
if (!q) if (!q) {
NL_SET_ERR_MSG(extack, "Specified qdisc not found");
return -ENOENT; return -ENOENT;
if (n->nlmsg_flags & NLM_F_EXCL) }
if (n->nlmsg_flags & NLM_F_EXCL) {
NL_SET_ERR_MSG(extack, "Exclusivity flag on, cannot modify");
return -EEXIST; return -EEXIST;
if (tca[TCA_KIND] && nla_strcmp(tca[TCA_KIND], q->ops->id)) }
if (tca[TCA_KIND] && nla_strcmp(tca[TCA_KIND], q->ops->id)) {
NL_SET_ERR_MSG(extack, "Invalid qdisc name");
return -EINVAL; return -EINVAL;
err = qdisc_change(q, tca); }
err = qdisc_change(q, tca, extack);
if (err == 0) if (err == 0)
qdisc_notify(net, skb, n, clid, NULL, q); qdisc_notify(net, skb, n, clid, NULL, q);
return err; return err;
create_n_graft: create_n_graft:
if (!(n->nlmsg_flags & NLM_F_CREATE)) if (!(n->nlmsg_flags & NLM_F_CREATE)) {
NL_SET_ERR_MSG(extack, "Qdisc not found. To create specify NLM_F_CREATE flag");
return -ENOENT; return -ENOENT;
}
if (clid == TC_H_INGRESS) { if (clid == TC_H_INGRESS) {
if (dev_ingress_queue(dev)) if (dev_ingress_queue(dev)) {
q = qdisc_create(dev, dev_ingress_queue(dev), p, q = qdisc_create(dev, dev_ingress_queue(dev), p,
tcm->tcm_parent, tcm->tcm_parent, tcm->tcm_parent, tcm->tcm_parent,
tca, &err); tca, &err, extack);
else } else {
NL_SET_ERR_MSG(extack, "Cannot find ingress queue for specified device");
err = -ENOENT; err = -ENOENT;
}
} else { } else {
struct netdev_queue *dev_queue; struct netdev_queue *dev_queue;
...@@ -1437,7 +1499,7 @@ static int tc_modify_qdisc(struct sk_buff *skb, struct nlmsghdr *n, ...@@ -1437,7 +1499,7 @@ static int tc_modify_qdisc(struct sk_buff *skb, struct nlmsghdr *n,
q = qdisc_create(dev, dev_queue, p, q = qdisc_create(dev, dev_queue, p,
tcm->tcm_parent, tcm->tcm_handle, tcm->tcm_parent, tcm->tcm_handle,
tca, &err); tca, &err, extack);
} }
if (q == NULL) { if (q == NULL) {
if (err == -EAGAIN) if (err == -EAGAIN)
...@@ -1446,7 +1508,7 @@ static int tc_modify_qdisc(struct sk_buff *skb, struct nlmsghdr *n, ...@@ -1446,7 +1508,7 @@ static int tc_modify_qdisc(struct sk_buff *skb, struct nlmsghdr *n,
} }
graft: graft:
err = qdisc_graft(dev, p, skb, n, clid, q, NULL); err = qdisc_graft(dev, p, skb, n, clid, q, NULL, extack);
if (err) { if (err) {
if (q) if (q)
qdisc_destroy(q); qdisc_destroy(q);
......
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