diff options
author | stephen hemminger <shemminger@vyatta.com> | 2011-02-23 08:04:21 -0500 |
---|---|---|
committer | David S. Miller <davem@davemloft.net> | 2011-02-25 01:11:56 -0500 |
commit | 661b79725fea030803a89a16cda506bac8eeca78 (patch) | |
tree | b13a294ecb7696c2c96db88f5b98627f5c4d4ad5 /net/sched | |
parent | 10f6dfcfde884441db89dc66b945d6c948e1d356 (diff) |
netem: revised correlated loss generator
This is a patch originated with Stefano Salsano and Fabio Ludovici.
It provides several alternative loss models for use with netem.
This patch adds two state machine based loss models.
See: http://netgroup.uniroma2.it/twiki/bin/view.cgi/Main/NetemCLG
Signed-off-by: Stephen Hemminger <shemminger@vyatta.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
Diffstat (limited to 'net/sched')
-rw-r--r-- | net/sched/sch_netem.c | 274 |
1 files changed, 270 insertions, 4 deletions
diff --git a/net/sched/sch_netem.c b/net/sched/sch_netem.c index f176890eeef0..5bbcccc353d0 100644 --- a/net/sched/sch_netem.c +++ b/net/sched/sch_netem.c | |||
@@ -47,6 +47,20 @@ | |||
47 | layering other disciplines. It does not need to do bandwidth | 47 | layering other disciplines. It does not need to do bandwidth |
48 | control either since that can be handled by using token | 48 | control either since that can be handled by using token |
49 | bucket or other rate control. | 49 | bucket or other rate control. |
50 | |||
51 | Correlated Loss Generator models | ||
52 | |||
53 | Added generation of correlated loss according to the | ||
54 | "Gilbert-Elliot" model, a 4-state markov model. | ||
55 | |||
56 | References: | ||
57 | [1] NetemCLG Home http://netgroup.uniroma2.it/NetemCLG | ||
58 | [2] S. Salsano, F. Ludovici, A. Ordine, "Definition of a general | ||
59 | and intuitive loss model for packet networks and its implementation | ||
60 | in the Netem module in the Linux kernel", available in [1] | ||
61 | |||
62 | Authors: Stefano Salsano <stefano.salsano at uniroma2.it | ||
63 | Fabio Ludovici <fabio.ludovici at yahoo.it> | ||
50 | */ | 64 | */ |
51 | 65 | ||
52 | struct netem_sched_data { | 66 | struct netem_sched_data { |
@@ -73,6 +87,26 @@ struct netem_sched_data { | |||
73 | u32 size; | 87 | u32 size; |
74 | s16 table[0]; | 88 | s16 table[0]; |
75 | } *delay_dist; | 89 | } *delay_dist; |
90 | |||
91 | enum { | ||
92 | CLG_RANDOM, | ||
93 | CLG_4_STATES, | ||
94 | CLG_GILB_ELL, | ||
95 | } loss_model; | ||
96 | |||
97 | /* Correlated Loss Generation models */ | ||
98 | struct clgstate { | ||
99 | /* state of the Markov chain */ | ||
100 | u8 state; | ||
101 | |||
102 | /* 4-states and Gilbert-Elliot models */ | ||
103 | u32 a1; /* p13 for 4-states or p for GE */ | ||
104 | u32 a2; /* p31 for 4-states or r for GE */ | ||
105 | u32 a3; /* p32 for 4-states or h for GE */ | ||
106 | u32 a4; /* p14 for 4-states or 1-k for GE */ | ||
107 | u32 a5; /* p23 used only in 4-states */ | ||
108 | } clg; | ||
109 | |||
76 | }; | 110 | }; |
77 | 111 | ||
78 | /* Time stamp put into socket buffer control block */ | 112 | /* Time stamp put into socket buffer control block */ |
@@ -115,6 +149,122 @@ static u32 get_crandom(struct crndstate *state) | |||
115 | return answer; | 149 | return answer; |
116 | } | 150 | } |
117 | 151 | ||
152 | /* loss_4state - 4-state model loss generator | ||
153 | * Generates losses according to the 4-state Markov chain adopted in | ||
154 | * the GI (General and Intuitive) loss model. | ||
155 | */ | ||
156 | static bool loss_4state(struct netem_sched_data *q) | ||
157 | { | ||
158 | struct clgstate *clg = &q->clg; | ||
159 | u32 rnd = net_random(); | ||
160 | |||
161 | /* | ||
162 | * Makes a comparision between rnd and the transition | ||
163 | * probabilities outgoing from the current state, then decides the | ||
164 | * next state and if the next packet has to be transmitted or lost. | ||
165 | * The four states correspond to: | ||
166 | * 1 => successfully transmitted packets within a gap period | ||
167 | * 4 => isolated losses within a gap period | ||
168 | * 3 => lost packets within a burst period | ||
169 | * 2 => successfully transmitted packets within a burst period | ||
170 | */ | ||
171 | switch (clg->state) { | ||
172 | case 1: | ||
173 | if (rnd < clg->a4) { | ||
174 | clg->state = 4; | ||
175 | return true; | ||
176 | } else if (clg->a4 < rnd && rnd < clg->a1) { | ||
177 | clg->state = 3; | ||
178 | return true; | ||
179 | } else if (clg->a1 < rnd) | ||
180 | clg->state = 1; | ||
181 | |||
182 | break; | ||
183 | case 2: | ||
184 | if (rnd < clg->a5) { | ||
185 | clg->state = 3; | ||
186 | return true; | ||
187 | } else | ||
188 | clg->state = 2; | ||
189 | |||
190 | break; | ||
191 | case 3: | ||
192 | if (rnd < clg->a3) | ||
193 | clg->state = 2; | ||
194 | else if (clg->a3 < rnd && rnd < clg->a2 + clg->a3) { | ||
195 | clg->state = 1; | ||
196 | return true; | ||
197 | } else if (clg->a2 + clg->a3 < rnd) { | ||
198 | clg->state = 3; | ||
199 | return true; | ||
200 | } | ||
201 | break; | ||
202 | case 4: | ||
203 | clg->state = 1; | ||
204 | break; | ||
205 | } | ||
206 | |||
207 | return false; | ||
208 | } | ||
209 | |||
210 | /* loss_gilb_ell - Gilbert-Elliot model loss generator | ||
211 | * Generates losses according to the Gilbert-Elliot loss model or | ||
212 | * its special cases (Gilbert or Simple Gilbert) | ||
213 | * | ||
214 | * Makes a comparision between random number and the transition | ||
215 | * probabilities outgoing from the current state, then decides the | ||
216 | * next state. A second random number is extracted and the comparision | ||
217 | * with the loss probability of the current state decides if the next | ||
218 | * packet will be transmitted or lost. | ||
219 | */ | ||
220 | static bool loss_gilb_ell(struct netem_sched_data *q) | ||
221 | { | ||
222 | struct clgstate *clg = &q->clg; | ||
223 | |||
224 | switch (clg->state) { | ||
225 | case 1: | ||
226 | if (net_random() < clg->a1) | ||
227 | clg->state = 2; | ||
228 | if (net_random() < clg->a4) | ||
229 | return true; | ||
230 | case 2: | ||
231 | if (net_random() < clg->a2) | ||
232 | clg->state = 1; | ||
233 | if (clg->a3 > net_random()) | ||
234 | return true; | ||
235 | } | ||
236 | |||
237 | return false; | ||
238 | } | ||
239 | |||
240 | static bool loss_event(struct netem_sched_data *q) | ||
241 | { | ||
242 | switch (q->loss_model) { | ||
243 | case CLG_RANDOM: | ||
244 | /* Random packet drop 0 => none, ~0 => all */ | ||
245 | return q->loss && q->loss >= get_crandom(&q->loss_cor); | ||
246 | |||
247 | case CLG_4_STATES: | ||
248 | /* 4state loss model algorithm (used also for GI model) | ||
249 | * Extracts a value from the markov 4 state loss generator, | ||
250 | * if it is 1 drops a packet and if needed writes the event in | ||
251 | * the kernel logs | ||
252 | */ | ||
253 | return loss_4state(q); | ||
254 | |||
255 | case CLG_GILB_ELL: | ||
256 | /* Gilbert-Elliot loss model algorithm | ||
257 | * Extracts a value from the Gilbert-Elliot loss generator, | ||
258 | * if it is 1 drops a packet and if needed writes the event in | ||
259 | * the kernel logs | ||
260 | */ | ||
261 | return loss_gilb_ell(q); | ||
262 | } | ||
263 | |||
264 | return false; /* not reached */ | ||
265 | } | ||
266 | |||
267 | |||
118 | /* tabledist - return a pseudo-randomly distributed value with mean mu and | 268 | /* tabledist - return a pseudo-randomly distributed value with mean mu and |
119 | * std deviation sigma. Uses table lookup to approximate the desired | 269 | * std deviation sigma. Uses table lookup to approximate the desired |
120 | * distribution, and a uniformly-distributed pseudo-random source. | 270 | * distribution, and a uniformly-distributed pseudo-random source. |
@@ -167,8 +317,8 @@ static int netem_enqueue(struct sk_buff *skb, struct Qdisc *sch) | |||
167 | if (q->duplicate && q->duplicate >= get_crandom(&q->dup_cor)) | 317 | if (q->duplicate && q->duplicate >= get_crandom(&q->dup_cor)) |
168 | ++count; | 318 | ++count; |
169 | 319 | ||
170 | /* Random packet drop 0 => none, ~0 => all */ | 320 | /* Drop packet? */ |
171 | if (q->loss && q->loss >= get_crandom(&q->loss_cor)) | 321 | if (loss_event(q)) |
172 | --count; | 322 | --count; |
173 | 323 | ||
174 | if (count == 0) { | 324 | if (count == 0) { |
@@ -385,10 +535,66 @@ static void get_corrupt(struct Qdisc *sch, const struct nlattr *attr) | |||
385 | init_crandom(&q->corrupt_cor, r->correlation); | 535 | init_crandom(&q->corrupt_cor, r->correlation); |
386 | } | 536 | } |
387 | 537 | ||
538 | static int get_loss_clg(struct Qdisc *sch, const struct nlattr *attr) | ||
539 | { | ||
540 | struct netem_sched_data *q = qdisc_priv(sch); | ||
541 | const struct nlattr *la; | ||
542 | int rem; | ||
543 | |||
544 | nla_for_each_nested(la, attr, rem) { | ||
545 | u16 type = nla_type(la); | ||
546 | |||
547 | switch(type) { | ||
548 | case NETEM_LOSS_GI: { | ||
549 | const struct tc_netem_gimodel *gi = nla_data(la); | ||
550 | |||
551 | if (nla_len(la) != sizeof(struct tc_netem_gimodel)) { | ||
552 | pr_info("netem: incorrect gi model size\n"); | ||
553 | return -EINVAL; | ||
554 | } | ||
555 | |||
556 | q->loss_model = CLG_4_STATES; | ||
557 | |||
558 | q->clg.state = 1; | ||
559 | q->clg.a1 = gi->p13; | ||
560 | q->clg.a2 = gi->p31; | ||
561 | q->clg.a3 = gi->p32; | ||
562 | q->clg.a4 = gi->p14; | ||
563 | q->clg.a5 = gi->p23; | ||
564 | break; | ||
565 | } | ||
566 | |||
567 | case NETEM_LOSS_GE: { | ||
568 | const struct tc_netem_gemodel *ge = nla_data(la); | ||
569 | |||
570 | if (nla_len(la) != sizeof(struct tc_netem_gemodel)) { | ||
571 | pr_info("netem: incorrect gi model size\n"); | ||
572 | return -EINVAL; | ||
573 | } | ||
574 | |||
575 | q->loss_model = CLG_GILB_ELL; | ||
576 | q->clg.state = 1; | ||
577 | q->clg.a1 = ge->p; | ||
578 | q->clg.a2 = ge->r; | ||
579 | q->clg.a3 = ge->h; | ||
580 | q->clg.a4 = ge->k1; | ||
581 | break; | ||
582 | } | ||
583 | |||
584 | default: | ||
585 | pr_info("netem: unknown loss type %u\n", type); | ||
586 | return -EINVAL; | ||
587 | } | ||
588 | } | ||
589 | |||
590 | return 0; | ||
591 | } | ||
592 | |||
388 | static const struct nla_policy netem_policy[TCA_NETEM_MAX + 1] = { | 593 | static const struct nla_policy netem_policy[TCA_NETEM_MAX + 1] = { |
389 | [TCA_NETEM_CORR] = { .len = sizeof(struct tc_netem_corr) }, | 594 | [TCA_NETEM_CORR] = { .len = sizeof(struct tc_netem_corr) }, |
390 | [TCA_NETEM_REORDER] = { .len = sizeof(struct tc_netem_reorder) }, | 595 | [TCA_NETEM_REORDER] = { .len = sizeof(struct tc_netem_reorder) }, |
391 | [TCA_NETEM_CORRUPT] = { .len = sizeof(struct tc_netem_corrupt) }, | 596 | [TCA_NETEM_CORRUPT] = { .len = sizeof(struct tc_netem_corrupt) }, |
597 | [TCA_NETEM_LOSS] = { .type = NLA_NESTED }, | ||
392 | }; | 598 | }; |
393 | 599 | ||
394 | static int parse_attr(struct nlattr *tb[], int maxtype, struct nlattr *nla, | 600 | static int parse_attr(struct nlattr *tb[], int maxtype, struct nlattr *nla, |
@@ -396,11 +602,15 @@ static int parse_attr(struct nlattr *tb[], int maxtype, struct nlattr *nla, | |||
396 | { | 602 | { |
397 | int nested_len = nla_len(nla) - NLA_ALIGN(len); | 603 | int nested_len = nla_len(nla) - NLA_ALIGN(len); |
398 | 604 | ||
399 | if (nested_len < 0) | 605 | if (nested_len < 0) { |
606 | pr_info("netem: invalid attributes len %d\n", nested_len); | ||
400 | return -EINVAL; | 607 | return -EINVAL; |
608 | } | ||
609 | |||
401 | if (nested_len >= nla_attr_size(0)) | 610 | if (nested_len >= nla_attr_size(0)) |
402 | return nla_parse(tb, maxtype, nla_data(nla) + NLA_ALIGN(len), | 611 | return nla_parse(tb, maxtype, nla_data(nla) + NLA_ALIGN(len), |
403 | nested_len, policy); | 612 | nested_len, policy); |
613 | |||
404 | memset(tb, 0, sizeof(struct nlattr *) * (maxtype + 1)); | 614 | memset(tb, 0, sizeof(struct nlattr *) * (maxtype + 1)); |
405 | return 0; | 615 | return 0; |
406 | } | 616 | } |
@@ -456,7 +666,11 @@ static int netem_change(struct Qdisc *sch, struct nlattr *opt) | |||
456 | if (tb[TCA_NETEM_CORRUPT]) | 666 | if (tb[TCA_NETEM_CORRUPT]) |
457 | get_corrupt(sch, tb[TCA_NETEM_CORRUPT]); | 667 | get_corrupt(sch, tb[TCA_NETEM_CORRUPT]); |
458 | 668 | ||
459 | return 0; | 669 | q->loss_model = CLG_RANDOM; |
670 | if (tb[TCA_NETEM_LOSS]) | ||
671 | ret = get_loss_clg(sch, tb[TCA_NETEM_LOSS]); | ||
672 | |||
673 | return ret; | ||
460 | } | 674 | } |
461 | 675 | ||
462 | /* | 676 | /* |
@@ -551,6 +765,7 @@ static int netem_init(struct Qdisc *sch, struct nlattr *opt) | |||
551 | 765 | ||
552 | qdisc_watchdog_init(&q->watchdog, sch); | 766 | qdisc_watchdog_init(&q->watchdog, sch); |
553 | 767 | ||
768 | q->loss_model = CLG_RANDOM; | ||
554 | q->qdisc = qdisc_create_dflt(sch->dev_queue, &tfifo_qdisc_ops, | 769 | q->qdisc = qdisc_create_dflt(sch->dev_queue, &tfifo_qdisc_ops, |
555 | TC_H_MAKE(sch->handle, 1)); | 770 | TC_H_MAKE(sch->handle, 1)); |
556 | if (!q->qdisc) { | 771 | if (!q->qdisc) { |
@@ -575,6 +790,54 @@ static void netem_destroy(struct Qdisc *sch) | |||
575 | dist_free(q->delay_dist); | 790 | dist_free(q->delay_dist); |
576 | } | 791 | } |
577 | 792 | ||
793 | static int dump_loss_model(const struct netem_sched_data *q, | ||
794 | struct sk_buff *skb) | ||
795 | { | ||
796 | struct nlattr *nest; | ||
797 | |||
798 | nest = nla_nest_start(skb, TCA_NETEM_LOSS); | ||
799 | if (nest == NULL) | ||
800 | goto nla_put_failure; | ||
801 | |||
802 | switch (q->loss_model) { | ||
803 | case CLG_RANDOM: | ||
804 | /* legacy loss model */ | ||
805 | nla_nest_cancel(skb, nest); | ||
806 | return 0; /* no data */ | ||
807 | |||
808 | case CLG_4_STATES: { | ||
809 | struct tc_netem_gimodel gi = { | ||
810 | .p13 = q->clg.a1, | ||
811 | .p31 = q->clg.a2, | ||
812 | .p32 = q->clg.a3, | ||
813 | .p14 = q->clg.a4, | ||
814 | .p23 = q->clg.a5, | ||
815 | }; | ||
816 | |||
817 | NLA_PUT(skb, NETEM_LOSS_GI, sizeof(gi), &gi); | ||
818 | break; | ||
819 | } | ||
820 | case CLG_GILB_ELL: { | ||
821 | struct tc_netem_gemodel ge = { | ||
822 | .p = q->clg.a1, | ||
823 | .r = q->clg.a2, | ||
824 | .h = q->clg.a3, | ||
825 | .k1 = q->clg.a4, | ||
826 | }; | ||
827 | |||
828 | NLA_PUT(skb, NETEM_LOSS_GE, sizeof(ge), &ge); | ||
829 | break; | ||
830 | } | ||
831 | } | ||
832 | |||
833 | nla_nest_end(skb, nest); | ||
834 | return 0; | ||
835 | |||
836 | nla_put_failure: | ||
837 | nla_nest_cancel(skb, nest); | ||
838 | return -1; | ||
839 | } | ||
840 | |||
578 | static int netem_dump(struct Qdisc *sch, struct sk_buff *skb) | 841 | static int netem_dump(struct Qdisc *sch, struct sk_buff *skb) |
579 | { | 842 | { |
580 | const struct netem_sched_data *q = qdisc_priv(sch); | 843 | const struct netem_sched_data *q = qdisc_priv(sch); |
@@ -605,6 +868,9 @@ static int netem_dump(struct Qdisc *sch, struct sk_buff *skb) | |||
605 | corrupt.correlation = q->corrupt_cor.rho; | 868 | corrupt.correlation = q->corrupt_cor.rho; |
606 | NLA_PUT(skb, TCA_NETEM_CORRUPT, sizeof(corrupt), &corrupt); | 869 | NLA_PUT(skb, TCA_NETEM_CORRUPT, sizeof(corrupt), &corrupt); |
607 | 870 | ||
871 | if (dump_loss_model(q, skb) != 0) | ||
872 | goto nla_put_failure; | ||
873 | |||
608 | return nla_nest_end(skb, nla); | 874 | return nla_nest_end(skb, nla); |
609 | 875 | ||
610 | nla_put_failure: | 876 | nla_put_failure: |