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