2562 lines · 61.4 KB · C
RawDownload
// SPDX-License-Identifier: GPL-2.0-or-later/* * net/sched/sch_api.c	Packet scheduler API. * * Authors:	Alexey Kuznetsov, <kuznet@ms2.inr.ac.ru> * * Fixes: * * Rani Assaf <rani@magic.metawire.com> :980802: JIFFIES and CPU clock sources are repaired. * Eduardo J. Blanco <ejbs@netlabs.com.uy> :990222: kmod support * Jamal Hadi Salim <hadi@nortelnetworks.com>: 990601: ingress support */ #include <linux/module.h>#include <linux/types.h>#include <linux/kernel.h>#include <linux/string.h>#include <linux/errno.h>#include <linux/skbuff.h>#include <linux/init.h>#include <linux/proc_fs.h>#include <linux/seq_file.h>#include <linux/kmod.h>#include <linux/list.h>#include <linux/hrtimer.h>#include <linux/slab.h>#include <linux/hashtable.h>#include <linux/bpf.h> #include <net/netdev_lock.h>#include <net/net_namespace.h>#include <net/sock.h>#include <net/netlink.h>#include <net/pkt_sched.h>#include <net/pkt_cls.h>#include <net/tc_wrapper.h> #include <trace/events/qdisc.h> /*    Short review.   -------------    This file consists of two interrelated parts:    1. queueing disciplines manager frontend.   2. traffic classes manager frontend.    Generally, queueing discipline ("qdisc") is a black box,   which is able to enqueue packets and to dequeue them (when   device is ready to send something) in order and at times   determined by algorithm hidden in it.    qdisc's are divided to two categories:   - "queues", which have no internal structure visible from outside.   - "schedulers", which split all the packets to "traffic classes",     using "packet classifiers" (look at cls_api.c)    In turn, classes may have child qdiscs (as rule, queues)   attached to them etc. etc. etc.    The goal of the routines in this file is to translate   information supplied by user in the form of handles   to more intelligible for kernel form, to make some sanity   checks and part of work, which is common to all qdiscs   and to provide rtnetlink notifications.    All real intelligent work is done inside qdisc modules.      Every discipline has two major routines: enqueue and dequeue.    ---dequeue    dequeue usually returns a skb to send. It is allowed to return NULL,   but it does not mean that queue is empty, it just means that   discipline does not want to send anything this time.   Queue is really empty if q->q.qlen == 0.   For complicated disciplines with multiple queues q->q is not   real packet queue, but however q->q.qlen must be valid.    ---enqueue    enqueue returns 0, if packet was enqueued successfully.   If packet (this one or another one) was dropped, it returns   not zero error code.   NET_XMIT_DROP 	- this packet dropped     Expected action: do not backoff, but wait until queue will clear.   NET_XMIT_CN	 	- probably this packet enqueued, but another one dropped.     Expected action: backoff or ignore    Auxiliary routines:    ---peek    like dequeue but without removing a packet from the queue    ---reset    returns qdisc to initial state: purge all buffers, clear all   timers, counters (except for statistics) etc.    ---init    initializes newly created qdisc.    ---destroy    destroys resources allocated by init and during lifetime of qdisc.    ---change    changes qdisc parameters. */ /* Protects list of registered TC modules. It is pure SMP lock. */static DEFINE_RWLOCK(qdisc_mod_lock);  /************************************************ *	Queueing disciplines manipulation.	* ************************************************/  /* The list of all installed queueing disciplines. */ static struct Qdisc_ops *qdisc_base; /* Register/unregister queueing discipline */ int register_qdisc(struct Qdisc_ops *qops){	struct Qdisc_ops *q, **qp;	int rc = -EEXIST; 	write_lock(&qdisc_mod_lock);	for (qp = &qdisc_base; (q = *qp) != NULL; qp = &q->next)		if (!strcmp(qops->id, q->id))			goto out; 	if (qops->enqueue == NULL)		qops->enqueue = noop_qdisc_ops.enqueue;	if (qops->peek == NULL) {		if (qops->dequeue == NULL)			qops->peek = noop_qdisc_ops.peek;		else			goto out_einval;	}	if (qops->dequeue == NULL)		qops->dequeue = noop_qdisc_ops.dequeue; 	if (qops->cl_ops) {		const struct Qdisc_class_ops *cops = qops->cl_ops; 		if (!(cops->find && cops->walk && cops->leaf))			goto out_einval; 		if (cops->tcf_block && !(cops->bind_tcf && cops->unbind_tcf))			goto out_einval;	} 	qops->next = NULL;	*qp = qops;	rc = 0;out:	write_unlock(&qdisc_mod_lock);	return rc; out_einval:	rc = -EINVAL;	goto out;}EXPORT_SYMBOL(register_qdisc); void unregister_qdisc(struct Qdisc_ops *qops){	struct Qdisc_ops *q, **qp;	int err = -ENOENT; 	write_lock(&qdisc_mod_lock);	for (qp = &qdisc_base; (q = *qp) != NULL; qp = &q->next)		if (q == qops)			break;	if (q) {		*qp = q->next;		q->next = NULL;		err = 0;	}	write_unlock(&qdisc_mod_lock); 	WARN(err, "unregister qdisc(%s) failed\n", qops->id);}EXPORT_SYMBOL(unregister_qdisc); /* Get default qdisc if not otherwise specified */void qdisc_get_default(char *name, size_t len){	read_lock(&qdisc_mod_lock);	strscpy(name, default_qdisc_ops->id, len);	read_unlock(&qdisc_mod_lock);} static struct Qdisc_ops *qdisc_lookup_default(const char *name){	struct Qdisc_ops *q = NULL; 	for (q = qdisc_base; q; q = q->next) {		if (!strcmp(name, q->id)) {			if (!bpf_try_module_get(q, q->owner))				q = NULL;			break;		}	} 	return q;} /* Set new default qdisc to use */int qdisc_set_default(const char *name){	const struct Qdisc_ops *ops; 	if (!capable(CAP_NET_ADMIN))		return -EPERM; 	write_lock(&qdisc_mod_lock);	ops = qdisc_lookup_default(name);	if (!ops) {		/* Not found, drop lock and try to load module */		write_unlock(&qdisc_mod_lock);		request_module(NET_SCH_ALIAS_PREFIX "%s", name);		write_lock(&qdisc_mod_lock); 		ops = qdisc_lookup_default(name);	} 	if (ops) {		/* Set new default */		bpf_module_put(default_qdisc_ops, default_qdisc_ops->owner);		default_qdisc_ops = ops;	}	write_unlock(&qdisc_mod_lock); 	return ops ? 0 : -ENOENT;} #ifdef CONFIG_NET_SCH_DEFAULT/* Set default value from kernel config */static int __init sch_default_qdisc(void){	return qdisc_set_default(CONFIG_DEFAULT_NET_SCH);}late_initcall(sch_default_qdisc);#endif /* We know handle. Find qdisc among all qdisc's attached to device * (root qdisc, all its children, children of children etc.) * Note: caller either uses rtnl or rcu_read_lock() */ static struct Qdisc *qdisc_match_from_root(struct Qdisc *root, u32 handle){	struct Qdisc *q; 	if (!qdisc_dev(root))		return (root->handle == handle ? root : NULL); 	if (!(root->flags & TCQ_F_BUILTIN) &&	    root->handle == handle)		return root; 	hash_for_each_possible_rcu(qdisc_dev(root)->qdisc_hash, q, hash, handle,				   lockdep_rtnl_is_held()) {		if (q->handle == handle)			return q;	}	return NULL;} void qdisc_hash_add(struct Qdisc *q, bool invisible){	if ((q->parent != TC_H_ROOT) && !(q->flags & TCQ_F_INGRESS)) {		ASSERT_RTNL();		hash_add_rcu(qdisc_dev(q)->qdisc_hash, &q->hash, q->handle);		if (invisible)			q->flags |= TCQ_F_INVISIBLE;	}}EXPORT_SYMBOL(qdisc_hash_add); void qdisc_hash_del(struct Qdisc *q){	if ((q->parent != TC_H_ROOT) && !(q->flags & TCQ_F_INGRESS)) {		ASSERT_RTNL();		hash_del_rcu(&q->hash);	}}EXPORT_SYMBOL(qdisc_hash_del); struct Qdisc *qdisc_lookup(struct net_device *dev, u32 handle){	struct Qdisc *q; 	if (!handle)		return NULL;	q = qdisc_match_from_root(rtnl_dereference(dev->qdisc), handle);	if (q)		goto out; 	if (dev_ingress_queue(dev))		q = qdisc_match_from_root(			rtnl_dereference(dev_ingress_queue(dev)->qdisc_sleeping),			handle);out:	return q;} struct Qdisc *qdisc_lookup_rcu(struct net_device *dev, u32 handle){	struct netdev_queue *nq;	struct Qdisc *q; 	if (!handle)		return NULL;	q = qdisc_match_from_root(rcu_dereference(dev->qdisc), handle);	if (q)		goto out; 	nq = dev_ingress_queue_rcu(dev);	if (nq)		q = qdisc_match_from_root(rcu_dereference(nq->qdisc_sleeping),					  handle);out:	return q;} static struct Qdisc *qdisc_leaf(struct Qdisc *p, u32 classid,				struct netlink_ext_ack *extack){	unsigned long cl;	const struct Qdisc_class_ops *cops = p->ops->cl_ops; 	if (cops == NULL) {		NL_SET_ERR_MSG(extack, "Parent qdisc is not classful");		return ERR_PTR(-EOPNOTSUPP);	}	cl = cops->find(p, classid); 	if (cl == 0) {		NL_SET_ERR_MSG(extack, "Specified class not found");		return ERR_PTR(-ENOENT);	}	return cops->leaf(p, cl);} /* Find queueing discipline by name */ static struct Qdisc_ops *qdisc_lookup_ops(struct nlattr *kind){	struct Qdisc_ops *q = NULL; 	if (kind) {		read_lock(&qdisc_mod_lock);		for (q = qdisc_base; q; q = q->next) {			if (nla_strcmp(kind, q->id) == 0) {				if (!bpf_try_module_get(q, q->owner))					q = NULL;				break;			}		}		read_unlock(&qdisc_mod_lock);	}	return q;} /* The linklayer setting were not transferred from iproute2, in older * versions, and the rate tables lookup systems have been dropped in * the kernel. To keep backward compatible with older iproute2 tc * utils, we detect the linklayer setting by detecting if the rate * table were modified. * * For linklayer ATM table entries, the rate table will be aligned to * 48 bytes, thus some table entries will contain the same value.  The * mpu (min packet unit) is also encoded into the old rate table, thus * starting from the mpu, we find low and high table entries for * mapping this cell.  If these entries contain the same value, when * the rate tables have been modified for linklayer ATM. * * This is done by rounding mpu to the nearest 48 bytes cell/entry, * and then roundup to the next cell, calc the table entry one below, * and compare. */static __u8 __detect_linklayer(struct tc_ratespec *r, __u32 *rtab){	int low       = roundup(r->mpu, 48);	int high      = roundup(low+1, 48);	int cell_low  = low >> r->cell_log;	int cell_high = (high >> r->cell_log) - 1; 	/* rtab is too inaccurate at rates > 100Mbit/s */	if ((r->rate > (100000000/8)) || (rtab[0] == 0)) {		pr_debug("TC linklayer: Giving up ATM detection\n");		return TC_LINKLAYER_ETHERNET;	} 	if ((cell_high > cell_low) && (cell_high < 256)	    && (rtab[cell_low] == rtab[cell_high])) {		pr_debug("TC linklayer: Detected ATM, low(%d)=high(%d)=%u\n",			 cell_low, cell_high, rtab[cell_high]);		return TC_LINKLAYER_ATM;	}	return TC_LINKLAYER_ETHERNET;} static struct qdisc_rate_table *qdisc_rtab_list;static DEFINE_SPINLOCK(qdisc_rtab_lock); struct qdisc_rate_table *qdisc_get_rtab(struct tc_ratespec *r,					struct nlattr *tab,					struct netlink_ext_ack *extack){	struct qdisc_rate_table *rtab, *new_rtab; 	if (tab == NULL || r->rate == 0 ||	    r->cell_log == 0 || r->cell_log >= 32 ||	    nla_len(tab) != TC_RTAB_SIZE) {		NL_SET_ERR_MSG(extack, "Invalid rate table parameters for searching");		return NULL;	} 	new_rtab = kmalloc_obj(*new_rtab); 	spin_lock(&qdisc_rtab_lock);	for (rtab = qdisc_rtab_list; rtab; rtab = rtab->next) {		if (!memcmp(&rtab->rate, r, sizeof(struct tc_ratespec)) &&		    !memcmp(&rtab->data, nla_data(tab), TC_RTAB_SIZE)) {			rtab->refcnt++;			spin_unlock(&qdisc_rtab_lock);			kfree(new_rtab);			return rtab;		}	} 	rtab = new_rtab;	if (rtab) {		rtab->rate = *r;		rtab->refcnt = 1;		memcpy(rtab->data, nla_data(tab), TC_RTAB_SIZE);		if (r->linklayer == TC_LINKLAYER_UNAWARE)			r->linklayer = __detect_linklayer(r, rtab->data);		rtab->next = qdisc_rtab_list;		qdisc_rtab_list = rtab;	} else {		NL_SET_ERR_MSG(extack, "Failed to allocate new qdisc rate table");	}	spin_unlock(&qdisc_rtab_lock);	return rtab;}EXPORT_SYMBOL(qdisc_get_rtab); void qdisc_put_rtab(struct qdisc_rate_table *tab){	struct qdisc_rate_table *rtab, **rtabp; 	if (!tab)		return; 	spin_lock(&qdisc_rtab_lock);	if (--tab->refcnt) {		spin_unlock(&qdisc_rtab_lock);		return;	} 	for (rtabp = &qdisc_rtab_list;	     (rtab = *rtabp) != NULL;	     rtabp = &rtab->next) {		if (rtab == tab) {			*rtabp = rtab->next;			break;		}	}	spin_unlock(&qdisc_rtab_lock);	kfree(tab);}EXPORT_SYMBOL(qdisc_put_rtab); static LIST_HEAD(qdisc_stab_list); static const struct nla_policy stab_policy[TCA_STAB_MAX + 1] = {	[TCA_STAB_BASE]	= { .len = sizeof(struct tc_sizespec) },	[TCA_STAB_DATA] = { .type = NLA_BINARY },}; 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;

Lines 1–500 of 2562.

Keyboard shortcuts

General

Show keyboard shortcuts
?
Focus the current search
/

Issues and pull requests

Open filters
f

Issues, pull requests and Inbox

Select next item
j↓
Select previous item
k↑
Open selected item
Enter
Dismiss filters or clear selection
Esc

Repository code

Go to file
t