Make the loss / delay sim available as an output feature
[vpp.git] / src / plugins / nsim / node.c
1 /*
2  * node.c - skeleton vpp engine plug-in dual-loop node skeleton
3  *
4  * Copyright (c) <current-year> <your-organization>
5  * Licensed under the Apache License, Version 2.0 (the "License");
6  * you may not use this file except in compliance with the License.
7  * You may obtain a copy of the License at:
8  *
9  *     http://www.apache.org/licenses/LICENSE-2.0
10  *
11  * Unless required by applicable law or agreed to in writing, software
12  * distributed under the License is distributed on an "AS IS" BASIS,
13  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14  * See the License for the specific language governing permissions and
15  * limitations under the License.
16  */
17 #include <vlib/vlib.h>
18 #include <vnet/vnet.h>
19 #include <vnet/pg/pg.h>
20 #include <vppinfra/error.h>
21 #include <nsim/nsim.h>
22
23 typedef struct
24 {
25   f64 expires;
26   u32 tx_sw_if_index;
27   int is_drop;
28   int is_lost;
29 } nsim_trace_t;
30
31 #ifndef CLIB_MARCH_VARIANT
32
33 /* packet trace format function */
34 static u8 *
35 format_nsim_trace (u8 * s, va_list * args)
36 {
37   CLIB_UNUSED (vlib_main_t * vm) = va_arg (*args, vlib_main_t *);
38   CLIB_UNUSED (vlib_node_t * node) = va_arg (*args, vlib_node_t *);
39   nsim_trace_t *t = va_arg (*args, nsim_trace_t *);
40
41   if (t->is_drop)
42     s = format (s, "NSIM: dropped, %s", t->is_lost ?
43                 "simulated network loss" : "no space in ring");
44   else
45     s = format (s, "NSIM: tx time %.6f sw_if_index %d",
46                 t->expires, t->tx_sw_if_index);
47
48   return s;
49 }
50
51 vlib_node_registration_t nsim_node;
52 #endif /* CLIB_MARCH_VARIANT */
53
54 #define foreach_nsim_error                              \
55 _(BUFFERED, "Packets buffered")                         \
56 _(DROPPED, "Packets dropped due to lack of space")      \
57 _(LOSS, "Network loss simulation drop packets")
58
59 typedef enum
60 {
61 #define _(sym,str) NSIM_ERROR_##sym,
62   foreach_nsim_error
63 #undef _
64     NSIM_N_ERROR,
65 } nsim_error_t;
66
67 #ifndef CLIB_MARCH_VARIANT
68 static char *nsim_error_strings[] = {
69 #define _(sym,string) string,
70   foreach_nsim_error
71 #undef _
72 };
73 #endif /* CLIB_MARCH_VARIANT */
74
75 typedef enum
76 {
77   NSIM_NEXT_DROP,
78   NSIM_N_NEXT,
79 } nsim_next_t;
80
81 always_inline uword
82 nsim_inline (vlib_main_t * vm,
83              vlib_node_runtime_t * node, vlib_frame_t * frame, int is_trace,
84              int is_cross_connect)
85 {
86   nsim_main_t *nsm = &nsim_main;
87   u32 n_left_from, *from;
88   u32 *to_next, n_left_to_next;
89   u32 drops[VLIB_FRAME_SIZE], *drop;
90   vlib_buffer_t *bufs[VLIB_FRAME_SIZE], **b;
91   u8 is_drop[4];
92   u16 nexts[VLIB_FRAME_SIZE], *next;
93   u32 my_thread_index = vm->thread_index;
94   nsim_wheel_t *wp = nsm->wheel_by_thread[my_thread_index];
95   f64 now = vlib_time_now (vm);
96   f64 expires = now + nsm->delay;
97   f64 rnd[4];
98   u32 no_buffer_error = node->errors[NSIM_ERROR_DROPPED];
99   u32 loss_error = node->errors[NSIM_ERROR_LOSS];
100   u32 buffered = 0;
101   nsim_wheel_entry_t *ep = 0;
102
103   ASSERT (wp);
104
105   from = vlib_frame_vector_args (frame);
106   n_left_from = frame->n_vectors;
107
108   vlib_get_buffers (vm, from, bufs, n_left_from);
109   b = bufs;
110   next = nexts;
111   drop = drops;
112
113   while (n_left_from >= 8)
114     {
115       vlib_prefetch_buffer_header (b[4], STORE);
116       vlib_prefetch_buffer_header (b[5], STORE);
117       vlib_prefetch_buffer_header (b[6], STORE);
118       vlib_prefetch_buffer_header (b[7], STORE);
119
120       memset (&is_drop, 0, sizeof (is_drop));
121       next[0] = next[1] = next[2] = next[3] = NSIM_NEXT_DROP;
122       if (PREDICT_FALSE (wp->cursize + 4 >= wp->wheel_size))
123         goto slow_path;
124       if (PREDICT_FALSE (nsm->drop_fraction != 0.0))
125         {
126           rnd[0] = random_f64 (&nsm->seed);
127           rnd[1] = random_f64 (&nsm->seed);
128           rnd[2] = random_f64 (&nsm->seed);
129           rnd[3] = random_f64 (&nsm->seed);
130
131           if (rnd[0] <= nsm->drop_fraction)
132             {
133               b[0]->error = loss_error;
134               is_drop[0] = 1;
135             }
136           if (rnd[1] <= nsm->drop_fraction)
137             {
138               b[1]->error = loss_error;
139               is_drop[1] = 1;
140             }
141           if (rnd[2] <= nsm->drop_fraction)
142             {
143               b[2]->error = loss_error;
144               is_drop[2] = 1;
145             }
146           if (rnd[3] <= nsm->drop_fraction)
147             {
148               b[3]->error = loss_error;
149               is_drop[3] = 1;
150             }
151         }
152
153       if (PREDICT_TRUE (is_drop[0] == 0))
154         {
155           ep = wp->entries + wp->tail;
156           wp->tail++;
157           if (wp->tail == wp->wheel_size)
158             wp->tail = 0;
159           wp->cursize++;
160
161           ep->tx_time = expires;
162           ep->rx_sw_if_index = vnet_buffer (b[0])->sw_if_index[VLIB_RX];
163           if (is_cross_connect)
164             {
165               ep->tx_sw_if_index =
166                 (vnet_buffer (b[0])->sw_if_index[VLIB_RX] ==
167                  nsm->sw_if_index0) ? nsm->sw_if_index1 : nsm->sw_if_index0;
168               ep->output_next_index =
169                 (ep->tx_sw_if_index ==
170                  nsm->sw_if_index0) ? nsm->
171                 output_next_index0 : nsm->output_next_index1;
172             }
173           else                  /* output feature, even easier... */
174             {
175               ep->tx_sw_if_index = vnet_buffer (b[0])->sw_if_index[VLIB_TX];
176               ep->output_next_index =
177                 nsm->output_next_index_by_sw_if_index[ep->tx_sw_if_index];
178             }
179           ep->buffer_index = from[0];
180           buffered++;
181         }
182
183       if (is_trace)
184         {
185           if (b[1]->flags & VLIB_BUFFER_IS_TRACED)
186             {
187               nsim_trace_t *t = vlib_add_trace (vm, node, b[1], sizeof (*t));
188               t->expires = expires;
189               t->is_drop = is_drop[1];
190               t->is_lost = b[1]->error == loss_error;
191               t->tx_sw_if_index = (is_drop[1] == 0) ? ep->tx_sw_if_index : 0;
192             }
193         }
194
195       if (PREDICT_TRUE (is_drop[1] == 0))
196         {
197           ep = wp->entries + wp->tail;
198           wp->tail++;
199           if (wp->tail == wp->wheel_size)
200             wp->tail = 0;
201           wp->cursize++;
202
203           ep->tx_time = expires;
204           ep->rx_sw_if_index = vnet_buffer (b[1])->sw_if_index[VLIB_RX];
205           if (is_cross_connect)
206             {
207               ep->tx_sw_if_index =
208                 (vnet_buffer (b[1])->sw_if_index[VLIB_RX] ==
209                  nsm->sw_if_index0) ? nsm->sw_if_index1 : nsm->sw_if_index0;
210               ep->output_next_index =
211                 (ep->tx_sw_if_index ==
212                  nsm->sw_if_index0) ? nsm->
213                 output_next_index0 : nsm->output_next_index1;
214             }
215           else                  /* output feature, even easier... */
216             {
217               ep->tx_sw_if_index = vnet_buffer (b[1])->sw_if_index[VLIB_TX];
218               ep->output_next_index =
219                 nsm->output_next_index_by_sw_if_index[ep->tx_sw_if_index];
220             }
221           ep->buffer_index = from[1];
222           buffered++;
223         }
224
225       if (is_trace)
226         {
227           if (b[2]->flags & VLIB_BUFFER_IS_TRACED)
228             {
229               nsim_trace_t *t = vlib_add_trace (vm, node, b[2], sizeof (*t));
230               t->expires = expires;
231               t->is_drop = is_drop[2];
232               t->is_lost = b[2]->error == loss_error;
233               t->tx_sw_if_index = (is_drop[2] == 0) ? ep->tx_sw_if_index : 0;
234             }
235         }
236       if (PREDICT_TRUE (is_drop[2] == 0))
237         {
238           ep = wp->entries + wp->tail;
239           wp->tail++;
240           if (wp->tail == wp->wheel_size)
241             wp->tail = 0;
242           wp->cursize++;
243
244           ep->tx_time = expires;
245           ep->rx_sw_if_index = vnet_buffer (b[2])->sw_if_index[VLIB_RX];
246           if (is_cross_connect)
247             {
248               ep->tx_sw_if_index =
249                 (vnet_buffer (b[2])->sw_if_index[VLIB_RX] ==
250                  nsm->sw_if_index0) ? nsm->sw_if_index1 : nsm->sw_if_index0;
251               ep->output_next_index =
252                 (ep->tx_sw_if_index ==
253                  nsm->sw_if_index0) ? nsm->
254                 output_next_index0 : nsm->output_next_index1;
255             }
256           else                  /* output feature, even easier... */
257             {
258               ep->tx_sw_if_index = vnet_buffer (b[2])->sw_if_index[VLIB_TX];
259               ep->output_next_index =
260                 nsm->output_next_index_by_sw_if_index[ep->tx_sw_if_index];
261             }
262           ep->buffer_index = from[2];
263           buffered++;
264         }
265
266       if (is_trace)
267         {
268           if (b[2]->flags & VLIB_BUFFER_IS_TRACED)
269             {
270               nsim_trace_t *t = vlib_add_trace (vm, node, b[2], sizeof (*t));
271               t->expires = expires;
272               t->is_drop = is_drop[2];
273               t->is_lost = b[2]->error == loss_error;
274               t->tx_sw_if_index = (is_drop[2] == 0) ? ep->tx_sw_if_index : 0;
275             }
276         }
277       if (PREDICT_TRUE (is_drop[3] == 0))
278         {
279           ep = wp->entries + wp->tail;
280           wp->tail++;
281           if (wp->tail == wp->wheel_size)
282             wp->tail = 0;
283           wp->cursize++;
284
285           ep->tx_time = expires;
286           ep->rx_sw_if_index = vnet_buffer (b[3])->sw_if_index[VLIB_RX];
287           if (is_cross_connect)
288             {
289               ep->tx_sw_if_index =
290                 (vnet_buffer (b[3])->sw_if_index[VLIB_RX] ==
291                  nsm->sw_if_index0) ? nsm->sw_if_index1 : nsm->sw_if_index0;
292               ep->output_next_index =
293                 (ep->tx_sw_if_index ==
294                  nsm->sw_if_index0) ? nsm->
295                 output_next_index0 : nsm->output_next_index1;
296             }
297           else                  /* output feature, even easier... */
298             {
299               ep->tx_sw_if_index = vnet_buffer (b[3])->sw_if_index[VLIB_TX];
300               ep->output_next_index =
301                 nsm->output_next_index_by_sw_if_index[ep->tx_sw_if_index];
302             }
303           ep->buffer_index = from[3];
304           buffered++;
305         }
306
307       if (is_trace)
308         {
309           if (b[3]->flags & VLIB_BUFFER_IS_TRACED)
310             {
311               nsim_trace_t *t = vlib_add_trace (vm, node, b[3], sizeof (*t));
312               t->expires = expires;
313               t->is_drop = is_drop[3];
314               t->is_lost = b[3]->error == loss_error;
315               t->tx_sw_if_index = (is_drop[3] == 0) ? ep->tx_sw_if_index : 0;
316             }
317         }
318
319       if (PREDICT_FALSE (is_drop[0]))
320         *drop++ = from[0];
321       if (PREDICT_FALSE (is_drop[1]))
322         *drop++ = from[1];
323       if (PREDICT_FALSE (is_drop[2]))
324         *drop++ = from[2];
325       if (PREDICT_FALSE (is_drop[3]))
326         *drop++ = from[3];
327
328       b += 4;
329       next += 4;
330       from += 4;
331       n_left_from -= 4;
332     }
333
334 slow_path:
335
336   while (n_left_from > 0)
337     {
338       next[0] = NSIM_NEXT_DROP;
339       is_drop[0] = 0;
340       if (PREDICT_TRUE (wp->cursize < wp->wheel_size))
341         {
342           if (PREDICT_FALSE (nsm->drop_fraction != 0.0))
343             {
344               /* Get a random number on the closed interval [0,1] */
345               rnd[0] = random_f64 (&nsm->seed);
346               /* Drop the pkt? */
347               if (rnd[0] <= nsm->drop_fraction)
348                 {
349                   b[0]->error = loss_error;
350                   is_drop[0] = 1;
351                   goto do_trace;
352                 }
353             }
354
355           ep = wp->entries + wp->tail;
356           wp->tail++;
357           if (wp->tail == wp->wheel_size)
358             wp->tail = 0;
359           wp->cursize++;
360
361           ep->tx_time = expires;
362           ep->rx_sw_if_index = vnet_buffer (b[0])->sw_if_index[VLIB_RX];
363           if (is_cross_connect)
364             {
365               ep->tx_sw_if_index =
366                 (vnet_buffer (b[0])->sw_if_index[VLIB_RX] ==
367                  nsm->sw_if_index0) ? nsm->sw_if_index1 : nsm->sw_if_index0;
368               ep->output_next_index =
369                 (ep->tx_sw_if_index ==
370                  nsm->sw_if_index0) ? nsm->
371                 output_next_index0 : nsm->output_next_index1;
372             }
373           else                  /* output feature, even easier... */
374             {
375               ep->tx_sw_if_index = vnet_buffer (b[0])->sw_if_index[VLIB_TX];
376               ep->output_next_index =
377                 nsm->output_next_index_by_sw_if_index[ep->tx_sw_if_index];
378             }
379           ep->buffer_index = from[0];
380           buffered++;
381         }
382       else                      /* out of wheel space, drop pkt */
383         {
384           b[0]->error = no_buffer_error;
385           is_drop[0] = 1;
386         }
387
388     do_trace:
389       if (is_trace)
390         {
391           if (b[0]->flags & VLIB_BUFFER_IS_TRACED)
392             {
393               nsim_trace_t *t = vlib_add_trace (vm, node, b[0], sizeof (*t));
394               t->expires = expires;
395               t->is_drop = is_drop[0];
396               t->is_lost = b[0]->error == loss_error;
397               t->tx_sw_if_index = (is_drop[0] == 0) ? ep->tx_sw_if_index : 0;
398             }
399         }
400
401       b += 1;
402       next += 1;
403       if (PREDICT_FALSE (is_drop[0]))
404         {
405           drop[0] = from[0];
406           drop++;
407         }
408       from++;
409       n_left_from -= 1;
410     }
411   if (PREDICT_FALSE (drop > drops))
412     {
413       u32 n_left_to_drop = drop - drops;
414       drop = drops;
415
416       while (n_left_to_drop > 0)
417         {
418           u32 this_copy_size;
419           vlib_get_next_frame (vm, node, NSIM_NEXT_DROP, to_next,
420                                n_left_to_next);
421           this_copy_size = clib_min (n_left_to_drop, n_left_to_next);
422           clib_memcpy_fast (to_next, drop, this_copy_size * sizeof (u32));
423           n_left_to_next -= this_copy_size;
424           vlib_put_next_frame (vm, node, NSIM_NEXT_DROP, n_left_to_next);
425           drop += this_copy_size;
426           n_left_to_drop -= this_copy_size;
427         }
428     }
429   vlib_node_increment_counter (vm, node->node_index,
430                                NSIM_ERROR_BUFFERED, buffered);
431   return frame->n_vectors;
432 }
433
434 VLIB_NODE_FN (nsim_node) (vlib_main_t * vm, vlib_node_runtime_t * node,
435                           vlib_frame_t * frame)
436 {
437   if (PREDICT_FALSE (node->flags & VLIB_NODE_FLAG_TRACE))
438     return nsim_inline (vm, node, frame,
439                         1 /* is_trace */ , 1 /* is_cross_connect */ );
440   else
441     return nsim_inline (vm, node, frame,
442                         0 /* is_trace */ , 1 /* is_cross_connect */ );
443 }
444
445 /* *INDENT-OFF* */
446 #ifndef CLIB_MARCH_VARIANT
447 VLIB_REGISTER_NODE (nsim_node) =
448 {
449   .name = "nsim",
450   .vector_size = sizeof (u32),
451   .format_trace = format_nsim_trace,
452   .type = VLIB_NODE_TYPE_INTERNAL,
453
454   .n_errors = ARRAY_LEN(nsim_error_strings),
455   .error_strings = nsim_error_strings,
456
457   .n_next_nodes = NSIM_N_NEXT,
458
459   /* edit / add dispositions here */
460   .next_nodes = {
461         [NSIM_NEXT_DROP] = "error-drop",
462   },
463 };
464 #endif /* CLIB_MARCH_VARIANT */
465 /* *INDENT-ON* */
466
467 VLIB_NODE_FN (nsim_feature_node) (vlib_main_t * vm,
468                                   vlib_node_runtime_t * node,
469                                   vlib_frame_t * frame)
470 {
471   if (PREDICT_FALSE (node->flags & VLIB_NODE_FLAG_TRACE))
472     return nsim_inline (vm, node, frame,
473                         1 /* is_trace */ , 0 /* is_cross_connect */ );
474   else
475     return nsim_inline (vm, node, frame,
476                         0 /* is_trace */ , 0 /* is_cross_connect */ );
477 }
478
479 /* *INDENT-OFF* */
480 #ifndef CLIB_MARCH_VARIANT
481 VLIB_REGISTER_NODE (nsim_feature_node) =
482 {
483   .name = "nsim-output-feature",
484   .vector_size = sizeof (u32),
485   .format_trace = format_nsim_trace,
486   .type = VLIB_NODE_TYPE_INTERNAL,
487
488   .n_errors = ARRAY_LEN(nsim_error_strings),
489   .error_strings = nsim_error_strings,
490
491   .n_next_nodes = NSIM_N_NEXT,
492
493   /* edit / add dispositions here */
494   .next_nodes = {
495         [NSIM_NEXT_DROP] = "error-drop",
496   },
497 };
498 #endif /* CLIB_MARCH_VARIANT */
499 /* *INDENT-ON* */
500
501 /*
502  * fd.io coding-style-patch-verification: ON
503  *
504  * Local Variables:
505  * eval: (c-set-style "gnu")
506  * End:
507  */