ip: extend punt CLI for exception packets
[vpp.git] / src / vlib / punt.c
1 /*
2  * Copyright (c) 2019 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
16 #include <vlib/punt.h>
17
18 /**
19  * The last allocated punt reason
20  * Value 0 is reserved for invalid index.
21  */
22 static vlib_punt_reason_t punt_reason_last = 1;
23
24 /**
25  * Counters per punt-reason
26  */
27 vlib_combined_counter_main_t punt_counters = {
28   .name = "punt",
29   .stat_segment_name = "/net/punt",
30 };
31
32 /**
33  * A punt reason
34  */
35 typedef struct punt_reason_data_t_
36 {
37   /**
38    * The reason name
39    */
40   u8 *pd_name;
41
42   /**
43    * The allocated reason value
44    */
45   vlib_punt_reason_t pd_reason;
46
47   /**
48    * Clients/owners that have registered this reason
49    */
50   u32 *pd_owners;
51
52   /**
53    * clients interested/listening to this reason
54    */
55   u32 pd_users;
56
57   /**
58    * function to invoke if a client becomes interested in the code.
59    */
60   punt_interested_listener_t pd_fn;
61
62   /**
63    * Data to pass to the callback
64    */
65   void *pd_data;
66
67   /**
68    * Flags associated to the reason
69    */
70   u32 flags;
71
72   /**
73    * Formatting function for flags;
74    */
75   format_function_t *flags_format;
76 } punt_reason_data_t;
77
78 /**
79  * data for each punt reason
80  */
81 static punt_reason_data_t *punt_reason_data;
82
83 typedef enum punt_format_flags_t_
84 {
85   PUNT_FORMAT_FLAG_NONE = 0,
86   PUNT_FORMAT_FLAG_DETAIL = (1 << 0),
87 } punt_format_flags_t;
88
89 /**
90  * A registration, by a client, to direct punted traffic to a given node
91  */
92 typedef struct punt_reg_t_
93 {
94   /**
95    * Reason the packets were punted
96    */
97   vlib_punt_reason_t pr_reason;
98
99   /**
100    * number of clients that have made this registration
101    */
102   u16 pr_locks;
103
104   /**
105    * The edge from the punt dispatch node to the requested node
106    */
107   u16 pr_edge;
108
109   /**
110    * node-index to send punted packets to
111    */
112   u32 pr_node_index;
113 } punt_reg_t;
114
115 /**
116  * Pool of registrations
117  */
118 static punt_reg_t *punt_reg_pool;
119
120 /**
121  * A DB of all the register nodes against punt reason and node index
122  */
123 static uword *punt_reg_db;
124
125 /**
126  * A DB used in the DP per-reason to dispatch packets to the requested nodes.
127  * this is a vector of edges per-reason
128  */
129 u16 **punt_dp_db;
130
131 /**
132  * A client using the punt serivce and its registrations
133  */
134 typedef struct punt_client_t_
135 {
136   /**
137    * The name of the client
138    */
139   u8 *pc_name;
140
141   /**
142    * The registrations is has made
143    */
144   u32 *pc_regs;
145 } punt_client_t;
146
147 /**
148  * Pool of clients
149  */
150 static punt_client_t *punt_client_pool;
151
152 /**
153  * DB of clients key'd by their name
154  */
155 static uword *punt_client_db;
156
157 u8 *
158 format_vlib_punt_reason (u8 * s, va_list * args)
159 {
160   vlib_punt_reason_t pr = va_arg (*args, int);
161   format_function_t *flags_format = punt_reason_data[pr].flags_format;
162   u32 flags = punt_reason_data[pr].flags;
163   if (flags_format)
164     return (format (s, "[%d] %v flags: %U", pr, punt_reason_data[pr].pd_name,
165                     flags_format, flags));
166   else
167     return (format (s, "[%d] %v", pr, punt_reason_data[pr].pd_name));
168 }
169
170 vlib_punt_hdl_t
171 vlib_punt_client_register (const char *who)
172 {
173   u8 *pc_name;
174   uword *p;
175   u32 pci;
176
177   pc_name = format (NULL, "%s", who);
178   p = hash_get_mem (punt_client_db, pc_name);
179
180   if (NULL == p)
181     {
182       punt_client_t *pc;
183
184       pool_get (punt_client_pool, pc);
185       pci = pc - punt_client_pool;
186
187       pc->pc_name = pc_name;
188
189       hash_set_mem (punt_client_db, pc->pc_name, pci);
190     }
191   else
192     {
193       pci = p[0];
194       vec_free (pc_name);
195     }
196
197   return (pci);
198 }
199
200 static int
201 punt_validate_client (vlib_punt_hdl_t client)
202 {
203   return (!pool_is_free_index (punt_client_pool, client));
204 }
205
206 static u64
207 punt_reg_mk_key (vlib_punt_reason_t reason, u32 node_index)
208 {
209   return (((u64) node_index) << 32 | reason);
210 }
211
212 static u32
213 punt_reg_find (vlib_punt_reason_t reason, u32 node_index)
214 {
215   uword *p;
216
217   p = hash_get (punt_reg_db, punt_reg_mk_key (reason, node_index));
218
219   if (p)
220     return p[0];
221
222   return ~0;
223 }
224
225 static void
226 punt_reg_add (const punt_reg_t * pr)
227 {
228   hash_set (punt_reg_db, punt_reg_mk_key (pr->pr_reason,
229                                           pr->pr_node_index),
230             pr - punt_reg_pool);
231 }
232
233 static void
234 punt_reg_remove (const punt_reg_t * pr)
235 {
236   hash_unset (punt_reg_db, punt_reg_mk_key (pr->pr_reason,
237                                             pr->pr_node_index));
238 }
239
240 /**
241  * reconstruct the DP per-reason DB
242  */
243 static void
244 punt_reg_mk_dp (vlib_punt_reason_t reason)
245 {
246   u32 pri, *prip, *pris;
247   const punt_reg_t *pr;
248   u16 *edges, *old;
249   u64 key;
250
251   pris = NULL;
252   edges = NULL;
253   vec_validate (punt_dp_db, reason);
254
255   old = punt_dp_db[reason];
256
257   /* *INDENT-OFF* */
258   hash_foreach (key, pri, punt_reg_db,
259     ({
260       vec_add1(pris, pri);
261     }));
262   /* *INDENT-ON* */
263
264   /*
265    * A check for an empty vector is done in the DP, so the a zero
266    * length vector here is ok
267    */
268   vec_foreach (prip, pris)
269   {
270     pr = pool_elt_at_index (punt_reg_pool, *prip);
271
272     if (pr->pr_reason == reason)
273       vec_add1 (edges, pr->pr_edge);
274   }
275
276   /* atomic update of the DP */
277   punt_dp_db[reason] = edges;
278
279   vec_free (old);
280 }
281
282 int
283 vlib_punt_register (vlib_punt_hdl_t client,
284                     vlib_punt_reason_t reason, const char *node_name)
285 {
286   vlib_node_t *punt_to, *punt_from;
287   punt_client_t *pc;
288   vlib_main_t *vm;
289   punt_reg_t *pr;
290   u32 pri;
291
292   if (reason >= punt_reason_last)
293     return -1;
294   if (!punt_validate_client (client))
295     return -2;
296
297   vm = vlib_get_main ();
298   pc = pool_elt_at_index (punt_client_pool, client);
299   punt_to = vlib_get_node_by_name (vm, (u8 *) node_name);
300   punt_from = vlib_get_node_by_name (vm, (u8 *) "punt-dispatch");
301
302   /*
303    * find a global matching registration
304    */
305   pri = punt_reg_find (reason, punt_to->index);
306
307   if (~0 != pri)
308     {
309       u32 pos;
310
311       pos = vec_search (pc->pc_regs, pri);
312
313       if (~0 != pos)
314         {
315           /* duplicate registration for this client */
316           return -1;
317         }
318
319       pr = pool_elt_at_index (punt_reg_pool, pri);
320     }
321   else
322     {
323       pool_get (punt_reg_pool, pr);
324
325       pr->pr_reason = reason;
326       pr->pr_node_index = punt_to->index;
327       pr->pr_edge = vlib_node_add_next (vm,
328                                         punt_from->index, pr->pr_node_index);
329
330       pri = pr - punt_reg_pool;
331
332       if (0 == punt_reason_data[reason].pd_users++ &&
333           NULL != punt_reason_data[reason].pd_fn)
334         punt_reason_data[reason].pd_fn (VLIB_ENABLE,
335                                         punt_reason_data[reason].pd_data);
336
337       punt_reg_add (pr);
338     }
339
340   /*
341    * add this reg to the list the client has made
342    */
343   pr->pr_locks++;
344   vec_add1 (pc->pc_regs, pri);
345
346   punt_reg_mk_dp (reason);
347
348   return 0;
349 }
350
351 int
352 vlib_punt_unregister (vlib_punt_hdl_t client,
353                       vlib_punt_reason_t reason, const char *node_name)
354 {
355   vlib_node_t *punt_to;
356   punt_client_t *pc;
357   vlib_main_t *vm;
358   punt_reg_t *pr;
359   u32 pri;
360
361   if (reason >= punt_reason_last)
362     return -1;
363
364   vm = vlib_get_main ();
365   pc = pool_elt_at_index (punt_client_pool, client);
366   punt_to = vlib_get_node_by_name (vm, (u8 *) node_name);
367
368   /*
369    * construct a registration and check if it's one this client already has
370    */
371   pri = punt_reg_find (reason, punt_to->index);
372
373   if (~0 != pri)
374     {
375       u32 pos;
376
377       pos = vec_search (pc->pc_regs, pri);
378
379       if (~0 == pos)
380         {
381           /* not a registration for this client */
382           return -1;
383         }
384       vec_del1 (pc->pc_regs, pos);
385
386       pr = pool_elt_at_index (punt_reg_pool, pri);
387
388       pr->pr_locks--;
389
390       if (0 == pr->pr_locks)
391         {
392           if (0 == --punt_reason_data[reason].pd_users &&
393               NULL != punt_reason_data[reason].pd_fn)
394             punt_reason_data[reason].pd_fn (VLIB_DISABLE,
395                                             punt_reason_data[reason].pd_data);
396           punt_reg_remove (pr);
397           pool_put (punt_reg_pool, pr);
398         }
399     }
400
401   /*
402    * rebuild the DP data-base
403    */
404   punt_reg_mk_dp (reason);
405
406   return (0);
407 }
408
409 int
410 vlib_punt_reason_validate (vlib_punt_reason_t reason)
411 {
412   if (reason < punt_reason_last)
413     return (0);
414
415   return (-1);
416 }
417
418 u32
419 vlib_punt_reason_get_flags (vlib_punt_reason_t pr)
420 {
421   return pr < punt_reason_last ? punt_reason_data[pr].flags : 0;
422 }
423
424 int
425 vlib_punt_reason_alloc (vlib_punt_hdl_t client, const char *reason_name,
426                         punt_interested_listener_t fn, void *data,
427                         vlib_punt_reason_t *reason, u32 flags,
428                         format_function_t *flags_format)
429 {
430   vlib_punt_reason_t new;
431
432   if (!punt_validate_client (client))
433     return -2;
434
435   new = punt_reason_last++;
436   vec_validate (punt_reason_data, new);
437   punt_reason_data[new].pd_name = format (NULL, "%s", reason_name);
438   punt_reason_data[new].pd_reason = new;
439   punt_reason_data[new].pd_fn = fn;
440   punt_reason_data[new].pd_data = data;
441   punt_reason_data[new].flags = flags;
442   punt_reason_data[new].flags_format = flags_format;
443   vec_add1 (punt_reason_data[new].pd_owners, client);
444
445   vlib_validate_combined_counter (&punt_counters, new);
446   vlib_zero_combined_counter (&punt_counters, new);
447
448   *reason = new;
449
450   /* build the DP data-base */
451   punt_reg_mk_dp (*reason);
452
453   return (0);
454 }
455
456 void
457 punt_reason_walk (punt_reason_walk_cb_t cb, void *ctx)
458 {
459   punt_reason_data_t *pd;
460
461   for (pd = punt_reason_data + 1; pd < vec_end (punt_reason_data); pd++)
462     {
463       cb (pd->pd_reason, pd->pd_name, ctx);
464     }
465 }
466
467 /* Parse node name -> node index. */
468 uword
469 unformat_punt_client (unformat_input_t * input, va_list * args)
470 {
471   u32 *result = va_arg (*args, u32 *);
472
473   return unformat_user (input, unformat_hash_vec_string,
474                         punt_client_db, result);
475 }
476
477 /* Parse punt reason */
478 uword
479 unformat_punt_reason (unformat_input_t *input, va_list *args)
480 {
481   u32 *result = va_arg (*args, u32 *);
482   u8 *s = 0;
483   u8 found = 0;
484   for (int i = 0; i < punt_reason_last - 1; i++)
485     {
486       punt_reason_data_t *pd = vec_elt_at_index (punt_reason_data, 1 + i);
487       vec_reset_length (s);
488       s = format (0, "%v%c", pd->pd_name, 0);
489       if (unformat (input, (const char *) s))
490         {
491           *result = pd->pd_reason;
492           found = 1;
493           break;
494         }
495     }
496   vec_free (s);
497   return found;
498 }
499
500 u8 *
501 format_punt_reg (u8 * s, va_list * args)
502 {
503   u32 pri = va_arg (*args, u32);
504   punt_reg_t *pr;
505
506   pr = pool_elt_at_index (punt_reg_pool, pri);
507
508   s = format (s, "%U -> %U",
509               format_vlib_punt_reason, pr->pr_reason,
510               format_vlib_node_name, vlib_get_main (), pr->pr_node_index);
511
512   return (s);
513 }
514
515 u8 *
516 format_punt_reason_data (u8 * s, va_list * args)
517 {
518   punt_reason_data_t *pd = va_arg (*args, punt_reason_data_t *);
519   punt_client_t *pc;
520   u32 *pci;
521   if (pd->flags_format)
522     s = format (s, "[%d] %v flags: %U from:[", pd->pd_reason, pd->pd_name,
523                 pd->flags_format, pd->flags);
524   else
525     s = format (s, "[%d] %v from:[", pd->pd_reason, pd->pd_name);
526   vec_foreach (pci, pd->pd_owners)
527   {
528     pc = pool_elt_at_index (punt_client_pool, *pci);
529     s = format (s, "%v ", pc->pc_name);
530   }
531   s = format (s, "]");
532
533   return (s);
534 }
535
536 u8 *
537 format_punt_client (u8 * s, va_list * args)
538 {
539   u32 pci = va_arg (*args, u32);
540   punt_format_flags_t flags = va_arg (*args, punt_format_flags_t);
541   punt_client_t *pc;
542
543   pc = pool_elt_at_index (punt_client_pool, pci);
544
545   s = format (s, "%v", pc->pc_name);
546
547   if (flags & PUNT_FORMAT_FLAG_DETAIL)
548     {
549       punt_reason_data_t *pd;
550       u32 *pri;
551
552       s = format (s, "\n registrations:");
553       vec_foreach (pri, pc->pc_regs)
554       {
555         s = format (s, "\n  [%U]", format_punt_reg, *pri);
556       }
557
558       s = format (s, "\n reasons:");
559
560       vec_foreach (pd, punt_reason_data)
561       {
562         u32 *tmp;
563
564         vec_foreach (tmp, pd->pd_owners)
565         {
566           if (*tmp == pci)
567             s = format (s, "\n  %U", format_punt_reason_data, pd);
568         }
569       }
570     }
571   return (s);
572 }
573
574 static clib_error_t *
575 punt_client_show (vlib_main_t * vm,
576                   unformat_input_t * input, vlib_cli_command_t * cmd)
577 {
578   u32 pci = ~0;
579
580   while (unformat_check_input (input) != UNFORMAT_END_OF_INPUT)
581     {
582       if (unformat (input, "%U", unformat_punt_client, &pci))
583         ;
584       else
585         break;
586     }
587
588   if (~0 != pci)
589     {
590       vlib_cli_output (vm, "%U", format_punt_client, pci,
591                        PUNT_FORMAT_FLAG_DETAIL);
592     }
593   else
594     {
595       u8 *name;
596
597       /* *INDENT-OFF* */
598       hash_foreach(name, pci, punt_client_db,
599         ({
600           vlib_cli_output (vm, "%U", format_punt_client, pci,
601                            PUNT_FORMAT_FLAG_NONE);
602         }));
603       /* *INDENT-ON* */
604     }
605
606   return (NULL);
607 }
608
609 /* *INDENT-OFF* */
610 VLIB_CLI_COMMAND (punt_client_show_command, static) =
611 {
612   .path = "show punt client",
613   .short_help = "show client[s] registered with the punt infra",
614   .function = punt_client_show,
615 };
616 /* *INDENT-ON* */
617
618 static clib_error_t *
619 punt_reason_show (vlib_main_t * vm,
620                   unformat_input_t * input, vlib_cli_command_t * cmd)
621 {
622   const punt_reason_data_t *pd;
623
624   vec_foreach (pd, punt_reason_data)
625   {
626     vlib_cli_output (vm, "%U", format_punt_reason_data, pd);
627   }
628
629   return (NULL);
630 }
631
632 /* *INDENT-OFF* */
633 VLIB_CLI_COMMAND (punt_reason_show_command, static) =
634 {
635   .path = "show punt reasons",
636   .short_help = "show all punt reasons",
637   .function = punt_reason_show,
638 };
639 /* *INDENT-ON* */
640
641 static clib_error_t *
642 punt_db_show (vlib_main_t * vm,
643               unformat_input_t * input, vlib_cli_command_t * cmd)
644 {
645   u32 pri, ii, jj;
646   u64 key;
647
648   /* *INDENT-OFF* */
649   hash_foreach (key, pri, punt_reg_db,
650     ({
651       vlib_cli_output (vm, " %U", format_punt_reg, pri);
652     }));
653   /* *INDENT-ON* */
654
655   vlib_cli_output (vm, "\nDerived data-plane data-base:");
656   vlib_cli_output (vm,
657                    "  (for each punt-reason the edge[s] from punt-dispatch)");
658
659   vec_foreach_index (ii, punt_dp_db)
660   {
661     u8 *s = NULL;
662     vlib_cli_output (vm, " %U", format_vlib_punt_reason, ii);
663
664     vec_foreach_index (jj, punt_dp_db[ii])
665     {
666       s = format (s, "%d ", punt_dp_db[ii][jj]);
667     }
668     vlib_cli_output (vm, "   [%v]", s);
669     vec_free (s);
670   }
671
672   return (NULL);
673 }
674
675 /* *INDENT-OFF* */
676 VLIB_CLI_COMMAND (punt_db_show_command, static) =
677 {
678   .path = "show punt db",
679   .short_help = "show the punt DB",
680   .function = punt_db_show,
681 };
682 /* *INDENT-ON* */
683
684 static clib_error_t *
685 punt_stats_show (vlib_main_t * vm,
686                  unformat_input_t * input, vlib_cli_command_t * cmd)
687 {
688   vlib_combined_counter_main_t *cm = &punt_counters;
689   vlib_counter_t c;
690   u32 ii;
691
692   for (ii = 0; ii < vlib_combined_counter_n_counters (cm); ii++)
693     {
694       vlib_get_combined_counter (cm, ii, &c);
695       vlib_cli_output (vm, "%U packets:%lld bytes:%lld",
696                        format_vlib_punt_reason, ii, c.packets, c.bytes);
697     }
698
699   return (NULL);
700 }
701
702 /* *INDENT-OFF* */
703 VLIB_CLI_COMMAND (punt_stats_show_command, static) =
704 {
705   .path = "show punt stats",
706   .short_help = "show the punt stats",
707   .function = punt_stats_show,
708 };
709 /* *INDENT-ON* */
710
711 static clib_error_t *
712 punt_init (vlib_main_t * vm)
713 {
714   punt_client_db = hash_create_vec (0, sizeof (u8), sizeof (u32));
715
716   return (NULL);
717 }
718
719 VLIB_INIT_FUNCTION (punt_init);
720
721 /*
722  * fd.io coding-style-patch-verification: ON
723  *
724  * Local Variables:
725  * eval: (c-set-style "gnu")
726  * End:
727  */