]> git.proxmox.com Git - mirror_ubuntu-jammy-kernel.git/blobdiff - net/sched/sch_api.c
net: sched: sch: add extack for block callback
[mirror_ubuntu-jammy-kernel.git] / net / sched / sch_api.c
index c669bb3b89b26980e311102078adbc332253380f..8c8c15b4da3bd8f516f77caf071505986fde805e 100644 (file)
@@ -449,7 +449,8 @@ static const struct nla_policy stab_policy[TCA_STAB_MAX + 1] = {
        [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 qdisc_size_table *stab;
@@ -458,23 +459,29 @@ static struct qdisc_size_table *qdisc_get_stab(struct nlattr *opt)
        u16 *tab = NULL;
        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)
                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);
+       }
 
        s = nla_data(tb[TCA_STAB_BASE]);
 
        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);
+               }
                tab = nla_data(tb[TCA_STAB_DATA]);
                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);
+       }
 
        list_for_each_entry(stab, &qdisc_stab_list, list) {
                if (memcmp(&stab->szopts, s, sizeof(*s)))
@@ -669,7 +676,7 @@ int qdisc_class_hash_init(struct Qdisc_class_hash *clhash)
        unsigned int size = 4;
 
        clhash->hash = qdisc_class_hash_alloc(size);
-       if (clhash->hash == NULL)
+       if (!clhash->hash)
                return -ENOMEM;
        clhash->hashsize  = size;
        clhash->hashmask  = size - 1;
@@ -795,6 +802,8 @@ static int tc_fill_qdisc(struct sk_buff *skb, struct Qdisc *q, u32 clid,
        tcm->tcm_info = refcount_read(&q->refcnt);
        if (nla_put_string(skb, TCA_KIND, q->ops->id))
                goto nla_put_failure;
+       if (nla_put_u8(skb, TCA_HW_OFFLOAD, !!(q->flags & TCQ_F_OFFLOADED)))
+               goto nla_put_failure;
        if (q->ops->dump && q->ops->dump(q, skb) < 0)
                goto nla_put_failure;
 
@@ -897,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,
                       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 net *net = dev_net(dev);
@@ -912,8 +922,10 @@ static int qdisc_graft(struct net_device *dev, struct Qdisc *parent,
                    (new && new->flags & TCQ_F_INGRESS)) {
                        num_q = 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;
+                       }
                }
 
                if (dev->flags & IFF_UP)
@@ -955,14 +967,21 @@ skip:
        } else {
                const struct Qdisc_class_ops *cops = parent->ops->cl_ops;
 
+               /* Only support running class lockless if parent is lockless */
+               if (new && (new->flags & TCQ_F_NOLOCK) &&
+                   parent && !(parent->flags & TCQ_F_NOLOCK))
+                       new->flags &= ~TCQ_F_NOLOCK;
+
                err = -EOPNOTSUPP;
                if (cops && cops->graft) {
                        unsigned long cl = cops->find(parent, classid);
 
-                       if (cl)
+                       if (cl) {
                                err = cops->graft(parent, cl, new, &old);
-                       else
+                       } else {
+                               NL_SET_ERR_MSG(extack, "Specified class not found");
                                err = -ENOENT;
+                       }
                }
                if (!err)
                        notify_and_destroy(net, skb, n, classid, old, new);
@@ -983,7 +1002,8 @@ static struct lock_class_key qdisc_rx_lock;
 static struct Qdisc *qdisc_create(struct net_device *dev,
                                  struct netdev_queue *dev_queue,
                                  struct Qdisc *p, u32 parent, u32 handle,
-                                 struct nlattr **tca, int *errp)
+                                 struct nlattr **tca, int *errp,
+                                 struct netlink_ext_ack *extack)
 {
        int err;
        struct nlattr *kind = tca[TCA_KIND];
@@ -1021,8 +1041,10 @@ static struct Qdisc *qdisc_create(struct net_device *dev,
 #endif
 
        err = -ENOENT;
-       if (!ops)
+       if (!ops) {
+               NL_SET_ERR_MSG(extack, "Specified qdisc not found");
                goto err_out;
+       }
 
        sch = qdisc_alloc(dev_queue, ops);
        if (IS_ERR(sch)) {
@@ -1062,7 +1084,7 @@ static struct Qdisc *qdisc_create(struct net_device *dev,
        }
 
        if (ops->init) {
-               err = ops->init(sch, tca[TCA_OPTIONS]);
+               err = ops->init(sch, tca[TCA_OPTIONS], extack);
                if (err != 0)
                        goto err_out5;
        }
@@ -1079,7 +1101,7 @@ static struct Qdisc *qdisc_create(struct net_device *dev,
        }
 
        if (tca[TCA_STAB]) {
-               stab = qdisc_get_stab(tca[TCA_STAB]);
+               stab = qdisc_get_stab(tca[TCA_STAB], extack);
                if (IS_ERR(stab)) {
                        err = PTR_ERR(stab);
                        goto err_out4;
@@ -1090,8 +1112,10 @@ static struct Qdisc *qdisc_create(struct net_device *dev,
                seqcount_t *running;
 
                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;
+               }
 
                if (sch->parent != TC_H_ROOT &&
                    !(sch->flags & TCQ_F_INGRESS) &&
@@ -1106,8 +1130,10 @@ static struct Qdisc *qdisc_create(struct net_device *dev,
                                        NULL,
                                        running,
                                        tca[TCA_RATE]);
-               if (err)
+               if (err) {
+                       NL_SET_ERR_MSG(extack, "Failed to generate new estimator");
                        goto err_out4;
+               }
        }
 
        qdisc_hash_add(sch, false);
@@ -1140,21 +1166,24 @@ err_out4:
        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;
        int err = 0;
 
        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;
-               err = sch->ops->change(sch, tca[TCA_OPTIONS]);
+               }
+               err = sch->ops->change(sch, tca[TCA_OPTIONS], extack);
                if (err)
                        return err;
        }
 
        if (tca[TCA_STAB]) {
-               stab = qdisc_get_stab(tca[TCA_STAB]);
+               stab = qdisc_get_stab(tca[TCA_STAB], extack);
                if (IS_ERR(stab))
                        return PTR_ERR(stab);
        }
@@ -1252,8 +1281,10 @@ static int tc_get_qdisc(struct sk_buff *skb, struct nlmsghdr *n,
                if (clid != TC_H_ROOT) {
                        if (TC_H_MAJ(clid) != TC_H_MAJ(TC_H_INGRESS)) {
                                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;
+                               }
                                q = qdisc_leaf(p, clid);
                        } else if (dev_ingress_queue(dev)) {
                                q = dev_ingress_queue(dev)->qdisc_sleeping;
@@ -1261,26 +1292,38 @@ static int tc_get_qdisc(struct sk_buff *skb, struct nlmsghdr *n,
                } else {
                        q = dev->qdisc;
                }
-               if (!q)
+               if (!q) {
+                       NL_SET_ERR_MSG(extack, "Cannot find specified qdisc on specified device");
                        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;
+               }
        } else {
                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;
+               }
        }
 
-       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;
+       }
 
        if (n->nlmsg_type == RTM_DELQDISC) {
-               if (!clid)
+               if (!clid) {
+                       NL_SET_ERR_MSG(extack, "Classid cannot be zero");
                        return -EINVAL;
-               if (q->handle == 0)
+               }
+               if (q->handle == 0) {
+                       NL_SET_ERR_MSG(extack, "Cannot delete qdisc with handle of zero");
                        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)
                        return err;
        } else {
@@ -1326,8 +1369,10 @@ replay:
                if (clid != TC_H_ROOT) {
                        if (clid != TC_H_INGRESS) {
                                p = qdisc_lookup(dev, TC_H_MAJ(clid));
-                               if (!p)
+                               if (!p) {
+                                       NL_SET_ERR_MSG(extack, "Failed to find specified qdisc");
                                        return -ENOENT;
+                               }
                                q = qdisc_leaf(p, clid);
                        } else if (dev_ingress_queue_create(dev)) {
                                q = dev_ingress_queue(dev)->qdisc_sleeping;
@@ -1342,21 +1387,33 @@ replay:
 
                if (!q || !tcm->tcm_handle || q->handle != 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;
-                               if (TC_H_MIN(tcm->tcm_handle))
+                               }
+                               if (TC_H_MIN(tcm->tcm_handle)) {
+                                       NL_SET_ERR_MSG(extack, "Invalid minor handle");
                                        return -EINVAL;
+                               }
                                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;
-                               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;
+                               }
                                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;
+                               }
                                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;
+                               }
                                qdisc_refcount_inc(q);
                                goto graft;
                        } else {
@@ -1391,33 +1448,45 @@ replay:
                        }
                }
        } else {
-               if (!tcm->tcm_handle)
+               if (!tcm->tcm_handle) {
+                       NL_SET_ERR_MSG(extack, "Handle cannot be zero");
                        return -EINVAL;
+               }
                q = qdisc_lookup(dev, tcm->tcm_handle);
        }
 
        /* Change qdisc parameters */
