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