xref: /openbmc/linux/net/sched/sch_netem.c (revision 378a2f09)
11da177e4SLinus Torvalds /*
21da177e4SLinus Torvalds  * net/sched/sch_netem.c	Network emulator
31da177e4SLinus Torvalds  *
41da177e4SLinus Torvalds  * 		This program is free software; you can redistribute it and/or
51da177e4SLinus Torvalds  * 		modify it under the terms of the GNU General Public License
61da177e4SLinus Torvalds  * 		as published by the Free Software Foundation; either version
7798b6b19SStephen Hemminger  * 		2 of the License.
81da177e4SLinus Torvalds  *
91da177e4SLinus Torvalds  *  		Many of the algorithms and ideas for this came from
101da177e4SLinus Torvalds  *		NIST Net which is not copyrighted.
111da177e4SLinus Torvalds  *
121da177e4SLinus Torvalds  * Authors:	Stephen Hemminger <shemminger@osdl.org>
131da177e4SLinus Torvalds  *		Catalin(ux aka Dino) BOIE <catab at umbrella dot ro>
141da177e4SLinus Torvalds  */
151da177e4SLinus Torvalds 
161da177e4SLinus Torvalds #include <linux/module.h>
171da177e4SLinus Torvalds #include <linux/types.h>
181da177e4SLinus Torvalds #include <linux/kernel.h>
191da177e4SLinus Torvalds #include <linux/errno.h>
201da177e4SLinus Torvalds #include <linux/skbuff.h>
211da177e4SLinus Torvalds #include <linux/rtnetlink.h>
221da177e4SLinus Torvalds 
23dc5fc579SArnaldo Carvalho de Melo #include <net/netlink.h>
241da177e4SLinus Torvalds #include <net/pkt_sched.h>
251da177e4SLinus Torvalds 
26c865e5d9SStephen Hemminger #define VERSION "1.2"
27eb229c4cSStephen Hemminger 
281da177e4SLinus Torvalds /*	Network Emulation Queuing algorithm.
291da177e4SLinus Torvalds 	====================================
301da177e4SLinus Torvalds 
311da177e4SLinus Torvalds 	Sources: [1] Mark Carson, Darrin Santay, "NIST Net - A Linux-based
321da177e4SLinus Torvalds 		 Network Emulation Tool
331da177e4SLinus Torvalds 		 [2] Luigi Rizzo, DummyNet for FreeBSD
341da177e4SLinus Torvalds 
351da177e4SLinus Torvalds 	 ----------------------------------------------------------------
361da177e4SLinus Torvalds 
371da177e4SLinus Torvalds 	 This started out as a simple way to delay outgoing packets to
381da177e4SLinus Torvalds 	 test TCP but has grown to include most of the functionality
391da177e4SLinus Torvalds 	 of a full blown network emulator like NISTnet. It can delay
401da177e4SLinus Torvalds 	 packets and add random jitter (and correlation). The random
411da177e4SLinus Torvalds 	 distribution can be loaded from a table as well to provide
421da177e4SLinus Torvalds 	 normal, Pareto, or experimental curves. Packet loss,
431da177e4SLinus Torvalds 	 duplication, and reordering can also be emulated.
441da177e4SLinus Torvalds 
451da177e4SLinus Torvalds 	 This qdisc does not do classification that can be handled in
461da177e4SLinus Torvalds 	 layering other disciplines.  It does not need to do bandwidth
471da177e4SLinus Torvalds 	 control either since that can be handled by using token
481da177e4SLinus Torvalds 	 bucket or other rate control.
491da177e4SLinus Torvalds 
501da177e4SLinus Torvalds 	 The simulator is limited by the Linux timer resolution
511da177e4SLinus Torvalds 	 and will create packet bursts on the HZ boundary (1ms).
521da177e4SLinus Torvalds */
531da177e4SLinus Torvalds 
541da177e4SLinus Torvalds struct netem_sched_data {
551da177e4SLinus Torvalds 	struct Qdisc	*qdisc;
5659cb5c67SPatrick McHardy 	struct qdisc_watchdog watchdog;
571da177e4SLinus Torvalds 
58b407621cSStephen Hemminger 	psched_tdiff_t latency;
59b407621cSStephen Hemminger 	psched_tdiff_t jitter;
60b407621cSStephen Hemminger 
611da177e4SLinus Torvalds 	u32 loss;
621da177e4SLinus Torvalds 	u32 limit;
631da177e4SLinus Torvalds 	u32 counter;
641da177e4SLinus Torvalds 	u32 gap;
651da177e4SLinus Torvalds 	u32 duplicate;
660dca51d3SStephen Hemminger 	u32 reorder;
67c865e5d9SStephen Hemminger 	u32 corrupt;
681da177e4SLinus Torvalds 
691da177e4SLinus Torvalds 	struct crndstate {
70b407621cSStephen Hemminger 		u32 last;
71b407621cSStephen Hemminger 		u32 rho;
72c865e5d9SStephen Hemminger 	} delay_cor, loss_cor, dup_cor, reorder_cor, corrupt_cor;
731da177e4SLinus Torvalds 
741da177e4SLinus Torvalds 	struct disttable {
751da177e4SLinus Torvalds 		u32  size;
761da177e4SLinus Torvalds 		s16 table[0];
771da177e4SLinus Torvalds 	} *delay_dist;
781da177e4SLinus Torvalds };
791da177e4SLinus Torvalds 
801da177e4SLinus Torvalds /* Time stamp put into socket buffer control block */
811da177e4SLinus Torvalds struct netem_skb_cb {
821da177e4SLinus Torvalds 	psched_time_t	time_to_send;
831da177e4SLinus Torvalds };
841da177e4SLinus Torvalds 
855f86173bSJussi Kivilinna static inline struct netem_skb_cb *netem_skb_cb(struct sk_buff *skb)
865f86173bSJussi Kivilinna {
87175f9c1bSJussi Kivilinna 	BUILD_BUG_ON(sizeof(skb->cb) <
88175f9c1bSJussi Kivilinna 		sizeof(struct qdisc_skb_cb) + sizeof(struct netem_skb_cb));
89175f9c1bSJussi Kivilinna 	return (struct netem_skb_cb *)qdisc_skb_cb(skb)->data;
905f86173bSJussi Kivilinna }
915f86173bSJussi Kivilinna 
921da177e4SLinus Torvalds /* init_crandom - initialize correlated random number generator
931da177e4SLinus Torvalds  * Use entropy source for initial seed.
941da177e4SLinus Torvalds  */
951da177e4SLinus Torvalds static void init_crandom(struct crndstate *state, unsigned long rho)
961da177e4SLinus Torvalds {
971da177e4SLinus Torvalds 	state->rho = rho;
981da177e4SLinus Torvalds 	state->last = net_random();
991da177e4SLinus Torvalds }
1001da177e4SLinus Torvalds 
1011da177e4SLinus Torvalds /* get_crandom - correlated random number generator
1021da177e4SLinus Torvalds  * Next number depends on last value.
1031da177e4SLinus Torvalds  * rho is scaled to avoid floating point.
1041da177e4SLinus Torvalds  */
105b407621cSStephen Hemminger static u32 get_crandom(struct crndstate *state)
1061da177e4SLinus Torvalds {
1071da177e4SLinus Torvalds 	u64 value, rho;
1081da177e4SLinus Torvalds 	unsigned long answer;
1091da177e4SLinus Torvalds 
110bb2f8cc0SStephen Hemminger 	if (state->rho == 0)	/* no correlation */
1111da177e4SLinus Torvalds 		return net_random();
1121da177e4SLinus Torvalds 
1131da177e4SLinus Torvalds 	value = net_random();
1141da177e4SLinus Torvalds 	rho = (u64)state->rho + 1;
1151da177e4SLinus Torvalds 	answer = (value * ((1ull<<32) - rho) + state->last * rho) >> 32;
1161da177e4SLinus Torvalds 	state->last = answer;
1171da177e4SLinus Torvalds 	return answer;
1181da177e4SLinus Torvalds }
1191da177e4SLinus Torvalds 
1201da177e4SLinus Torvalds /* tabledist - return a pseudo-randomly distributed value with mean mu and
1211da177e4SLinus Torvalds  * std deviation sigma.  Uses table lookup to approximate the desired
1221da177e4SLinus Torvalds  * distribution, and a uniformly-distributed pseudo-random source.
1231da177e4SLinus Torvalds  */
124b407621cSStephen Hemminger static psched_tdiff_t tabledist(psched_tdiff_t mu, psched_tdiff_t sigma,
125b407621cSStephen Hemminger 				struct crndstate *state,
126b407621cSStephen Hemminger 				const struct disttable *dist)
1271da177e4SLinus Torvalds {
128b407621cSStephen Hemminger 	psched_tdiff_t x;
129b407621cSStephen Hemminger 	long t;
130b407621cSStephen Hemminger 	u32 rnd;
1311da177e4SLinus Torvalds 
1321da177e4SLinus Torvalds 	if (sigma == 0)
1331da177e4SLinus Torvalds 		return mu;
1341da177e4SLinus Torvalds 
1351da177e4SLinus Torvalds 	rnd = get_crandom(state);
1361da177e4SLinus Torvalds 
1371da177e4SLinus Torvalds 	/* default uniform distribution */
1381da177e4SLinus Torvalds 	if (dist == NULL)
1391da177e4SLinus Torvalds 		return (rnd % (2*sigma)) - sigma + mu;
1401da177e4SLinus Torvalds 
1411da177e4SLinus Torvalds 	t = dist->table[rnd % dist->size];
1421da177e4SLinus Torvalds 	x = (sigma % NETEM_DIST_SCALE) * t;
1431da177e4SLinus Torvalds 	if (x >= 0)
1441da177e4SLinus Torvalds 		x += NETEM_DIST_SCALE/2;
1451da177e4SLinus Torvalds 	else
1461da177e4SLinus Torvalds 		x -= NETEM_DIST_SCALE/2;
1471da177e4SLinus Torvalds 
1481da177e4SLinus Torvalds 	return  x / NETEM_DIST_SCALE + (sigma / NETEM_DIST_SCALE) * t + mu;
1491da177e4SLinus Torvalds }
1501da177e4SLinus Torvalds 
1510afb51e7SStephen Hemminger /*
1520afb51e7SStephen Hemminger  * Insert one skb into qdisc.
1530afb51e7SStephen Hemminger  * Note: parent depends on return value to account for queue length.
1540afb51e7SStephen Hemminger  * 	NET_XMIT_DROP: queue length didn't change.
1550afb51e7SStephen Hemminger  *      NET_XMIT_SUCCESS: one skb was queued.
1560afb51e7SStephen Hemminger  */
1571da177e4SLinus Torvalds static int netem_enqueue(struct sk_buff *skb, struct Qdisc *sch)
1581da177e4SLinus Torvalds {
1591da177e4SLinus Torvalds 	struct netem_sched_data *q = qdisc_priv(sch);
16089e1df74SGuillaume Chazarain 	/* We don't fill cb now as skb_unshare() may invalidate it */
16189e1df74SGuillaume Chazarain 	struct netem_skb_cb *cb;
1620afb51e7SStephen Hemminger 	struct sk_buff *skb2;
1631da177e4SLinus Torvalds 	int ret;
1640afb51e7SStephen Hemminger 	int count = 1;
1651da177e4SLinus Torvalds 
166771018e7SStephen Hemminger 	pr_debug("netem_enqueue skb=%p\n", skb);
1671da177e4SLinus Torvalds 
1680afb51e7SStephen Hemminger 	/* Random duplication */
1690afb51e7SStephen Hemminger 	if (q->duplicate && q->duplicate >= get_crandom(&q->dup_cor))
1700afb51e7SStephen Hemminger 		++count;
1710afb51e7SStephen Hemminger 
1721da177e4SLinus Torvalds 	/* Random packet drop 0 => none, ~0 => all */
1730afb51e7SStephen Hemminger 	if (q->loss && q->loss >= get_crandom(&q->loss_cor))
1740afb51e7SStephen Hemminger 		--count;
1750afb51e7SStephen Hemminger 
1760afb51e7SStephen Hemminger 	if (count == 0) {
1771da177e4SLinus Torvalds 		sch->qstats.drops++;
1781da177e4SLinus Torvalds 		kfree_skb(skb);
17989bbb0a3SStephen Hemminger 		return NET_XMIT_BYPASS;
1801da177e4SLinus Torvalds 	}
1811da177e4SLinus Torvalds 
1824e8a5201SDavid S. Miller 	skb_orphan(skb);
1834e8a5201SDavid S. Miller 
1840afb51e7SStephen Hemminger 	/*
1850afb51e7SStephen Hemminger 	 * If we need to duplicate packet, then re-insert at top of the
1860afb51e7SStephen Hemminger 	 * qdisc tree, since parent queuer expects that only one
1870afb51e7SStephen Hemminger 	 * skb will be queued.
188d5d75cd6SStephen Hemminger 	 */
1890afb51e7SStephen Hemminger 	if (count > 1 && (skb2 = skb_clone(skb, GFP_ATOMIC)) != NULL) {
1907698b4fcSDavid S. Miller 		struct Qdisc *rootq = qdisc_root(sch);
1910afb51e7SStephen Hemminger 		u32 dupsave = q->duplicate; /* prevent duplicating a dup... */
1920afb51e7SStephen Hemminger 		q->duplicate = 0;
193d5d75cd6SStephen Hemminger 
1945f86173bSJussi Kivilinna 		qdisc_enqueue_root(skb2, rootq);
1950afb51e7SStephen Hemminger 		q->duplicate = dupsave;
1961da177e4SLinus Torvalds 	}
1971da177e4SLinus Torvalds 
198c865e5d9SStephen Hemminger 	/*
199c865e5d9SStephen Hemminger 	 * Randomized packet corruption.
200c865e5d9SStephen Hemminger 	 * Make copy if needed since we are modifying
201c865e5d9SStephen Hemminger 	 * If packet is going to be hardware checksummed, then
202c865e5d9SStephen Hemminger 	 * do it now in software before we mangle it.
203c865e5d9SStephen Hemminger 	 */
204c865e5d9SStephen Hemminger 	if (q->corrupt && q->corrupt >= get_crandom(&q->corrupt_cor)) {
205c865e5d9SStephen Hemminger 		if (!(skb = skb_unshare(skb, GFP_ATOMIC))
20684fa7933SPatrick McHardy 		    || (skb->ip_summed == CHECKSUM_PARTIAL
20784fa7933SPatrick McHardy 			&& skb_checksum_help(skb))) {
208c865e5d9SStephen Hemminger 			sch->qstats.drops++;
209c865e5d9SStephen Hemminger 			return NET_XMIT_DROP;
210c865e5d9SStephen Hemminger 		}
211c865e5d9SStephen Hemminger 
212c865e5d9SStephen Hemminger 		skb->data[net_random() % skb_headlen(skb)] ^= 1<<(net_random() % 8);
213c865e5d9SStephen Hemminger 	}
214c865e5d9SStephen Hemminger 
2155f86173bSJussi Kivilinna 	cb = netem_skb_cb(skb);
2160dca51d3SStephen Hemminger 	if (q->gap == 0 		/* not doing reordering */
2170dca51d3SStephen Hemminger 	    || q->counter < q->gap 	/* inside last reordering gap */
2180dca51d3SStephen Hemminger 	    || q->reorder < get_crandom(&q->reorder_cor)) {
2190f9f32acSStephen Hemminger 		psched_time_t now;
22007aaa115SStephen Hemminger 		psched_tdiff_t delay;
22107aaa115SStephen Hemminger 
22207aaa115SStephen Hemminger 		delay = tabledist(q->latency, q->jitter,
22307aaa115SStephen Hemminger 				  &q->delay_cor, q->delay_dist);
22407aaa115SStephen Hemminger 
2253bebcda2SPatrick McHardy 		now = psched_get_time();
2267c59e25fSPatrick McHardy 		cb->time_to_send = now + delay;
2271da177e4SLinus Torvalds 		++q->counter;
2285f86173bSJussi Kivilinna 		ret = qdisc_enqueue(skb, q->qdisc);
2291da177e4SLinus Torvalds 	} else {
2300dca51d3SStephen Hemminger 		/*
2310dca51d3SStephen Hemminger 		 * Do re-ordering by putting one out of N packets at the front
2320dca51d3SStephen Hemminger 		 * of the queue.
2330dca51d3SStephen Hemminger 		 */
2343bebcda2SPatrick McHardy 		cb->time_to_send = psched_get_time();
2350dca51d3SStephen Hemminger 		q->counter = 0;
2360f9f32acSStephen Hemminger 		ret = q->qdisc->ops->requeue(skb, q->qdisc);
2371da177e4SLinus Torvalds 	}
2381da177e4SLinus Torvalds 
2391da177e4SLinus Torvalds 	if (likely(ret == NET_XMIT_SUCCESS)) {
2401da177e4SLinus Torvalds 		sch->q.qlen++;
2410abf77e5SJussi Kivilinna 		sch->bstats.bytes += qdisc_pkt_len(skb);
2421da177e4SLinus Torvalds 		sch->bstats.packets++;
243378a2f09SJarek Poplawski 	} else if (net_xmit_drop_count(ret)) {
2441da177e4SLinus Torvalds 		sch->qstats.drops++;
245378a2f09SJarek Poplawski 	}
2461da177e4SLinus Torvalds 
247d5d75cd6SStephen Hemminger 	pr_debug("netem: enqueue ret %d\n", ret);
2481da177e4SLinus Torvalds 	return ret;
2491da177e4SLinus Torvalds }
2501da177e4SLinus Torvalds 
2511da177e4SLinus Torvalds /* Requeue packets but don't change time stamp */
2521da177e4SLinus Torvalds static int netem_requeue(struct sk_buff *skb, struct Qdisc *sch)
2531da177e4SLinus Torvalds {
2541da177e4SLinus Torvalds 	struct netem_sched_data *q = qdisc_priv(sch);
2551da177e4SLinus Torvalds 	int ret;
2561da177e4SLinus Torvalds 
2571da177e4SLinus Torvalds 	if ((ret = q->qdisc->ops->requeue(skb, q->qdisc)) == 0) {
2581da177e4SLinus Torvalds 		sch->q.qlen++;
2591da177e4SLinus Torvalds 		sch->qstats.requeues++;
2601da177e4SLinus Torvalds 	}
2611da177e4SLinus Torvalds 
2621da177e4SLinus Torvalds 	return ret;
2631da177e4SLinus Torvalds }
2641da177e4SLinus Torvalds 
2651da177e4SLinus Torvalds static unsigned int netem_drop(struct Qdisc* sch)
2661da177e4SLinus Torvalds {
2671da177e4SLinus Torvalds 	struct netem_sched_data *q = qdisc_priv(sch);
2686d037a26SPatrick McHardy 	unsigned int len = 0;
2691da177e4SLinus Torvalds 
2706d037a26SPatrick McHardy 	if (q->qdisc->ops->drop && (len = q->qdisc->ops->drop(q->qdisc)) != 0) {
2711da177e4SLinus Torvalds 		sch->q.qlen--;
2721da177e4SLinus Torvalds 		sch->qstats.drops++;
2731da177e4SLinus Torvalds 	}
2741da177e4SLinus Torvalds 	return len;
2751da177e4SLinus Torvalds }
2761da177e4SLinus Torvalds 
2771da177e4SLinus Torvalds static struct sk_buff *netem_dequeue(struct Qdisc *sch)
2781da177e4SLinus Torvalds {
2791da177e4SLinus Torvalds 	struct netem_sched_data *q = qdisc_priv(sch);
2801da177e4SLinus Torvalds 	struct sk_buff *skb;
2811da177e4SLinus Torvalds 
28211274e5aSStephen Hemminger 	smp_mb();
28311274e5aSStephen Hemminger 	if (sch->flags & TCQ_F_THROTTLED)
28411274e5aSStephen Hemminger 		return NULL;
28511274e5aSStephen Hemminger 
2861da177e4SLinus Torvalds 	skb = q->qdisc->dequeue(q->qdisc);
287771018e7SStephen Hemminger 	if (skb) {
2885f86173bSJussi Kivilinna 		const struct netem_skb_cb *cb = netem_skb_cb(skb);
2893bebcda2SPatrick McHardy 		psched_time_t now = psched_get_time();
2900f9f32acSStephen Hemminger 
2910f9f32acSStephen Hemminger 		/* if more time remaining? */
292104e0878SPatrick McHardy 		if (cb->time_to_send <= now) {
293771018e7SStephen Hemminger 			pr_debug("netem_dequeue: return skb=%p\n", skb);
2941da177e4SLinus Torvalds 			sch->q.qlen--;
2950f9f32acSStephen Hemminger 			return skb;
29611274e5aSStephen Hemminger 		}
29707aaa115SStephen Hemminger 
29811274e5aSStephen Hemminger 		if (unlikely(q->qdisc->ops->requeue(skb, q->qdisc) != NET_XMIT_SUCCESS)) {
299e488eafcSPatrick McHardy 			qdisc_tree_decrease_qlen(q->qdisc, 1);
30007aaa115SStephen Hemminger 			sch->qstats.drops++;
30111274e5aSStephen Hemminger 			printk(KERN_ERR "netem: %s could not requeue\n",
30207aaa115SStephen Hemminger 			       q->qdisc->ops->id);
303771018e7SStephen Hemminger 		}
30411274e5aSStephen Hemminger 
30511274e5aSStephen Hemminger 		qdisc_watchdog_schedule(&q->watchdog, cb->time_to_send);
3060f9f32acSStephen Hemminger 	}
3070f9f32acSStephen Hemminger 
3080f9f32acSStephen Hemminger 	return NULL;
3091da177e4SLinus Torvalds }
3101da177e4SLinus Torvalds 
3111da177e4SLinus Torvalds static void netem_reset(struct Qdisc *sch)
3121da177e4SLinus Torvalds {
3131da177e4SLinus Torvalds 	struct netem_sched_data *q = qdisc_priv(sch);
3141da177e4SLinus Torvalds 
3151da177e4SLinus Torvalds 	qdisc_reset(q->qdisc);
3161da177e4SLinus Torvalds 	sch->q.qlen = 0;
31759cb5c67SPatrick McHardy 	qdisc_watchdog_cancel(&q->watchdog);
3181da177e4SLinus Torvalds }
3191da177e4SLinus Torvalds 
3201da177e4SLinus Torvalds /*
3211da177e4SLinus Torvalds  * Distribution data is a variable size payload containing
3221da177e4SLinus Torvalds  * signed 16 bit values.
3231da177e4SLinus Torvalds  */
3241e90474cSPatrick McHardy static int get_dist_table(struct Qdisc *sch, const struct nlattr *attr)
3251da177e4SLinus Torvalds {
3261da177e4SLinus Torvalds 	struct netem_sched_data *q = qdisc_priv(sch);
3271e90474cSPatrick McHardy 	unsigned long n = nla_len(attr)/sizeof(__s16);
3281e90474cSPatrick McHardy 	const __s16 *data = nla_data(attr);
3297698b4fcSDavid S. Miller 	spinlock_t *root_lock;
3301da177e4SLinus Torvalds 	struct disttable *d;
3311da177e4SLinus Torvalds 	int i;
3321da177e4SLinus Torvalds 
3331da177e4SLinus Torvalds 	if (n > 65536)
3341da177e4SLinus Torvalds 		return -EINVAL;
3351da177e4SLinus Torvalds 
3361da177e4SLinus Torvalds 	d = kmalloc(sizeof(*d) + n*sizeof(d->table[0]), GFP_KERNEL);
3371da177e4SLinus Torvalds 	if (!d)
3381da177e4SLinus Torvalds 		return -ENOMEM;
3391da177e4SLinus Torvalds 
3401da177e4SLinus Torvalds 	d->size = n;
3411da177e4SLinus Torvalds 	for (i = 0; i < n; i++)
3421da177e4SLinus Torvalds 		d->table[i] = data[i];
3431da177e4SLinus Torvalds 
3447698b4fcSDavid S. Miller 	root_lock = qdisc_root_lock(sch);
3457698b4fcSDavid S. Miller 
3467698b4fcSDavid S. Miller 	spin_lock_bh(root_lock);
3471da177e4SLinus Torvalds 	d = xchg(&q->delay_dist, d);
3487698b4fcSDavid S. Miller 	spin_unlock_bh(root_lock);
3491da177e4SLinus Torvalds 
3501da177e4SLinus Torvalds 	kfree(d);
3511da177e4SLinus Torvalds 	return 0;
3521da177e4SLinus Torvalds }
3531da177e4SLinus Torvalds 
3541e90474cSPatrick McHardy static int get_correlation(struct Qdisc *sch, const struct nlattr *attr)
3551da177e4SLinus Torvalds {
3561da177e4SLinus Torvalds 	struct netem_sched_data *q = qdisc_priv(sch);
3571e90474cSPatrick McHardy 	const struct tc_netem_corr *c = nla_data(attr);
3581da177e4SLinus Torvalds 
3591da177e4SLinus Torvalds 	init_crandom(&q->delay_cor, c->delay_corr);
3601da177e4SLinus Torvalds 	init_crandom(&q->loss_cor, c->loss_corr);
3611da177e4SLinus Torvalds 	init_crandom(&q->dup_cor, c->dup_corr);
3621da177e4SLinus Torvalds 	return 0;
3631da177e4SLinus Torvalds }
3641da177e4SLinus Torvalds 
3651e90474cSPatrick McHardy static int get_reorder(struct Qdisc *sch, const struct nlattr *attr)
3660dca51d3SStephen Hemminger {
3670dca51d3SStephen Hemminger 	struct netem_sched_data *q = qdisc_priv(sch);
3681e90474cSPatrick McHardy 	const struct tc_netem_reorder *r = nla_data(attr);
3690dca51d3SStephen Hemminger 
3700dca51d3SStephen Hemminger 	q->reorder = r->probability;
3710dca51d3SStephen Hemminger 	init_crandom(&q->reorder_cor, r->correlation);
3720dca51d3SStephen Hemminger 	return 0;
3730dca51d3SStephen Hemminger }
3740dca51d3SStephen Hemminger 
3751e90474cSPatrick McHardy static int get_corrupt(struct Qdisc *sch, const struct nlattr *attr)
376c865e5d9SStephen Hemminger {
377c865e5d9SStephen Hemminger 	struct netem_sched_data *q = qdisc_priv(sch);
3781e90474cSPatrick McHardy 	const struct tc_netem_corrupt *r = nla_data(attr);
379c865e5d9SStephen Hemminger 
380c865e5d9SStephen Hemminger 	q->corrupt = r->probability;
381c865e5d9SStephen Hemminger 	init_crandom(&q->corrupt_cor, r->correlation);
382c865e5d9SStephen Hemminger 	return 0;
383c865e5d9SStephen Hemminger }
384c865e5d9SStephen Hemminger 
38527a3421eSPatrick McHardy static const struct nla_policy netem_policy[TCA_NETEM_MAX + 1] = {
38627a3421eSPatrick McHardy 	[TCA_NETEM_CORR]	= { .len = sizeof(struct tc_netem_corr) },
38727a3421eSPatrick McHardy 	[TCA_NETEM_REORDER]	= { .len = sizeof(struct tc_netem_reorder) },
38827a3421eSPatrick McHardy 	[TCA_NETEM_CORRUPT]	= { .len = sizeof(struct tc_netem_corrupt) },
38927a3421eSPatrick McHardy };
39027a3421eSPatrick McHardy 
391c865e5d9SStephen Hemminger /* Parse netlink message to set options */
3921e90474cSPatrick McHardy static int netem_change(struct Qdisc *sch, struct nlattr *opt)
3931da177e4SLinus Torvalds {
3941da177e4SLinus Torvalds 	struct netem_sched_data *q = qdisc_priv(sch);
395b03f4672SPatrick McHardy 	struct nlattr *tb[TCA_NETEM_MAX + 1];
3961da177e4SLinus Torvalds 	struct tc_netem_qopt *qopt;
3971da177e4SLinus Torvalds 	int ret;
3981da177e4SLinus Torvalds 
399b03f4672SPatrick McHardy 	if (opt == NULL)
4001da177e4SLinus Torvalds 		return -EINVAL;
4011da177e4SLinus Torvalds 
40227a3421eSPatrick McHardy 	ret = nla_parse_nested_compat(tb, TCA_NETEM_MAX, opt, netem_policy,
40327a3421eSPatrick McHardy 				      qopt, sizeof(*qopt));
404b03f4672SPatrick McHardy 	if (ret < 0)
405b03f4672SPatrick McHardy 		return ret;
406b03f4672SPatrick McHardy 
407fb0305ceSPatrick McHardy 	ret = fifo_set_limit(q->qdisc, qopt->limit);
4081da177e4SLinus Torvalds 	if (ret) {
4091da177e4SLinus Torvalds 		pr_debug("netem: can't set fifo limit\n");
4101da177e4SLinus Torvalds 		return ret;
4111da177e4SLinus Torvalds 	}
4121da177e4SLinus Torvalds 
4131da177e4SLinus Torvalds 	q->latency = qopt->latency;
4141da177e4SLinus Torvalds 	q->jitter = qopt->jitter;
4151da177e4SLinus Torvalds 	q->limit = qopt->limit;
4161da177e4SLinus Torvalds 	q->gap = qopt->gap;
4170dca51d3SStephen Hemminger 	q->counter = 0;
4181da177e4SLinus Torvalds 	q->loss = qopt->loss;
4191da177e4SLinus Torvalds 	q->duplicate = qopt->duplicate;
4201da177e4SLinus Torvalds 
421bb2f8cc0SStephen Hemminger 	/* for compatibility with earlier versions.
422bb2f8cc0SStephen Hemminger 	 * if gap is set, need to assume 100% probability
4230dca51d3SStephen Hemminger 	 */
424a362e0a7SStephen Hemminger 	if (q->gap)
4250dca51d3SStephen Hemminger 		q->reorder = ~0;
4260dca51d3SStephen Hemminger 
4271e90474cSPatrick McHardy 	if (tb[TCA_NETEM_CORR]) {
4281e90474cSPatrick McHardy 		ret = get_correlation(sch, tb[TCA_NETEM_CORR]);
4291da177e4SLinus Torvalds 		if (ret)
4301da177e4SLinus Torvalds 			return ret;
4311da177e4SLinus Torvalds 	}
4321da177e4SLinus Torvalds 
4331e90474cSPatrick McHardy 	if (tb[TCA_NETEM_DELAY_DIST]) {
4341e90474cSPatrick McHardy 		ret = get_dist_table(sch, tb[TCA_NETEM_DELAY_DIST]);
4351da177e4SLinus Torvalds 		if (ret)
4361da177e4SLinus Torvalds 			return ret;
4371da177e4SLinus Torvalds 	}
438c865e5d9SStephen Hemminger 
4391e90474cSPatrick McHardy 	if (tb[TCA_NETEM_REORDER]) {
4401e90474cSPatrick McHardy 		ret = get_reorder(sch, tb[TCA_NETEM_REORDER]);
4410dca51d3SStephen Hemminger 		if (ret)
4420dca51d3SStephen Hemminger 			return ret;
4430dca51d3SStephen Hemminger 	}
4441da177e4SLinus Torvalds 
4451e90474cSPatrick McHardy 	if (tb[TCA_NETEM_CORRUPT]) {
4461e90474cSPatrick McHardy 		ret = get_corrupt(sch, tb[TCA_NETEM_CORRUPT]);
447c865e5d9SStephen Hemminger 		if (ret)
448c865e5d9SStephen Hemminger 			return ret;
449c865e5d9SStephen Hemminger 	}
4501da177e4SLinus Torvalds 
4511da177e4SLinus Torvalds 	return 0;
4521da177e4SLinus Torvalds }
4531da177e4SLinus Torvalds 
454300ce174SStephen Hemminger /*
455300ce174SStephen Hemminger  * Special case version of FIFO queue for use by netem.
456300ce174SStephen Hemminger  * It queues in order based on timestamps in skb's
457300ce174SStephen Hemminger  */
458300ce174SStephen Hemminger struct fifo_sched_data {
459300ce174SStephen Hemminger 	u32 limit;
460075aa573SStephen Hemminger 	psched_time_t oldest;
461300ce174SStephen Hemminger };
462300ce174SStephen Hemminger 
463300ce174SStephen Hemminger static int tfifo_enqueue(struct sk_buff *nskb, struct Qdisc *sch)
464300ce174SStephen Hemminger {
465300ce174SStephen Hemminger 	struct fifo_sched_data *q = qdisc_priv(sch);
466300ce174SStephen Hemminger 	struct sk_buff_head *list = &sch->q;
4675f86173bSJussi Kivilinna 	psched_time_t tnext = netem_skb_cb(nskb)->time_to_send;
468300ce174SStephen Hemminger 	struct sk_buff *skb;
469300ce174SStephen Hemminger 
470300ce174SStephen Hemminger 	if (likely(skb_queue_len(list) < q->limit)) {
471075aa573SStephen Hemminger 		/* Optimize for add at tail */
472104e0878SPatrick McHardy 		if (likely(skb_queue_empty(list) || tnext >= q->oldest)) {
473075aa573SStephen Hemminger 			q->oldest = tnext;
474075aa573SStephen Hemminger 			return qdisc_enqueue_tail(nskb, sch);
475075aa573SStephen Hemminger 		}
476075aa573SStephen Hemminger 
477300ce174SStephen Hemminger 		skb_queue_reverse_walk(list, skb) {
4785f86173bSJussi Kivilinna 			const struct netem_skb_cb *cb = netem_skb_cb(skb);
479300ce174SStephen Hemminger 
480104e0878SPatrick McHardy 			if (tnext >= cb->time_to_send)
481300ce174SStephen Hemminger 				break;
482300ce174SStephen Hemminger 		}
483300ce174SStephen Hemminger 
484300ce174SStephen Hemminger 		__skb_queue_after(list, skb, nskb);
485300ce174SStephen Hemminger 
4860abf77e5SJussi Kivilinna 		sch->qstats.backlog += qdisc_pkt_len(nskb);
4870abf77e5SJussi Kivilinna 		sch->bstats.bytes += qdisc_pkt_len(nskb);
488300ce174SStephen Hemminger 		sch->bstats.packets++;
489300ce174SStephen Hemminger 
490300ce174SStephen Hemminger 		return NET_XMIT_SUCCESS;
491300ce174SStephen Hemminger 	}
492300ce174SStephen Hemminger 
493075aa573SStephen Hemminger 	return qdisc_reshape_fail(nskb, sch);
494300ce174SStephen Hemminger }
495300ce174SStephen Hemminger 
4961e90474cSPatrick McHardy static int tfifo_init(struct Qdisc *sch, struct nlattr *opt)
497300ce174SStephen Hemminger {
498300ce174SStephen Hemminger 	struct fifo_sched_data *q = qdisc_priv(sch);
499300ce174SStephen Hemminger 
500300ce174SStephen Hemminger 	if (opt) {
5011e90474cSPatrick McHardy 		struct tc_fifo_qopt *ctl = nla_data(opt);
5021e90474cSPatrick McHardy 		if (nla_len(opt) < sizeof(*ctl))
503300ce174SStephen Hemminger 			return -EINVAL;
504300ce174SStephen Hemminger 
505300ce174SStephen Hemminger 		q->limit = ctl->limit;
506300ce174SStephen Hemminger 	} else
5075ce2d488SDavid S. Miller 		q->limit = max_t(u32, qdisc_dev(sch)->tx_queue_len, 1);
508300ce174SStephen Hemminger 
509a084980dSPatrick McHardy 	q->oldest = PSCHED_PASTPERFECT;
510300ce174SStephen Hemminger 	return 0;
511300ce174SStephen Hemminger }
512300ce174SStephen Hemminger 
513300ce174SStephen Hemminger static int tfifo_dump(struct Qdisc *sch, struct sk_buff *skb)
514300ce174SStephen Hemminger {
515300ce174SStephen Hemminger 	struct fifo_sched_data *q = qdisc_priv(sch);
516300ce174SStephen Hemminger 	struct tc_fifo_qopt opt = { .limit = q->limit };
517300ce174SStephen Hemminger 
5181e90474cSPatrick McHardy 	NLA_PUT(skb, TCA_OPTIONS, sizeof(opt), &opt);
519300ce174SStephen Hemminger 	return skb->len;
520300ce174SStephen Hemminger 
5211e90474cSPatrick McHardy nla_put_failure:
522300ce174SStephen Hemminger 	return -1;
523300ce174SStephen Hemminger }
524300ce174SStephen Hemminger 
52520fea08bSEric Dumazet static struct Qdisc_ops tfifo_qdisc_ops __read_mostly = {
526300ce174SStephen Hemminger 	.id		=	"tfifo",
527300ce174SStephen Hemminger 	.priv_size	=	sizeof(struct fifo_sched_data),
528300ce174SStephen Hemminger 	.enqueue	=	tfifo_enqueue,
529300ce174SStephen Hemminger 	.dequeue	=	qdisc_dequeue_head,
530300ce174SStephen Hemminger 	.requeue	=	qdisc_requeue,
531300ce174SStephen Hemminger 	.drop		=	qdisc_queue_drop,
532300ce174SStephen Hemminger 	.init		=	tfifo_init,
533300ce174SStephen Hemminger 	.reset		=	qdisc_reset_queue,
534300ce174SStephen Hemminger 	.change		=	tfifo_init,
535300ce174SStephen Hemminger 	.dump		=	tfifo_dump,
536300ce174SStephen Hemminger };
537300ce174SStephen Hemminger 
5381e90474cSPatrick McHardy static int netem_init(struct Qdisc *sch, struct nlattr *opt)
5391da177e4SLinus Torvalds {
5401da177e4SLinus Torvalds 	struct netem_sched_data *q = qdisc_priv(sch);
5411da177e4SLinus Torvalds 	int ret;
5421da177e4SLinus Torvalds 
5431da177e4SLinus Torvalds 	if (!opt)
5441da177e4SLinus Torvalds 		return -EINVAL;
5451da177e4SLinus Torvalds 
54659cb5c67SPatrick McHardy 	qdisc_watchdog_init(&q->watchdog, sch);
5471da177e4SLinus Torvalds 
5485ce2d488SDavid S. Miller 	q->qdisc = qdisc_create_dflt(qdisc_dev(sch), sch->dev_queue,
549bb949fbdSDavid S. Miller 				     &tfifo_qdisc_ops,
5509f9afec4SPatrick McHardy 				     TC_H_MAKE(sch->handle, 1));
5511da177e4SLinus Torvalds 	if (!q->qdisc) {
5521da177e4SLinus Torvalds 		pr_debug("netem: qdisc create failed\n");
5531da177e4SLinus Torvalds 		return -ENOMEM;
5541da177e4SLinus Torvalds 	}
5551da177e4SLinus Torvalds 
5561da177e4SLinus Torvalds 	ret = netem_change(sch, opt);
5571da177e4SLinus Torvalds 	if (ret) {
5581da177e4SLinus Torvalds 		pr_debug("netem: change failed\n");
5591da177e4SLinus Torvalds 		qdisc_destroy(q->qdisc);
5601da177e4SLinus Torvalds 	}
5611da177e4SLinus Torvalds 	return ret;
5621da177e4SLinus Torvalds }
5631da177e4SLinus Torvalds 
5641da177e4SLinus Torvalds static void netem_destroy(struct Qdisc *sch)
5651da177e4SLinus Torvalds {
5661da177e4SLinus Torvalds 	struct netem_sched_data *q = qdisc_priv(sch);
5671da177e4SLinus Torvalds 
56859cb5c67SPatrick McHardy 	qdisc_watchdog_cancel(&q->watchdog);
5691da177e4SLinus Torvalds 	qdisc_destroy(q->qdisc);
5701da177e4SLinus Torvalds 	kfree(q->delay_dist);
5711da177e4SLinus Torvalds }
5721da177e4SLinus Torvalds 
5731da177e4SLinus Torvalds static int netem_dump(struct Qdisc *sch, struct sk_buff *skb)
5741da177e4SLinus Torvalds {
5751da177e4SLinus Torvalds 	const struct netem_sched_data *q = qdisc_priv(sch);
57627a884dcSArnaldo Carvalho de Melo 	unsigned char *b = skb_tail_pointer(skb);
5771e90474cSPatrick McHardy 	struct nlattr *nla = (struct nlattr *) b;
5781da177e4SLinus Torvalds 	struct tc_netem_qopt qopt;
5791da177e4SLinus Torvalds 	struct tc_netem_corr cor;
5800dca51d3SStephen Hemminger 	struct tc_netem_reorder reorder;
581c865e5d9SStephen Hemminger 	struct tc_netem_corrupt corrupt;
5821da177e4SLinus Torvalds 
5831da177e4SLinus Torvalds 	qopt.latency = q->latency;
5841da177e4SLinus Torvalds 	qopt.jitter = q->jitter;
5851da177e4SLinus Torvalds 	qopt.limit = q->limit;
5861da177e4SLinus Torvalds 	qopt.loss = q->loss;
5871da177e4SLinus Torvalds 	qopt.gap = q->gap;
5881da177e4SLinus Torvalds 	qopt.duplicate = q->duplicate;
5891e90474cSPatrick McHardy 	NLA_PUT(skb, TCA_OPTIONS, sizeof(qopt), &qopt);
5901da177e4SLinus Torvalds 
5911da177e4SLinus Torvalds 	cor.delay_corr = q->delay_cor.rho;
5921da177e4SLinus Torvalds 	cor.loss_corr = q->loss_cor.rho;
5931da177e4SLinus Torvalds 	cor.dup_corr = q->dup_cor.rho;
5941e90474cSPatrick McHardy 	NLA_PUT(skb, TCA_NETEM_CORR, sizeof(cor), &cor);
5950dca51d3SStephen Hemminger 
5960dca51d3SStephen Hemminger 	reorder.probability = q->reorder;
5970dca51d3SStephen Hemminger 	reorder.correlation = q->reorder_cor.rho;
5981e90474cSPatrick McHardy 	NLA_PUT(skb, TCA_NETEM_REORDER, sizeof(reorder), &reorder);
5990dca51d3SStephen Hemminger 
600c865e5d9SStephen Hemminger 	corrupt.probability = q->corrupt;
601c865e5d9SStephen Hemminger 	corrupt.correlation = q->corrupt_cor.rho;
6021e90474cSPatrick McHardy 	NLA_PUT(skb, TCA_NETEM_CORRUPT, sizeof(corrupt), &corrupt);
603c865e5d9SStephen Hemminger 
6041e90474cSPatrick McHardy 	nla->nla_len = skb_tail_pointer(skb) - b;
6051da177e4SLinus Torvalds 
6061da177e4SLinus Torvalds 	return skb->len;
6071da177e4SLinus Torvalds 
6081e90474cSPatrick McHardy nla_put_failure:
609dc5fc579SArnaldo Carvalho de Melo 	nlmsg_trim(skb, b);
6101da177e4SLinus Torvalds 	return -1;
6111da177e4SLinus Torvalds }
6121da177e4SLinus Torvalds 
6131da177e4SLinus Torvalds static int netem_dump_class(struct Qdisc *sch, unsigned long cl,
6141da177e4SLinus Torvalds 			  struct sk_buff *skb, struct tcmsg *tcm)
6151da177e4SLinus Torvalds {
6161da177e4SLinus Torvalds 	struct netem_sched_data *q = qdisc_priv(sch);
6171da177e4SLinus Torvalds 
6181da177e4SLinus Torvalds 	if (cl != 1) 	/* only one class */
6191da177e4SLinus Torvalds 		return -ENOENT;
6201da177e4SLinus Torvalds 
6211da177e4SLinus Torvalds 	tcm->tcm_handle |= TC_H_MIN(1);
6221da177e4SLinus Torvalds 	tcm->tcm_info = q->qdisc->handle;
6231da177e4SLinus Torvalds 
6241da177e4SLinus Torvalds 	return 0;
6251da177e4SLinus Torvalds }
6261da177e4SLinus Torvalds 
6271da177e4SLinus Torvalds static int netem_graft(struct Qdisc *sch, unsigned long arg, struct Qdisc *new,
6281da177e4SLinus Torvalds 		     struct Qdisc **old)
6291da177e4SLinus Torvalds {
6301da177e4SLinus Torvalds 	struct netem_sched_data *q = qdisc_priv(sch);
6311da177e4SLinus Torvalds 
6321da177e4SLinus Torvalds 	if (new == NULL)
6331da177e4SLinus Torvalds 		new = &noop_qdisc;
6341da177e4SLinus Torvalds 
6351da177e4SLinus Torvalds 	sch_tree_lock(sch);
6361da177e4SLinus Torvalds 	*old = xchg(&q->qdisc, new);
6375e50da01SPatrick McHardy 	qdisc_tree_decrease_qlen(*old, (*old)->q.qlen);
6381da177e4SLinus Torvalds 	qdisc_reset(*old);
6391da177e4SLinus Torvalds 	sch_tree_unlock(sch);
6401da177e4SLinus Torvalds 
6411da177e4SLinus Torvalds 	return 0;
6421da177e4SLinus Torvalds }
6431da177e4SLinus Torvalds 
6441da177e4SLinus Torvalds static struct Qdisc *netem_leaf(struct Qdisc *sch, unsigned long arg)
6451da177e4SLinus Torvalds {
6461da177e4SLinus Torvalds 	struct netem_sched_data *q = qdisc_priv(sch);
6471da177e4SLinus Torvalds 	return q->qdisc;
6481da177e4SLinus Torvalds }
6491da177e4SLinus Torvalds 
6501da177e4SLinus Torvalds static unsigned long netem_get(struct Qdisc *sch, u32 classid)
6511da177e4SLinus Torvalds {
6521da177e4SLinus Torvalds 	return 1;
6531da177e4SLinus Torvalds }
6541da177e4SLinus Torvalds 
6551da177e4SLinus Torvalds static void netem_put(struct Qdisc *sch, unsigned long arg)
6561da177e4SLinus Torvalds {
6571da177e4SLinus Torvalds }
6581da177e4SLinus Torvalds 
6591da177e4SLinus Torvalds static int netem_change_class(struct Qdisc *sch, u32 classid, u32 parentid,
6601e90474cSPatrick McHardy 			    struct nlattr **tca, unsigned long *arg)
6611da177e4SLinus Torvalds {
6621da177e4SLinus Torvalds 	return -ENOSYS;
6631da177e4SLinus Torvalds }
6641da177e4SLinus Torvalds 
6651da177e4SLinus Torvalds static int netem_delete(struct Qdisc *sch, unsigned long arg)
6661da177e4SLinus Torvalds {
6671da177e4SLinus Torvalds 	return -ENOSYS;
6681da177e4SLinus Torvalds }
6691da177e4SLinus Torvalds 
6701da177e4SLinus Torvalds static void netem_walk(struct Qdisc *sch, struct qdisc_walker *walker)
6711da177e4SLinus Torvalds {
6721da177e4SLinus Torvalds 	if (!walker->stop) {
6731da177e4SLinus Torvalds 		if (walker->count >= walker->skip)
6741da177e4SLinus Torvalds 			if (walker->fn(sch, 1, walker) < 0) {
6751da177e4SLinus Torvalds 				walker->stop = 1;
6761da177e4SLinus Torvalds 				return;
6771da177e4SLinus Torvalds 			}
6781da177e4SLinus Torvalds 		walker->count++;
6791da177e4SLinus Torvalds 	}
6801da177e4SLinus Torvalds }
6811da177e4SLinus Torvalds 
6821da177e4SLinus Torvalds static struct tcf_proto **netem_find_tcf(struct Qdisc *sch, unsigned long cl)
6831da177e4SLinus Torvalds {
6841da177e4SLinus Torvalds 	return NULL;
6851da177e4SLinus Torvalds }
6861da177e4SLinus Torvalds 
68720fea08bSEric Dumazet static const struct Qdisc_class_ops netem_class_ops = {
6881da177e4SLinus Torvalds 	.graft		=	netem_graft,
6891da177e4SLinus Torvalds 	.leaf		=	netem_leaf,
6901da177e4SLinus Torvalds 	.get		=	netem_get,
6911da177e4SLinus Torvalds 	.put		=	netem_put,
6921da177e4SLinus Torvalds 	.change		=	netem_change_class,
6931da177e4SLinus Torvalds 	.delete		=	netem_delete,
6941da177e4SLinus Torvalds 	.walk		=	netem_walk,
6951da177e4SLinus Torvalds 	.tcf_chain	=	netem_find_tcf,
6961da177e4SLinus Torvalds 	.dump		=	netem_dump_class,
6971da177e4SLinus Torvalds };
6981da177e4SLinus Torvalds 
69920fea08bSEric Dumazet static struct Qdisc_ops netem_qdisc_ops __read_mostly = {
7001da177e4SLinus Torvalds 	.id		=	"netem",
7011da177e4SLinus Torvalds 	.cl_ops		=	&netem_class_ops,
7021da177e4SLinus Torvalds 	.priv_size	=	sizeof(struct netem_sched_data),
7031da177e4SLinus Torvalds 	.enqueue	=	netem_enqueue,
7041da177e4SLinus Torvalds 	.dequeue	=	netem_dequeue,
7051da177e4SLinus Torvalds 	.requeue	=	netem_requeue,
7061da177e4SLinus Torvalds 	.drop		=	netem_drop,
7071da177e4SLinus Torvalds 	.init		=	netem_init,
7081da177e4SLinus Torvalds 	.reset		=	netem_reset,
7091da177e4SLinus Torvalds 	.destroy	=	netem_destroy,
7101da177e4SLinus Torvalds 	.change		=	netem_change,
7111da177e4SLinus Torvalds 	.dump		=	netem_dump,
7121da177e4SLinus Torvalds 	.owner		=	THIS_MODULE,
7131da177e4SLinus Torvalds };
7141da177e4SLinus Torvalds 
7151da177e4SLinus Torvalds 
7161da177e4SLinus Torvalds static int __init netem_module_init(void)
7171da177e4SLinus Torvalds {
718eb229c4cSStephen Hemminger 	pr_info("netem: version " VERSION "\n");
7191da177e4SLinus Torvalds 	return register_qdisc(&netem_qdisc_ops);
7201da177e4SLinus Torvalds }
7211da177e4SLinus Torvalds static void __exit netem_module_exit(void)
7221da177e4SLinus Torvalds {
7231da177e4SLinus Torvalds 	unregister_qdisc(&netem_qdisc_ops);
7241da177e4SLinus Torvalds }
7251da177e4SLinus Torvalds module_init(netem_module_init)
7261da177e4SLinus Torvalds module_exit(netem_module_exit)
7271da177e4SLinus Torvalds MODULE_LICENSE("GPL");
728