1579f562a6766ac995460523bbc40a8f03bb7e7d
[vpp.git] / src / vpp / oam / oam.c
1 /*
2  * Copyright (c) 2015 Cisco and/or its affiliates.
3  * Licensed under the Apache License, Version 2.0 (the "License");
4  * you may not use this file except in compliance with the License.
5  * You may obtain a copy of the License at:
6  *
7  *     http://www.apache.org/licenses/LICENSE-2.0
8  *
9  * Unless required by applicable law or agreed to in writing, software
10  * distributed under the License is distributed on an "AS IS" BASIS,
11  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12  * See the License for the specific language governing permissions and
13  * limitations under the License.
14  */
15 #include <vpp/oam/oam.h>
16
17 oam_main_t oam_main;
18
19 static vlib_node_registration_t oam_node;
20
21 static void
22 init_oam_packet_template (oam_main_t * om, oam_target_t * t)
23 {
24   oam_template_t *h;
25   int i;
26   ip_csum_t sum;
27   u16 csum;
28
29   vec_validate (t->template, 0);
30
31   h = t->template;
32   memset (h, 0, sizeof (*h));
33
34   h->ip4.src_address.as_u32 = t->src_address.as_u32;
35   h->ip4.dst_address.as_u32 = t->dst_address.as_u32;
36   h->ip4.ip_version_and_header_length = 0x45;
37   h->ip4.length = clib_host_to_net_u16 (sizeof (*h));
38   h->ip4.ttl = 64;              /* as in linux */
39   h->ip4.protocol = IP_PROTOCOL_ICMP;
40   h->ip4.checksum = ip4_header_checksum (&h->ip4);
41
42   /*
43    * Template has seq = 0. Each time we send one of these puppies,
44    * change the sequence number and fix the execrated checksum
45    */
46   h->icmp.type = ICMP4_echo_request;
47   h->id = clib_host_to_net_u16 (t->id);
48
49   for (i = 0; i < ARRAY_LEN (h->data); i++)
50     h->data[i] = 'A' + i;
51
52   sum = ip_incremental_checksum (0, &h->icmp,
53                                  sizeof (h->icmp) + sizeof (h->id) +
54                                  sizeof (h->seq) + sizeof (h->data));
55   csum = ~ip_csum_fold (sum);
56   h->icmp.checksum = csum;
57 }
58
59 int
60 vpe_oam_add_del_target (ip4_address_t * src_address,
61                         ip4_address_t * dst_address, u32 fib_id, int is_add)
62 {
63   u64 key;
64   uword *p;
65   oam_main_t *om = &oam_main;
66   oam_target_t *t;
67   ip4_main_t *im = &ip4_main;
68   u32 fib_index;
69
70   /* Make sure the FIB actually exists */
71   p = hash_get (im->fib_index_by_table_id, fib_id);
72   if (!p)
73     return VNET_API_ERROR_NO_SUCH_FIB;
74
75   fib_index = p[0];
76
77   key = ((u64) fib_index << 32) | (dst_address->as_u32);
78   p = hash_get (om->target_by_address_and_fib_id, key);
79
80   if (is_add)
81     {
82       if (p)
83         return VNET_API_ERROR_INVALID_REGISTRATION;     /* already there... */
84
85       pool_get (om->targets, t);
86       memset (t, 0, sizeof (*t));
87       t->src_address.as_u32 = src_address->as_u32;
88       t->dst_address.as_u32 = dst_address->as_u32;
89       t->fib_id = fib_id;
90       t->fib_index = fib_index;
91       t->state = OAM_STATE_DEAD;
92       t->last_heard_time = vlib_time_now (om->vlib_main);
93       t->last_heard_seq = (u16) ~ om->misses_allowed;
94       t->id = (u16) random_u32 (&om->random_seed);
95       t->seq = 1;
96       init_oam_packet_template (om, t);
97       hash_set (om->target_by_address_and_fib_id, key, t - om->targets);
98     }
99   else
100     {
101       if (!p)
102         return VNET_API_ERROR_NO_SUCH_ENTRY;    /* no such oam target */
103       t = pool_elt_at_index (om->targets, p[0]);
104       vec_free (t->template);
105       hash_unset (om->target_by_address_and_fib_id, key);
106       pool_put (om->targets, t);
107     }
108   return 0;
109 }
110
111 static clib_error_t *
112 oam_add_del_target_command_fn (vlib_main_t * vm,
113                                unformat_input_t * input,
114                                vlib_cli_command_t * cmd)
115 {
116   int is_add = -1;
117   ip4_address_t src_address;
118   int src_set = 0;
119   ip4_address_t dst_address;
120   int dst_set = 0;
121   u32 fib_id = 0;
122
123   while (unformat_check_input (input) != UNFORMAT_END_OF_INPUT)
124     {
125       if (unformat (input, "add"))
126         is_add = 1;
127       else if (unformat (input, "del"))
128         is_add = 0;
129       else if (unformat (input, "src %U", unformat_ip4_address, &src_address))
130         src_set = 1;
131       else if (unformat (input, "dst %U", unformat_ip4_address, &dst_address))
132         dst_set = 1;
133       else if (unformat (input, "fib %d", &fib_id))
134         ;
135       else
136         return clib_error_return (0, "unknown input `%U'",
137                                   format_unformat_error, input);
138     }
139
140   if (is_add == -1)
141     return clib_error_return (0, "missing add / del qualifier");
142   if (src_set == 0)
143     return clib_error_return (0, "src address not set");
144   if (dst_set == 0)
145     return clib_error_return (0, "dst address not set");
146
147   (void) vpe_oam_add_del_target (&src_address, &dst_address, fib_id, is_add);
148
149   return 0;
150 }
151
152 /* *INDENT-OFF* */
153 VLIB_CLI_COMMAND (oam_add_del_target_command, static) = {
154   .path = "oam",
155   .short_help = "oam [add|del] target <ip4-address> fib <fib-id>",
156   .function = oam_add_del_target_command_fn,
157 };
158 /* *INDENT-ON* */
159
160 static uword
161 oam_process (vlib_main_t * vm, vlib_node_runtime_t * rt, vlib_frame_t * f_arg)
162 {
163   oam_main_t *om = &oam_main;
164   uword *event_data = 0;
165   oam_target_t *t;
166   oam_template_t *h0;
167   u32 bi0;
168   u16 new_seq;
169   ip_csum_t sum0;
170   vlib_frame_t *f;
171   u32 *to_next, *from;
172   u32 ip4_lookup_node_index;
173   vlib_node_t *ip4_lookup_node;
174   vlib_buffer_t *b0;
175   static u32 *buffers;
176   oam_template_copy_t *copy_src, *copy_dst;
177   void send_oam_event (oam_target_t * t);
178   u32 nalloc;
179
180   /* Enqueue pkts to ip4-lookup */
181   ip4_lookup_node = vlib_get_node_by_name (vm, (u8 *) "ip4-lookup");
182   ip4_lookup_node_index = ip4_lookup_node->index;
183
184   while (1)
185     {
186       /* Only timeout events at the moment */
187       vlib_process_wait_for_event_or_clock (vm, om->interval);
188       vec_reset_length (event_data);
189
190       if (pool_elts (om->targets) == 0)
191         continue;
192
193       if (vec_len (buffers) < pool_elts (om->targets))
194         vec_validate (buffers, pool_elts (om->targets) - 1);
195
196       nalloc = vlib_buffer_alloc (vm, buffers, pool_elts (om->targets));
197       if (nalloc < pool_elts (om->targets))
198         {
199           vlib_buffer_free (vm, buffers, nalloc);
200           continue;
201         }
202
203       f = vlib_get_frame_to_node (vm, ip4_lookup_node_index);
204       f->n_vectors = 0;
205       to_next = vlib_frame_vector_args (f);
206       from = buffers;
207
208       /* *INDENT-OFF* */
209       pool_foreach (t, om->targets,
210       ({
211         /* State transition announcement... */
212         if ((t->seq - t->last_heard_seq) >= om->misses_allowed)
213           {
214             if (t->state == OAM_STATE_ALIVE)
215               {
216                 if (CLIB_DEBUG > 0)
217                   clib_warning ("oam target %U now DEAD",
218                                 format_ip4_address, &t->dst_address);
219                 t->state = OAM_STATE_DEAD;
220                 send_oam_event (t);
221               }
222           }
223         else
224           {
225             if (t->state == OAM_STATE_DEAD)
226               {
227                 if (CLIB_DEBUG > 0)
228                   clib_warning ("oam target %U now ALIVE",
229                                 format_ip4_address, &t->dst_address);
230                 t->state = OAM_STATE_ALIVE;
231                 send_oam_event (t);
232               }
233           }
234
235         /* Send a new icmp */
236         t->seq++;
237         new_seq = clib_host_to_net_u16 (t->seq);
238
239         bi0 = from[0];
240         from++;
241
242         b0 = vlib_get_buffer (vm, bi0);
243         vnet_buffer (b0)->sw_if_index[VLIB_RX] = 0;
244         vnet_buffer (b0)->sw_if_index [VLIB_TX] = t->fib_index;
245
246         /* Marginally faster than memcpy, probably */
247         copy_dst = (oam_template_copy_t *) b0->data;
248         copy_src = (oam_template_copy_t *) t->template;
249
250         copy_dst->v8[0] = copy_src->v8[0];
251         copy_dst->v8[1] = copy_src->v8[1];
252         copy_dst->v8[2] = copy_src->v8[2];
253         copy_dst->v8[3] = copy_src->v8[3];
254         copy_dst->v4 = copy_src->v4;
255
256         b0->current_data = 0;
257         b0->current_length = sizeof (*t->template);
258         h0 = vlib_buffer_get_current (b0);
259
260         sum0 = h0->icmp.checksum;
261         sum0 = ip_csum_update(sum0, 0 /* old seq */,
262                               new_seq, oam_template_t, seq);
263         h0->seq = new_seq;
264         h0->icmp.checksum = ip_csum_fold (sum0);
265
266         to_next[0] = bi0;
267         to_next++;
268         f->n_vectors++;
269         if (f->n_vectors == VLIB_FRAME_SIZE)
270           {
271             clib_warning ("Too many OAM clients...");
272             goto out;
273           }
274       }));
275       /* *INDENT-ON* */
276
277     out:
278       vlib_put_frame_to_node (vm, ip4_lookup_node_index, f);
279     }
280   return 0;                     /* not so much */
281 }
282
283 /* *INDENT-OFF* */
284 VLIB_REGISTER_NODE (oam_process_node,static) = {
285   .function = oam_process,
286   .type = VLIB_NODE_TYPE_PROCESS,
287   .name = "vpe-oam-process",
288 };
289 /* *INDENT-ON* */
290
291 static clib_error_t *
292 oam_config (vlib_main_t * vm, unformat_input_t * input)
293 {
294   oam_main_t *om = &oam_main;
295   f64 interval;
296   u32 misses_allowed;
297
298   while (unformat_check_input (input) != UNFORMAT_END_OF_INPUT)
299     {
300       if (unformat (input, "interval %f", &interval))
301         om->interval = interval;
302       else if (unformat (input, "misses-allowed %d", &misses_allowed))
303         om->interval = misses_allowed;
304       else
305         return clib_error_return (0, "unknown input `%U'",
306                                   format_unformat_error, input);
307     }
308   return 0;
309 }
310
311 VLIB_CONFIG_FUNCTION (oam_config, "oam");
312
313 static clib_error_t *
314 oam_init (vlib_main_t * vm)
315 {
316   oam_main_t *om = &oam_main;
317
318   om->vlib_main = vm;
319   om->vnet_main = vnet_get_main ();
320   om->interval = 2.04;
321   om->misses_allowed = 3;
322   om->random_seed = (u32) (vlib_time_now (vm) * 1e6);
323   om->target_by_address_and_fib_id = hash_create (0, sizeof (uword));
324   om->icmp_id = random_u32 (&om->random_seed);
325
326   ip4_icmp_register_type (vm, ICMP4_echo_reply, oam_node.index);
327
328   return 0;
329 }
330
331 VLIB_INIT_FUNCTION (oam_init);
332
333 static u8 *
334 format_oam_target (u8 * s, va_list * args)
335 {
336   oam_target_t *t = va_arg (*args, oam_target_t *);
337   int verbose = va_arg (*args, int);
338
339   if (t == 0)
340     return format (s, "%=6s%=14s%=14s%=12s%=10s",
341                    "Fib", "Src", "Dst", "Last Heard", "State");
342
343   s = format (s, "%=6d%=14U%=14U%=12.2f%=10s",
344               t->fib_id,
345               format_ip4_address, &t->src_address,
346               format_ip4_address, &t->dst_address,
347               t->last_heard_time,
348               (t->state == OAM_STATE_ALIVE) ? "alive" : "dead");
349   if (verbose)
350     s = format (s, "   seq %d last_heard_seq %d", t->seq, t->last_heard_seq);
351
352   return s;
353 }
354
355 static clib_error_t *
356 show_oam_command_fn (vlib_main_t * vm,
357                      unformat_input_t * input, vlib_cli_command_t * cmd)
358 {
359   oam_main_t *om = &oam_main;
360   oam_target_t *t;
361   int verbose = 0;
362
363   if (unformat (input, "verbose") || unformat (input, "v"))
364     verbose = 1;
365
366   /* print header */
367   vlib_cli_output (vm, "%U", format_oam_target, 0, verbose);
368
369   /* *INDENT-OFF* */
370   pool_foreach (t, om->targets,
371   ({
372     vlib_cli_output (vm, "%U", format_oam_target, t, verbose);
373   }));
374   /* *INDENT-ON* */
375
376   return 0;
377 }
378
379 /* *INDENT-OFF* */
380 VLIB_CLI_COMMAND (show_oam_command, static) = {
381   .path = "show oam",
382   .short_help = "show oam",
383   .function = show_oam_command_fn,
384 };
385 /* *INDENT-ON* */
386
387 typedef struct
388 {
389   u32 target_pool_index;
390   ip4_address_t address;
391 } oam_trace_t;
392
393 /* packet trace format function */
394 static u8 *
395 format_swap_trace (u8 * s, va_list * args)
396 {
397   CLIB_UNUSED (vlib_main_t * vm) = va_arg (*args, vlib_main_t *);
398   CLIB_UNUSED (vlib_node_t * node) = va_arg (*args, vlib_node_t *);
399   oam_trace_t *t = va_arg (*args, oam_trace_t *);
400
401   s = format (s, "OAM: rx from address %U, target index %d",
402               format_ip4_address, &t->address, t->target_pool_index);
403   return s;
404 }
405
406
407 #define foreach_oam_error                               \
408 _(PROCESSED, "vpe icmp4 oam replies processed")         \
409 _(DROPPED, "icmp4 replies dropped (no registration)")
410
411 typedef enum
412 {
413 #define _(sym,str) OAM_ERROR_##sym,
414   foreach_oam_error
415 #undef _
416     OAM_N_ERROR,
417 } oam_error_t;
418
419 static char *oam_error_strings[] = {
420 #define _(sym,string) string,
421   foreach_oam_error
422 #undef _
423 };
424
425 /*
426  * To drop a pkt and increment one of the previous counters:
427  *
428  * set b0->error = error_node->errors[OAM_ERROR_EXAMPLE];
429  * set next0 to a disposition index bound to "error-drop".
430  *
431  * To manually increment the specific counter OAM_ERROR_EXAMPLE:
432  *
433  *  vlib_node_t *n = vlib_get_node (vm, oam.index);
434  *  u32 node_counter_base_index = n->error_heap_index;
435  *  vlib_error_main_t * em = &vm->error_main;
436  *  em->counters[node_counter_base_index + OAM_ERROR_EXAMPLE] += 1;
437  *
438  */
439
440 typedef enum
441 {
442   OAM_NEXT_DROP,
443   OAM_NEXT_PUNT,
444   OAM_N_NEXT,
445 } oam_next_t;
446
447 static uword
448 oam_node_fn (vlib_main_t * vm,
449              vlib_node_runtime_t * node, vlib_frame_t * frame)
450 {
451   u32 n_left_from, *from, *to_next;
452   oam_next_t next_index;
453   oam_main_t *om = &oam_main;
454   u32 next0 = OAM_NEXT_DROP;    /* all pkts go to the hopper... */
455   u32 next1 = OAM_NEXT_DROP;
456   uword *u0, *u1;
457   oam_template_t *oam0, *oam1;
458   u32 fib_index0, fib_index1;
459   u64 key0, key1;
460   oam_target_t *t0, *t1;
461   ip4_main_t *im = &ip4_main;
462
463   from = vlib_frame_vector_args (frame);
464   n_left_from = frame->n_vectors;
465   next_index = node->cached_next_index;
466
467   while (n_left_from > 0)
468     {
469       u32 n_left_to_next;
470
471       vlib_get_next_frame (vm, node, next_index, to_next, n_left_to_next);
472
473       while (n_left_from >= 4 && n_left_to_next >= 2)
474         {
475           u32 bi0, bi1;
476           vlib_buffer_t *b0, *b1;
477           u32 sw_if_index0, sw_if_index1;
478
479           /* Prefetch next iteration. */
480           {
481             vlib_buffer_t *p2, *p3;
482
483             p2 = vlib_get_buffer (vm, from[2]);
484             p3 = vlib_get_buffer (vm, from[3]);
485
486             vlib_prefetch_buffer_header (p2, LOAD);
487             vlib_prefetch_buffer_header (p3, LOAD);
488
489             CLIB_PREFETCH (p2->data, CLIB_CACHE_LINE_BYTES, STORE);
490             CLIB_PREFETCH (p3->data, CLIB_CACHE_LINE_BYTES, STORE);
491           }
492
493           /* speculatively enqueue b0 and b1 to the current next frame */
494           to_next[0] = bi0 = from[0];
495           to_next[1] = bi1 = from[1];
496           from += 2;
497           to_next += 2;
498           n_left_from -= 2;
499           n_left_to_next -= 2;
500
501           b0 = vlib_get_buffer (vm, bi0);
502           b1 = vlib_get_buffer (vm, bi1);
503
504           sw_if_index0 = vnet_buffer (b0)->sw_if_index[VLIB_RX];
505           sw_if_index1 = vnet_buffer (b1)->sw_if_index[VLIB_RX];
506
507           oam0 = vlib_buffer_get_current (b0);
508           oam1 = vlib_buffer_get_current (b1);
509           fib_index0 = vec_elt (im->fib_index_by_sw_if_index, sw_if_index0);
510           fib_index1 = vec_elt (im->fib_index_by_sw_if_index, sw_if_index1);
511
512           key0 = ((u64) fib_index0 << 32) | oam0->ip4.src_address.as_u32;
513           u0 = hash_get (om->target_by_address_and_fib_id, key0);
514           if (u0)
515             {
516               t0 = pool_elt_at_index (om->targets, u0[0]);
517               t0->last_heard_time = vlib_time_now (vm);
518               t0->last_heard_seq = clib_net_to_host_u16 (oam0->seq);
519               b0->error = node->errors[OAM_ERROR_PROCESSED];
520             }
521           else
522             b0->error = node->errors[OAM_ERROR_DROPPED];
523
524           key1 = ((u64) fib_index1 << 32) | oam1->ip4.src_address.as_u32;
525           u1 = hash_get (om->target_by_address_and_fib_id, key1);
526           if (u1)
527             {
528               t1 = pool_elt_at_index (om->targets, u1[0]);
529               t1->last_heard_time = vlib_time_now (vm);
530               t1->last_heard_seq = clib_net_to_host_u16 (oam1->seq);
531               b1->error = node->errors[OAM_ERROR_PROCESSED];
532             }
533           else
534             b1->error = node->errors[OAM_ERROR_DROPPED];
535
536           if (PREDICT_FALSE ((node->flags & VLIB_NODE_FLAG_TRACE)))
537             {
538               if (b0->flags & VLIB_BUFFER_IS_TRACED)
539                 {
540                   oam_trace_t *t = vlib_add_trace (vm, node, b0, sizeof (*t));
541                   t->target_pool_index = u0 ? u0[0] : (u32) ~ 0;
542                   t->address.as_u32 = oam0->ip4.src_address.as_u32;
543                 }
544               if (b1->flags & VLIB_BUFFER_IS_TRACED)
545                 {
546                   oam_trace_t *t = vlib_add_trace (vm, node, b1, sizeof (*t));
547                   t->target_pool_index = u1 ? u1[0] : (u32) ~ 0;
548                   t->address.as_u32 = oam1->ip4.src_address.as_u32;
549
550                 }
551             }
552
553           if (vm->os_punt_frame)
554             next0 = next1 = OAM_NEXT_PUNT;
555
556           /* verify speculative enqueues, maybe switch current next frame */
557           vlib_validate_buffer_enqueue_x2 (vm, node, next_index,
558                                            to_next, n_left_to_next,
559                                            bi0, bi1, next0, next1);
560         }
561
562       while (n_left_from > 0 && n_left_to_next > 0)
563         {
564           u32 bi0, sw_if_index0;
565           vlib_buffer_t *b0;
566
567           /* speculatively enqueue b0 to the current next frame */
568           bi0 = from[0];
569           to_next[0] = bi0;
570           from += 1;
571           to_next += 1;
572           n_left_from -= 1;
573           n_left_to_next -= 1;
574
575           b0 = vlib_get_buffer (vm, bi0);
576
577           sw_if_index0 = vnet_buffer (b0)->sw_if_index[VLIB_RX];
578
579           oam0 = vlib_buffer_get_current (b0);
580           fib_index0 = vec_elt (im->fib_index_by_sw_if_index, sw_if_index0);
581
582           key0 = ((u64) fib_index0 << 32) | oam0->ip4.src_address.as_u32;
583           u0 = hash_get (om->target_by_address_and_fib_id, key0);
584           if (u0)
585             {
586               t0 = pool_elt_at_index (om->targets, u0[0]);
587               t0->last_heard_time = vlib_time_now (vm);
588               t0->last_heard_seq = clib_net_to_host_u16 (oam0->seq);
589               b0->error = node->errors[OAM_ERROR_PROCESSED];
590             }
591           else
592             b0->error = node->errors[OAM_ERROR_DROPPED];
593
594           if (PREDICT_FALSE ((node->flags & VLIB_NODE_FLAG_TRACE)
595                              && (b0->flags & VLIB_BUFFER_IS_TRACED)))
596             {
597               oam_trace_t *t = vlib_add_trace (vm, node, b0, sizeof (*t));
598               t->target_pool_index = u0 ? u0[0] : (u32) ~ 0;
599               t->address.as_u32 = oam0->ip4.src_address.as_u32;
600             }
601
602           if (vm->os_punt_frame)
603             next0 = OAM_NEXT_PUNT;
604
605           /* verify speculative enqueue, maybe switch current next frame */
606           vlib_validate_buffer_enqueue_x1 (vm, node, next_index,
607                                            to_next, n_left_to_next,
608                                            bi0, next0);
609         }
610
611       vlib_put_next_frame (vm, node, next_index, n_left_to_next);
612     }
613
614   return frame->n_vectors;
615 }
616
617 /* *INDENT-OFF* */
618 VLIB_REGISTER_NODE (oam_node,static) = {
619   .function = oam_node_fn,
620   .name = "vpe-icmp4-oam",
621   .vector_size = sizeof (u32),
622   .format_trace = format_swap_trace,
623   .type = VLIB_NODE_TYPE_INTERNAL,
624
625   .n_errors = ARRAY_LEN(oam_error_strings),
626   .error_strings = oam_error_strings,
627
628   .n_next_nodes = OAM_N_NEXT,
629
630   /* edit / add dispositions here */
631   .next_nodes = {
632     [OAM_NEXT_DROP] = "error-drop",
633     [OAM_NEXT_PUNT] = "error-punt",
634   },
635 };
636 /* *INDENT-ON* */
637
638 /*
639  * fd.io coding-style-patch-verification: ON
640  *
641  * Local Variables:
642  * eval: (c-set-style "gnu")
643  * End:
644  */