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