[PKT_SCHED] netem: packet corruption option
[linux-2.6.git] / net / sched / sch_netem.c
1 /*
2  * net/sched/sch_netem.c        Network emulator
3  *
4  *              This program is free software; you can redistribute it and/or
5  *              modify it under the terms of the GNU General Public License
6  *              as published by the Free Software Foundation; either version
7  *              2 of the License, or (at your option) any later version.
8  *
9  *              Many of the algorithms and ideas for this came from
10  *              NIST Net which is not copyrighted. 
11  *
12  * Authors:     Stephen Hemminger <shemminger@osdl.org>
13  *              Catalin(ux aka Dino) BOIE <catab at umbrella dot ro>
14  */
15
16 #include <linux/config.h>
17 #include <linux/module.h>
18 #include <linux/bitops.h>
19 #include <linux/types.h>
20 #include <linux/kernel.h>
21 #include <linux/errno.h>
22 #include <linux/netdevice.h>
23 #include <linux/skbuff.h>
24 #include <linux/rtnetlink.h>
25
26 #include <net/pkt_sched.h>
27
28 #define VERSION "1.2"
29
30 /*      Network Emulation Queuing algorithm.
31         ====================================
32
33         Sources: [1] Mark Carson, Darrin Santay, "NIST Net - A Linux-based
34                  Network Emulation Tool
35                  [2] Luigi Rizzo, DummyNet for FreeBSD
36
37          ----------------------------------------------------------------
38
39          This started out as a simple way to delay outgoing packets to
40          test TCP but has grown to include most of the functionality
41          of a full blown network emulator like NISTnet. It can delay
42          packets and add random jitter (and correlation). The random
43          distribution can be loaded from a table as well to provide
44          normal, Pareto, or experimental curves. Packet loss,
45          duplication, and reordering can also be emulated.
46
47          This qdisc does not do classification that can be handled in
48          layering other disciplines.  It does not need to do bandwidth
49          control either since that can be handled by using token
50          bucket or other rate control.
51
52          The simulator is limited by the Linux timer resolution
53          and will create packet bursts on the HZ boundary (1ms).
54 */
55
56 struct netem_sched_data {
57         struct Qdisc    *qdisc;
58         struct timer_list timer;
59
60         u32 latency;
61         u32 loss;
62         u32 limit;
63         u32 counter;
64         u32 gap;
65         u32 jitter;
66         u32 duplicate;
67         u32 reorder;
68         u32 corrupt;
69
70         struct crndstate {
71                 unsigned long last;
72                 unsigned long rho;
73         } delay_cor, loss_cor, dup_cor, reorder_cor, corrupt_cor;
74
75         struct disttable {
76                 u32  size;
77                 s16 table[0];
78         } *delay_dist;
79 };
80
81 /* Time stamp put into socket buffer control block */
82 struct netem_skb_cb {
83         psched_time_t   time_to_send;
84 };
85
86 /* init_crandom - initialize correlated random number generator
87  * Use entropy source for initial seed.
88  */
89 static void init_crandom(struct crndstate *state, unsigned long rho)
90 {
91         state->rho = rho;
92         state->last = net_random();
93 }
94
95 /* get_crandom - correlated random number generator
96  * Next number depends on last value.
97  * rho is scaled to avoid floating point.
98  */
99 static unsigned long get_crandom(struct crndstate *state)
100 {
101         u64 value, rho;
102         unsigned long answer;
103
104         if (state->rho == 0)    /* no correllation */
105                 return net_random();
106
107         value = net_random();
108         rho = (u64)state->rho + 1;
109         answer = (value * ((1ull<<32) - rho) + state->last * rho) >> 32;
110         state->last = answer;
111         return answer;
112 }
113
114 /* tabledist - return a pseudo-randomly distributed value with mean mu and
115  * std deviation sigma.  Uses table lookup to approximate the desired
116  * distribution, and a uniformly-distributed pseudo-random source.
117  */
118 static long tabledist(unsigned long mu, long sigma, 
119                       struct crndstate *state, const struct disttable *dist)
120 {
121         long t, x;
122         unsigned long rnd;
123
124         if (sigma == 0)
125                 return mu;
126
127         rnd = get_crandom(state);
128
129         /* default uniform distribution */
130         if (dist == NULL) 
131                 return (rnd % (2*sigma)) - sigma + mu;
132
133         t = dist->table[rnd % dist->size];
134         x = (sigma % NETEM_DIST_SCALE) * t;
135         if (x >= 0)
136                 x += NETEM_DIST_SCALE/2;
137         else
138                 x -= NETEM_DIST_SCALE/2;
139
140         return  x / NETEM_DIST_SCALE + (sigma / NETEM_DIST_SCALE) * t + mu;
141 }
142
143 /*
144  * Insert one skb into qdisc.
145  * Note: parent depends on return value to account for queue length.
146  *      NET_XMIT_DROP: queue length didn't change.
147  *      NET_XMIT_SUCCESS: one skb was queued.
148  */
149 static int netem_enqueue(struct sk_buff *skb, struct Qdisc *sch)
150 {
151         struct netem_sched_data *q = qdisc_priv(sch);
152         struct netem_skb_cb *cb = (struct netem_skb_cb *)skb->cb;
153         struct sk_buff *skb2;
154         int ret;
155         int count = 1;
156
157         pr_debug("netem_enqueue skb=%p\n", skb);
158
159         /* Random duplication */
160         if (q->duplicate && q->duplicate >= get_crandom(&q->dup_cor))
161                 ++count;
162
163         /* Random packet drop 0 => none, ~0 => all */
164         if (q->loss && q->loss >= get_crandom(&q->loss_cor))
165                 --count;
166
167         if (count == 0) {
168                 sch->qstats.drops++;
169                 kfree_skb(skb);
170                 return NET_XMIT_DROP;
171         }
172
173         /*
174          * If we need to duplicate packet, then re-insert at top of the
175          * qdisc tree, since parent queuer expects that only one
176          * skb will be queued.
177          */
178         if (count > 1 && (skb2 = skb_clone(skb, GFP_ATOMIC)) != NULL) {
179                 struct Qdisc *rootq = sch->dev->qdisc;
180                 u32 dupsave = q->duplicate; /* prevent duplicating a dup... */
181                 q->duplicate = 0;
182
183                 rootq->enqueue(skb2, rootq);
184                 q->duplicate = dupsave;
185         }
186
187         /*
188          * Randomized packet corruption.
189          * Make copy if needed since we are modifying
190          * If packet is going to be hardware checksummed, then
191          * do it now in software before we mangle it.
192          */
193         if (q->corrupt && q->corrupt >= get_crandom(&q->corrupt_cor)) {
194                 if (!(skb = skb_unshare(skb, GFP_ATOMIC))
195                     || (skb->ip_summed == CHECKSUM_HW
196                         && skb_checksum_help(skb, 0))) {
197                         sch->qstats.drops++;
198                         return NET_XMIT_DROP;
199                 }
200
201                 skb->data[net_random() % skb_headlen(skb)] ^= 1<<(net_random() % 8);
202         }
203
204         if (q->gap == 0                 /* not doing reordering */
205             || q->counter < q->gap      /* inside last reordering gap */
206             || q->reorder < get_crandom(&q->reorder_cor)) {
207                 psched_time_t now;
208                 psched_tdiff_t delay;
209
210                 delay = tabledist(q->latency, q->jitter,
211                                   &q->delay_cor, q->delay_dist);
212
213                 PSCHED_GET_TIME(now);
214                 PSCHED_TADD2(now, delay, cb->time_to_send);
215                 ++q->counter;
216                 ret = q->qdisc->enqueue(skb, q->qdisc);
217         } else {
218                 /* 
219                  * Do re-ordering by putting one out of N packets at the front
220                  * of the queue.
221                  */
222                 PSCHED_GET_TIME(cb->time_to_send);
223                 q->counter = 0;
224                 ret = q->qdisc->ops->requeue(skb, q->qdisc);
225         }
226
227         if (likely(ret == NET_XMIT_SUCCESS)) {
228                 sch->q.qlen++;
229                 sch->bstats.bytes += skb->len;
230                 sch->bstats.packets++;
231         } else
232                 sch->qstats.drops++;
233
234         pr_debug("netem: enqueue ret %d\n", ret);
235         return ret;
236 }
237
238 /* Requeue packets but don't change time stamp */
239 static int netem_requeue(struct sk_buff *skb, struct Qdisc *sch)
240 {
241         struct netem_sched_data *q = qdisc_priv(sch);
242         int ret;
243
244         if ((ret = q->qdisc->ops->requeue(skb, q->qdisc)) == 0) {
245                 sch->q.qlen++;
246                 sch->qstats.requeues++;
247         }
248
249         return ret;
250 }
251
252 static unsigned int netem_drop(struct Qdisc* sch)
253 {
254         struct netem_sched_data *q = qdisc_priv(sch);
255         unsigned int len;
256
257         if ((len = q->qdisc->ops->drop(q->qdisc)) != 0) {
258                 sch->q.qlen--;
259                 sch->qstats.drops++;
260         }
261         return len;
262 }
263
264 static struct sk_buff *netem_dequeue(struct Qdisc *sch)
265 {
266         struct netem_sched_data *q = qdisc_priv(sch);
267         struct sk_buff *skb;
268
269         skb = q->qdisc->dequeue(q->qdisc);
270         if (skb) {
271                 const struct netem_skb_cb *cb
272                         = (const struct netem_skb_cb *)skb->cb;
273                 psched_time_t now;
274
275                 /* if more time remaining? */
276                 PSCHED_GET_TIME(now);
277
278                 if (PSCHED_TLESS(cb->time_to_send, now)) {
279                         pr_debug("netem_dequeue: return skb=%p\n", skb);
280                         sch->q.qlen--;
281                         sch->flags &= ~TCQ_F_THROTTLED;
282                         return skb;
283                 } else {
284                         psched_tdiff_t delay = PSCHED_TDIFF(cb->time_to_send, now);
285
286                         if (q->qdisc->ops->requeue(skb, q->qdisc) != NET_XMIT_SUCCESS) {
287                                 sch->qstats.drops++;
288
289                                 /* After this qlen is confused */
290                                 printk(KERN_ERR "netem: queue discpline %s could not requeue\n",
291                                        q->qdisc->ops->id);
292
293                                 sch->q.qlen--;
294                         }
295
296                         mod_timer(&q->timer, jiffies + PSCHED_US2JIFFIE(delay));
297                         sch->flags |= TCQ_F_THROTTLED;
298                 }
299         }
300
301         return NULL;
302 }
303
304 static void netem_watchdog(unsigned long arg)
305 {
306         struct Qdisc *sch = (struct Qdisc *)arg;
307
308         pr_debug("netem_watchdog qlen=%d\n", sch->q.qlen);
309         sch->flags &= ~TCQ_F_THROTTLED;
310         netif_schedule(sch->dev);
311 }
312
313 static void netem_reset(struct Qdisc *sch)
314 {
315         struct netem_sched_data *q = qdisc_priv(sch);
316
317         qdisc_reset(q->qdisc);
318         sch->q.qlen = 0;
319         sch->flags &= ~TCQ_F_THROTTLED;
320         del_timer_sync(&q->timer);
321 }
322
323 /* Pass size change message down to embedded FIFO */
324 static int set_fifo_limit(struct Qdisc *q, int limit)
325 {
326         struct rtattr *rta;
327         int ret = -ENOMEM;
328
329         /* Hack to avoid sending change message to non-FIFO */
330         if (strncmp(q->ops->id + 1, "fifo", 4) != 0)
331                 return 0;
332
333         rta = kmalloc(RTA_LENGTH(sizeof(struct tc_fifo_qopt)), GFP_KERNEL);
334         if (rta) {
335                 rta->rta_type = RTM_NEWQDISC;
336                 rta->rta_len = RTA_LENGTH(sizeof(struct tc_fifo_qopt)); 
337                 ((struct tc_fifo_qopt *)RTA_DATA(rta))->limit = limit;
338                 
339                 ret = q->ops->change(q, rta);
340                 kfree(rta);
341         }
342         return ret;
343 }
344
345 /*
346  * Distribution data is a variable size payload containing
347  * signed 16 bit values.
348  */
349 static int get_dist_table(struct Qdisc *sch, const struct rtattr *attr)
350 {
351         struct netem_sched_data *q = qdisc_priv(sch);
352         unsigned long n = RTA_PAYLOAD(attr)/sizeof(__s16);
353         const __s16 *data = RTA_DATA(attr);
354         struct disttable *d;
355         int i;
356
357         if (n > 65536)
358                 return -EINVAL;
359
360         d = kmalloc(sizeof(*d) + n*sizeof(d->table[0]), GFP_KERNEL);
361         if (!d)
362                 return -ENOMEM;
363
364         d->size = n;
365         for (i = 0; i < n; i++)
366                 d->table[i] = data[i];
367         
368         spin_lock_bh(&sch->dev->queue_lock);
369         d = xchg(&q->delay_dist, d);
370         spin_unlock_bh(&sch->dev->queue_lock);
371
372         kfree(d);
373         return 0;
374 }
375
376 static int get_correlation(struct Qdisc *sch, const struct rtattr *attr)
377 {
378         struct netem_sched_data *q = qdisc_priv(sch);
379         const struct tc_netem_corr *c = RTA_DATA(attr);
380
381         if (RTA_PAYLOAD(attr) != sizeof(*c))
382                 return -EINVAL;
383
384         init_crandom(&q->delay_cor, c->delay_corr);
385         init_crandom(&q->loss_cor, c->loss_corr);
386         init_crandom(&q->dup_cor, c->dup_corr);
387         return 0;
388 }
389
390 static int get_reorder(struct Qdisc *sch, const struct rtattr *attr)
391 {
392         struct netem_sched_data *q = qdisc_priv(sch);
393         const struct tc_netem_reorder *r = RTA_DATA(attr);
394
395         if (RTA_PAYLOAD(attr) != sizeof(*r))
396                 return -EINVAL;
397
398         q->reorder = r->probability;
399         init_crandom(&q->reorder_cor, r->correlation);
400         return 0;
401 }
402
403 static int get_corrupt(struct Qdisc *sch, const struct rtattr *attr)
404 {
405         struct netem_sched_data *q = qdisc_priv(sch);
406         const struct tc_netem_corrupt *r = RTA_DATA(attr);
407
408         if (RTA_PAYLOAD(attr) != sizeof(*r))
409                 return -EINVAL;
410
411         q->corrupt = r->probability;
412         init_crandom(&q->corrupt_cor, r->correlation);
413         return 0;
414 }
415
416 /* Parse netlink message to set options */
417 static int netem_change(struct Qdisc *sch, struct rtattr *opt)
418 {
419         struct netem_sched_data *q = qdisc_priv(sch);
420         struct tc_netem_qopt *qopt;
421         int ret;
422         
423         if (opt == NULL || RTA_PAYLOAD(opt) < sizeof(*qopt))
424                 return -EINVAL;
425
426         qopt = RTA_DATA(opt);
427         ret = set_fifo_limit(q->qdisc, qopt->limit);
428         if (ret) {
429                 pr_debug("netem: can't set fifo limit\n");
430                 return ret;
431         }
432         
433         q->latency = qopt->latency;
434         q->jitter = qopt->jitter;
435         q->limit = qopt->limit;
436         q->gap = qopt->gap;
437         q->counter = 0;
438         q->loss = qopt->loss;
439         q->duplicate = qopt->duplicate;
440
441         /* for compatiablity with earlier versions.
442          * if gap is set, need to assume 100% probablity
443          */
444         q->reorder = ~0;
445
446         /* Handle nested options after initial queue options.
447          * Should have put all options in nested format but too late now.
448          */ 
449         if (RTA_PAYLOAD(opt) > sizeof(*qopt)) {
450                 struct rtattr *tb[TCA_NETEM_MAX];
451                 if (rtattr_parse(tb, TCA_NETEM_MAX, 
452                                  RTA_DATA(opt) + sizeof(*qopt),
453                                  RTA_PAYLOAD(opt) - sizeof(*qopt)))
454                         return -EINVAL;
455
456                 if (tb[TCA_NETEM_CORR-1]) {
457                         ret = get_correlation(sch, tb[TCA_NETEM_CORR-1]);
458                         if (ret)
459                                 return ret;
460                 }
461
462                 if (tb[TCA_NETEM_DELAY_DIST-1]) {
463                         ret = get_dist_table(sch, tb[TCA_NETEM_DELAY_DIST-1]);
464                         if (ret)
465                                 return ret;
466                 }
467
468                 if (tb[TCA_NETEM_REORDER-1]) {
469                         ret = get_reorder(sch, tb[TCA_NETEM_REORDER-1]);
470                         if (ret)
471                                 return ret;
472                 }
473
474                 if (tb[TCA_NETEM_CORRUPT-1]) {
475                         ret = get_corrupt(sch, tb[TCA_NETEM_CORRUPT-1]);
476                         if (ret)
477                                 return ret;
478                 }
479         }
480
481         return 0;
482 }
483
484 /*
485  * Special case version of FIFO queue for use by netem.
486  * It queues in order based on timestamps in skb's
487  */
488 struct fifo_sched_data {
489         u32 limit;
490 };
491
492 static int tfifo_enqueue(struct sk_buff *nskb, struct Qdisc *sch)
493 {
494         struct fifo_sched_data *q = qdisc_priv(sch);
495         struct sk_buff_head *list = &sch->q;
496         const struct netem_skb_cb *ncb
497                 = (const struct netem_skb_cb *)nskb->cb;
498         struct sk_buff *skb;
499
500         if (likely(skb_queue_len(list) < q->limit)) {
501                 skb_queue_reverse_walk(list, skb) {
502                         const struct netem_skb_cb *cb
503                                 = (const struct netem_skb_cb *)skb->cb;
504
505                         if (!PSCHED_TLESS(ncb->time_to_send, cb->time_to_send))
506                                 break;
507                 }
508
509                 __skb_queue_after(list, skb, nskb);
510
511                 sch->qstats.backlog += nskb->len;
512                 sch->bstats.bytes += nskb->len;
513                 sch->bstats.packets++;
514
515                 return NET_XMIT_SUCCESS;
516         }
517
518         return qdisc_drop(nskb, sch);
519 }
520
521 static int tfifo_init(struct Qdisc *sch, struct rtattr *opt)
522 {
523         struct fifo_sched_data *q = qdisc_priv(sch);
524
525         if (opt) {
526                 struct tc_fifo_qopt *ctl = RTA_DATA(opt);
527                 if (RTA_PAYLOAD(opt) < sizeof(*ctl))
528                         return -EINVAL;
529
530                 q->limit = ctl->limit;
531         } else
532                 q->limit = max_t(u32, sch->dev->tx_queue_len, 1);
533
534         return 0;
535 }
536
537 static int tfifo_dump(struct Qdisc *sch, struct sk_buff *skb)
538 {
539         struct fifo_sched_data *q = qdisc_priv(sch);
540         struct tc_fifo_qopt opt = { .limit = q->limit };
541
542         RTA_PUT(skb, TCA_OPTIONS, sizeof(opt), &opt);
543         return skb->len;
544
545 rtattr_failure:
546         return -1;
547 }
548
549 static struct Qdisc_ops tfifo_qdisc_ops = {
550         .id             =       "tfifo",
551         .priv_size      =       sizeof(struct fifo_sched_data),
552         .enqueue        =       tfifo_enqueue,
553         .dequeue        =       qdisc_dequeue_head,
554         .requeue        =       qdisc_requeue,
555         .drop           =       qdisc_queue_drop,
556         .init           =       tfifo_init,
557         .reset          =       qdisc_reset_queue,
558         .change         =       tfifo_init,
559         .dump           =       tfifo_dump,
560 };
561
562 static int netem_init(struct Qdisc *sch, struct rtattr *opt)
563 {
564         struct netem_sched_data *q = qdisc_priv(sch);
565         int ret;
566
567         if (!opt)
568                 return -EINVAL;
569
570         init_timer(&q->timer);
571         q->timer.function = netem_watchdog;
572         q->timer.data = (unsigned long) sch;
573
574         q->qdisc = qdisc_create_dflt(sch->dev, &tfifo_qdisc_ops);
575         if (!q->qdisc) {
576                 pr_debug("netem: qdisc create failed\n");
577                 return -ENOMEM;
578         }
579
580         ret = netem_change(sch, opt);
581         if (ret) {
582                 pr_debug("netem: change failed\n");
583                 qdisc_destroy(q->qdisc);
584         }
585         return ret;
586 }
587
588 static void netem_destroy(struct Qdisc *sch)
589 {
590         struct netem_sched_data *q = qdisc_priv(sch);
591
592         del_timer_sync(&q->timer);
593         qdisc_destroy(q->qdisc);
594         kfree(q->delay_dist);
595 }
596
597 static int netem_dump(struct Qdisc *sch, struct sk_buff *skb)
598 {
599         const struct netem_sched_data *q = qdisc_priv(sch);
600         unsigned char    *b = skb->tail;
601         struct rtattr *rta = (struct rtattr *) b;
602         struct tc_netem_qopt qopt;
603         struct tc_netem_corr cor;
604         struct tc_netem_reorder reorder;
605         struct tc_netem_corrupt corrupt;
606
607         qopt.latency = q->latency;
608         qopt.jitter = q->jitter;
609         qopt.limit = q->limit;
610         qopt.loss = q->loss;
611         qopt.gap = q->gap;
612         qopt.duplicate = q->duplicate;
613         RTA_PUT(skb, TCA_OPTIONS, sizeof(qopt), &qopt);
614
615         cor.delay_corr = q->delay_cor.rho;
616         cor.loss_corr = q->loss_cor.rho;
617         cor.dup_corr = q->dup_cor.rho;
618         RTA_PUT(skb, TCA_NETEM_CORR, sizeof(cor), &cor);
619
620         reorder.probability = q->reorder;
621         reorder.correlation = q->reorder_cor.rho;
622         RTA_PUT(skb, TCA_NETEM_REORDER, sizeof(reorder), &reorder);
623
624         corrupt.probability = q->corrupt;
625         corrupt.correlation = q->corrupt_cor.rho;
626         RTA_PUT(skb, TCA_NETEM_CORRUPT, sizeof(corrupt), &corrupt);
627
628         rta->rta_len = skb->tail - b;
629
630         return skb->len;
631
632 rtattr_failure:
633         skb_trim(skb, b - skb->data);
634         return -1;
635 }
636
637 static int netem_dump_class(struct Qdisc *sch, unsigned long cl,
638                           struct sk_buff *skb, struct tcmsg *tcm)
639 {
640         struct netem_sched_data *q = qdisc_priv(sch);
641
642         if (cl != 1)    /* only one class */
643                 return -ENOENT;
644
645         tcm->tcm_handle |= TC_H_MIN(1);
646         tcm->tcm_info = q->qdisc->handle;
647
648         return 0;
649 }
650
651 static int netem_graft(struct Qdisc *sch, unsigned long arg, struct Qdisc *new,
652                      struct Qdisc **old)
653 {
654         struct netem_sched_data *q = qdisc_priv(sch);
655
656         if (new == NULL)
657                 new = &noop_qdisc;
658
659         sch_tree_lock(sch);
660         *old = xchg(&q->qdisc, new);
661         qdisc_reset(*old);
662         sch->q.qlen = 0;
663         sch_tree_unlock(sch);
664
665         return 0;
666 }
667
668 static struct Qdisc *netem_leaf(struct Qdisc *sch, unsigned long arg)
669 {
670         struct netem_sched_data *q = qdisc_priv(sch);
671         return q->qdisc;
672 }
673
674 static unsigned long netem_get(struct Qdisc *sch, u32 classid)
675 {
676         return 1;
677 }
678
679 static void netem_put(struct Qdisc *sch, unsigned long arg)
680 {
681 }
682
683 static int netem_change_class(struct Qdisc *sch, u32 classid, u32 parentid, 
684                             struct rtattr **tca, unsigned long *arg)
685 {
686         return -ENOSYS;
687 }
688
689 static int netem_delete(struct Qdisc *sch, unsigned long arg)
690 {
691         return -ENOSYS;
692 }
693
694 static void netem_walk(struct Qdisc *sch, struct qdisc_walker *walker)
695 {
696         if (!walker->stop) {
697                 if (walker->count >= walker->skip)
698                         if (walker->fn(sch, 1, walker) < 0) {
699                                 walker->stop = 1;
700                                 return;
701                         }
702                 walker->count++;
703         }
704 }
705
706 static struct tcf_proto **netem_find_tcf(struct Qdisc *sch, unsigned long cl)
707 {
708         return NULL;
709 }
710
711 static struct Qdisc_class_ops netem_class_ops = {
712         .graft          =       netem_graft,
713         .leaf           =       netem_leaf,
714         .get            =       netem_get,
715         .put            =       netem_put,
716         .change         =       netem_change_class,
717         .delete         =       netem_delete,
718         .walk           =       netem_walk,
719         .tcf_chain      =       netem_find_tcf,
720         .dump           =       netem_dump_class,
721 };
722
723 static struct Qdisc_ops netem_qdisc_ops = {
724         .id             =       "netem",
725         .cl_ops         =       &netem_class_ops,
726         .priv_size      =       sizeof(struct netem_sched_data),
727         .enqueue        =       netem_enqueue,
728         .dequeue        =       netem_dequeue,
729         .requeue        =       netem_requeue,
730         .drop           =       netem_drop,
731         .init           =       netem_init,
732         .reset          =       netem_reset,
733         .destroy        =       netem_destroy,
734         .change         =       netem_change,
735         .dump           =       netem_dump,
736         .owner          =       THIS_MODULE,
737 };
738
739
740 static int __init netem_module_init(void)
741 {
742         pr_info("netem: version " VERSION "\n");
743         return register_qdisc(&netem_qdisc_ops);
744 }
745 static void __exit netem_module_exit(void)
746 {
747         unregister_qdisc(&netem_qdisc_ops);
748 }
749 module_init(netem_module_init)
750 module_exit(netem_module_exit)
751 MODULE_LICENSE("GPL");