1/*
2 * lib/route/sch/netem.c		Network Emulator Qdisc
3 *
4 *	This library is free software; you can redistribute it and/or
5 *	modify it under the terms of the GNU Lesser General Public
6 *	License as published by the Free Software Foundation version 2.1
7 *	of the License.
8 *
9 * Copyright (c) 2003-2008 Thomas Graf <tgraf@suug.ch>
10 */
11
12/**
13 * @ingroup qdisc_api
14 * @defgroup netem Network Emulator
15 * @brief
16 *
17 * For further documentation see http://linux-net.osdl.org/index.php/Netem
18 * @{
19 */
20
21#include <netlink-local.h>
22#include <netlink-tc.h>
23#include <netlink/netlink.h>
24#include <netlink/utils.h>
25#include <netlink/route/qdisc.h>
26#include <netlink/route/qdisc-modules.h>
27#include <netlink/route/sch/netem.h>
28
29/** @cond SKIP */
30#define SCH_NETEM_ATTR_LATENCY		0x0001
31#define SCH_NETEM_ATTR_LIMIT		0x0002
32#define SCH_NETEM_ATTR_LOSS			0x0004
33#define SCH_NETEM_ATTR_GAP			0x0008
34#define SCH_NETEM_ATTR_DUPLICATE	0x0010
35#define SCH_NETEM_ATTR_JITTER		0x0020
36#define SCH_NETEM_ATTR_DELAY_CORR	0x0040
37#define SCH_NETEM_ATTR_LOSS_CORR	0x0080
38#define SCH_NETEM_ATTR_DUP_CORR		0x0100
39#define SCH_NETEM_ATTR_RO_PROB		0x0200
40#define SCH_NETEM_ATTR_RO_CORR		0x0400
41#define SCH_NETEM_ATTR_CORRUPT_PROB	0x0800
42#define SCH_NETEM_ATTR_CORRUPT_CORR	0x1000
43#define SCH_NETEM_ATTR_DIST         0x2000
44/** @endcond */
45
46static inline struct rtnl_netem *netem_qdisc(struct rtnl_qdisc *qdisc)
47{
48	return (struct rtnl_netem *) qdisc->q_subdata;
49}
50
51static inline struct rtnl_netem *netem_alloc(struct rtnl_qdisc *qdisc)
52{
53	if (!qdisc->q_subdata)
54		qdisc->q_subdata = calloc(1, sizeof(struct rtnl_netem));
55
56	return netem_qdisc(qdisc);
57}
58
59static struct nla_policy netem_policy[TCA_NETEM_MAX+1] = {
60	[TCA_NETEM_CORR]	= { .minlen = sizeof(struct tc_netem_corr) },
61	[TCA_NETEM_REORDER]	= { .minlen = sizeof(struct tc_netem_reorder) },
62	[TCA_NETEM_CORRUPT]	= { .minlen = sizeof(struct tc_netem_corrupt) },
63};
64
65static int netem_msg_parser(struct rtnl_qdisc *qdisc)
66{
67	int len, err = 0;
68	struct rtnl_netem *netem;
69	struct tc_netem_qopt *opts;
70
71	if (qdisc->q_opts->d_size < sizeof(*opts))
72		return -NLE_INVAL;
73
74	netem = netem_alloc(qdisc);
75	if (!netem)
76		return -NLE_NOMEM;
77
78	opts = (struct tc_netem_qopt *) qdisc->q_opts->d_data;
79	netem->qnm_latency = opts->latency;
80	netem->qnm_limit = opts->limit;
81	netem->qnm_loss = opts->loss;
82	netem->qnm_gap = opts->gap;
83	netem->qnm_duplicate = opts->duplicate;
84	netem->qnm_jitter = opts->jitter;
85
86	netem->qnm_mask = (SCH_NETEM_ATTR_LATENCY | SCH_NETEM_ATTR_LIMIT |
87			   SCH_NETEM_ATTR_LOSS | SCH_NETEM_ATTR_GAP |
88			   SCH_NETEM_ATTR_DUPLICATE | SCH_NETEM_ATTR_JITTER);
89
90	len = qdisc->q_opts->d_size - sizeof(*opts);
91
92	if (len > 0) {
93		struct nlattr *tb[TCA_NETEM_MAX+1];
94
95		err = nla_parse(tb, TCA_NETEM_MAX, (struct nlattr *)
96				(qdisc->q_opts->d_data + sizeof(*opts)),
97				len, netem_policy);
98		if (err < 0) {
99			free(netem);
100			return err;
101		}
102
103		if (tb[TCA_NETEM_CORR]) {
104			struct tc_netem_corr cor;
105
106			nla_memcpy(&cor, tb[TCA_NETEM_CORR], sizeof(cor));
107			netem->qnm_corr.nmc_delay = cor.delay_corr;
108			netem->qnm_corr.nmc_loss = cor.loss_corr;
109			netem->qnm_corr.nmc_duplicate = cor.dup_corr;
110
111			netem->qnm_mask |= (SCH_NETEM_ATTR_DELAY_CORR |
112					    SCH_NETEM_ATTR_LOSS_CORR |
113					SCH_NETEM_ATTR_DUP_CORR);
114		}
115
116		if (tb[TCA_NETEM_REORDER]) {
117			struct tc_netem_reorder ro;
118
119			nla_memcpy(&ro, tb[TCA_NETEM_REORDER], sizeof(ro));
120			netem->qnm_ro.nmro_probability = ro.probability;
121			netem->qnm_ro.nmro_correlation = ro.correlation;
122
123			netem->qnm_mask |= (SCH_NETEM_ATTR_RO_PROB |
124					    SCH_NETEM_ATTR_RO_CORR);
125		}
126
127		if (tb[TCA_NETEM_CORRUPT]) {
128			struct tc_netem_corrupt corrupt;
129
130			nla_memcpy(&corrupt, tb[TCA_NETEM_CORRUPT], sizeof(corrupt));
131			netem->qnm_crpt.nmcr_probability = corrupt.probability;
132			netem->qnm_crpt.nmcr_correlation = corrupt.correlation;
133
134			netem->qnm_mask |= (SCH_NETEM_ATTR_CORRUPT_PROB |
135						SCH_NETEM_ATTR_CORRUPT_CORR);
136		}
137
138		/* sch_netem does not currently dump TCA_NETEM_DELAY_DIST */
139		netem->qnm_dist.dist_data = NULL;
140		netem->qnm_dist.dist_size = 0;
141	}
142
143	return 0;
144}
145
146static void netem_free_data(struct rtnl_qdisc *qdisc)
147{
148	struct rtnl_netem *netem;
149
150	if ( ! qdisc ) return;
151
152	netem = netem_qdisc(qdisc);
153	if ( ! netem ) return;
154
155	if ( netem->qnm_dist.dist_data )
156		free(netem->qnm_dist.dist_data);
157
158	netem = NULL;
159
160	free (qdisc->q_subdata);
161}
162
163static void netem_dump_line(struct rtnl_qdisc *qdisc, struct nl_dump_params *p)
164{
165	struct rtnl_netem *netem = netem_qdisc(qdisc);
166
167	if (netem)
168		nl_dump(p, "limit %d", netem->qnm_limit);
169}
170
171int netem_build_msg(struct rtnl_qdisc *qdisc, struct nl_msg *msg)
172{
173	int err = 0;
174	struct tc_netem_qopt opts;
175	struct tc_netem_corr cor;
176	struct tc_netem_reorder reorder;
177	struct tc_netem_corrupt corrupt;
178	struct rtnl_netem *netem;
179
180	unsigned char set_correlation = 0, set_reorder = 0,
181		set_corrupt = 0, set_dist = 0;
182
183	memset(&opts, 0, sizeof(opts));
184	memset(&cor, 0, sizeof(cor));
185	memset(&reorder, 0, sizeof(reorder));
186	memset(&corrupt, 0, sizeof(corrupt));
187
188	netem = netem_qdisc(qdisc);
189	if (!netem || !msg)
190		return EFAULT;
191
192	msg->nm_nlh->nlmsg_flags |= NLM_F_REQUEST;
193
194	if ( netem->qnm_ro.nmro_probability != 0 ) {
195		if (netem->qnm_latency == 0) {
196			return -NLE_MISSING_ATTR;
197		}
198		if (netem->qnm_gap == 0) netem->qnm_gap = 1;
199	}
200	else if ( netem->qnm_gap ) {
201		return -NLE_MISSING_ATTR;
202	}
203
204	if ( netem->qnm_corr.nmc_delay != 0 ) {
205		if ( netem->qnm_latency == 0 || netem->qnm_jitter == 0) {
206			return -NLE_MISSING_ATTR;
207		}
208		set_correlation = 1;
209	}
210
211	if ( netem->qnm_corr.nmc_loss != 0 ) {
212		if ( netem->qnm_loss == 0 ) {
213			return -NLE_MISSING_ATTR;
214		}
215		set_correlation = 1;
216	}
217
218	if ( netem->qnm_corr.nmc_duplicate != 0 ) {
219		if ( netem->qnm_duplicate == 0 ) {
220			return -NLE_MISSING_ATTR;
221		}
222		set_correlation = 1;
223	}
224
225	if ( netem->qnm_ro.nmro_probability != 0 ) set_reorder = 1;
226	else if ( netem->qnm_ro.nmro_correlation != 0 ) {
227			return -NLE_MISSING_ATTR;
228	}
229
230	if ( netem->qnm_crpt.nmcr_probability != 0 ) set_corrupt = 1;
231	else if ( netem->qnm_crpt.nmcr_correlation != 0 ) {
232			return -NLE_MISSING_ATTR;
233	}
234
235	if ( netem->qnm_dist.dist_data && netem->qnm_dist.dist_size ) {
236		if (netem->qnm_latency == 0 || netem->qnm_jitter == 0) {
237			return -NLE_MISSING_ATTR;
238	}
239	else {
240		/* Resize to accomodate the large distribution table */
241		int new_msg_len = msg->nm_size + netem->qnm_dist.dist_size *
242			sizeof(netem->qnm_dist.dist_data[0]);
243
244		msg->nm_nlh = (struct nlmsghdr *) realloc(msg->nm_nlh, new_msg_len);
245		if ( msg->nm_nlh == NULL )
246			return -NLE_NOMEM;
247		msg->nm_size = new_msg_len;
248			set_dist = 1;
249		}
250	}
251
252	opts.latency = netem->qnm_latency;
253	opts.limit = netem->qnm_limit ? netem->qnm_limit : 1000;
254	opts.loss = netem->qnm_loss;
255	opts.gap = netem->qnm_gap;
256	opts.duplicate = netem->qnm_duplicate;
257	opts.jitter = netem->qnm_jitter;
258
259	NLA_PUT(msg, TCA_OPTIONS, sizeof(opts), &opts);
260
261	if ( set_correlation ) {
262		cor.delay_corr = netem->qnm_corr.nmc_delay;
263		cor.loss_corr = netem->qnm_corr.nmc_loss;
264		cor.dup_corr = netem->qnm_corr.nmc_duplicate;
265
266		NLA_PUT(msg, TCA_NETEM_CORR, sizeof(cor), &cor);
267	}
268
269	if ( set_reorder ) {
270		reorder.probability = netem->qnm_ro.nmro_probability;
271		reorder.correlation = netem->qnm_ro.nmro_correlation;
272
273		NLA_PUT(msg, TCA_NETEM_REORDER, sizeof(reorder), &reorder);
274	}
275
276	if ( set_corrupt ) {
277		corrupt.probability = netem->qnm_crpt.nmcr_probability;
278		corrupt.correlation = netem->qnm_crpt.nmcr_correlation;
279
280		NLA_PUT(msg, TCA_NETEM_CORRUPT, sizeof(corrupt), &corrupt);
281	}
282
283	if ( set_dist ) {
284		NLA_PUT(msg, TCA_NETEM_DELAY_DIST,
285			netem->qnm_dist.dist_size * sizeof(netem->qnm_dist.dist_data[0]),
286			netem->qnm_dist.dist_data);
287	}
288
289	/* Length specified in the TCA_OPTIONS section must span the entire
290	 * remainder of the message. That's just the way that sch_netem expects it.
291	 * Maybe there's a more succinct way to do this at a higher level.
292	 */
293	struct nlattr* head = (struct nlattr *)(NLMSG_DATA(msg->nm_nlh) +
294		NLMSG_LENGTH(sizeof(struct tcmsg)) - NLMSG_ALIGNTO);
295
296	struct nlattr* tail = (struct nlattr *)(((void *) (msg->nm_nlh)) +
297		NLMSG_ALIGN(msg->nm_nlh->nlmsg_len));
298
299	int old_len = head->nla_len;
300	head->nla_len = (void *)tail - (void *)head;
301	msg->nm_nlh->nlmsg_len += (head->nla_len - old_len);
302
303	return err;
304nla_put_failure:
305	return -NLE_MSGSIZE;
306}
307
308/**
309 * @name Queue Limit
310 * @{
311 */
312
313/**
314 * Set limit of netem qdisc.
315 * @arg qdisc		Netem qdisc to be modified.
316 * @arg limit		New limit in bytes.
317 * @return 0 on success or a negative error code.
318 */
319int rtnl_netem_set_limit(struct rtnl_qdisc *qdisc, int limit)
320{
321	struct rtnl_netem *netem;
322
323	netem = netem_alloc(qdisc);
324	if (!netem)
325		return -NLE_NOMEM;
326
327	netem->qnm_limit = limit;
328	netem->qnm_mask |= SCH_NETEM_ATTR_LIMIT;
329
330	return 0;
331}
332
333/**
334 * Get limit of netem qdisc.
335 * @arg qdisc		Netem qdisc.
336 * @return Limit in bytes or a negative error code.
337 */
338int rtnl_netem_get_limit(struct rtnl_qdisc *qdisc)
339{
340	struct rtnl_netem *netem;
341
342	netem = netem_qdisc(qdisc);
343	if (netem && (netem->qnm_mask & SCH_NETEM_ATTR_LIMIT))
344		return netem->qnm_limit;
345	else
346		return -NLE_NOATTR;
347}
348
349/** @} */
350
351/**
352 * @name Packet Re-ordering
353 * @{
354 */
355
356/**
357 * Set re-ordering gap of netem qdisc.
358 * @arg qdisc		Netem qdisc to be modified.
359 * @arg gap		New gap in number of packets.
360 * @return 0 on success or a negative error code.
361 */
362int rtnl_netem_set_gap(struct rtnl_qdisc *qdisc, int gap)
363{
364	struct rtnl_netem *netem;
365
366	netem = netem_alloc(qdisc);
367	if (!netem)
368		return -NLE_NOMEM;
369
370	netem->qnm_gap = gap;
371	netem->qnm_mask |= SCH_NETEM_ATTR_GAP;
372
373	return 0;
374}
375
376/**
377 * Get re-ordering gap of netem qdisc.
378 * @arg qdisc		Netem qdisc.
379 * @return Re-ordering gap in packets or a negative error code.
380 */
381int rtnl_netem_get_gap(struct rtnl_qdisc *qdisc)
382{
383	struct rtnl_netem *netem;
384
385	netem = netem_qdisc(qdisc);
386	if (netem && (netem->qnm_mask & SCH_NETEM_ATTR_GAP))
387		return netem->qnm_gap;
388	else
389		return -NLE_NOATTR;
390}
391
392/**
393 * Set re-ordering probability of netem qdisc.
394 * @arg qdisc		Netem qdisc to be modified.
395 * @arg prob		New re-ordering probability.
396 * @return 0 on success or a negative error code.
397 */
398int rtnl_netem_set_reorder_probability(struct rtnl_qdisc *qdisc, int prob)
399{
400	struct rtnl_netem *netem;
401
402	netem = netem_alloc(qdisc);
403	if (!netem)
404		return -NLE_NOMEM;
405
406	netem->qnm_ro.nmro_probability = prob;
407	netem->qnm_mask |= SCH_NETEM_ATTR_RO_PROB;
408
409	return 0;
410}
411
412/**
413 * Get re-ordering probability of netem qdisc.
414 * @arg qdisc		Netem qdisc.
415 * @return Re-ordering probability or a negative error code.
416 */
417int rtnl_netem_get_reorder_probability(struct rtnl_qdisc *qdisc)
418{
419	struct rtnl_netem *netem;
420
421	netem = netem_qdisc(qdisc);
422	if (netem && (netem->qnm_mask & SCH_NETEM_ATTR_RO_PROB))
423		return netem->qnm_ro.nmro_probability;
424	else
425		return -NLE_NOATTR;
426}
427
428/**
429 * Set re-order correlation probability of netem qdisc.
430 * @arg qdisc		Netem qdisc to be modified.
431 * @arg prob		New re-ordering correlation probability.
432 * @return 0 on success or a negative error code.
433 */
434int rtnl_netem_set_reorder_correlation(struct rtnl_qdisc *qdisc, int prob)
435{
436	struct rtnl_netem *netem;
437
438	netem = netem_alloc(qdisc);
439	if (!netem)
440		return -NLE_NOMEM;
441
442	netem->qnm_ro.nmro_correlation = prob;
443	netem->qnm_mask |= SCH_NETEM_ATTR_RO_CORR;
444
445	return 0;
446}
447
448/**
449 * Get re-ordering correlation probability of netem qdisc.
450 * @arg qdisc		Netem qdisc.
451 * @return Re-ordering correlation probability or a negative error code.
452 */
453int rtnl_netem_get_reorder_correlation(struct rtnl_qdisc *qdisc)
454{
455	struct rtnl_netem *netem;
456
457	netem = netem_qdisc(qdisc);
458	if (netem && (netem->qnm_mask & SCH_NETEM_ATTR_RO_CORR))
459		return netem->qnm_ro.nmro_correlation;
460	else
461		return -NLE_NOATTR;
462}
463
464/** @} */
465
466/**
467 * @name Corruption
468 * @{
469 */
470
471/**
472 * Set corruption probability of netem qdisc.
473 * @arg qdisc		Netem qdisc to be modified.
474 * @arg prob		New corruption probability.
475 * @return 0 on success or a negative error code.
476 */
477int rtnl_netem_set_corruption_probability(struct rtnl_qdisc *qdisc, int prob)
478{
479	struct rtnl_netem *netem;
480
481	netem = netem_alloc(qdisc);
482	if (!netem)
483		return -NLE_NOMEM;
484
485	netem->qnm_crpt.nmcr_probability = prob;
486	netem->qnm_mask |= SCH_NETEM_ATTR_CORRUPT_PROB;
487
488	return 0;
489}
490
491/**
492 * Get corruption probability of netem qdisc.
493 * @arg qdisc		Netem qdisc.
494 * @return Corruption probability or a negative error code.
495 */
496int rtnl_netem_get_corruption_probability(struct rtnl_qdisc *qdisc)
497{
498	struct rtnl_netem *netem;
499
500	netem = netem_qdisc(qdisc);
501	if (netem && (netem->qnm_mask & SCH_NETEM_ATTR_CORRUPT_PROB))
502		return netem->qnm_crpt.nmcr_probability;
503	else
504		return -NLE_NOATTR;
505}
506
507/**
508 * Set corruption correlation probability of netem qdisc.
509 * @arg qdisc		Netem qdisc to be modified.
510 * @arg prob		New corruption correlation probability.
511 * @return 0 on success or a negative error code.
512 */
513int rtnl_netem_set_corruption_correlation(struct rtnl_qdisc *qdisc, int prob)
514{
515	struct rtnl_netem *netem;
516
517	netem = netem_alloc(qdisc);
518	if (!netem)
519		return -NLE_NOMEM;
520
521	netem->qnm_crpt.nmcr_correlation = prob;
522	netem->qnm_mask |= SCH_NETEM_ATTR_CORRUPT_CORR;
523
524	return 0;
525}
526
527/**
528 * Get corruption correlation probability of netem qdisc.
529 * @arg qdisc		Netem qdisc.
530 * @return Corruption correlation probability or a negative error code.
531 */
532int rtnl_netem_get_corruption_correlation(struct rtnl_qdisc *qdisc)
533{
534	struct rtnl_netem *netem;
535
536	netem = netem_qdisc(qdisc);
537	if (netem && (netem->qnm_mask & SCH_NETEM_ATTR_CORRUPT_CORR))
538		return netem->qnm_crpt.nmcr_correlation;
539	else
540		return -NLE_NOATTR;
541}
542
543/** @} */
544
545/**
546 * @name Packet Loss
547 * @{
548 */
549
550/**
551 * Set packet loss probability of netem qdisc.
552 * @arg qdisc		Netem qdisc to be modified.
553 * @arg prob		New packet loss probability.
554 * @return 0 on success or a negative error code.
555 */
556int rtnl_netem_set_loss(struct rtnl_qdisc *qdisc, int prob)
557{
558	struct rtnl_netem *netem;
559
560	netem = netem_alloc(qdisc);
561	if (!netem)
562		return -NLE_NOMEM;
563
564	netem->qnm_loss = prob;
565	netem->qnm_mask |= SCH_NETEM_ATTR_LOSS;
566
567	return 0;
568}
569
570/**
571 * Get packet loss probability of netem qdisc.
572 * @arg qdisc		Netem qdisc.
573 * @return Packet loss probability or a negative error code.
574 */
575int rtnl_netem_get_loss(struct rtnl_qdisc *qdisc)
576{
577	struct rtnl_netem *netem;
578
579	netem = netem_qdisc(qdisc);
580	if (netem && (netem->qnm_mask & SCH_NETEM_ATTR_LOSS))
581		return netem->qnm_loss;
582	else
583		return -NLE_NOATTR;
584}
585
586/**
587 * Set packet loss correlation probability of netem qdisc.
588 * @arg qdisc		Netem qdisc to be modified.
589 * @arg prob	New packet loss correlation.
590 * @return 0 on success or a negative error code.
591 */
592int rtnl_netem_set_loss_correlation(struct rtnl_qdisc *qdisc, int prob)
593{
594	struct rtnl_netem *netem;
595
596	netem = netem_alloc(qdisc);
597	if (!netem)
598		return -NLE_NOMEM;
599
600	netem->qnm_corr.nmc_loss = prob;
601	netem->qnm_mask |= SCH_NETEM_ATTR_LOSS_CORR;
602
603	return 0;
604}
605
606/**
607 * Get packet loss correlation probability of netem qdisc.
608 * @arg qdisc		Netem qdisc.
609 * @return Packet loss correlation probability or a negative error code.
610 */
611int rtnl_netem_get_loss_correlation(struct rtnl_qdisc *qdisc)
612{
613	struct rtnl_netem *netem;
614
615	netem = netem_qdisc(qdisc);
616	if (netem && (netem->qnm_mask & SCH_NETEM_ATTR_LOSS_CORR))
617		return netem->qnm_corr.nmc_loss;
618	else
619		return -NLE_NOATTR;
620}
621
622/** @} */
623
624/**
625 * @name Packet Duplication
626 * @{
627 */
628
629/**
630 * Set packet duplication probability of netem qdisc.
631 * @arg qdisc		Netem qdisc to be modified.
632 * @arg prob	New packet duplication probability.
633 * @return 0 on success or a negative error code.
634 */
635int rtnl_netem_set_duplicate(struct rtnl_qdisc *qdisc, int prob)
636{
637	struct rtnl_netem *netem;
638
639	netem = netem_alloc(qdisc);
640	if (!netem)
641		return -NLE_NOMEM;
642
643	netem->qnm_duplicate = prob;
644	netem->qnm_mask |= SCH_NETEM_ATTR_DUPLICATE;
645
646	return 0;
647}
648
649/**
650 * Get packet duplication probability of netem qdisc.
651 * @arg qdisc		Netem qdisc.
652 * @return Packet duplication probability or a negative error code.
653 */
654int rtnl_netem_get_duplicate(struct rtnl_qdisc *qdisc)
655{
656	struct rtnl_netem *netem;
657
658	netem = netem_qdisc(qdisc);
659	if (netem && (netem->qnm_mask & SCH_NETEM_ATTR_DUPLICATE))
660		return netem->qnm_duplicate;
661	else
662		return -NLE_NOATTR;
663}
664
665/**
666 * Set packet duplication correlation probability of netem qdisc.
667 * @arg qdisc		Netem qdisc to be modified.
668 * @arg prob		New packet duplication correlation probability.
669 * @return 0 on sucess or a negative error code.
670 */
671int rtnl_netem_set_duplicate_correlation(struct rtnl_qdisc *qdisc, int prob)
672{
673	struct rtnl_netem *netem;
674
675	netem = netem_alloc(qdisc);
676	if (!netem)
677		return -NLE_NOMEM;
678
679	netem->qnm_corr.nmc_duplicate = prob;
680	netem->qnm_mask |= SCH_NETEM_ATTR_DUP_CORR;
681
682	return 0;
683}
684
685/**
686 * Get packet duplication correlation probability of netem qdisc.
687 * @arg qdisc		Netem qdisc.
688 * @return Packet duplication correlation probability or a negative error code.
689 */
690int rtnl_netem_get_duplicate_correlation(struct rtnl_qdisc *qdisc)
691{
692	struct rtnl_netem *netem;
693
694	netem = netem_qdisc(qdisc);
695	if (netem && (netem->qnm_mask & SCH_NETEM_ATTR_DUP_CORR))
696		return netem->qnm_corr.nmc_duplicate;
697	else
698		return -NLE_NOATTR;
699}
700
701/** @} */
702
703/**
704 * @name Packet Delay
705 * @{
706 */
707
708/**
709 * Set packet delay of netem qdisc.
710 * @arg qdisc		Netem qdisc to be modified.
711 * @arg delay		New packet delay in micro seconds.
712 * @return 0 on success or a negative error code.
713 */
714int rtnl_netem_set_delay(struct rtnl_qdisc *qdisc, int delay)
715{
716	struct rtnl_netem *netem;
717
718	netem = netem_alloc(qdisc);
719	if (!netem)
720		return -NLE_NOMEM;
721
722	netem->qnm_latency = nl_us2ticks(delay);
723	netem->qnm_mask |= SCH_NETEM_ATTR_LATENCY;
724
725	return 0;
726}
727
728/**
729 * Get packet delay of netem qdisc.
730 * @arg qdisc		Netem qdisc.
731 * @return Packet delay in micro seconds or a negative error code.
732 */
733int rtnl_netem_get_delay(struct rtnl_qdisc *qdisc)
734{
735	struct rtnl_netem *netem;
736
737	netem = netem_qdisc(qdisc);
738	if (netem && (netem->qnm_mask & SCH_NETEM_ATTR_LATENCY))
739		return nl_ticks2us(netem->qnm_latency);
740	else
741		return -NLE_NOATTR;
742}
743
744/**
745 * Set packet delay jitter of netem qdisc.
746 * @arg qdisc		Netem qdisc to be modified.
747 * @arg jitter		New packet delay jitter in micro seconds.
748 * @return 0 on success or a negative error code.
749 */
750int rtnl_netem_set_jitter(struct rtnl_qdisc *qdisc, int jitter)
751{
752	struct rtnl_netem *netem;
753
754	netem = netem_alloc(qdisc);
755	if (!netem)
756		return -NLE_NOMEM;
757
758	netem->qnm_jitter = nl_us2ticks(jitter);
759	netem->qnm_mask |= SCH_NETEM_ATTR_JITTER;
760
761	return 0;
762}
763
764/**
765 * Get packet delay jitter of netem qdisc.
766 * @arg qdisc		Netem qdisc.
767 * @return Packet delay jitter in micro seconds or a negative error code.
768 */
769int rtnl_netem_get_jitter(struct rtnl_qdisc *qdisc)
770{
771	struct rtnl_netem *netem;
772
773	netem = netem_qdisc(qdisc);
774	if (netem && (netem->qnm_mask & SCH_NETEM_ATTR_JITTER))
775		return nl_ticks2us(netem->qnm_jitter);
776	else
777		return -NLE_NOATTR;
778}
779
780/**
781 * Set packet delay correlation probability of netem qdisc.
782 * @arg qdisc		Netem qdisc to be modified.
783 * @arg prob		New packet delay correlation probability.
784 */
785int rtnl_netem_set_delay_correlation(struct rtnl_qdisc *qdisc, int prob)
786{
787	struct rtnl_netem *netem;
788
789	netem = netem_alloc(qdisc);
790	if (!netem)
791		return -NLE_NOMEM;
792
793	netem->qnm_corr.nmc_delay = prob;
794	netem->qnm_mask |= SCH_NETEM_ATTR_DELAY_CORR;
795
796	return 0;
797}
798
799/**
800 * Get packet delay correlation probability of netem qdisc.
801 * @arg qdisc		Netem qdisc.
802 * @return Packet delay correlation probability or a negative error code.
803 */
804int rtnl_netem_get_delay_correlation(struct rtnl_qdisc *qdisc)
805{
806	struct rtnl_netem *netem;
807
808	netem = netem_qdisc(qdisc);
809	if (netem && (netem->qnm_mask & SCH_NETEM_ATTR_DELAY_CORR))
810		return netem->qnm_corr.nmc_delay;
811	else
812		return -NLE_NOATTR;
813}
814
815/**
816 * Get the size of the distribution table.
817 * @arg qdisc		Netem qdisc.
818 * @return Distribution table size or a negative error code.
819 */
820int rtnl_netem_get_delay_distribution_size(struct rtnl_qdisc *qdisc)
821{
822	struct rtnl_netem *netem;
823
824	netem = netem_qdisc(qdisc);
825	if (netem && (netem->qnm_mask & SCH_NETEM_ATTR_DIST))
826		return netem->qnm_dist.dist_size;
827	else
828		return -NLE_NOATTR;
829}
830
831/**
832 * Get a pointer to the distribution table.
833 * @arg qdisc		Netem qdisc.
834 * @arg dist_ptr	The pointer to set.
835 * @return Negative error code on failure or 0 on success.
836 */
837int rtnl_netem_get_delay_distribution(struct rtnl_qdisc *qdisc, int16_t **dist_ptr)
838{
839	struct rtnl_netem *netem;
840
841	netem = netem_qdisc(qdisc);
842	if (netem && (netem->qnm_mask & SCH_NETEM_ATTR_DIST)) {
843		*dist_ptr = netem->qnm_dist.dist_data;
844		return 0;
845	}
846	else
847		return -NLE_NOATTR;
848}
849
850/**
851 * Set the delay distribution. Latency/jitter must be set before applying.
852 * @arg qdisc Netem qdisc.
853 * @arg dist_type The name of the distribution (type, file, path/file).
854 * @return 0 on success, error code on failure.
855 */
856int rtnl_netem_set_delay_distribution(struct rtnl_qdisc *qdisc, const char *dist_type) {
857	struct rtnl_netem *netem;
858
859	netem = netem_alloc(qdisc);
860	if (!netem)
861		return -NLE_NOMEM;
862
863	FILE *f = NULL;
864	int i, n = 0;
865	size_t len = 2048;
866	char *line;
867	char name[NAME_MAX];
868	char dist_suffix[] = ".dist";
869
870	/* If the given filename already ends in .dist, don't append it later */
871	char *test_suffix = strstr(dist_type, dist_suffix);
872	if (test_suffix != NULL && strlen(test_suffix) == 5)
873		strcpy(dist_suffix, "");
874
875	/* Check several locations for the dist file */
876	char *test_path[] = { "", "./", "/usr/lib/tc/", "/usr/local/lib/tc/" };
877
878	for (i = 0; i < sizeof(test_path) && f == NULL; i++) {
879		snprintf(name, NAME_MAX, "%s%s%s", test_path[i], dist_type, dist_suffix);
880		f = fopen(name, "r");
881	}
882
883	if ( f == NULL )
884		return -nl_syserr2nlerr(errno);
885
886	netem->qnm_dist.dist_data = (int16_t *) calloc (MAXDIST, sizeof(int16_t));
887
888	line = (char *) calloc (sizeof(char), len + 1);
889
890	while (getline(&line, &len, f) != -1) {
891		char *p, *endp;
892
893		if (*line == '\n' || *line == '#')
894			continue;
895
896		for (p = line; ; p = endp) {
897			long x = strtol(p, &endp, 0);
898			if (endp == p) break;
899
900			if (n >= MAXDIST) {
901				free(line);
902				fclose(f);
903				return -NLE_INVAL;
904			}
905			netem->qnm_dist.dist_data[n++] = x;
906		}
907	}
908
909	free(line);
910
911	netem->qnm_dist.dist_size = n;
912	netem->qnm_mask |= SCH_NETEM_ATTR_DIST;
913
914	fclose(f);
915	return 0;
916}
917
918/** @} */
919
920static struct rtnl_qdisc_ops netem_ops = {
921	.qo_kind		= "netem",
922	.qo_msg_parser		= netem_msg_parser,
923	.qo_free_data		= netem_free_data,
924	.qo_dump[NL_DUMP_LINE]	= netem_dump_line,
925	.qo_get_opts		= 0,
926	.qo_build_msg	= netem_build_msg
927};
928
929static void __init netem_init(void)
930{
931	rtnl_qdisc_register(&netem_ops);
932}
933
934static void __exit netem_exit(void)
935{
936	rtnl_qdisc_unregister(&netem_ops);
937}
938
939/** @} */
940