d925d12cde77f43d835c9841766ce4cb82b52db6
[vpp.git] / src / plugins / ip_session_redirect / redirect.c
1 /* Copyright (c) 2021-2022 Cisco and/or its affiliates.
2  * Licensed under the Apache License, Version 2.0 (the "License");
3  * you may not use this file except in compliance with the License.
4  * You may obtain a copy of the License at:
5  *
6  *     http://www.apache.org/licenses/LICENSE-2.0
7  *
8  * Unless required by applicable law or agreed to in writing, software
9  * distributed under the License is distributed on an "AS IS" BASIS,
10  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11  * See the License for the specific language governing permissions and
12  * limitations under the License. */
13 #include <vlib/vlib.h>
14 #include <vnet/fib/fib_path_list.h>
15 #include <vnet/classify/vnet_classify.h>
16 #include <vnet/classify/in_out_acl.h>
17 #include <vnet/plugin/plugin.h>
18 #include <vpp/app/version.h>
19 #include "ip_session_redirect.h"
20
21 typedef struct
22 {
23   u8 *match_and_table_index;
24   dpo_id_t dpo;    /* forwarding dpo */
25   fib_node_t node; /* linkage into the FIB graph */
26   fib_node_index_t pl;
27   u32 sibling;
28   u32 parent_node_index;
29   u32 opaque_index;
30   u32 table_index;
31   fib_forward_chain_type_t payload_type;
32   u8 is_punt : 1;
33   u8 is_ip6 : 1;
34 } ip_session_redirect_t;
35
36 typedef struct
37 {
38   ip_session_redirect_t *pool;
39   u32 *session_by_match_and_table_index;
40   fib_node_type_t fib_node_type;
41 } ip_session_redirect_main_t;
42
43 static ip_session_redirect_main_t ip_session_redirect_main;
44
45 static int
46 ip_session_redirect_stack (ip_session_redirect_t *ipr)
47 {
48   dpo_id_t dpo = DPO_INVALID;
49
50   fib_path_list_contribute_forwarding (ipr->pl, ipr->payload_type,
51                                        fib_path_list_is_popular (ipr->pl) ?
52                                                FIB_PATH_LIST_FWD_FLAG_NONE :
53                                                FIB_PATH_LIST_FWD_FLAG_COLLAPSE,
54                                        &dpo);
55   dpo_stack_from_node (ipr->parent_node_index, &ipr->dpo, &dpo);
56   dpo_reset (&dpo);
57
58   /* update session with new next_index */
59   return vnet_classify_add_del_session (
60     &vnet_classify_main, ipr->table_index, ipr->match_and_table_index,
61     ipr->dpo.dpoi_next_node /* hit_next_index */, ipr->opaque_index,
62     0 /* advance */, CLASSIFY_ACTION_SET_METADATA,
63     ipr->dpo.dpoi_index /* metadata */, 1 /* is_add */);
64 }
65
66 static ip_session_redirect_t *
67 ip_session_redirect_find (ip_session_redirect_main_t *im, u32 table_index,
68                           const u8 *match)
69 {
70   /* we are adding the table index at the end of the match string so we
71    * can disambiguiate identical matches in different tables in
72    * im->session_by_match_and_table_index */
73   u8 *match_and_table_index = vec_dup (match);
74   vec_add (match_and_table_index, (void *) &table_index, 4);
75   uword *p =
76     hash_get_mem (im->session_by_match_and_table_index, match_and_table_index);
77   vec_free (match_and_table_index);
78   if (!p)
79     return 0;
80   return pool_elt_at_index (im->pool, p[0]);
81 }
82
83 int
84 ip_session_redirect_add (vlib_main_t *vm, u32 table_index, u32 opaque_index,
85                          dpo_proto_t proto, int is_punt, const u8 *match,
86                          const fib_route_path_t *rpaths)
87 {
88   ip_session_redirect_main_t *im = &ip_session_redirect_main;
89   fib_forward_chain_type_t payload_type;
90   ip_session_redirect_t *ipr;
91   const char *pname;
92
93   payload_type = fib_forw_chain_type_from_dpo_proto (proto);
94   switch (payload_type)
95     {
96     case FIB_FORW_CHAIN_TYPE_UNICAST_IP4:
97       pname = is_punt ? "ip4-punt-acl" : "ip4-inacl";
98       break;
99     case FIB_FORW_CHAIN_TYPE_UNICAST_IP6:
100       pname = is_punt ? "ip6-punt-acl" : "ip6-inacl";
101       break;
102     default:
103       return VNET_API_ERROR_INVALID_ADDRESS_FAMILY;
104     }
105
106   ipr = ip_session_redirect_find (im, table_index, match);
107   if (ipr)
108     {
109       /* update to an existing session */
110       fib_path_list_child_remove (ipr->pl, ipr->sibling);
111       dpo_reset (&ipr->dpo);
112     }
113   else
114     {
115       /* allocate a new entry */
116       pool_get (im->pool, ipr);
117       fib_node_init (&ipr->node, im->fib_node_type);
118       ipr->match_and_table_index = vec_dup ((u8 *) match);
119       /* we are adding the table index at the end of the match string so we
120        * can disambiguiate identical matches in different tables in
121        * im->session_by_match_and_table_index */
122       vec_add (ipr->match_and_table_index, (void *) &table_index, 4);
123       ipr->table_index = table_index;
124       hash_set_mem (im->session_by_match_and_table_index,
125                     ipr->match_and_table_index, ipr - im->pool);
126     }
127
128   ipr->payload_type = payload_type;
129   ipr->pl = fib_path_list_create (
130     FIB_PATH_LIST_FLAG_SHARED | FIB_PATH_LIST_FLAG_NO_URPF, rpaths);
131   ipr->sibling =
132     fib_path_list_child_add (ipr->pl, im->fib_node_type, ipr - im->pool);
133   ipr->parent_node_index = vlib_get_node_by_name (vm, (u8 *) pname)->index;
134   ipr->opaque_index = opaque_index;
135   ipr->is_punt = is_punt;
136   ipr->is_ip6 = payload_type == FIB_FORW_CHAIN_TYPE_UNICAST_IP6;
137
138   return ip_session_redirect_stack (ipr);
139 }
140
141 int
142 ip_session_redirect_del (vlib_main_t *vm, u32 table_index, const u8 *match)
143 {
144   ip_session_redirect_main_t *im = &ip_session_redirect_main;
145   vnet_classify_main_t *cm = &vnet_classify_main;
146   ip_session_redirect_t *ipr;
147   int rv;
148
149   ipr = ip_session_redirect_find (im, table_index, match);
150   if (!ipr)
151     return VNET_API_ERROR_NO_SUCH_ENTRY;
152
153   rv = vnet_classify_add_del_session (
154     cm, ipr->table_index, ipr->match_and_table_index, 0 /* hit_next_index */,
155     0 /* opaque_index */, 0 /* advance */, 0 /* action */, 0 /* metadata */,
156     0 /* is_add */);
157   if (rv)
158     return rv;
159
160   hash_unset_mem (im->session_by_match_and_table_index,
161                   ipr->match_and_table_index);
162   vec_free (ipr->match_and_table_index);
163   fib_path_list_child_remove (ipr->pl, ipr->sibling);
164   dpo_reset (&ipr->dpo);
165   pool_put (im->pool, ipr);
166   return 0;
167 }
168
169 static int
170 ip_session_redirect_show_yield (vlib_main_t *vm, f64 *start)
171 {
172   /* yields for 2 clock ticks every 1 tick to avoid blocking the main thread
173    * when dumping huge data structures */
174   f64 now = vlib_time_now (vm);
175   if (now - *start > 11e-6)
176     {
177       vlib_process_suspend (vm, 21e-6);
178       *start = vlib_time_now (vm);
179       return 1;
180     }
181
182   return 0;
183 }
184
185 static u8 *
186 format_ip_session_redirect (u8 *s, va_list *args)
187 {
188   const ip_session_redirect_main_t *im = &ip_session_redirect_main;
189   const ip_session_redirect_t *ipr =
190     va_arg (*args, const ip_session_redirect_t *);
191   index_t ipri = ipr - im->pool;
192   const char *type = ipr->is_punt ? "[punt]" : "[acl]";
193   const char *ip = ipr->is_ip6 ? "[ip6]" : "[ip4]";
194   s =
195     format (s, "[%u] %s %s table %d key %U opaque_index 0x%x\n", ipri, type,
196             ip, ipr->table_index, format_hex_bytes, ipr->match_and_table_index,
197             vec_len (ipr->match_and_table_index) - 4, ipr->opaque_index);
198   s = format (s, " via:\n");
199   s = format (s, "  %U", format_fib_path_list, ipr->pl, 2);
200   s = format (s, " forwarding\n");
201   s = format (s, "  %U", format_dpo_id, &ipr->dpo, 0);
202   return s;
203 }
204
205 static clib_error_t *
206 ip_session_redirect_show_cmd (vlib_main_t *vm, unformat_input_t *main_input,
207                               vlib_cli_command_t *cmd)
208 {
209   ip_session_redirect_main_t *im = &ip_session_redirect_main;
210   unformat_input_t _line_input, *line_input = &_line_input;
211   vnet_classify_main_t *cm = &vnet_classify_main;
212   ip_session_redirect_t *ipr;
213   clib_error_t *error = 0;
214   u32 table_index = ~0;
215   int is_punt = -1;
216   int is_ip6 = -1;
217   u8 *match = 0;
218   int max = 50;
219   u8 *s = 0;
220
221   if (unformat_is_eof (main_input))
222     unformat_init (line_input, 0,
223                    0); /* support straight "sh ip session redirect" */
224   else if (!unformat_user (main_input, unformat_line_input, line_input))
225     return 0;
226
227   while (unformat_check_input (line_input) != UNFORMAT_END_OF_INPUT)
228     {
229       if (unformat (line_input, "all"))
230         ;
231       else if (unformat (line_input, "punt"))
232         is_punt = 1;
233       else if (unformat (line_input, "acl"))
234         is_punt = 0;
235       else if (unformat (line_input, "ip4"))
236         is_ip6 = 0;
237       else if (unformat (line_input, "ip6"))
238         is_ip6 = 1;
239       else if (unformat (line_input, "table %u", &table_index))
240         ;
241       else if (unformat (line_input, "match %U", unformat_classify_match, cm,
242                          &match, table_index))
243         ;
244       else if (unformat (line_input, "max %d", &max))
245         ;
246       else
247         {
248           error = unformat_parse_error (line_input);
249           goto out;
250         }
251     }
252
253   if (match)
254     {
255       ipr = ip_session_redirect_find (im, table_index, match);
256       if (!ipr)
257         vlib_cli_output (vm, "none");
258       else
259         vlib_cli_output (vm, "%U", format_ip_session_redirect, ipr);
260     }
261   else
262     {
263       f64 start = vlib_time_now (vm);
264       ip_session_redirect_t *iprs = im->pool;
265       int n = 0;
266       pool_foreach (ipr, iprs)
267         {
268           if (n >= max)
269             {
270               n = -1; /* signal overflow */
271               break;
272             }
273           if ((~0 == table_index || ipr->table_index == table_index) &&
274               (-1 == is_punt || ipr->is_punt == is_punt) &&
275               (-1 == is_ip6 || ipr->is_ip6 == is_ip6))
276             {
277               s = format (s, "%U\n", format_ip_session_redirect, ipr);
278               n++;
279             }
280           if (ip_session_redirect_show_yield (vm, &start))
281             {
282               /* we must reload the pool as it might have moved */
283               u32 ii = ipr - iprs;
284               iprs = im->pool;
285               ipr = iprs + ii;
286             }
287         }
288       vec_add1 (s, 0);
289       vlib_cli_output (vm, (char *) s);
290       vec_free (s);
291       if (-1 == n)
292         {
293           vlib_cli_output (
294             vm,
295             "\nPlease note: only the first %d entries displayed. "
296             "To display more, specify max.",
297             max);
298         }
299     }
300
301 out:
302   vec_free (match);
303   unformat_free (line_input);
304   return error;
305 }
306
307 VLIB_CLI_COMMAND (ip_session_redirect_show_command, static) = {
308   .path = "show ip session redirect",
309   .function = ip_session_redirect_show_cmd,
310   .short_help = "show ip session redirect [all|[table <table-index>] "
311                 "[punt|acl] [ip4|ip6] [match]]",
312 };
313
314 static clib_error_t *
315 ip_session_redirect_cmd (vlib_main_t *vm, unformat_input_t *main_input,
316                          vlib_cli_command_t *cmd)
317 {
318   unformat_input_t _line_input, *line_input = &_line_input;
319   vnet_classify_main_t *cm = &vnet_classify_main;
320   dpo_proto_t proto = DPO_PROTO_IP4;
321   fib_route_path_t *rpaths = 0, rpath;
322   clib_error_t *error = 0;
323   u32 opaque_index = ~0;
324   u32 table_index = ~0;
325   int is_punt = 0;
326   int is_add = 1;
327   u8 *match = 0;
328   int rv;
329
330   if (!unformat_user (main_input, unformat_line_input, line_input))
331     return 0;
332
333   while (unformat_check_input (line_input) != UNFORMAT_END_OF_INPUT)
334     {
335       if (unformat (line_input, "del"))
336         is_add = 0;
337       else if (unformat (line_input, "add"))
338         is_add = 1;
339       else if (unformat (line_input, "punt"))
340         is_punt = 1;
341       else if (unformat (line_input, "table %u", &table_index))
342         ;
343       else if (unformat (line_input, "opaque-index %u", &opaque_index))
344         ;
345       else if (unformat (line_input, "match %U", unformat_classify_match, cm,
346                          &match, table_index))
347         ;
348       else if (unformat (line_input, "via %U", unformat_fib_route_path, &rpath,
349                          &proto))
350         vec_add1 (rpaths, rpath);
351       else
352         {
353           error = unformat_parse_error (line_input);
354           goto out;
355         }
356     }
357
358   if (~0 == table_index || 0 == match)
359     {
360       error = clib_error_create ("missing table index or match");
361       goto out;
362     }
363
364   if (is_add)
365     {
366       if (0 == rpaths)
367         {
368           error = clib_error_create ("missing path");
369           goto out;
370         }
371       rv = ip_session_redirect_add (vm, table_index, opaque_index, proto,
372                                     is_punt, match, rpaths);
373     }
374   else
375     {
376       rv = ip_session_redirect_del (vm, table_index, match);
377     }
378
379   if (rv)
380     error = clib_error_create ("failed with error %d", rv);
381
382 out:
383   vec_free (rpaths);
384   vec_free (match);
385   unformat_free (line_input);
386   return error;
387 }
388
389 VLIB_CLI_COMMAND (ip_session_redirect_command, static) = {
390   .path = "ip session redirect",
391   .function = ip_session_redirect_cmd,
392   .short_help = "ip session redirect [add] [punt] table <index> match <match> "
393                 "via <path> | del table <index> match <match>"
394 };
395
396 static fib_node_t *
397 ip_session_redirect_get_node (fib_node_index_t index)
398 {
399   ip_session_redirect_main_t *im = &ip_session_redirect_main;
400   ip_session_redirect_t *ipr = pool_elt_at_index (im->pool, index);
401   return &ipr->node;
402 }
403
404 static ip_session_redirect_t *
405 ip_session_redirect_get_from_node (fib_node_t *node)
406 {
407   return (
408     ip_session_redirect_t *) (((char *) node) -
409                               STRUCT_OFFSET_OF (ip_session_redirect_t, node));
410 }
411
412 static void
413 ip_session_redirect_last_lock_gone (fib_node_t *node)
414 {
415   /* the lifetime of the entry is managed by the table. */
416   ASSERT (0);
417 }
418
419 /* A back walk has reached this entry */
420 static fib_node_back_walk_rc_t
421 ip_session_redirect_back_walk_notify (fib_node_t *node,
422                                       fib_node_back_walk_ctx_t *ctx)
423 {
424   int rv;
425   ip_session_redirect_t *ipr = ip_session_redirect_get_from_node (node);
426   rv = ip_session_redirect_stack (ipr);
427   ASSERT (0 == rv);
428   if (rv)
429     clib_warning ("ip_session_redirect_stack() error %d", rv);
430   return FIB_NODE_BACK_WALK_CONTINUE;
431 }
432
433 static const fib_node_vft_t ip_session_redirect_vft = {
434   .fnv_get = ip_session_redirect_get_node,
435   .fnv_last_lock = ip_session_redirect_last_lock_gone,
436   .fnv_back_walk = ip_session_redirect_back_walk_notify,
437 };
438
439 static clib_error_t *
440 ip_session_redirect_init (vlib_main_t *vm)
441 {
442   ip_session_redirect_main_t *im = &ip_session_redirect_main;
443   im->session_by_match_and_table_index =
444     hash_create_vec (0, sizeof (u8), sizeof (u32));
445   im->fib_node_type = fib_node_register_new_type ("ip-session-redirect",
446                                                   &ip_session_redirect_vft);
447   return 0;
448 }
449
450 VLIB_INIT_FUNCTION (ip_session_redirect_init);
451
452 VLIB_PLUGIN_REGISTER () = {
453   .version = VPP_BUILD_VER,
454   .description = "IP session redirect",
455 };
456
457 /*
458  * fd.io coding-style-patch-verification: ON
459  *
460  * Local Variables:
461  * eval: (c-set-style "gnu")
462  * End:
463  */