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