-       if (!q)
+       if (!q) {
+               NL_SET_ERR_MSG(extack, "Specified qdisc not found");
                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;
-       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;
-       err = qdisc_change(q, tca);
+       }
+       err = qdisc_change(q, tca, extack);
        if (err == 0)
                qdisc_notify(net, skb, n, clid, NULL, q);
        return err;
 
 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;
+       }
        if (clid == TC_H_INGRESS) {
-               if (dev_ingress_queue(dev))
+               if (dev_ingress_queue(dev)) {
                        q = qdisc_create(dev, dev_ingress_queue(dev), p,
                                         tcm->tcm_parent, tcm->tcm_parent,
-                                        tca, &err);
-               else
+                                        tca, &err, extack);
+               } else {
+                       NL_SET_ERR_MSG(extack, "Cannot find ingress queue for specified device");
                        err = -ENOENT;
+               }
        } else {
                struct netdev_queue *dev_queue;
 
@@ -1430,7 +1499,7 @@ create_n_graft:
 
                q = qdisc_create(dev, dev_queue, p,
                                 tcm->tcm_parent, tcm->tcm_handle,
-                                tca, &err);
+                                tca, &err, extack);
        }
        if (q == NULL) {
                if (err == -EAGAIN)
@@ -1439,7 +1508,7 @@ create_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 (q)
                        qdisc_destroy(q);
@@ -1691,7 +1760,7 @@ static void tc_bind_tclass(struct Qdisc *q, u32 portid, u32 clid,
        cl = cops->find(q, portid);
        if (!cl)
                return;
-       block = cops->tcf_block(q, cl);
+       block = cops->tcf_block(q, cl, NULL);
        if (!block)
                return;
        list_for_each_entry(chain, &block->chain_list, list) {
@@ -1838,7 +1907,7 @@ static int tc_ctl_tclass(struct sk_buff *skb, struct nlmsghdr *n,
        new_cl = cl;
        err = -EOPNOTSUPP;
        if (cops->change)
-               err = cops->change(q, clid, portid, tca, &new_cl);
+               err = cops->change(q, clid, portid, tca, &new_cl, extack);
        if (err == 0) {
                tclass_notify(net, skb, n, q, new_cl, RTM_NEWTCLASS);
                /* We just create a new class, need to do reverse binding. */