diff options
Diffstat (limited to 'net/sched/sch_api.c')
| -rw-r--r-- | net/sched/sch_api.c | 88 | 
1 files changed, 64 insertions, 24 deletions
diff --git a/net/sched/sch_api.c b/net/sched/sch_api.c index fdb8f429333d..aa6b1fe65151 100644 --- a/net/sched/sch_api.c +++ b/net/sched/sch_api.c @@ -309,7 +309,7 @@ struct Qdisc *qdisc_lookup(struct net_device *dev, u32 handle)  	if (dev_ingress_queue(dev))  		q = qdisc_match_from_root( -			dev_ingress_queue(dev)->qdisc_sleeping, +			rtnl_dereference(dev_ingress_queue(dev)->qdisc_sleeping),  			handle);  out:  	return q; @@ -328,7 +328,8 @@ struct Qdisc *qdisc_lookup_rcu(struct net_device *dev, u32 handle)  	nq = dev_ingress_queue_rcu(dev);  	if (nq) -		q = qdisc_match_from_root(nq->qdisc_sleeping, handle); +		q = qdisc_match_from_root(rcu_dereference(nq->qdisc_sleeping), +					  handle);  out:  	return q;  } @@ -634,8 +635,13 @@ EXPORT_SYMBOL(qdisc_watchdog_init);  void qdisc_watchdog_schedule_range_ns(struct qdisc_watchdog *wd, u64 expires,  				      u64 delta_ns)  { -	if (test_bit(__QDISC_STATE_DEACTIVATED, -		     &qdisc_root_sleeping(wd->qdisc)->state)) +	bool deactivated; + +	rcu_read_lock(); +	deactivated = test_bit(__QDISC_STATE_DEACTIVATED, +			       &qdisc_root_sleeping(wd->qdisc)->state); +	rcu_read_unlock(); +	if (deactivated)  		return;  	if (hrtimer_is_queued(&wd->timer)) { @@ -1073,17 +1079,29 @@ static int qdisc_graft(struct net_device *dev, struct Qdisc *parent,  	if (parent == NULL) {  		unsigned int i, num_q, ingress; +		struct netdev_queue *dev_queue;  		ingress = 0;  		num_q = dev->num_tx_queues;  		if ((q && q->flags & TCQ_F_INGRESS) ||  		    (new && new->flags & TCQ_F_INGRESS)) { -			num_q = 1;  			ingress = 1; -			if (!dev_ingress_queue(dev)) { +			dev_queue = dev_ingress_queue(dev); +			if (!dev_queue) {  				NL_SET_ERR_MSG(extack, "Device does not have an ingress queue");  				return -ENOENT;  			} + +			q = rtnl_dereference(dev_queue->qdisc_sleeping); + +			/* This is the counterpart of that qdisc_refcount_inc_nz() call in +			 * __tcf_qdisc_find() for filter requests. +			 */ +			if (!qdisc_refcount_dec_if_one(q)) { +				NL_SET_ERR_MSG(extack, +					       "Current ingress or clsact Qdisc has ongoing filter requests"); +				return -EBUSY; +			}  		}  		if (dev->flags & IFF_UP) @@ -1094,18 +1112,26 @@ static int qdisc_graft(struct net_device *dev, struct Qdisc *parent,  		if (new && new->ops->attach && !ingress)  			goto skip; -		for (i = 0; i < num_q; i++) { -			struct netdev_queue *dev_queue = dev_ingress_queue(dev); - -			if (!ingress) +		if (!ingress) { +			for (i = 0; i < num_q; i++) {  				dev_queue = netdev_get_tx_queue(dev, i); +				old = dev_graft_qdisc(dev_queue, new); -			old = dev_graft_qdisc(dev_queue, new); -			if (new && i > 0) -				qdisc_refcount_inc(new); - -			if (!ingress) +				if (new && i > 0) +					qdisc_refcount_inc(new);  				qdisc_put(old); +			} +		} else { +			old = dev_graft_qdisc(dev_queue, NULL); + +			/* {ingress,clsact}_destroy() @old before grafting @new to avoid +			 * unprotected concurrent accesses to net_device::miniq_{in,e}gress +			 * pointer(s) in mini_qdisc_pair_swap(). +			 */ +			qdisc_notify(net, skb, n, classid, old, new, extack); +			qdisc_destroy(old); + +			dev_graft_qdisc(dev_queue, new);  		}  skip: @@ -1119,8 +1145,6 @@ skip:  			if (new && new->ops->attach)  				new->ops->attach(new); -		} else { -			notify_and_destroy(net, skb, n, classid, old, new, extack);  		}  		if (dev->flags & IFF_UP) @@ -1252,7 +1276,12 @@ static struct Qdisc *qdisc_create(struct net_device *dev,  	sch->parent = parent;  	if (handle == TC_H_INGRESS) { -		sch->flags |= TCQ_F_INGRESS; +		if (!(sch->flags & TCQ_F_INGRESS)) { +			NL_SET_ERR_MSG(extack, +				       "Specified parent ID is reserved for ingress and clsact Qdiscs"); +			err = -EINVAL; +			goto err_out3; +		}  		handle = TC_H_MAKE(TC_H_INGRESS, 0);  	} else {  		if (handle == 0) { @@ -1473,7 +1502,7 @@ static int tc_get_qdisc(struct sk_buff *skb, struct nlmsghdr *n,  				}  				q = qdisc_leaf(p, clid);  			} else if (dev_ingress_queue(dev)) { -				q = dev_ingress_queue(dev)->qdisc_sleeping; +				q = rtnl_dereference(dev_ingress_queue(dev)->qdisc_sleeping);  			}  		} else {  			q = rtnl_dereference(dev->qdisc); @@ -1559,7 +1588,7 @@ replay:  				}  				q = qdisc_leaf(p, clid);  			} else if (dev_ingress_queue_create(dev)) { -				q = dev_ingress_queue(dev)->qdisc_sleeping; +				q = rtnl_dereference(dev_ingress_queue(dev)->qdisc_sleeping);  			}  		} else {  			q = rtnl_dereference(dev->qdisc); @@ -1591,11 +1620,20 @@ replay:  					NL_SET_ERR_MSG(extack, "Invalid qdisc name");  					return -EINVAL;  				} +				if (q->flags & TCQ_F_INGRESS) { +					NL_SET_ERR_MSG(extack, +						       "Cannot regraft ingress or clsact Qdiscs"); +					return -EINVAL; +				}  				if (q == p ||  				    (p && check_loop(q, p, 0))) {  					NL_SET_ERR_MSG(extack, "Qdisc parent/child loop detected");  					return -ELOOP;  				} +				if (clid == TC_H_INGRESS) { +					NL_SET_ERR_MSG(extack, "Ingress cannot graft directly"); +					return -EINVAL; +				}  				qdisc_refcount_inc(q);  				goto graft;  			} else { @@ -1791,8 +1829,8 @@ static int tc_dump_qdisc(struct sk_buff *skb, struct netlink_callback *cb)  		dev_queue = dev_ingress_queue(dev);  		if (dev_queue && -		    tc_dump_qdisc_root(dev_queue->qdisc_sleeping, skb, cb, -				       &q_idx, s_q_idx, false, +		    tc_dump_qdisc_root(rtnl_dereference(dev_queue->qdisc_sleeping), +				       skb, cb, &q_idx, s_q_idx, false,  				       tca[TCA_DUMP_INVISIBLE]) < 0)  			goto done; @@ -2235,8 +2273,8 @@ static int tc_dump_tclass(struct sk_buff *skb, struct netlink_callback *cb)  	dev_queue = dev_ingress_queue(dev);  	if (dev_queue && -	    tc_dump_tclass_root(dev_queue->qdisc_sleeping, skb, tcm, cb, -				&t, s_t, false) < 0) +	    tc_dump_tclass_root(rtnl_dereference(dev_queue->qdisc_sleeping), +				skb, tcm, cb, &t, s_t, false) < 0)  		goto done;  done: @@ -2288,7 +2326,9 @@ static struct pernet_operations psched_net_ops = {  	.exit = psched_net_exit,  }; +#if IS_ENABLED(CONFIG_RETPOLINE)  DEFINE_STATIC_KEY_FALSE(tc_skip_wrapper); +#endif  static int __init pktsched_init(void)  {  | 
