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