Line data Source code
1 : /*
2 : * Copyright (c) 2016 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 : * @brief
17 : * A Data-Path Object is an object that represents actions that are
18 : * applied to packets are they are switched through VPP.
19 : *
20 : * The DPO is a base class that is specialised by other objects to provide
21 : * concrete actions
22 : *
23 : * The VLIB graph nodes are graph of types, the DPO graph is a graph of instances.
24 : */
25 :
26 : // clang-format off
27 :
28 : #include <vnet/dpo/dpo.h>
29 : #include <vnet/ip/lookup.h>
30 : #include <vnet/ip/format.h>
31 : #include <vnet/adj/adj.h>
32 :
33 : #include <vnet/dpo/load_balance.h>
34 : #include <vnet/dpo/mpls_label_dpo.h>
35 : #include <vnet/dpo/lookup_dpo.h>
36 : #include <vnet/dpo/drop_dpo.h>
37 : #include <vnet/dpo/receive_dpo.h>
38 : #include <vnet/dpo/punt_dpo.h>
39 : #include <vnet/dpo/classify_dpo.h>
40 : #include <vnet/dpo/ip_null_dpo.h>
41 : #include <vnet/dpo/replicate_dpo.h>
42 : #include <vnet/dpo/interface_rx_dpo.h>
43 : #include <vnet/dpo/interface_tx_dpo.h>
44 : #include <vnet/dpo/mpls_disposition.h>
45 : #include <vnet/dpo/dvr_dpo.h>
46 : #include <vnet/dpo/l3_proxy_dpo.h>
47 : #include <vnet/dpo/ip6_ll_dpo.h>
48 : #include <vnet/dpo/pw_cw.h>
49 :
50 : /**
51 : * Array of char* names for the DPO types and protos
52 : */
53 : static const char* dpo_type_names[] = DPO_TYPES;
54 : static const char* dpo_proto_names[] = DPO_PROTOS;
55 :
56 : /**
57 : * @brief Vector of virtual function tables for the DPO types
58 : *
59 : * This is a vector so we can dynamically register new DPO types in plugins.
60 : */
61 : static dpo_vft_t *dpo_vfts;
62 :
63 : /**
64 : * @brief vector of graph node names associated with each DPO type and protocol.
65 : *
66 : * dpo_nodes[child_type][child_proto][node_X] = node_name;
67 : * i.e.
68 : * dpo_node[DPO_LOAD_BALANCE][DPO_PROTO_IP4][0] = "ip4-lookup"
69 : * dpo_node[DPO_LOAD_BALANCE][DPO_PROTO_IP4][1] = "ip4-load-balance"
70 : *
71 : * This is a vector so we can dynamically register new DPO types in plugins.
72 : */
73 : static const char* const * const ** dpo_nodes;
74 :
75 : /**
76 : * @brief Vector of edge indicies from parent DPO nodes to child
77 : *
78 : * dpo_edges[child_type][child_proto][parent_type][parent_proto] = edge_index
79 : *
80 : * This array is derived at init time from the dpo_nodes above. Note that
81 : * the third dimension in dpo_nodes is lost, hence, the edge index from each
82 : * node MUST be the same.
83 : * Including both the child and parent protocol is required to support the
84 : * case where it changes as the graph is traversed, most notably when an
85 : * MPLS label is popped.
86 : *
87 : * Note that this array is child type specific, not child instance specific.
88 : */
89 : static u32 ****dpo_edges;
90 :
91 : /**
92 : * @brief The DPO type value that can be assigned to the next dynamic
93 : * type registration.
94 : */
95 : static dpo_type_t dpo_dynamic = DPO_LAST;
96 :
97 : dpo_proto_t
98 54112 : vnet_link_to_dpo_proto (vnet_link_t linkt)
99 : {
100 54112 : switch (linkt)
101 : {
102 20441 : case VNET_LINK_IP6:
103 20441 : return (DPO_PROTO_IP6);
104 32553 : case VNET_LINK_IP4:
105 32553 : return (DPO_PROTO_IP4);
106 1006 : case VNET_LINK_MPLS:
107 1006 : return (DPO_PROTO_MPLS);
108 112 : case VNET_LINK_ETHERNET:
109 112 : return (DPO_PROTO_ETHERNET);
110 0 : case VNET_LINK_NSH:
111 0 : return (DPO_PROTO_NSH);
112 0 : case VNET_LINK_ARP:
113 0 : break;
114 : }
115 0 : ASSERT(0);
116 0 : return (0);
117 : }
118 :
119 : vnet_link_t
120 52177 : dpo_proto_to_link (dpo_proto_t dp)
121 : {
122 52177 : switch (dp)
123 : {
124 20174 : case DPO_PROTO_IP6:
125 20174 : return (VNET_LINK_IP6);
126 32003 : case DPO_PROTO_IP4:
127 32003 : return (VNET_LINK_IP4);
128 0 : case DPO_PROTO_MPLS:
129 : case DPO_PROTO_BIER:
130 0 : return (VNET_LINK_MPLS);
131 0 : case DPO_PROTO_ETHERNET:
132 0 : return (VNET_LINK_ETHERNET);
133 0 : case DPO_PROTO_NSH:
134 0 : return (VNET_LINK_NSH);
135 : }
136 0 : return (~0);
137 : }
138 :
139 : u8 *
140 10781 : format_dpo_type (u8 * s, va_list * args)
141 : {
142 10781 : dpo_type_t type = va_arg (*args, int);
143 :
144 10781 : s = format(s, "%s", dpo_type_names[type]);
145 :
146 10781 : return (s);
147 : }
148 :
149 : u8 *
150 70853 : format_dpo_id (u8 * s, va_list * args)
151 : {
152 70853 : dpo_id_t *dpo = va_arg (*args, dpo_id_t*);
153 70853 : u32 indent = va_arg (*args, u32);
154 :
155 70853 : s = format(s, "[@%d]: ", dpo->dpoi_next_node);
156 :
157 70853 : if (NULL != dpo_vfts[dpo->dpoi_type].dv_format)
158 : {
159 70853 : s = format(s, "%U",
160 70853 : dpo_vfts[dpo->dpoi_type].dv_format,
161 : dpo->dpoi_index,
162 : indent);
163 : }
164 : else
165 : {
166 0 : switch (dpo->dpoi_type)
167 : {
168 0 : case DPO_FIRST:
169 0 : s = format(s, "unset");
170 0 : break;
171 0 : default:
172 0 : s = format(s, "unknown");
173 0 : break;
174 : }
175 : }
176 70853 : return (s);
177 : }
178 :
179 : u8 *
180 24525 : format_dpo_proto (u8 * s, va_list * args)
181 : {
182 24525 : dpo_proto_t proto = va_arg (*args, int);
183 :
184 24525 : return (format(s, "%s", dpo_proto_names[proto]));
185 : }
186 :
187 : void
188 1280090 : dpo_set (dpo_id_t *dpo,
189 : dpo_type_t type,
190 : dpo_proto_t proto,
191 : index_t index)
192 : {
193 1280090 : dpo_id_t tmp = *dpo;
194 :
195 1280090 : dpo->dpoi_type = type;
196 1280090 : dpo->dpoi_proto = proto,
197 1280090 : dpo->dpoi_index = index;
198 :
199 1280090 : if (DPO_ADJACENCY == type)
200 : {
201 : /*
202 : * set the adj subtype
203 : */
204 : ip_adjacency_t *adj;
205 :
206 21648 : adj = adj_get(index);
207 :
208 21648 : switch (adj->lookup_next_index)
209 : {
210 5910 : case IP_LOOKUP_NEXT_ARP:
211 5910 : dpo->dpoi_type = DPO_ADJACENCY_INCOMPLETE;
212 5910 : break;
213 1492 : case IP_LOOKUP_NEXT_MIDCHAIN:
214 1492 : dpo->dpoi_type = DPO_ADJACENCY_MIDCHAIN;
215 1492 : break;
216 0 : case IP_LOOKUP_NEXT_MCAST_MIDCHAIN:
217 0 : dpo->dpoi_type = DPO_ADJACENCY_MCAST_MIDCHAIN;
218 0 : break;
219 3078 : case IP_LOOKUP_NEXT_MCAST:
220 3078 : dpo->dpoi_type = DPO_ADJACENCY_MCAST;
221 3078 : break;
222 0 : case IP_LOOKUP_NEXT_GLEAN:
223 0 : dpo->dpoi_type = DPO_ADJACENCY_GLEAN;
224 0 : break;
225 11168 : default:
226 11168 : break;
227 : }
228 1258440 : }
229 1280090 : dpo_lock(dpo);
230 1280090 : dpo_unlock(&tmp);
231 1280090 : }
232 :
233 : void
234 1867540 : dpo_reset (dpo_id_t *dpo)
235 : {
236 1867540 : dpo_id_t tmp = DPO_INVALID;
237 :
238 : /*
239 : * use the atomic copy operation.
240 : */
241 1867540 : dpo_copy(dpo, &tmp);
242 1867540 : }
243 :
244 : /**
245 : * \brief
246 : * Compare two Data-path objects
247 : *
248 : * like memcmp, return 0 is matching, !0 otherwise.
249 : */
250 : int
251 55 : dpo_cmp (const dpo_id_t *dpo1,
252 : const dpo_id_t *dpo2)
253 : {
254 : int res;
255 :
256 55 : res = dpo1->dpoi_type - dpo2->dpoi_type;
257 :
258 55 : if (0 != res) return (res);
259 :
260 49 : return (dpo1->dpoi_index - dpo2->dpoi_index);
261 : }
262 :
263 : void
264 4703140 : dpo_copy (dpo_id_t *dst,
265 : const dpo_id_t *src)
266 : {
267 4703140 : dpo_id_t tmp = {
268 4703140 : .as_u64 = dst->as_u64
269 : };
270 :
271 : /*
272 : * the destination is written in a single u64 write - hence atomically w.r.t
273 : * any packets inflight.
274 : */
275 4703140 : dst->as_u64 = src->as_u64;
276 :
277 4703140 : dpo_lock(dst);
278 4703140 : dpo_unlock(&tmp);
279 4703140 : }
280 :
281 : int
282 174895 : dpo_is_adj (const dpo_id_t *dpo)
283 : {
284 321631 : return ((dpo->dpoi_type == DPO_ADJACENCY) ||
285 146736 : (dpo->dpoi_type == DPO_ADJACENCY_INCOMPLETE) ||
286 130262 : (dpo->dpoi_type == DPO_ADJACENCY_GLEAN) ||
287 20477 : (dpo->dpoi_type == DPO_ADJACENCY_MCAST) ||
288 20477 : (dpo->dpoi_type == DPO_ADJACENCY_MCAST_MIDCHAIN) ||
289 336877 : (dpo->dpoi_type == DPO_ADJACENCY_MIDCHAIN) ||
290 15246 : (dpo->dpoi_type == DPO_ADJACENCY_GLEAN));
291 : }
292 :
293 : static u32 *
294 1066190 : dpo_default_get_next_node (const dpo_id_t *dpo)
295 : {
296 1066190 : u32 *node_indices = NULL;
297 : const char *node_name;
298 1066190 : u32 ii = 0;
299 :
300 1066190 : node_name = dpo_nodes[dpo->dpoi_type][dpo->dpoi_proto][ii];
301 2132380 : while (NULL != node_name)
302 : {
303 : vlib_node_t *node;
304 :
305 1066190 : node = vlib_get_node_by_name(vlib_get_main(), (u8*) node_name);
306 1066190 : ASSERT(NULL != node);
307 1066190 : vec_add1(node_indices, node->index);
308 :
309 1066190 : ++ii;
310 1066190 : node_name = dpo_nodes[dpo->dpoi_type][dpo->dpoi_proto][ii];
311 : }
312 :
313 1066190 : return (node_indices);
314 : }
315 :
316 : /**
317 : * A default variant of the make interpose function that just returns
318 : * the original
319 : */
320 : static void
321 0 : dpo_default_mk_interpose (const dpo_id_t *original,
322 : const dpo_id_t *parent,
323 : dpo_id_t *clone)
324 : {
325 0 : dpo_copy(clone, original);
326 0 : }
327 :
328 : void
329 45279 : dpo_register (dpo_type_t type,
330 : const dpo_vft_t *vft,
331 : const char * const * const * nodes)
332 : {
333 45279 : vec_validate(dpo_vfts, type);
334 45279 : dpo_vfts[type] = *vft;
335 45279 : if (NULL == dpo_vfts[type].dv_get_next_node)
336 : {
337 44720 : dpo_vfts[type].dv_get_next_node = dpo_default_get_next_node;
338 : }
339 45279 : if (NULL == dpo_vfts[type].dv_mk_interpose)
340 : {
341 41925 : dpo_vfts[type].dv_mk_interpose = dpo_default_mk_interpose;
342 : }
343 :
344 45279 : vec_validate(dpo_nodes, type);
345 45279 : dpo_nodes[type] = nodes;
346 45279 : }
347 :
348 : dpo_type_t
349 29068 : dpo_register_new_type (const dpo_vft_t *vft,
350 : const char * const * const * nodes)
351 : {
352 29068 : dpo_type_t type = dpo_dynamic++;
353 :
354 29068 : dpo_register(type, vft, nodes);
355 :
356 29068 : return (type);
357 : }
358 :
359 : void
360 44 : dpo_mk_interpose (const dpo_id_t *original,
361 : const dpo_id_t *parent,
362 : dpo_id_t *clone)
363 : {
364 44 : if (!dpo_id_is_valid(original))
365 0 : return;
366 :
367 44 : dpo_vfts[original->dpoi_type].dv_mk_interpose(original, parent, clone);
368 : }
369 :
370 : void
371 5983220 : dpo_lock (dpo_id_t *dpo)
372 : {
373 5983220 : if (!dpo_id_is_valid(dpo))
374 1878980 : return;
375 :
376 4104250 : dpo_vfts[dpo->dpoi_type].dv_lock(dpo);
377 : }
378 :
379 : void
380 6000840 : dpo_unlock (dpo_id_t *dpo)
381 : {
382 6000840 : if (!dpo_id_is_valid(dpo))
383 3005700 : return;
384 :
385 2995140 : dpo_vfts[dpo->dpoi_type].dv_unlock(dpo);
386 : }
387 :
388 : u32
389 24492 : dpo_get_urpf(const dpo_id_t *dpo)
390 : {
391 24492 : if (dpo_id_is_valid(dpo) &&
392 24492 : (NULL != dpo_vfts[dpo->dpoi_type].dv_get_urpf))
393 : {
394 16 : return (dpo_vfts[dpo->dpoi_type].dv_get_urpf(dpo));
395 : }
396 :
397 24476 : return (~0);
398 : }
399 :
400 : u16
401 14 : dpo_get_mtu(const dpo_id_t *dpo)
402 : {
403 14 : if (dpo_id_is_valid(dpo) &&
404 14 : (NULL != dpo_vfts[dpo->dpoi_type].dv_get_mtu))
405 : {
406 14 : return (dpo_vfts[dpo->dpoi_type].dv_get_mtu(dpo));
407 : }
408 :
409 0 : return (0xffff);
410 : }
411 :
412 : static u32
413 206626 : dpo_get_next_node (dpo_type_t child_type,
414 : dpo_proto_t child_proto,
415 : const dpo_id_t *parent_dpo)
416 : {
417 : dpo_proto_t parent_proto;
418 : dpo_type_t parent_type;
419 :
420 206626 : parent_type = parent_dpo->dpoi_type;
421 206626 : parent_proto = parent_dpo->dpoi_proto;
422 :
423 206626 : vec_validate(dpo_edges, child_type);
424 206626 : vec_validate(dpo_edges[child_type], child_proto);
425 206626 : vec_validate(dpo_edges[child_type][child_proto], parent_type);
426 221915 : vec_validate_init_empty(
427 : dpo_edges[child_type][child_proto][parent_type],
428 : parent_proto, ~0);
429 :
430 : /*
431 : * if the edge index has not yet been created for this node to node transition
432 : */
433 206626 : if (~0 == dpo_edges[child_type][child_proto][parent_type][parent_proto])
434 : {
435 : vlib_node_t *child_node;
436 : u32 *parent_indices;
437 : vlib_main_t *vm;
438 : u32 edge, *pi, cc;
439 :
440 9007 : vm = vlib_get_main();
441 :
442 9007 : ASSERT(NULL != dpo_vfts[parent_type].dv_get_next_node);
443 9007 : ASSERT(NULL != dpo_nodes[child_type]);
444 9007 : ASSERT(NULL != dpo_nodes[child_type][child_proto]);
445 :
446 9007 : cc = 0;
447 9007 : parent_indices = dpo_vfts[parent_type].dv_get_next_node(parent_dpo);
448 :
449 9007 : vlib_worker_thread_barrier_sync(vm);
450 :
451 : /*
452 : * create a graph arc from each of the child's registered node types,
453 : * to each of the parent's.
454 : */
455 18014 : while (NULL != dpo_nodes[child_type][child_proto][cc])
456 : {
457 : child_node =
458 9007 : vlib_get_node_by_name(vm,
459 9007 : (u8*) dpo_nodes[child_type][child_proto][cc]);
460 :
461 18014 : vec_foreach(pi, parent_indices)
462 : {
463 9007 : edge = vlib_node_add_next(vm, child_node->index, *pi);
464 :
465 9007 : if (~0 == dpo_edges[child_type][child_proto][parent_type][parent_proto])
466 : {
467 9007 : dpo_edges[child_type][child_proto][parent_type][parent_proto] = edge;
468 : }
469 : else
470 : {
471 0 : ASSERT(dpo_edges[child_type][child_proto][parent_type][parent_proto] == edge);
472 : }
473 : }
474 9007 : cc++;
475 : }
476 :
477 9007 : vlib_worker_thread_barrier_release(vm);
478 9007 : vec_free(parent_indices);
479 : }
480 :
481 206626 : return (dpo_edges[child_type][child_proto][parent_type][parent_proto]);
482 : }
483 :
484 : /**
485 : * @brief return already stacked up next node index for a given
486 : * child_type/child_proto and parent_type/patent_proto.
487 : * The VLIB graph arc used is taken from the parent and child types
488 : * passed.
489 : */
490 : u32
491 0 : dpo_get_next_node_by_type_and_proto (dpo_type_t child_type,
492 : dpo_proto_t child_proto,
493 : dpo_type_t parent_type,
494 : dpo_proto_t parent_proto)
495 : {
496 0 : return (dpo_edges[child_type][child_proto][parent_type][parent_proto]);
497 : }
498 :
499 : /**
500 : * @brief Stack one DPO object on another, and thus establish a child parent
501 : * relationship. The VLIB graph arc used is taken from the parent and child types
502 : * passed.
503 : */
504 : static void
505 1263820 : dpo_stack_i (u32 edge,
506 : dpo_id_t *dpo,
507 : const dpo_id_t *parent)
508 : {
509 : /*
510 : * in order to get an atomic update of the parent we create a temporary,
511 : * from a copy of the child, and add the next_node. then we copy to the parent
512 : */
513 1263820 : dpo_id_t tmp = DPO_INVALID;
514 1263820 : dpo_copy(&tmp, parent);
515 :
516 : /*
517 : * get the edge index for the parent to child VLIB graph transition
518 : */
519 1263820 : tmp.dpoi_next_node = edge;
520 :
521 : /*
522 : * this update is atomic.
523 : */
524 1263820 : dpo_copy(dpo, &tmp);
525 :
526 1263820 : dpo_reset(&tmp);
527 1263820 : }
528 :
529 : /**
530 : * @brief Stack one DPO object on another, and thus establish a child-parent
531 : * relationship. The VLIB graph arc used is taken from the parent and child types
532 : * passed.
533 : */
534 : void
535 206626 : dpo_stack (dpo_type_t child_type,
536 : dpo_proto_t child_proto,
537 : dpo_id_t *dpo,
538 : const dpo_id_t *parent)
539 : {
540 206626 : dpo_stack_i(dpo_get_next_node(child_type, child_proto, parent), dpo, parent);
541 206626 : }
542 :
543 : /**
544 : * @brief Stack one DPO object on another, and thus establish a child parent
545 : * relationship. A new VLIB graph arc is created from the child node passed
546 : * to the nodes registered by the parent. The VLIB infra will ensure this arc
547 : * is added only once.
548 : */
549 : void
550 1057190 : dpo_stack_from_node (u32 child_node_index,
551 : dpo_id_t *dpo,
552 : const dpo_id_t *parent)
553 : {
554 : dpo_type_t parent_type;
555 : u32 *parent_indices;
556 : vlib_main_t *vm;
557 : u32 edge, *pi;
558 :
559 1057190 : edge = 0;
560 1057190 : parent_type = parent->dpoi_type;
561 1057190 : vm = vlib_get_main();
562 :
563 1057190 : ASSERT(NULL != dpo_vfts[parent_type].dv_get_next_node);
564 1057190 : parent_indices = dpo_vfts[parent_type].dv_get_next_node(parent);
565 1057190 : ASSERT(parent_indices);
566 :
567 : /*
568 : * This loop is purposefully written with the worker thread lock in the
569 : * inner loop because;
570 : * 1) the likelihood that the edge does not exist is smaller
571 : * 2) the likelihood there is more than one node is even smaller
572 : * so we are optimising for not need to take the lock
573 : */
574 2114380 : vec_foreach(pi, parent_indices)
575 : {
576 1057190 : edge = vlib_node_get_next(vm, child_node_index, *pi);
577 :
578 1057190 : if (~0 == edge)
579 : {
580 574 : vlib_worker_thread_barrier_sync(vm);
581 :
582 574 : edge = vlib_node_add_next(vm, child_node_index, *pi);
583 :
584 574 : vlib_worker_thread_barrier_release(vm);
585 : }
586 : }
587 1057190 : dpo_stack_i(edge, dpo, parent);
588 :
589 : /* should free this local vector to avoid memory leak */
590 1057190 : vec_free(parent_indices);
591 1057190 : }
592 :
593 : static clib_error_t *
594 559 : dpo_module_init (vlib_main_t * vm)
595 : {
596 559 : drop_dpo_module_init();
597 559 : punt_dpo_module_init();
598 559 : receive_dpo_module_init();
599 559 : load_balance_module_init();
600 559 : mpls_label_dpo_module_init();
601 559 : classify_dpo_module_init();
602 559 : lookup_dpo_module_init();
603 559 : ip_null_dpo_module_init();
604 559 : ip6_ll_dpo_module_init();
605 559 : replicate_module_init();
606 559 : interface_rx_dpo_module_init();
607 559 : interface_tx_dpo_module_init();
608 559 : mpls_disp_dpo_module_init();
609 559 : dvr_dpo_module_init();
610 559 : l3_proxy_dpo_module_init();
611 559 : pw_cw_dpo_module_init();
612 :
613 559 : return (NULL);
614 : }
615 :
616 : /* *INDENT-OFF* */
617 13439 : VLIB_INIT_FUNCTION(dpo_module_init) =
618 : {
619 : .runs_before = VLIB_INITS ("ip_main_init"),
620 : };
621 : /* *INDENT-ON* */
622 :
623 : static clib_error_t *
624 0 : dpo_memory_show (vlib_main_t * vm,
625 : unformat_input_t * input,
626 : vlib_cli_command_t * cmd)
627 : {
628 : dpo_vft_t *vft;
629 :
630 0 : vlib_cli_output (vm, "DPO memory");
631 0 : vlib_cli_output (vm, "%=30s %=5s %=8s/%=9s totals",
632 : "Name","Size", "in-use", "allocated");
633 :
634 0 : vec_foreach(vft, dpo_vfts)
635 : {
636 0 : if (NULL != vft->dv_mem_show)
637 0 : vft->dv_mem_show();
638 : }
639 :
640 0 : return (NULL);
641 : }
642 :
643 : /* *INDENT-OFF* */
644 : /*?
645 : * The '<em>sh dpo memory </em>' command displays the memory usage for each
646 : * data-plane object type.
647 : *
648 : * @cliexpar
649 : * @cliexstart{show dpo memory}
650 : * DPO memory
651 : * Name Size in-use /allocated totals
652 : * load-balance 64 12 / 12 768/768
653 : * Adjacency 256 1 / 1 256/256
654 : * Receive 24 5 / 5 120/120
655 : * Lookup 12 0 / 0 0/0
656 : * Classify 12 0 / 0 0/0
657 : * MPLS label 24 0 / 0 0/0
658 : * @cliexend
659 : ?*/
660 272887 : VLIB_CLI_COMMAND (show_fib_memory, static) = {
661 : .path = "show dpo memory",
662 : .function = dpo_memory_show,
663 : .short_help = "show dpo memory",
664 : };
665 : /* *INDENT-ON* */
666 :
667 : // clang-format on
|