L3 cross connect
[vpp.git] / src / plugins / l3xc / l3xc.c
1 /*
2  * Copyright (c) 2018 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 <plugins/l3xc/l3xc.h>
17
18 #include <vlib/vlib.h>
19 #include <vnet/plugin/plugin.h>
20 #include <vnet/fib/fib_path_list.h>
21
22 /**
23  * FIB node type the attachment is registered
24  */
25 fib_node_type_t l3xc_fib_node_type;
26
27 /**
28  * Pool of L3XC objects
29  */
30 l3xc_t *l3xc_pool;
31
32 /**
33  * DB of L3XC objects
34  */
35 static u32 *l3xc_db[FIB_PROTOCOL_IP_MAX];
36
37 index_t
38 l3xc_find (u32 sw_if_index, fib_protocol_t fproto)
39 {
40   if (vec_len (l3xc_db[fproto]) <= sw_if_index)
41     return ~0;
42
43   return (l3xc_db[fproto][sw_if_index]);
44 }
45
46 static void
47 l3xc_db_add (u32 sw_if_index, fib_protocol_t fproto, index_t l3xci)
48 {
49   vec_validate_init_empty (l3xc_db[fproto], sw_if_index, ~0);
50
51   l3xc_db[fproto][sw_if_index] = l3xci;
52 }
53
54 static void
55 l3xc_db_remove (u32 sw_if_index, fib_protocol_t fproto)
56 {
57   vec_validate_init_empty (l3xc_db[fproto], sw_if_index, ~0);
58
59   l3xc_db[fproto][sw_if_index] = ~0;
60 }
61
62 static void
63 l3xc_stack (l3xc_t * l3xc)
64 {
65   /*
66    * stack the DPO on the forwarding contributed by the path-list
67    */
68   dpo_id_t via_dpo = DPO_INVALID;
69
70   fib_path_list_contribute_forwarding (l3xc->l3xc_pl,
71                                        (FIB_PROTOCOL_IP4 == l3xc->l3xc_proto ?
72                                         FIB_FORW_CHAIN_TYPE_UNICAST_IP4 :
73                                         FIB_FORW_CHAIN_TYPE_UNICAST_IP6),
74                                        FIB_PATH_LIST_FWD_FLAG_NONE, &via_dpo);
75
76   dpo_stack_from_node ((FIB_PROTOCOL_IP4 == l3xc->l3xc_proto ?
77                         l3xc_ip4_node.index :
78                         l3xc_ip6_node.index), &l3xc->l3xc_dpo, &via_dpo);
79   dpo_reset (&via_dpo);
80 }
81
82 int
83 l3xc_update (u32 sw_if_index, u8 is_ip6, const fib_route_path_t * rpaths)
84 {
85   fib_protocol_t fproto;
86   l3xc_t *l3xc;
87   u32 l3xci;
88
89   fproto = (is_ip6 ? FIB_PROTOCOL_IP6 : FIB_PROTOCOL_IP4);
90
91   l3xci = l3xc_find (sw_if_index, fproto);
92
93   if (INDEX_INVALID == l3xci)
94     {
95       /*
96        * create a new x-connect
97        */
98       pool_get_aligned_zero (l3xc_pool, l3xc, CLIB_CACHE_LINE_BYTES);
99
100       l3xci = l3xc - l3xc_pool;
101       fib_node_init (&l3xc->l3xc_node, l3xc_fib_node_type);
102       l3xc->l3xc_sw_if_index = sw_if_index;
103       l3xc->l3xc_proto = fproto;
104
105       /*
106        * create and become a child of a path list so we get poked when
107        * the forwarding changes and stack on the DPO the path-list provides
108        */
109       l3xc->l3xc_pl = fib_path_list_create ((FIB_PATH_LIST_FLAG_SHARED |
110                                              FIB_PATH_LIST_FLAG_NO_URPF),
111                                             rpaths);
112       l3xc->l3xc_sibling = fib_path_list_child_add (l3xc->l3xc_pl,
113                                                     l3xc_fib_node_type,
114                                                     l3xci);
115       l3xc_stack (l3xc);
116
117       /*
118        * add this new policy to the DB and enable the feature on input interface
119        */
120       l3xc_db_add (sw_if_index, fproto, l3xci);
121
122       vnet_feature_enable_disable ((FIB_PROTOCOL_IP4 == fproto ?
123                                     "ip4-unicast" :
124                                     "ip6-unicast"),
125                                    (FIB_PROTOCOL_IP4 == fproto ?
126                                     "l3xc-input-ip4" :
127                                     "l3xc-input-ip6"),
128                                    l3xc->l3xc_sw_if_index,
129                                    1, &l3xci, sizeof (l3xci));
130     }
131   else
132     {
133       /*
134        * update an existing x-connect.
135        * - add the path to the path-list and swap our ancestry
136        */
137       l3xc = l3xc_get (l3xci);
138
139       if (FIB_NODE_INDEX_INVALID != l3xc->l3xc_pl)
140         {
141           fib_path_list_child_remove (l3xc->l3xc_pl, l3xc->l3xc_sibling);
142         }
143
144       l3xc->l3xc_pl = fib_path_list_create ((FIB_PATH_LIST_FLAG_SHARED |
145                                              FIB_PATH_LIST_FLAG_NO_URPF),
146                                             rpaths);
147
148       l3xc->l3xc_sibling = fib_path_list_child_add (l3xc->l3xc_pl,
149                                                     l3xc_fib_node_type,
150                                                     l3xci);
151     }
152   return (0);
153 }
154
155 int
156 l3xc_delete (u32 sw_if_index, u8 is_ip6)
157 {
158   fib_protocol_t fproto;
159   l3xc_t *l3xc;
160   u32 l3xci;
161
162   fproto = (is_ip6 ? FIB_PROTOCOL_IP6 : FIB_PROTOCOL_IP4);
163
164   l3xci = l3xc_find (sw_if_index, fproto);
165
166   if (INDEX_INVALID == l3xci)
167     {
168       /*
169        * no such policy
170        */
171       return (VNET_API_ERROR_INVALID_VALUE);
172     }
173   else
174     {
175       l3xc = l3xc_get (l3xci);
176
177       vnet_feature_enable_disable ((FIB_PROTOCOL_IP4 == fproto ?
178                                     "ip4-unicast" :
179                                     "ip6-unicast"),
180                                    (FIB_PROTOCOL_IP4 == fproto ?
181                                     "l3xc-input-ip4" :
182                                     "l3xc-input-ip6"),
183                                    l3xc->l3xc_sw_if_index,
184                                    0, &l3xci, sizeof (l3xci));
185
186       fib_path_list_child_remove (l3xc->l3xc_pl, l3xc->l3xc_sibling);
187
188       l3xc_db_remove (l3xc->l3xc_sw_if_index, fproto);
189       pool_put (l3xc_pool, l3xc);
190     }
191
192   return (0);
193 }
194
195 static clib_error_t *
196 l3xc_cmd (vlib_main_t * vm,
197           unformat_input_t * main_input, vlib_cli_command_t * cmd)
198 {
199   unformat_input_t _line_input, *line_input = &_line_input;
200   fib_route_path_t *rpaths = NULL, rpath;
201   u32 sw_if_index, is_del, is_ip6;
202   vnet_main_t *vnm;
203   int rv = 0;
204
205   is_ip6 = is_del = 0;
206   sw_if_index = ~0;
207   vnm = vnet_get_main ();
208
209   /* Get a line of input. */
210   if (!unformat_user (main_input, unformat_line_input, line_input))
211     return 0;
212
213   while (unformat_check_input (line_input) != UNFORMAT_END_OF_INPUT)
214     {
215       if (unformat
216           (line_input, "%U", unformat_vnet_sw_interface, vnm, &sw_if_index))
217         ;
218       else if (unformat (line_input, "ip6"))
219         is_ip6 = 1;
220       else if (unformat (line_input, "ip4"))
221         is_ip6 = 0;
222       else if (unformat (line_input, "del"))
223         is_del = 1;
224       else if (unformat (line_input, "add"))
225         is_del = 0;
226       else if (unformat (line_input, "via %U",
227                          unformat_fib_route_path, &rpath))
228         vec_add1 (rpaths, rpath);
229       else
230         return (clib_error_return (0, "unknown input '%U'",
231                                    format_unformat_error, line_input));
232     }
233
234   if (~0 == sw_if_index)
235     {
236       vlib_cli_output (vm, "Specify an input interface");
237       goto out;
238     }
239   if (vec_len (rpaths) == 0)
240     {
241       vlib_cli_output (vm, "Specify some paths");
242       goto out;
243     }
244
245   if (!is_del)
246     {
247       rv = l3xc_update (sw_if_index, is_ip6, rpaths);
248
249       if (rv)
250         {
251           vlib_cli_output (vm, "Failed: %d", rv);
252           goto out;
253         }
254     }
255   else
256     {
257       l3xc_delete (sw_if_index, is_ip6);
258     }
259
260 out:
261   unformat_free (line_input);
262   return (NULL);
263 }
264
265 /* *INDENT-OFF* */
266 /**
267  * Create an L3XC policy.
268  */
269 VLIB_CLI_COMMAND (l3xc_cmd_node, static) = {
270   .path = "l3xc",
271   .function = l3xc_cmd,
272   .short_help = "l3xc [add|del] <INTERFACE> via ...",
273   .is_mp_safe = 1,
274 };
275 /* *INDENT-ON* */
276
277 static u8 *
278 format_l3xc (u8 * s, va_list * args)
279 {
280   l3xc_t *l3xc = va_arg (*args, l3xc_t *);
281   vnet_main_t *vnm = vnet_get_main ();
282
283   s = format (s, "l3xc:[%d]: %U",
284               l3xc - l3xc_pool, format_vnet_sw_if_index_name,
285               vnm, l3xc->l3xc_sw_if_index);
286   s = format (s, "\n");
287   if (FIB_NODE_INDEX_INVALID == l3xc->l3xc_pl)
288     {
289       s = format (s, "no forwarding");
290     }
291   else
292     {
293       s = fib_path_list_format (l3xc->l3xc_pl, s);
294
295       s = format (s, "\n  %U", format_dpo_id, &l3xc->l3xc_dpo, 4);
296     }
297
298   return (s);
299 }
300
301 void
302 l3xc_walk (l3xc_walk_cb_t cb, void *ctx)
303 {
304   u32 l3xci;
305
306   /* *INDENT-OFF* */
307   pool_foreach_index(l3xci, l3xc_pool,
308   ({
309     if (!cb(l3xci, ctx))
310       break;
311   }));
312   /* *INDENT-ON* */
313 }
314
315 static clib_error_t *
316 l3xc_show_cmd (vlib_main_t * vm,
317                unformat_input_t * input, vlib_cli_command_t * cmd)
318 {
319   l3xc_t *l3xc;
320
321   /* *INDENT-OFF* */
322   pool_foreach(l3xc, l3xc_pool,
323   ({
324     vlib_cli_output(vm, "%U", format_l3xc, l3xc);
325   }));
326   /* *INDENT-ON* */
327
328   return (NULL);
329 }
330
331 /* *INDENT-OFF* */
332 VLIB_CLI_COMMAND (l3xc_show_cmd_node, static) = {
333   .path = "show l3xc",
334   .function = l3xc_show_cmd,
335   .short_help = "show l3xc",
336   .is_mp_safe = 1,
337 };
338 /* *INDENT-ON* */
339
340 static fib_node_t *
341 l3xc_get_node (fib_node_index_t index)
342 {
343   l3xc_t *l3xc = l3xc_get (index);
344   return (&(l3xc->l3xc_node));
345 }
346
347 static l3xc_t *
348 l3xc_get_from_node (fib_node_t * node)
349 {
350   return ((l3xc_t *) (((char *) node) -
351                       STRUCT_OFFSET_OF (l3xc_t, l3xc_node)));
352 }
353
354 static void
355 l3xc_last_lock_gone (fib_node_t * node)
356 {
357 }
358
359 /*
360  * A back walk has reached this L3XC policy
361  */
362 static fib_node_back_walk_rc_t
363 l3xc_back_walk_notify (fib_node_t * node, fib_node_back_walk_ctx_t * ctx)
364 {
365   l3xc_stack (l3xc_get_from_node (node));
366
367   return (FIB_NODE_BACK_WALK_CONTINUE);
368 }
369
370 /*
371  * The BIER fmask's graph node virtual function table
372  */
373 static const fib_node_vft_t l3xc_vft = {
374   .fnv_get = l3xc_get_node,
375   .fnv_last_lock = l3xc_last_lock_gone,
376   .fnv_back_walk = l3xc_back_walk_notify,
377 };
378
379 static clib_error_t *
380 l3xc_init (vlib_main_t * vm)
381 {
382   l3xc_fib_node_type = fib_node_register_new_type (&l3xc_vft);
383
384   return (NULL);
385 }
386
387 VLIB_INIT_FUNCTION (l3xc_init);
388
389 /*
390  * fd.io coding-style-patch-verification: ON
391  *
392  * Local Variables:
393  * eval: (c-set-style "gnu")
394  * End:
395  */