Line data Source code
1 : /*
2 : * Copyright (c) 2015 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 : #include <vnet/l2/l2_xcrw.h>
16 :
17 : /**
18 : * @file
19 : * General L2 / L3 cross-connect, used to set up
20 : * "L2 interface <--> your-favorite-tunnel-encap" tunnels.
21 : *
22 : * We set up a typical L2 cross-connect or (future) bridge
23 : * to hook L2 interface(s) up to the L3 stack in arbitrary ways.
24 : *
25 : * Each l2_xcrw adjacency specifies 3 things:
26 : *
27 : * 1. The next graph node (presumably in the L3 stack) to
28 : * process the (L2 -> L3) packet
29 : *
30 : * 2. A new value for vnet_buffer(b)->sw_if_index[VLIB_TX]
31 : * (i.e. a lookup FIB index),
32 : *
33 : * 3. A rewrite string to apply.
34 : *
35 : * Example: to cross-connect an L2 interface or (future) bridge
36 : * to an mpls-o-gre tunnel, set up the L2 rewrite string as shown in
37 : * mpls_gre_rewrite, and use "mpls-post-rewrite" to fix the
38 : * GRE IP header checksum and length fields.
39 : */
40 :
41 : typedef struct
42 : {
43 : u32 next_index;
44 : u32 tx_fib_index;
45 : } l2_xcrw_trace_t;
46 :
47 : /* packet trace format function */
48 : static u8 *
49 0 : format_l2_xcrw_trace (u8 * s, va_list * args)
50 : {
51 0 : CLIB_UNUSED (vlib_main_t * vm) = va_arg (*args, vlib_main_t *);
52 0 : CLIB_UNUSED (vlib_node_t * node) = va_arg (*args, vlib_node_t *);
53 0 : l2_xcrw_trace_t *t = va_arg (*args, l2_xcrw_trace_t *);
54 :
55 0 : s = format (s, "L2_XCRW: next index %d tx_fib_index %d",
56 : t->next_index, t->tx_fib_index);
57 0 : return s;
58 : }
59 :
60 : extern l2_xcrw_main_t l2_xcrw_main;
61 :
62 : #ifndef CLIB_MARCH_VARIANT
63 : l2_xcrw_main_t l2_xcrw_main;
64 : #endif /* CLIB_MARCH_VARIANT */
65 :
66 : static char *l2_xcrw_error_strings[] = {
67 : #define _(sym,string) string,
68 : foreach_l2_xcrw_error
69 : #undef _
70 : };
71 :
72 2236 : VLIB_NODE_FN (l2_xcrw_node) (vlib_main_t * vm,
73 : vlib_node_runtime_t * node, vlib_frame_t * frame)
74 : {
75 : u32 n_left_from, *from, *to_next;
76 : l2_xcrw_next_t next_index;
77 0 : l2_xcrw_main_t *xcm = &l2_xcrw_main;
78 0 : vlib_node_t *n = vlib_get_node (vm, l2_xcrw_node.index);
79 0 : u32 node_counter_base_index = n->error_heap_index;
80 0 : vlib_error_main_t *em = &vm->error_main;
81 :
82 0 : from = vlib_frame_vector_args (frame);
83 0 : n_left_from = frame->n_vectors;
84 0 : next_index = node->cached_next_index;
85 :
86 0 : while (n_left_from > 0)
87 : {
88 : u32 n_left_to_next;
89 :
90 0 : vlib_get_next_frame (vm, node, next_index, to_next, n_left_to_next);
91 :
92 0 : while (n_left_from >= 4 && n_left_to_next >= 2)
93 : {
94 : u32 bi0, bi1;
95 : vlib_buffer_t *b0, *b1;
96 : u32 next0, next1;
97 : u32 sw_if_index0, sw_if_index1;
98 : l2_xcrw_adjacency_t *adj0, *adj1;
99 :
100 : /* Prefetch next iteration. */
101 : {
102 : vlib_buffer_t *p2, *p3;
103 :
104 0 : p2 = vlib_get_buffer (vm, from[2]);
105 0 : p3 = vlib_get_buffer (vm, from[3]);
106 :
107 0 : vlib_prefetch_buffer_header (p2, LOAD);
108 0 : vlib_prefetch_buffer_header (p3, LOAD);
109 :
110 0 : clib_prefetch_store (p2->data);
111 0 : clib_prefetch_store (p3->data);
112 : }
113 :
114 : /* speculatively enqueue b0 and b1 to the current next frame */
115 0 : to_next[0] = bi0 = from[0];
116 0 : to_next[1] = bi1 = from[1];
117 0 : from += 2;
118 0 : to_next += 2;
119 0 : n_left_from -= 2;
120 0 : n_left_to_next -= 2;
121 :
122 0 : b0 = vlib_get_buffer (vm, bi0);
123 0 : b1 = vlib_get_buffer (vm, bi1);
124 :
125 0 : sw_if_index0 = vnet_buffer (b0)->sw_if_index[VLIB_RX];
126 0 : sw_if_index1 = vnet_buffer (b1)->sw_if_index[VLIB_RX];
127 :
128 0 : adj0 = vec_elt_at_index (xcm->adj_by_sw_if_index, sw_if_index0);
129 0 : adj1 = vec_elt_at_index (xcm->adj_by_sw_if_index, sw_if_index1);
130 :
131 0 : next0 = adj0->rewrite_header.next_index;
132 0 : vnet_buffer (b0)->sw_if_index[VLIB_TX] =
133 0 : adj0->rewrite_header.sw_if_index;
134 :
135 0 : next1 = adj1->rewrite_header.next_index;
136 0 : vnet_buffer (b1)->sw_if_index[VLIB_TX] =
137 0 : adj1->rewrite_header.sw_if_index;
138 :
139 0 : em->counters[node_counter_base_index + next1]++;
140 :
141 0 : if (PREDICT_TRUE (next0 > 0))
142 : {
143 0 : u8 *h0 = vlib_buffer_get_current (b0);
144 0 : vnet_rewrite_one_header (adj0[0], h0,
145 : adj0->rewrite_header.data_bytes);
146 0 : vlib_buffer_advance (b0, -adj0->rewrite_header.data_bytes);
147 0 : em->counters[node_counter_base_index + L2_XCRW_ERROR_FWD]++;
148 : }
149 :
150 0 : if (PREDICT_TRUE (next1 > 0))
151 : {
152 0 : u8 *h1 = vlib_buffer_get_current (b1);
153 0 : vnet_rewrite_one_header (adj1[0], h1,
154 : adj1->rewrite_header.data_bytes);
155 0 : vlib_buffer_advance (b1, -adj1->rewrite_header.data_bytes);
156 0 : em->counters[node_counter_base_index + L2_XCRW_ERROR_FWD]++;
157 : }
158 :
159 :
160 0 : if (PREDICT_FALSE ((node->flags & VLIB_NODE_FLAG_TRACE)))
161 : {
162 0 : if (PREDICT_FALSE ((node->flags & VLIB_NODE_FLAG_TRACE)
163 : && (b0->flags & VLIB_BUFFER_IS_TRACED)))
164 : {
165 : l2_xcrw_trace_t *t =
166 0 : vlib_add_trace (vm, node, b0, sizeof (*t));
167 0 : t->next_index = next0;
168 0 : t->tx_fib_index = adj0->rewrite_header.sw_if_index;
169 : }
170 0 : if (PREDICT_FALSE ((node->flags & VLIB_NODE_FLAG_TRACE)
171 : && (b1->flags & VLIB_BUFFER_IS_TRACED)))
172 : {
173 : l2_xcrw_trace_t *t =
174 0 : vlib_add_trace (vm, node, b1, sizeof (*t));
175 0 : t->next_index = next1;
176 0 : t->tx_fib_index = adj1->rewrite_header.sw_if_index;
177 : }
178 : }
179 :
180 : /* verify speculative enqueues, maybe switch current next frame */
181 0 : vlib_validate_buffer_enqueue_x2 (vm, node, next_index,
182 : to_next, n_left_to_next,
183 : bi0, bi1, next0, next1);
184 : }
185 :
186 0 : while (n_left_from > 0 && n_left_to_next > 0)
187 : {
188 : u32 bi0;
189 : vlib_buffer_t *b0;
190 : u32 next0;
191 : u32 sw_if_index0;
192 : l2_xcrw_adjacency_t *adj0;
193 :
194 : /* speculatively enqueue b0 to the current next frame */
195 0 : bi0 = from[0];
196 0 : to_next[0] = bi0;
197 0 : from += 1;
198 0 : to_next += 1;
199 0 : n_left_from -= 1;
200 0 : n_left_to_next -= 1;
201 :
202 0 : b0 = vlib_get_buffer (vm, bi0);
203 :
204 0 : sw_if_index0 = vnet_buffer (b0)->sw_if_index[VLIB_RX];
205 :
206 0 : adj0 = vec_elt_at_index (xcm->adj_by_sw_if_index, sw_if_index0);
207 :
208 0 : next0 = adj0->rewrite_header.next_index;
209 0 : vnet_buffer (b0)->sw_if_index[VLIB_TX] =
210 0 : adj0->rewrite_header.sw_if_index;
211 :
212 0 : if (PREDICT_TRUE (next0 > 0))
213 : {
214 0 : u8 *h0 = vlib_buffer_get_current (b0);
215 0 : vnet_rewrite_one_header (adj0[0], h0,
216 : adj0->rewrite_header.data_bytes);
217 0 : vlib_buffer_advance (b0, -adj0->rewrite_header.data_bytes);
218 0 : em->counters[node_counter_base_index + L2_XCRW_ERROR_FWD]++;
219 : }
220 :
221 0 : if (PREDICT_FALSE ((node->flags & VLIB_NODE_FLAG_TRACE)
222 : && (b0->flags & VLIB_BUFFER_IS_TRACED)))
223 : {
224 0 : l2_xcrw_trace_t *t = vlib_add_trace (vm, node, b0, sizeof (*t));
225 0 : t->next_index = next0;
226 0 : t->tx_fib_index = adj0->rewrite_header.sw_if_index;
227 : }
228 :
229 : /* verify speculative enqueue, maybe switch current next frame */
230 0 : vlib_validate_buffer_enqueue_x1 (vm, node, next_index,
231 : to_next, n_left_to_next,
232 : bi0, next0);
233 : }
234 :
235 0 : vlib_put_next_frame (vm, node, next_index, n_left_to_next);
236 : }
237 :
238 0 : return frame->n_vectors;
239 : }
240 :
241 : /* *INDENT-OFF* */
242 178120 : VLIB_REGISTER_NODE (l2_xcrw_node) = {
243 : .name = "l2-xcrw",
244 : .vector_size = sizeof (u32),
245 : .format_trace = format_l2_xcrw_trace,
246 : .type = VLIB_NODE_TYPE_INTERNAL,
247 :
248 : .n_errors = ARRAY_LEN(l2_xcrw_error_strings),
249 : .error_strings = l2_xcrw_error_strings,
250 :
251 : .n_next_nodes = L2_XCRW_N_NEXT,
252 :
253 : /* edit / add dispositions here */
254 : .next_nodes = {
255 : [L2_XCRW_NEXT_DROP] = "error-drop",
256 : },
257 : };
258 : /* *INDENT-ON* */
259 :
260 : #ifndef CLIB_MARCH_VARIANT
261 : clib_error_t *
262 559 : l2_xcrw_init (vlib_main_t * vm)
263 : {
264 559 : l2_xcrw_main_t *mp = &l2_xcrw_main;
265 :
266 559 : mp->vlib_main = vm;
267 559 : mp->vnet_main = &vnet_main;
268 559 : mp->tunnel_index_by_l2_sw_if_index = hash_create (0, sizeof (uword));
269 :
270 559 : return 0;
271 : }
272 :
273 25199 : VLIB_INIT_FUNCTION (l2_xcrw_init);
274 :
275 : static u8 *
276 0 : format_xcrw_name (u8 * s, va_list * args)
277 : {
278 0 : u32 dev_instance = va_arg (*args, u32);
279 0 : return format (s, "xcrw%d", dev_instance);
280 : }
281 :
282 : /* *INDENT-OFF* */
283 11199 : VNET_DEVICE_CLASS (xcrw_device_class,static) = {
284 : .name = "Xcrw",
285 : .format_device_name = format_xcrw_name,
286 : };
287 : /* *INDENT-ON* */
288 :
289 : /* Create a sham tunnel interface and return its sw_if_index */
290 : static u32
291 0 : create_xcrw_interface (vlib_main_t * vm)
292 : {
293 0 : vnet_main_t *vnm = vnet_get_main ();
294 0 : vnet_eth_interface_registration_t eir = {};
295 : static u32 instance;
296 : u8 address[6];
297 : u32 hw_if_index;
298 : vnet_hw_interface_t *hi;
299 : u32 sw_if_index;
300 :
301 : /* mac address doesn't really matter */
302 0 : clib_memset (address, 0, sizeof (address));
303 0 : address[2] = 0x12;
304 :
305 0 : eir.dev_class_index = xcrw_device_class.index;
306 0 : eir.dev_instance = instance++, eir.address = address;
307 0 : hw_if_index = vnet_eth_register_interface (vnm, &eir);
308 :
309 0 : hi = vnet_get_hw_interface (vnm, hw_if_index);
310 0 : sw_if_index = hi->sw_if_index;
311 0 : vnet_sw_interface_set_flags (vnm, sw_if_index,
312 : VNET_SW_INTERFACE_FLAG_ADMIN_UP);
313 :
314 : /* Output to the sham tunnel invokes the encap node */
315 0 : hi->output_node_index = l2_xcrw_node.index;
316 :
317 0 : return sw_if_index;
318 : }
319 :
320 : int
321 0 : vnet_configure_l2_xcrw (vlib_main_t * vm, vnet_main_t * vnm,
322 : u32 l2_sw_if_index, u32 tx_fib_index,
323 : u8 * rewrite, u32 next_node_index, int is_add)
324 : {
325 0 : l2_xcrw_main_t *xcm = &l2_xcrw_main;
326 : l2_xcrw_adjacency_t *a;
327 : l2_xcrw_tunnel_t *t;
328 : uword *p;
329 :
330 0 : if (is_add)
331 : {
332 :
333 0 : pool_get (xcm->tunnels, t);
334 :
335 : /* No interface allocated? Do it. Otherwise, set admin up */
336 0 : if (t->tunnel_sw_if_index == 0)
337 0 : t->tunnel_sw_if_index = create_xcrw_interface (vm);
338 : else
339 0 : vnet_sw_interface_set_flags (vnm, t->tunnel_sw_if_index,
340 : VNET_SW_INTERFACE_FLAG_ADMIN_UP);
341 :
342 0 : t->l2_sw_if_index = l2_sw_if_index;
343 :
344 0 : vec_validate (xcm->adj_by_sw_if_index, t->l2_sw_if_index);
345 :
346 0 : a = vec_elt_at_index (xcm->adj_by_sw_if_index, t->l2_sw_if_index);
347 0 : clib_memset (a, 0, sizeof (*a));
348 :
349 0 : a->rewrite_header.sw_if_index = tx_fib_index;
350 :
351 : /*
352 : * Add or find a dynamic disposition for the successor node,
353 : * e.g. so we can ship pkts to mpls_post_rewrite...
354 : */
355 0 : a->rewrite_header.next_index =
356 0 : vlib_node_add_next (vm, l2_xcrw_node.index, next_node_index);
357 :
358 0 : if (vec_len (rewrite))
359 0 : vnet_rewrite_set_data (a[0], rewrite, vec_len (rewrite));
360 :
361 0 : set_int_l2_mode (vm, vnm, MODE_L2_XC, t->l2_sw_if_index, 0,
362 0 : L2_BD_PORT_TYPE_NORMAL, 0, t->tunnel_sw_if_index);
363 0 : hash_set (xcm->tunnel_index_by_l2_sw_if_index,
364 : t->l2_sw_if_index, t - xcm->tunnels);
365 0 : return 0;
366 : }
367 : else
368 : {
369 0 : p = hash_get (xcm->tunnel_index_by_l2_sw_if_index, l2_sw_if_index);
370 0 : if (p == 0)
371 0 : return VNET_API_ERROR_INVALID_SW_IF_INDEX;
372 :
373 0 : t = pool_elt_at_index (xcm->tunnels, p[0]);
374 :
375 0 : a = vec_elt_at_index (xcm->adj_by_sw_if_index, t->l2_sw_if_index);
376 : /* Reset adj to drop traffic */
377 0 : clib_memset (a, 0, sizeof (*a));
378 :
379 0 : set_int_l2_mode (vm, vnm, MODE_L3, t->l2_sw_if_index, 0,
380 : L2_BD_PORT_TYPE_NORMAL, 0, 0);
381 :
382 0 : vnet_sw_interface_set_flags (vnm, t->tunnel_sw_if_index, 0 /* down */ );
383 :
384 0 : hash_unset (xcm->tunnel_index_by_l2_sw_if_index, l2_sw_if_index);
385 0 : pool_put (xcm->tunnels, t);
386 : }
387 0 : return 0;
388 : }
389 :
390 :
391 : static clib_error_t *
392 0 : set_l2_xcrw_command_fn (vlib_main_t * vm,
393 : unformat_input_t * input, vlib_cli_command_t * cmd)
394 : {
395 0 : unformat_input_t _line_input, *line_input = &_line_input;
396 0 : int is_add = 1;
397 0 : int is_ipv6 = 0; /* for fib id -> fib index mapping */
398 0 : u32 tx_fib_id = ~0;
399 0 : u32 tx_fib_index = ~0;
400 0 : u32 next_node_index = ~0;
401 : u32 l2_sw_if_index;
402 0 : u8 *rw = 0;
403 0 : vnet_main_t *vnm = vnet_get_main ();
404 : int rv;
405 0 : clib_error_t *error = NULL;
406 :
407 :
408 0 : if (!unformat_user (input, unformat_line_input, line_input))
409 0 : return 0;
410 :
411 0 : if (!unformat (line_input, "%U",
412 : unformat_vnet_sw_interface, vnm, &l2_sw_if_index))
413 : {
414 0 : error = clib_error_return (0, "unknown input '%U'",
415 : format_unformat_error, line_input);
416 0 : goto done;
417 : }
418 :
419 0 : while (unformat_check_input (line_input) != UNFORMAT_END_OF_INPUT)
420 : {
421 0 : if (unformat (line_input, "next %U",
422 : unformat_vlib_node, vm, &next_node_index))
423 : ;
424 0 : else if (unformat (line_input, "tx-fib-id %d", &tx_fib_id))
425 : ;
426 0 : else if (unformat (line_input, "del"))
427 0 : is_add = 0;
428 0 : else if (unformat (line_input, "ipv6"))
429 0 : is_ipv6 = 1;
430 0 : else if (unformat (line_input, "rw %U", unformat_hex_string, &rw));
431 : else
432 0 : break;
433 : }
434 :
435 0 : if (next_node_index == ~0)
436 : {
437 0 : error = clib_error_return (0, "next node not specified");
438 0 : goto done;
439 : }
440 :
441 0 : if (tx_fib_id != ~0)
442 : {
443 : uword *p;
444 :
445 0 : if (is_ipv6)
446 0 : p = hash_get (ip6_main.fib_index_by_table_id, tx_fib_id);
447 : else
448 0 : p = hash_get (ip4_main.fib_index_by_table_id, tx_fib_id);
449 :
450 0 : if (p == 0)
451 : {
452 : error =
453 0 : clib_error_return (0, "nonexistent tx_fib_id %d", tx_fib_id);
454 0 : goto done;
455 : }
456 :
457 0 : tx_fib_index = p[0];
458 : }
459 :
460 0 : rv = vnet_configure_l2_xcrw (vm, vnm, l2_sw_if_index, tx_fib_index,
461 : rw, next_node_index, is_add);
462 :
463 0 : switch (rv)
464 : {
465 :
466 0 : case 0:
467 0 : break;
468 :
469 0 : case VNET_API_ERROR_INVALID_SW_IF_INDEX:
470 0 : error = clib_error_return (0, "%U not cross-connected",
471 : format_vnet_sw_if_index_name,
472 : vnm, l2_sw_if_index);
473 0 : goto done;
474 :
475 0 : default:
476 0 : error = clib_error_return (0, "vnet_configure_l2_xcrw returned %d", rv);
477 0 : goto done;
478 : }
479 :
480 0 : done:
481 0 : vec_free (rw);
482 0 : unformat_free (line_input);
483 :
484 0 : return error;
485 : }
486 :
487 : /*?
488 : * Add or delete a Layer 2 to Layer 3 rewrite cross-connect. This is
489 : * used to hook Layer 2 interface(s) up to the Layer 3 stack in
490 : * arbitrary ways. For example, cross-connect an L2 interface or
491 : * (future) bridge to an mpls-o-gre tunnel. Set up the L2 rewrite
492 : * string as shown in mpls_gre_rewrite, and use \"mpls-post-rewrite\"
493 : * to fix the GRE IP header checksum and length fields.
494 : *
495 : * @cliexpar
496 : * @todo This is incomplete. This needs a detailed description and a
497 : * practical example.
498 : ?*/
499 : /* *INDENT-OFF* */
500 272887 : VLIB_CLI_COMMAND (set_l2_xcrw_command, static) = {
501 : .path = "set interface l2 xcrw",
502 : .short_help =
503 : "set interface l2 xcrw <interface> next <node-name>\n"
504 : " [del] [tx-fib-id <id>] [ipv6] rw <hex-bytes>",
505 : .function = set_l2_xcrw_command_fn,
506 : };
507 : /* *INDENT-ON* */
508 :
509 : #endif /* CLIB_MARCH_VARIANT */
510 :
511 : static u8 *
512 0 : format_l2xcrw (u8 * s, va_list * args)
513 : {
514 0 : vnet_main_t *vnm = va_arg (*args, vnet_main_t *);
515 0 : l2_xcrw_tunnel_t *t = va_arg (*args, l2_xcrw_tunnel_t *);
516 0 : l2_xcrw_main_t *xcm = &l2_xcrw_main;
517 0 : vlib_main_t *vm = vlib_get_main ();
518 : l2_xcrw_adjacency_t *a;
519 : u8 *rewrite_string;
520 :
521 0 : if (t == 0)
522 : {
523 0 : s = format (s, "%-25s%s", "L2 interface", "Tunnel Details");
524 0 : return s;
525 : }
526 :
527 0 : s = format (s, "%-25U %U ",
528 : format_vnet_sw_if_index_name, vnm, t->l2_sw_if_index,
529 : format_vnet_sw_if_index_name, vnm, t->tunnel_sw_if_index);
530 :
531 0 : a = vec_elt_at_index (xcm->adj_by_sw_if_index, t->l2_sw_if_index);
532 :
533 0 : s = format (s, "next %U ",
534 : format_vlib_next_node_name, vm, l2_xcrw_node.index,
535 0 : a->rewrite_header.next_index);
536 :
537 0 : if (a->rewrite_header.sw_if_index != ~0)
538 0 : s = format (s, "tx fib index %d ", a->rewrite_header.sw_if_index);
539 :
540 0 : if (a->rewrite_header.data_bytes)
541 : {
542 0 : rewrite_string = (u8 *) (a + 1);
543 0 : rewrite_string -= a->rewrite_header.data_bytes;
544 0 : s = format (s, "rewrite data: %U ",
545 : format_hex_bytes, rewrite_string,
546 0 : a->rewrite_header.data_bytes);
547 : }
548 :
549 0 : s = format (s, "\n");
550 :
551 0 : return s;
552 : }
553 :
554 :
555 : static clib_error_t *
556 0 : show_l2xcrw_command_fn (vlib_main_t * vm,
557 : unformat_input_t * input, vlib_cli_command_t * cmd)
558 : {
559 0 : vnet_main_t *vnm = vnet_get_main ();
560 0 : l2_xcrw_main_t *xcm = &l2_xcrw_main;
561 : l2_xcrw_tunnel_t *t;
562 :
563 0 : if (pool_elts (xcm->tunnels) == 0)
564 : {
565 0 : vlib_cli_output (vm, "No L2 / L3 rewrite cross-connects configured");
566 0 : return 0;
567 : }
568 :
569 0 : vlib_cli_output (vm, "%U", format_l2xcrw, 0, 0);
570 :
571 : /* *INDENT-OFF* */
572 0 : pool_foreach (t, xcm->tunnels)
573 : {
574 0 : vlib_cli_output (vm, "%U", format_l2xcrw, vnm, t);
575 : }
576 : /* *INDENT-ON* */
577 :
578 0 : return 0;
579 : }
580 :
581 : /*?
582 : * Display a Layer 2 to Layer 3 rewrite cross-connect. This is used to
583 : * hook Layer 2 interface(s) up to the Layer 3 stack in arbitrary ways.
584 : *
585 : * @todo This is incomplete. This needs a detailed description and a
586 : * practical example.
587 : ?*/
588 : /* *INDENT-OFF* */
589 272887 : VLIB_CLI_COMMAND (show_l2xcrw_command, static) = {
590 : .path = "show l2xcrw",
591 : .short_help = "show l2xcrw",
592 : .function = show_l2xcrw_command_fn,
593 : };
594 : /* *INDENT-ON* */
595 :
596 : /*
597 : * fd.io coding-style-patch-verification: ON
598 : *
599 : * Local Variables:
600 : * eval: (c-set-style "gnu")
601 : * End:
602 : */
|