• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 <syslog.h>
19 #include <fcntl.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 
explain(void)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 }
45 
explain1(const char * arg)46 static void explain1(const char *arg)
47 {
48 	fprintf(stderr, "Illegal \"%s\"\n", arg);
49 }
50 
51 /* Upper bound on size of distribution
52  *  really (TCA_BUF_MAX - other headers) / sizeof (__s16)
53  */
54 #define MAX_DIST	(16*1024)
55 
56 static const double max_percent_value = 0xffffffff;
57 
58 /* scaled value used to percent of maximum. */
set_percent(__u32 * percent,double per)59 static void set_percent(__u32 *percent, double per)
60 {
61 	*percent = (unsigned) rint(per * max_percent_value);
62 }
63 
64 
65 /* Parse either a fraction '.3' or percent '30%
66  * return: 0 = ok, -1 = error, 1 = out of range
67  */
parse_percent(double * val,const char * str)68 static int parse_percent(double *val, const char *str)
69 {
70 	char *p;
71 
72 	*val = strtod(str, &p) / 100.;
73 	if (*p && strcmp(p, "%") )
74 		return -1;
75 
76 	return 0;
77 }
78 
get_percent(__u32 * percent,const char * str)79 static int get_percent(__u32 *percent, const char *str)
80 {
81 	double per;
82 
83 	if (parse_percent(&per, str))
84 		return -1;
85 
86 	set_percent(percent, per);
87 	return 0;
88 }
89 
print_percent(char * buf,int len,__u32 per)90 static void print_percent(char *buf, int len, __u32 per)
91 {
92 	snprintf(buf, len, "%g%%", 100. * (double) per / max_percent_value);
93 }
94 
sprint_percent(__u32 per,char * buf)95 static char * sprint_percent(__u32 per, char *buf)
96 {
97 	print_percent(buf, SPRINT_BSIZE-1, per);
98 	return buf;
99 }
100 
101 /*
102  * Simplistic file parser for distrbution data.
103  * Format is:
104  *	# comment line(s)
105  *	data0 data1 ...
106  */
get_distribution(const char * type,__s16 * data,int maxdata)107 static int get_distribution(const char *type, __s16 *data, int maxdata)
108 {
109 	FILE *f;
110 	int n;
111 	long x;
112 	size_t len;
113 	char *line = NULL;
114 	char name[128];
115 
116 	snprintf(name, sizeof(name), "%s/%s.dist", get_tc_lib(), type);
117 	if ((f = fopen(name, "r")) == NULL) {
118 		fprintf(stderr, "No distribution data for %s (%s: %s)\n",
119 			type, name, strerror(errno));
120 		return -1;
121 	}
122 
123 	n = 0;
124 	while (getline(&line, &len, f) != -1) {
125 		char *p, *endp;
126 		if (*line == '\n' || *line == '#')
127 			continue;
128 
129 		for (p = line; ; p = endp) {
130 			x = strtol(p, &endp, 0);
131 			if (endp == p)
132 				break;
133 
134 			if (n >= maxdata) {
135 				fprintf(stderr, "%s: too much data\n",
136 					name);
137 				n = -1;
138 				goto error;
139 			}
140 			data[n++] = x;
141 		}
142 	}
143  error:
144 	free(line);
145 	fclose(f);
146 	return n;
147 }
148 
149 #define NEXT_IS_NUMBER() (NEXT_ARG_OK() && isdigit(argv[1][0]))
150 #define NEXT_IS_SIGNED_NUMBER() \
151 	(NEXT_ARG_OK() && (isdigit(argv[1][0]) || argv[1][0] == '-'))
152 
153 /* Adjust for the fact that psched_ticks aren't always usecs
154    (based on kernel PSCHED_CLOCK configuration */
get_ticks(__u32 * ticks,const char * str)155 static int get_ticks(__u32 *ticks, const char *str)
156 {
157 	unsigned t;
158 
159 	if(get_time(&t, str))
160 		return -1;
161 
162 	if (tc_core_time2big(t)) {
163 		fprintf(stderr, "Illegal %u time (too large)\n", t);
164 		return -1;
165 	}
166 
167 	*ticks = tc_core_time2tick(t);
168 	return 0;
169 }
170 
netem_parse_opt(struct qdisc_util * qu,int argc,char ** argv,struct nlmsghdr * n)171 static int netem_parse_opt(struct qdisc_util *qu, int argc, char **argv,
172 			   struct nlmsghdr *n)
173 {
174 	int dist_size = 0;
175 	struct rtattr *tail;
176 	struct tc_netem_qopt opt = { .limit = 1000 };
177 	struct tc_netem_corr cor;
178 	struct tc_netem_reorder reorder;
179 	struct tc_netem_corrupt corrupt;
180 	struct tc_netem_gimodel gimodel;
181 	struct tc_netem_gemodel gemodel;
182 	struct tc_netem_rate rate;
183 	__s16 *dist_data = NULL;
184 	__u16 loss_type = NETEM_LOSS_UNSPEC;
185 	int present[__TCA_NETEM_MAX];
186 	__u64 rate64 = 0;
187 
188 	memset(&cor, 0, sizeof(cor));
189 	memset(&reorder, 0, sizeof(reorder));
190 	memset(&corrupt, 0, sizeof(corrupt));
191 	memset(&rate, 0, sizeof(rate));
192 	memset(present, 0, sizeof(present));
193 
194 	for( ; argc > 0; --argc, ++argv) {
195 		if (matches(*argv, "limit") == 0) {
196 			NEXT_ARG();
197 			if (get_size(&opt.limit, *argv)) {
198 				explain1("limit");
199 				return -1;
200 			}
201 		} else if (matches(*argv, "latency") == 0 ||
202 			   matches(*argv, "delay") == 0) {
203 			NEXT_ARG();
204 			if (get_ticks(&opt.latency, *argv)) {
205 				explain1("latency");
206 				return -1;
207 			}
208 
209 			if (NEXT_IS_NUMBER()) {
210 				NEXT_ARG();
211 				if (get_ticks(&opt.jitter, *argv)) {
212 					explain1("latency");
213 					return -1;
214 				}
215 
216 				if (NEXT_IS_NUMBER()) {
217 					NEXT_ARG();
218 					++present[TCA_NETEM_CORR];
219 					if (get_percent(&cor.delay_corr, *argv)) {
220 						explain1("latency");
221 						return -1;
222 					}
223 				}
224 			}
225 		} else if (matches(*argv, "loss") == 0 ||
226 			   matches(*argv, "drop") == 0) {
227 			if (opt.loss > 0 || loss_type != NETEM_LOSS_UNSPEC) {
228 				explain1("duplicate loss argument\n");
229 				return -1;
230 			}
231 
232 			NEXT_ARG();
233 			/* Old (deprecated) random loss model syntax */
234 			if (isdigit(argv[0][0]))
235 				goto random_loss_model;
236 
237 			if (!strcmp(*argv, "random")) {
238 				NEXT_ARG();
239 	random_loss_model:
240 				if (get_percent(&opt.loss, *argv)) {
241 					explain1("loss percent");
242 					return -1;
243 				}
244 				if (NEXT_IS_NUMBER()) {
245 					NEXT_ARG();
246 					++present[TCA_NETEM_CORR];
247 					if (get_percent(&cor.loss_corr, *argv)) {
248 						explain1("loss correllation");
249 						return -1;
250 					}
251 				}
252 			} else if (!strcmp(*argv, "state")) {
253 				double p13;
254 
255 				NEXT_ARG();
256 				if (parse_percent(&p13, *argv)) {
257 					explain1("loss p13");
258 					return -1;
259 				}
260 
261 				/* set defaults */
262 				set_percent(&gimodel.p13, p13);
263 				set_percent(&gimodel.p31, 1. - p13);
264 				set_percent(&gimodel.p32, 0);
265 				set_percent(&gimodel.p23, 1.);
266 				set_percent(&gimodel.p14, 0);
267 				loss_type = NETEM_LOSS_GI;
268 
269 				if (!NEXT_IS_NUMBER())
270 					continue;
271 				NEXT_ARG();
272 				if (get_percent(&gimodel.p31, *argv)) {
273 					explain1("loss p31");
274 					return -1;
275 				}
276 
277 				if (!NEXT_IS_NUMBER())
278 					continue;
279 				NEXT_ARG();
280 				if (get_percent(&gimodel.p32, *argv)) {
281 					explain1("loss p32");
282 					return -1;
283 				}
284 
285 				if (!NEXT_IS_NUMBER())
286 					continue;
287 				NEXT_ARG();
288 				if (get_percent(&gimodel.p23, *argv)) {
289 					explain1("loss p23");
290 					return -1;
291 				}
292 				if (!NEXT_IS_NUMBER())
293 					continue;
294 				NEXT_ARG();
295 				if (get_percent(&gimodel.p14, *argv)) {
296 					explain1("loss p14");
297 					return -1;
298 				}
299 
300 			} else if (!strcmp(*argv, "gemodel")) {
301 				NEXT_ARG();
302 				if (get_percent(&gemodel.p, *argv)) {
303 					explain1("loss gemodel p");
304 					return -1;
305 				}
306 
307 				/* set defaults */
308 				set_percent(&gemodel.r, 1.);
309 				set_percent(&gemodel.h, 0);
310 				set_percent(&gemodel.k1, 0);
311 				loss_type = NETEM_LOSS_GE;
312 
313 				if (!NEXT_IS_NUMBER())
314 					continue;
315 				NEXT_ARG();
316 				if (get_percent(&gemodel.r, *argv)) {
317 					explain1("loss gemodel r");
318 					return -1;
319 				}
320 
321 				if (!NEXT_IS_NUMBER())
322 					continue;
323 				NEXT_ARG();
324 				if (get_percent(&gemodel.h, *argv)) {
325 					explain1("loss gemodel h");
326 					return -1;
327 				}
328 				/* netem option is "1-h" but kernel
329 				 * expects "h".
330 				 */
331 				gemodel.h = max_percent_value - gemodel.h;
332 
333 				if (!NEXT_IS_NUMBER())
334 					continue;
335 				NEXT_ARG();
336 				if (get_percent(&gemodel.k1, *argv)) {
337 					explain1("loss gemodel k");
338 					return -1;
339 				}
340 			} else {
341 				fprintf(stderr, "Unknown loss parameter: %s\n",
342 					*argv);
343 				return -1;
344 			}
345 		} else if (matches(*argv, "ecn") == 0) {
346 				present[TCA_NETEM_ECN] = 1;
347 		} else if (matches(*argv, "reorder") == 0) {
348 			NEXT_ARG();
349 			present[TCA_NETEM_REORDER] = 1;
350 			if (get_percent(&reorder.probability, *argv)) {
351 				explain1("reorder");
352 				return -1;
353 			}
354 			if (NEXT_IS_NUMBER()) {
355 				NEXT_ARG();
356 				++present[TCA_NETEM_CORR];
357 				if (get_percent(&reorder.correlation, *argv)) {
358 					explain1("reorder");
359 					return -1;
360 				}
361 			}
362 		} else if (matches(*argv, "corrupt") == 0) {
363 			NEXT_ARG();
364 			present[TCA_NETEM_CORRUPT] = 1;
365 			if (get_percent(&corrupt.probability, *argv)) {
366 				explain1("corrupt");
367 				return -1;
368 			}
369 			if (NEXT_IS_NUMBER()) {
370 				NEXT_ARG();
371 				++present[TCA_NETEM_CORR];
372 				if (get_percent(&corrupt.correlation, *argv)) {
373 					explain1("corrupt");
374 					return -1;
375 				}
376 			}
377 		} else if (matches(*argv, "gap") == 0) {
378 			NEXT_ARG();
379 			if (get_u32(&opt.gap, *argv, 0)) {
380 				explain1("gap");
381 				return -1;
382 			}
383 		} else if (matches(*argv, "duplicate") == 0) {
384 			NEXT_ARG();
385 			if (get_percent(&opt.duplicate, *argv)) {
386 				explain1("duplicate");
387 				return -1;
388 			}
389 			if (NEXT_IS_NUMBER()) {
390 				NEXT_ARG();
391 				if (get_percent(&cor.dup_corr, *argv)) {
392 					explain1("duplicate");
393 					return -1;
394 				}
395 			}
396 		} else if (matches(*argv, "distribution") == 0) {
397 			NEXT_ARG();
398 			dist_data = calloc(sizeof(dist_data[0]), MAX_DIST);
399 			dist_size = get_distribution(*argv, dist_data, MAX_DIST);
400 			if (dist_size <= 0) {
401 				free(dist_data);
402 				return -1;
403 			}
404 		} else if (matches(*argv, "rate") == 0) {
405 			++present[TCA_NETEM_RATE];
406 			NEXT_ARG();
407 			if (get_rate64(&rate64, *argv)) {
408 				explain1("rate");
409 				return -1;
410 			}
411 			if (NEXT_IS_SIGNED_NUMBER()) {
412 				NEXT_ARG();
413 				if (get_s32(&rate.packet_overhead, *argv, 0)) {
414 					explain1("rate");
415 					return -1;
416 				}
417 			}
418 			if (NEXT_IS_NUMBER()) {
419 				NEXT_ARG();
420 				if (get_u32(&rate.cell_size, *argv, 0)) {
421 					explain1("rate");
422 					return -1;
423 				}
424 			}
425 			if (NEXT_IS_SIGNED_NUMBER()) {
426 				NEXT_ARG();
427 				if (get_s32(&rate.cell_overhead, *argv, 0)) {
428 					explain1("rate");
429 					return -1;
430 				}
431 			}
432 		} else if (strcmp(*argv, "help") == 0) {
433 			explain();
434 			return -1;
435 		} else {
436 			fprintf(stderr, "What is \"%s\"?\n", *argv);
437 			explain();
438 			return -1;
439 		}
440 	}
441 
442 	tail = NLMSG_TAIL(n);
443 
444 	if (reorder.probability) {
445 		if (opt.latency == 0) {
446 			fprintf(stderr, "reordering not possible without specifying some delay\n");
447 			explain();
448 			return -1;
449 		}
450 		if (opt.gap == 0)
451 			opt.gap = 1;
452 	} else if (opt.gap > 0) {
453 		fprintf(stderr, "gap specified without reorder probability\n");
454 		explain();
455 		return -1;
456 	}
457 
458 	if (present[TCA_NETEM_ECN]) {
459 		if (opt.loss <= 0 && loss_type == NETEM_LOSS_UNSPEC) {
460 			fprintf(stderr, "ecn requested without loss model\n");
461 			explain();
462 			return -1;
463 		}
464 	}
465 
466 	if (dist_data && (opt.latency == 0 || opt.jitter == 0)) {
467 		fprintf(stderr, "distribution specified but no latency and jitter values\n");
468 		explain();
469 		return -1;
470 	}
471 
472 	if (addattr_l(n, 1024, TCA_OPTIONS, &opt, sizeof(opt)) < 0)
473 		return -1;
474 
475 	if (present[TCA_NETEM_CORR] &&
476 	    addattr_l(n, 1024, TCA_NETEM_CORR, &cor, sizeof(cor)) < 0)
477 			return -1;
478 
479 	if (present[TCA_NETEM_REORDER] &&
480 	    addattr_l(n, 1024, TCA_NETEM_REORDER, &reorder, sizeof(reorder)) < 0)
481 		return -1;
482 
483 	if (present[TCA_NETEM_ECN] &&
484 	    addattr_l(n, 1024, TCA_NETEM_ECN, &present[TCA_NETEM_ECN],
485 		      sizeof(present[TCA_NETEM_ECN])) < 0)
486 			return -1;
487 
488 	if (present[TCA_NETEM_CORRUPT] &&
489 	    addattr_l(n, 1024, TCA_NETEM_CORRUPT, &corrupt, sizeof(corrupt)) < 0)
490 		return -1;
491 
492 	if (loss_type != NETEM_LOSS_UNSPEC) {
493 		struct rtattr *start;
494 
495 		start = addattr_nest(n, 1024, TCA_NETEM_LOSS | NLA_F_NESTED);
496 		if (loss_type == NETEM_LOSS_GI) {
497 			if (addattr_l(n, 1024, NETEM_LOSS_GI,
498 				      &gimodel, sizeof(gimodel)) < 0)
499 			    return -1;
500 		} else if (loss_type == NETEM_LOSS_GE) {
501 			if (addattr_l(n, 1024, NETEM_LOSS_GE,
502 				      &gemodel, sizeof(gemodel)) < 0)
503 			    return -1;
504 		} else {
505 			fprintf(stderr, "loss in the weeds!\n");
506 			return -1;
507 		}
508 
509 		addattr_nest_end(n, start);
510 	}
511 
512 	if (present[TCA_NETEM_RATE]) {
513 		if (rate64 >= (1ULL << 32)) {
514 			if (addattr_l(n, 1024,
515 				      TCA_NETEM_RATE64, &rate64, sizeof(rate64)) < 0)
516 				return -1;
517 			rate.rate = ~0U;
518 		} else {
519 			rate.rate = rate64;
520 		}
521 		if (addattr_l(n, 1024, TCA_NETEM_RATE, &rate, sizeof(rate)) < 0)
522 			return -1;
523 	}
524 
525 	if (dist_data) {
526 		if (addattr_l(n, MAX_DIST * sizeof(dist_data[0]),
527 			      TCA_NETEM_DELAY_DIST,
528 			      dist_data, dist_size * sizeof(dist_data[0])) < 0)
529 			return -1;
530 		free(dist_data);
531 	}
532 	tail->rta_len = (void *) NLMSG_TAIL(n) - (void *) tail;
533 	return 0;
534 }
535 
netem_print_opt(struct qdisc_util * qu,FILE * f,struct rtattr * opt)536 static int netem_print_opt(struct qdisc_util *qu, FILE *f, struct rtattr *opt)
537 {
538 	const struct tc_netem_corr *cor = NULL;
539 	const struct tc_netem_reorder *reorder = NULL;
540 	const struct tc_netem_corrupt *corrupt = NULL;
541 	const struct tc_netem_gimodel *gimodel = NULL;
542 	const struct tc_netem_gemodel *gemodel = NULL;
543 	int *ecn = NULL;
544 	struct tc_netem_qopt qopt;
545 	const struct tc_netem_rate *rate = NULL;
546 	int len = RTA_PAYLOAD(opt) - sizeof(qopt);
547 	__u64 rate64 = 0;
548 	SPRINT_BUF(b1);
549 
550 	if (opt == NULL)
551 		return 0;
552 
553 	if (len < 0) {
554 		fprintf(stderr, "options size error\n");
555 		return -1;
556 	}
557 	memcpy(&qopt, RTA_DATA(opt), sizeof(qopt));
558 
559 	if (len > 0) {
560 		struct rtattr *tb[TCA_NETEM_MAX+1];
561 		parse_rtattr(tb, TCA_NETEM_MAX, RTA_DATA(opt) + sizeof(qopt),
562 			     len);
563 
564 		if (tb[TCA_NETEM_CORR]) {
565 			if (RTA_PAYLOAD(tb[TCA_NETEM_CORR]) < sizeof(*cor))
566 				return -1;
567 			cor = RTA_DATA(tb[TCA_NETEM_CORR]);
568 		}
569 		if (tb[TCA_NETEM_REORDER]) {
570 			if (RTA_PAYLOAD(tb[TCA_NETEM_REORDER]) < sizeof(*reorder))
571 				return -1;
572 			reorder = RTA_DATA(tb[TCA_NETEM_REORDER]);
573 		}
574 		if (tb[TCA_NETEM_CORRUPT]) {
575 			if (RTA_PAYLOAD(tb[TCA_NETEM_CORRUPT]) < sizeof(*corrupt))
576 				return -1;
577 			corrupt = RTA_DATA(tb[TCA_NETEM_CORRUPT]);
578 		}
579 		if (tb[TCA_NETEM_LOSS]) {
580 			struct rtattr *lb[NETEM_LOSS_MAX + 1];
581 
582 			parse_rtattr_nested(lb, NETEM_LOSS_MAX, tb[TCA_NETEM_LOSS]);
583 			if (lb[NETEM_LOSS_GI])
584 				gimodel = RTA_DATA(lb[NETEM_LOSS_GI]);
585 			if (lb[NETEM_LOSS_GE])
586 				gemodel = RTA_DATA(lb[NETEM_LOSS_GE]);
587 		}
588 		if (tb[TCA_NETEM_RATE]) {
589 			if (RTA_PAYLOAD(tb[TCA_NETEM_RATE]) < sizeof(*rate))
590 				return -1;
591 			rate = RTA_DATA(tb[TCA_NETEM_RATE]);
592 		}
593 		if (tb[TCA_NETEM_ECN]) {
594 			if (RTA_PAYLOAD(tb[TCA_NETEM_ECN]) < sizeof(*ecn))
595 				return -1;
596 			ecn = RTA_DATA(tb[TCA_NETEM_ECN]);
597 		}
598 		if (tb[TCA_NETEM_RATE64]) {
599 			if (RTA_PAYLOAD(tb[TCA_NETEM_RATE64]) < sizeof(rate64))
600 				return -1;
601 			rate64 = rta_getattr_u64(tb[TCA_NETEM_RATE64]);
602 		}
603 	}
604 
605 	fprintf(f, "limit %d", qopt.limit);
606 
607 	if (qopt.latency) {
608 		fprintf(f, " delay %s", sprint_ticks(qopt.latency, b1));
609 
610 		if (qopt.jitter) {
611 			fprintf(f, "  %s", sprint_ticks(qopt.jitter, b1));
612 			if (cor && cor->delay_corr)
613 				fprintf(f, " %s", sprint_percent(cor->delay_corr, b1));
614 		}
615 	}
616 
617 	if (qopt.loss) {
618 		fprintf(f, " loss %s", sprint_percent(qopt.loss, b1));
619 		if (cor && cor->loss_corr)
620 			fprintf(f, " %s", sprint_percent(cor->loss_corr, b1));
621 	}
622 
623 	if (gimodel) {
624 		fprintf(f, " loss state p13 %s", sprint_percent(gimodel->p13, b1));
625 		fprintf(f, " p31 %s", sprint_percent(gimodel->p31, b1));
626 		fprintf(f, " p32 %s", sprint_percent(gimodel->p32, b1));
627 		fprintf(f, " p23 %s", sprint_percent(gimodel->p23, b1));
628 		fprintf(f, " p14 %s", sprint_percent(gimodel->p14, b1));
629 	}
630 
631 	if (gemodel) {
632 		fprintf(f, " loss gemodel p %s",
633 			sprint_percent(gemodel->p, b1));
634 		fprintf(f, " r %s", sprint_percent(gemodel->r, b1));
635 		fprintf(f, " 1-h %s", sprint_percent(max_percent_value -
636 						     gemodel->h, b1));
637 		fprintf(f, " 1-k %s", sprint_percent(gemodel->k1, b1));
638 	}
639 
640 	if (qopt.duplicate) {
641 		fprintf(f, " duplicate %s",
642 			sprint_percent(qopt.duplicate, b1));
643 		if (cor && cor->dup_corr)
644 			fprintf(f, " %s", sprint_percent(cor->dup_corr, b1));
645 	}
646 
647 	if (reorder && reorder->probability) {
648 		fprintf(f, " reorder %s",
649 			sprint_percent(reorder->probability, b1));
650 		if (reorder->correlation)
651 			fprintf(f, " %s",
652 				sprint_percent(reorder->correlation, b1));
653 	}
654 
655 	if (corrupt && corrupt->probability) {
656 		fprintf(f, " corrupt %s",
657 			sprint_percent(corrupt->probability, b1));
658 		if (corrupt->correlation)
659 			fprintf(f, " %s",
660 				sprint_percent(corrupt->correlation, b1));
661 	}
662 
663 	if (rate && rate->rate) {
664 		if (rate64)
665 			fprintf(f, " rate %s", sprint_rate(rate64, b1));
666 		else
667 			fprintf(f, " rate %s", sprint_rate(rate->rate, b1));
668 		if (rate->packet_overhead)
669 			fprintf(f, " packetoverhead %d", rate->packet_overhead);
670 		if (rate->cell_size)
671 			fprintf(f, " cellsize %u", rate->cell_size);
672 		if (rate->cell_overhead)
673 			fprintf(f, " celloverhead %d", rate->cell_overhead);
674 	}
675 
676 	if (ecn)
677 		fprintf(f, " ecn ");
678 
679 	if (qopt.gap)
680 		fprintf(f, " gap %lu", (unsigned long)qopt.gap);
681 
682 
683 	return 0;
684 }
685 
686 struct qdisc_util netem_qdisc_util = {
687 	.id	   	= "netem",
688 	.parse_qopt	= netem_parse_opt,
689 	.print_qopt	= netem_print_opt,
690 };
691