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