]> git.proxmox.com Git - mirror_iproute2.git/blob - tc/q_netem.c
6e0e8a8cbfde508a59318812103b991b3fd093e3
[mirror_iproute2.git] / tc / q_netem.c
1 /*
2 * q_netem.c NETEM.
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 * Authors: Stephen Hemminger <shemminger@linux-foundation.org>
10 *
11 */
12
13 #include <stdio.h>
14 #include <stdlib.h>
15 #include <math.h>
16 #include <ctype.h>
17 #include <unistd.h>
18 #include <fcntl.h>
19 #include <stdint.h>
20 #include <sys/socket.h>
21 #include <netinet/in.h>
22 #include <arpa/inet.h>
23 #include <string.h>
24 #include <errno.h>
25
26 #include "utils.h"
27 #include "tc_util.h"
28 #include "tc_common.h"
29
30 static void explain(void)
31 {
32 fprintf(stderr,
33 "Usage: ... netem [ limit PACKETS ]\n" \
34 " [ delay TIME [ JITTER [CORRELATION]]]\n" \
35 " [ distribution {uniform|normal|pareto|paretonormal} ]\n" \
36 " [ corrupt PERCENT [CORRELATION]]\n" \
37 " [ duplicate PERCENT [CORRELATION]]\n" \
38 " [ loss random PERCENT [CORRELATION]]\n" \
39 " [ loss state P13 [P31 [P32 [P23 P14]]]\n" \
40 " [ loss gemodel PERCENT [R [1-H [1-K]]]\n" \
41 " [ ecn ]\n" \
42 " [ reorder PRECENT [CORRELATION] [ gap DISTANCE ]]\n" \
43 " [ rate RATE [PACKETOVERHEAD] [CELLSIZE] [CELLOVERHEAD]]\n" \
44 " [ slot MIN_DELAY [MAX_DELAY] [packets MAX_PACKETS]" \
45 " [bytes MAX_BYTES]]\n" \
46 " [ slot distribution" \
47 " {uniform|normal|pareto|paretonormal|custom} DELAY JITTER" \
48 " [packets MAX_PACKETS] [bytes MAX_BYTES]]\n");
49 }
50
51 static void explain1(const char *arg)
52 {
53 fprintf(stderr, "Illegal \"%s\"\n", arg);
54 }
55
56 /* Upper bound on size of distribution
57 * really (TCA_BUF_MAX - other headers) / sizeof (__s16)
58 */
59 #define MAX_DIST (16*1024)
60
61 /* scaled value used to percent of maximum. */
62 static void set_percent(__u32 *percent, double per)
63 {
64 *percent = rint(per * UINT32_MAX);
65 }
66
67 static int get_percent(__u32 *percent, const char *str)
68 {
69 double per;
70
71 if (parse_percent(&per, str))
72 return -1;
73
74 set_percent(percent, per);
75 return 0;
76 }
77
78 static void print_percent(char *buf, int len, __u32 per)
79 {
80 snprintf(buf, len, "%g%%", (100. * per) / UINT32_MAX);
81 }
82
83 static char *sprint_percent(__u32 per, char *buf)
84 {
85 print_percent(buf, SPRINT_BSIZE-1, per);
86 return buf;
87 }
88
89 /*
90 * Simplistic file parser for distrbution data.
91 * Format is:
92 * # comment line(s)
93 * data0 data1 ...
94 */
95 static int get_distribution(const char *type, __s16 *data, int maxdata)
96 {
97 FILE *f;
98 int n;
99 long x;
100 size_t len;
101 char *line = NULL;
102 char name[128];
103
104 snprintf(name, sizeof(name), "%s/%s.dist", get_tc_lib(), type);
105 if ((f = fopen(name, "r")) == NULL) {
106 fprintf(stderr, "No distribution data for %s (%s: %s)\n",
107 type, name, strerror(errno));
108 return -1;
109 }
110
111 n = 0;
112 while (getline(&line, &len, f) != -1) {
113 char *p, *endp;
114
115 if (*line == '\n' || *line == '#')
116 continue;
117
118 for (p = line; ; p = endp) {
119 x = strtol(p, &endp, 0);
120 if (endp == p)
121 break;
122
123 if (n >= maxdata) {
124 fprintf(stderr, "%s: too much data\n",
125 name);
126 n = -1;
127 goto error;
128 }
129 data[n++] = x;
130 }
131 }
132 error:
133 free(line);
134 fclose(f);
135 return n;
136 }
137
138 #define NEXT_IS_NUMBER() (NEXT_ARG_OK() && isdigit(argv[1][0]))
139 #define NEXT_IS_SIGNED_NUMBER() \
140 (NEXT_ARG_OK() && (isdigit(argv[1][0]) || argv[1][0] == '-'))
141
142 /* Adjust for the fact that psched_ticks aren't always usecs
143 (based on kernel PSCHED_CLOCK configuration */
144 static int get_ticks(__u32 *ticks, const char *str)
145 {
146 unsigned int t;
147
148 if (get_time(&t, str))
149 return -1;
150
151 if (tc_core_time2big(t)) {
152 fprintf(stderr, "Illegal %u time (too large)\n", t);
153 return -1;
154 }
155
156 *ticks = tc_core_time2tick(t);
157 return 0;
158 }
159
160 static int netem_parse_opt(struct qdisc_util *qu, int argc, char **argv,
161 struct nlmsghdr *n, const char *dev)
162 {
163 int dist_size = 0;
164 int slot_dist_size = 0;
165 struct rtattr *tail;
166 struct tc_netem_qopt opt = { .limit = 1000 };
167 struct tc_netem_corr cor = {};
168 struct tc_netem_reorder reorder = {};
169 struct tc_netem_corrupt corrupt = {};
170 struct tc_netem_gimodel gimodel;
171 struct tc_netem_gemodel gemodel;
172 struct tc_netem_rate rate = {};
173 struct tc_netem_slot slot = {};
174 __s16 *dist_data = NULL;
175 __s16 *slot_dist_data = NULL;
176 __u16 loss_type = NETEM_LOSS_UNSPEC;
177 int present[__TCA_NETEM_MAX] = {};
178 __u64 rate64 = 0;
179
180 for ( ; argc > 0; --argc, ++argv) {
181 if (matches(*argv, "limit") == 0) {
182 NEXT_ARG();
183 if (get_size(&opt.limit, *argv)) {
184 explain1("limit");
185 return -1;
186 }
187 } else if (matches(*argv, "latency") == 0 ||
188 matches(*argv, "delay") == 0) {
189 NEXT_ARG();
190 if (get_ticks(&opt.latency, *argv)) {
191 explain1("latency");
192 return -1;
193 }
194
195 if (NEXT_IS_NUMBER()) {
196 NEXT_ARG();
197 if (get_ticks(&opt.jitter, *argv)) {
198 explain1("latency");
199 return -1;
200 }
201
202 if (NEXT_IS_NUMBER()) {
203 NEXT_ARG();
204 ++present[TCA_NETEM_CORR];
205 if (get_percent(&cor.delay_corr, *argv)) {
206 explain1("latency");
207 return -1;
208 }
209 }
210 }
211 } else if (matches(*argv, "loss") == 0 ||
212 matches(*argv, "drop") == 0) {
213 if (opt.loss > 0 || loss_type != NETEM_LOSS_UNSPEC) {
214 explain1("duplicate loss argument\n");
215 return -1;
216 }
217
218 NEXT_ARG();
219 /* Old (deprecated) random loss model syntax */
220 if (isdigit(argv[0][0]))
221 goto random_loss_model;
222
223 if (!strcmp(*argv, "random")) {
224 NEXT_ARG();
225 random_loss_model:
226 if (get_percent(&opt.loss, *argv)) {
227 explain1("loss percent");
228 return -1;
229 }
230 if (NEXT_IS_NUMBER()) {
231 NEXT_ARG();
232 ++present[TCA_NETEM_CORR];
233 if (get_percent(&cor.loss_corr, *argv)) {
234 explain1("loss correllation");
235 return -1;
236 }
237 }
238 } else if (!strcmp(*argv, "state")) {
239 double p13;
240
241 NEXT_ARG();
242 if (parse_percent(&p13, *argv)) {
243 explain1("loss p13");
244 return -1;
245 }
246
247 /* set defaults */
248 set_percent(&gimodel.p13, p13);
249 set_percent(&gimodel.p31, 1. - p13);
250 set_percent(&gimodel.p32, 0);
251 set_percent(&gimodel.p23, 1.);
252 set_percent(&gimodel.p14, 0);
253 loss_type = NETEM_LOSS_GI;
254
255 if (!NEXT_IS_NUMBER())
256 continue;
257 NEXT_ARG();
258 if (get_percent(&gimodel.p31, *argv)) {
259 explain1("loss p31");
260 return -1;
261 }
262
263 if (!NEXT_IS_NUMBER())
264 continue;
265 NEXT_ARG();
266 if (get_percent(&gimodel.p32, *argv)) {
267 explain1("loss p32");
268 return -1;
269 }
270
271 if (!NEXT_IS_NUMBER())
272 continue;
273 NEXT_ARG();
274 if (get_percent(&gimodel.p23, *argv)) {
275 explain1("loss p23");
276 return -1;
277 }
278 if (!NEXT_IS_NUMBER())
279 continue;
280 NEXT_ARG();
281 if (get_percent(&gimodel.p14, *argv)) {
282 explain1("loss p14");
283 return -1;
284 }
285
286 } else if (!strcmp(*argv, "gemodel")) {
287 NEXT_ARG();
288 if (get_percent(&gemodel.p, *argv)) {
289 explain1("loss gemodel p");
290 return -1;
291 }
292
293 /* set defaults */
294 set_percent(&gemodel.r, 1.);
295 set_percent(&gemodel.h, 0);
296 set_percent(&gemodel.k1, 0);
297 loss_type = NETEM_LOSS_GE;
298
299 if (!NEXT_IS_NUMBER())
300 continue;
301 NEXT_ARG();
302 if (get_percent(&gemodel.r, *argv)) {
303 explain1("loss gemodel r");
304 return -1;
305 }
306
307 if (!NEXT_IS_NUMBER())
308 continue;
309 NEXT_ARG();
310 if (get_percent(&gemodel.h, *argv)) {
311 explain1("loss gemodel h");
312 return -1;
313 }
314 /* netem option is "1-h" but kernel
315 * expects "h".
316 */
317 gemodel.h = UINT32_MAX - gemodel.h;
318
319 if (!NEXT_IS_NUMBER())
320 continue;
321 NEXT_ARG();
322 if (get_percent(&gemodel.k1, *argv)) {
323 explain1("loss gemodel k");
324 return -1;
325 }
326 } else {
327 fprintf(stderr, "Unknown loss parameter: %s\n",
328 *argv);
329 return -1;
330 }
331 } else if (matches(*argv, "ecn") == 0) {
332 present[TCA_NETEM_ECN] = 1;
333 } else if (matches(*argv, "reorder") == 0) {
334 NEXT_ARG();
335 present[TCA_NETEM_REORDER] = 1;
336 if (get_percent(&reorder.probability, *argv)) {
337 explain1("reorder");
338 return -1;
339 }
340 if (NEXT_IS_NUMBER()) {
341 NEXT_ARG();
342 ++present[TCA_NETEM_CORR];
343 if (get_percent(&reorder.correlation, *argv)) {
344 explain1("reorder");
345 return -1;
346 }
347 }
348 } else if (matches(*argv, "corrupt") == 0) {
349 NEXT_ARG();
350 present[TCA_NETEM_CORRUPT] = 1;
351 if (get_percent(&corrupt.probability, *argv)) {
352 explain1("corrupt");
353 return -1;
354 }
355 if (NEXT_IS_NUMBER()) {
356 NEXT_ARG();
357 ++present[TCA_NETEM_CORR];
358 if (get_percent(&corrupt.correlation, *argv)) {
359 explain1("corrupt");
360 return -1;
361 }
362 }
363 } else if (matches(*argv, "gap") == 0) {
364 NEXT_ARG();
365 if (get_u32(&opt.gap, *argv, 0)) {
366 explain1("gap");
367 return -1;
368 }
369 } else if (matches(*argv, "duplicate") == 0) {
370 NEXT_ARG();
371 if (get_percent(&opt.duplicate, *argv)) {
372 explain1("duplicate");
373 return -1;
374 }
375 if (NEXT_IS_NUMBER()) {
376 NEXT_ARG();
377 if (get_percent(&cor.dup_corr, *argv)) {
378 explain1("duplicate");
379 return -1;
380 }
381 }
382 } else if (matches(*argv, "distribution") == 0) {
383 NEXT_ARG();
384 dist_data = calloc(sizeof(dist_data[0]), MAX_DIST);
385 dist_size = get_distribution(*argv, dist_data, MAX_DIST);
386 if (dist_size <= 0) {
387 free(dist_data);
388 return -1;
389 }
390 } else if (matches(*argv, "rate") == 0) {
391 ++present[TCA_NETEM_RATE];
392 NEXT_ARG();
393 if (strchr(*argv, '%')) {
394 if (get_percent_rate64(&rate64, *argv, dev)) {
395 explain1("rate");
396 return -1;
397 }
398 } else if (get_rate64(&rate64, *argv)) {
399 explain1("rate");
400 return -1;
401 }
402 if (NEXT_IS_SIGNED_NUMBER()) {
403 NEXT_ARG();
404 if (get_s32(&rate.packet_overhead, *argv, 0)) {
405 explain1("rate");
406 return -1;
407 }
408 }
409 if (NEXT_IS_NUMBER()) {
410 NEXT_ARG();
411 if (get_u32(&rate.cell_size, *argv, 0)) {
412 explain1("rate");
413 return -1;
414 }
415 }
416 if (NEXT_IS_SIGNED_NUMBER()) {
417 NEXT_ARG();
418 if (get_s32(&rate.cell_overhead, *argv, 0)) {
419 explain1("rate");
420 return -1;
421 }
422 }
423 } else if (matches(*argv, "slot") == 0) {
424 if (NEXT_IS_NUMBER()) {
425 NEXT_ARG();
426 present[TCA_NETEM_SLOT] = 1;
427 if (get_time64(&slot.min_delay, *argv)) {
428 explain1("slot min_delay");
429 return -1;
430 }
431 if (NEXT_IS_NUMBER()) {
432 NEXT_ARG();
433 if (get_time64(&slot.max_delay, *argv) ||
434 slot.max_delay < slot.min_delay) {
435 explain1("slot max_delay");
436 return -1;
437 }
438 } else {
439 slot.max_delay = slot.min_delay;
440 }
441 } else {
442 NEXT_ARG();
443 if (strcmp(*argv, "distribution") == 0) {
444 present[TCA_NETEM_SLOT] = 1;
445 NEXT_ARG();
446 slot_dist_data = calloc(sizeof(slot_dist_data[0]), MAX_DIST);
447 if (!slot_dist_data)
448 return -1;
449 slot_dist_size = get_distribution(*argv, slot_dist_data, MAX_DIST);
450 if (slot_dist_size <= 0) {
451 free(slot_dist_data);
452 return -1;
453 }
454 NEXT_ARG();
455 if (get_time64(&slot.dist_delay, *argv)) {
456 explain1("slot delay");
457 return -1;
458 }
459 NEXT_ARG();
460 if (get_time64(&slot.dist_jitter, *argv)) {
461 explain1("slot jitter");
462 return -1;
463 }
464 if (slot.dist_jitter <= 0) {
465 fprintf(stderr, "Non-positive jitter\n");
466 return -1;
467 }
468 } else {
469 fprintf(stderr, "Unknown slot parameter: %s\n",
470 *argv);
471 return -1;
472 }
473 }
474 if (NEXT_ARG_OK() &&
475 matches(*(argv+1), "packets") == 0) {
476 NEXT_ARG();
477 if (!NEXT_ARG_OK() ||
478 get_s32(&slot.max_packets, *(argv+1), 0)) {
479 explain1("slot packets");
480 return -1;
481 }
482 NEXT_ARG();
483 }
484 if (NEXT_ARG_OK() &&
485 matches(*(argv+1), "bytes") == 0) {
486 unsigned int max_bytes;
487 NEXT_ARG();
488 if (!NEXT_ARG_OK() ||
489 get_size(&max_bytes, *(argv+1))) {
490 explain1("slot bytes");
491 return -1;
492 }
493 slot.max_bytes = (int) max_bytes;
494 NEXT_ARG();
495 }
496 } else if (strcmp(*argv, "help") == 0) {
497 explain();
498 return -1;
499 } else {
500 fprintf(stderr, "What is \"%s\"?\n", *argv);
501 explain();
502 return -1;
503 }
504 }
505
506 tail = NLMSG_TAIL(n);
507
508 if (reorder.probability) {
509 if (opt.latency == 0) {
510 fprintf(stderr, "reordering not possible without specifying some delay\n");
511 explain();
512 return -1;
513 }
514 if (opt.gap == 0)
515 opt.gap = 1;
516 } else if (opt.gap > 0) {
517 fprintf(stderr, "gap specified without reorder probability\n");
518 explain();
519 return -1;
520 }
521
522 if (present[TCA_NETEM_ECN]) {
523 if (opt.loss <= 0 && loss_type == NETEM_LOSS_UNSPEC) {
524 fprintf(stderr, "ecn requested without loss model\n");
525 explain();
526 return -1;
527 }
528 }
529
530 if (dist_data && (opt.latency == 0 || opt.jitter == 0)) {
531 fprintf(stderr, "distribution specified but no latency and jitter values\n");
532 explain();
533 return -1;
534 }
535
536 if (addattr_l(n, 1024, TCA_OPTIONS, &opt, sizeof(opt)) < 0)
537 return -1;
538
539 if (present[TCA_NETEM_CORR] &&
540 addattr_l(n, 1024, TCA_NETEM_CORR, &cor, sizeof(cor)) < 0)
541 return -1;
542
543 if (present[TCA_NETEM_REORDER] &&
544 addattr_l(n, 1024, TCA_NETEM_REORDER, &reorder, sizeof(reorder)) < 0)
545 return -1;
546
547 if (present[TCA_NETEM_ECN] &&
548 addattr_l(n, 1024, TCA_NETEM_ECN, &present[TCA_NETEM_ECN],
549 sizeof(present[TCA_NETEM_ECN])) < 0)
550 return -1;
551
552 if (present[TCA_NETEM_CORRUPT] &&
553 addattr_l(n, 1024, TCA_NETEM_CORRUPT, &corrupt, sizeof(corrupt)) < 0)
554 return -1;
555
556 if (present[TCA_NETEM_SLOT] &&
557 addattr_l(n, 1024, TCA_NETEM_SLOT, &slot, sizeof(slot)) < 0)
558 return -1;
559
560 if (loss_type != NETEM_LOSS_UNSPEC) {
561 struct rtattr *start;
562
563 start = addattr_nest(n, 1024, TCA_NETEM_LOSS | NLA_F_NESTED);
564 if (loss_type == NETEM_LOSS_GI) {
565 if (addattr_l(n, 1024, NETEM_LOSS_GI,
566 &gimodel, sizeof(gimodel)) < 0)
567 return -1;
568 } else if (loss_type == NETEM_LOSS_GE) {
569 if (addattr_l(n, 1024, NETEM_LOSS_GE,
570 &gemodel, sizeof(gemodel)) < 0)
571 return -1;
572 } else {
573 fprintf(stderr, "loss in the weeds!\n");
574 return -1;
575 }
576
577 addattr_nest_end(n, start);
578 }
579
580 if (present[TCA_NETEM_RATE]) {
581 if (rate64 >= (1ULL << 32)) {
582 if (addattr_l(n, 1024,
583 TCA_NETEM_RATE64, &rate64, sizeof(rate64)) < 0)
584 return -1;
585 rate.rate = ~0U;
586 } else {
587 rate.rate = rate64;
588 }
589 if (addattr_l(n, 1024, TCA_NETEM_RATE, &rate, sizeof(rate)) < 0)
590 return -1;
591 }
592
593 if (dist_data) {
594 if (addattr_l(n, MAX_DIST * sizeof(dist_data[0]),
595 TCA_NETEM_DELAY_DIST,
596 dist_data, dist_size * sizeof(dist_data[0])) < 0)
597 return -1;
598 free(dist_data);
599 }
600
601 if (slot_dist_data) {
602 if (addattr_l(n, MAX_DIST * sizeof(slot_dist_data[0]),
603 TCA_NETEM_SLOT_DIST,
604 slot_dist_data, slot_dist_size * sizeof(slot_dist_data[0])) < 0)
605 return -1;
606 free(slot_dist_data);
607 }
608 tail->rta_len = (void *) NLMSG_TAIL(n) - (void *) tail;
609 return 0;
610 }
611
612 static int netem_print_opt(struct qdisc_util *qu, FILE *f, struct rtattr *opt)
613 {
614 const struct tc_netem_corr *cor = NULL;
615 const struct tc_netem_reorder *reorder = NULL;
616 const struct tc_netem_corrupt *corrupt = NULL;
617 const struct tc_netem_gimodel *gimodel = NULL;
618 const struct tc_netem_gemodel *gemodel = NULL;
619 int *ecn = NULL;
620 struct tc_netem_qopt qopt;
621 const struct tc_netem_rate *rate = NULL;
622 const struct tc_netem_slot *slot = NULL;
623 int len;
624 __u64 rate64 = 0;
625
626 SPRINT_BUF(b1);
627
628 if (opt == NULL)
629 return 0;
630
631 len = RTA_PAYLOAD(opt) - sizeof(qopt);
632 if (len < 0) {
633 fprintf(stderr, "options size error\n");
634 return -1;
635 }
636 memcpy(&qopt, RTA_DATA(opt), sizeof(qopt));
637
638 if (len > 0) {
639 struct rtattr *tb[TCA_NETEM_MAX+1];
640
641 parse_rtattr(tb, TCA_NETEM_MAX, RTA_DATA(opt) + sizeof(qopt),
642 len);
643
644 if (tb[TCA_NETEM_CORR]) {
645 if (RTA_PAYLOAD(tb[TCA_NETEM_CORR]) < sizeof(*cor))
646 return -1;
647 cor = RTA_DATA(tb[TCA_NETEM_CORR]);
648 }
649 if (tb[TCA_NETEM_REORDER]) {
650 if (RTA_PAYLOAD(tb[TCA_NETEM_REORDER]) < sizeof(*reorder))
651 return -1;
652 reorder = RTA_DATA(tb[TCA_NETEM_REORDER]);
653 }
654 if (tb[TCA_NETEM_CORRUPT]) {
655 if (RTA_PAYLOAD(tb[TCA_NETEM_CORRUPT]) < sizeof(*corrupt))
656 return -1;
657 corrupt = RTA_DATA(tb[TCA_NETEM_CORRUPT]);
658 }
659 if (tb[TCA_NETEM_LOSS]) {
660 struct rtattr *lb[NETEM_LOSS_MAX + 1];
661
662 parse_rtattr_nested(lb, NETEM_LOSS_MAX, tb[TCA_NETEM_LOSS]);
663 if (lb[NETEM_LOSS_GI])
664 gimodel = RTA_DATA(lb[NETEM_LOSS_GI]);
665 if (lb[NETEM_LOSS_GE])
666 gemodel = RTA_DATA(lb[NETEM_LOSS_GE]);
667 }
668 if (tb[TCA_NETEM_RATE]) {
669 if (RTA_PAYLOAD(tb[TCA_NETEM_RATE]) < sizeof(*rate))
670 return -1;
671 rate = RTA_DATA(tb[TCA_NETEM_RATE]);
672 }
673 if (tb[TCA_NETEM_ECN]) {
674 if (RTA_PAYLOAD(tb[TCA_NETEM_ECN]) < sizeof(*ecn))
675 return -1;
676 ecn = RTA_DATA(tb[TCA_NETEM_ECN]);
677 }
678 if (tb[TCA_NETEM_RATE64]) {
679 if (RTA_PAYLOAD(tb[TCA_NETEM_RATE64]) < sizeof(rate64))
680 return -1;
681 rate64 = rta_getattr_u64(tb[TCA_NETEM_RATE64]);
682 }
683 if (tb[TCA_NETEM_SLOT]) {
684 if (RTA_PAYLOAD(tb[TCA_NETEM_SLOT]) < sizeof(*slot))
685 return -1;
686 slot = RTA_DATA(tb[TCA_NETEM_SLOT]);
687 }
688 }
689
690 fprintf(f, "limit %d", qopt.limit);
691
692 if (qopt.latency) {
693 fprintf(f, " delay %s", sprint_ticks(qopt.latency, b1));
694
695 if (qopt.jitter) {
696 fprintf(f, " %s", sprint_ticks(qopt.jitter, b1));
697 if (cor && cor->delay_corr)
698 fprintf(f, " %s", sprint_percent(cor->delay_corr, b1));
699 }
700 }
701
702 if (qopt.loss) {
703 fprintf(f, " loss %s", sprint_percent(qopt.loss, b1));
704 if (cor && cor->loss_corr)
705 fprintf(f, " %s", sprint_percent(cor->loss_corr, b1));
706 }
707
708 if (gimodel) {
709 fprintf(f, " loss state p13 %s", sprint_percent(gimodel->p13, b1));
710 fprintf(f, " p31 %s", sprint_percent(gimodel->p31, b1));
711 fprintf(f, " p32 %s", sprint_percent(gimodel->p32, b1));
712 fprintf(f, " p23 %s", sprint_percent(gimodel->p23, b1));
713 fprintf(f, " p14 %s", sprint_percent(gimodel->p14, b1));
714 }
715
716 if (gemodel) {
717 fprintf(f, " loss gemodel p %s",
718 sprint_percent(gemodel->p, b1));
719 fprintf(f, " r %s", sprint_percent(gemodel->r, b1));
720 fprintf(f, " 1-h %s", sprint_percent(UINT32_MAX -
721 gemodel->h, b1));
722 fprintf(f, " 1-k %s", sprint_percent(gemodel->k1, b1));
723 }
724
725 if (qopt.duplicate) {
726 fprintf(f, " duplicate %s",
727 sprint_percent(qopt.duplicate, b1));
728 if (cor && cor->dup_corr)
729 fprintf(f, " %s", sprint_percent(cor->dup_corr, b1));
730 }
731
732 if (reorder && reorder->probability) {
733 fprintf(f, " reorder %s",
734 sprint_percent(reorder->probability, b1));
735 if (reorder->correlation)
736 fprintf(f, " %s",
737 sprint_percent(reorder->correlation, b1));
738 }
739
740 if (corrupt && corrupt->probability) {
741 fprintf(f, " corrupt %s",
742 sprint_percent(corrupt->probability, b1));
743 if (corrupt->correlation)
744 fprintf(f, " %s",
745 sprint_percent(corrupt->correlation, b1));
746 }
747
748 if (rate && rate->rate) {
749 if (rate64)
750 fprintf(f, " rate %s", sprint_rate(rate64, b1));
751 else
752 fprintf(f, " rate %s", sprint_rate(rate->rate, b1));
753 if (rate->packet_overhead)
754 fprintf(f, " packetoverhead %d", rate->packet_overhead);
755 if (rate->cell_size)
756 fprintf(f, " cellsize %u", rate->cell_size);
757 if (rate->cell_overhead)
758 fprintf(f, " celloverhead %d", rate->cell_overhead);
759 }
760
761 if (slot) {
762 if (slot->dist_jitter > 0) {
763 fprintf(f, " slot distribution %s", sprint_time64(slot->dist_delay, b1));
764 fprintf(f, " %s", sprint_time64(slot->dist_jitter, b1));
765 } else {
766 fprintf(f, " slot %s", sprint_time64(slot->min_delay, b1));
767 fprintf(f, " %s", sprint_time64(slot->max_delay, b1));
768 }
769 if (slot->max_packets)
770 fprintf(f, " packets %d", slot->max_packets);
771 if (slot->max_bytes)
772 fprintf(f, " bytes %d", slot->max_bytes);
773 }
774
775 if (ecn)
776 fprintf(f, " ecn ");
777
778 if (qopt.gap)
779 fprintf(f, " gap %lu", (unsigned long)qopt.gap);
780
781
782 return 0;
783 }
784
785 struct qdisc_util netem_qdisc_util = {
786 .id = "netem",
787 .parse_qopt = netem_parse_opt,
788 .print_qopt = netem_print_opt,
789 };