1 // SPDX-License-Identifier: GPL-2.0-or-later
3 * BGP Label Pool - Manage label chunk allocations from zebra asynchronously
5 * Copyright (C) 2018 LabN Consulting, L.L.C.
17 #include "workqueue.h"
21 #include "bgpd/bgpd.h"
22 #include "bgpd/bgp_labelpool.h"
23 #include "bgpd/bgp_debug.h"
24 #include "bgpd/bgp_errors.h"
25 #include "bgpd/bgp_route.h"
26 #include "bgpd/bgp_zebra.h"
27 #include "bgpd/bgp_vty.h"
28 #include "bgpd/bgp_rd.h"
30 #define BGP_LABELPOOL_ENABLE_TESTS 0
32 #include "bgpd/bgp_labelpool_clippy.c"
36 * Definitions and external declarations.
38 extern struct zclient
*zclient
;
40 #if BGP_LABELPOOL_ENABLE_TESTS
41 static void lptest_init(void);
42 static void lptest_finish(void);
46 * Remember where pool data are kept
48 static struct labelpool
*lp
;
51 * Number of labels requested at a time from the zebra label manager.
52 * We start small but double the request size each time up to a
55 * The label space is 20 bits which is shared with other FRR processes
56 * on this host, so to avoid greedily requesting a mostly wasted chunk,
57 * we limit the chunk size to 1/16 of the label space (that's the -4 bits
58 * in the definition below). This limit slightly increases our cost of
59 * finding free labels in our allocated chunks.
61 #define LP_CHUNK_SIZE_MIN 128
62 #define LP_CHUNK_SIZE_MAX (1 << (20 - 4))
64 DEFINE_MTYPE_STATIC(BGPD
, BGP_LABEL_CHUNK
, "BGP Label Chunk");
65 DEFINE_MTYPE_STATIC(BGPD
, BGP_LABEL_FIFO
, "BGP Label FIFO item");
66 DEFINE_MTYPE_STATIC(BGPD
, BGP_LABEL_CB
, "BGP Dynamic Label Assignment");
67 DEFINE_MTYPE_STATIC(BGPD
, BGP_LABEL_CBQ
, "BGP Dynamic Label Callback");
72 uint32_t nfree
; /* un-allocated count */
73 uint32_t idx_last_allocated
; /* start looking here */
74 bitfield_t allocated_map
;
81 mpls_label_t label
; /* MPLS_LABEL_NONE = not allocated */
83 void *labelid
; /* unique ID */
85 * callback for label allocation and loss
87 * allocated: false = lost
89 int (*cbfunc
)(mpls_label_t label
, void *lblid
, bool alloc
);
93 struct lp_fifo_item fifo
;
97 DECLARE_LIST(lp_fifo
, struct lp_fifo
, fifo
);
100 int (*cbfunc
)(mpls_label_t label
, void *lblid
, bool alloc
);
104 bool allocated
; /* false = lost */
107 static wq_item_status
lp_cbq_docallback(struct work_queue
*wq
, void *data
)
109 struct lp_cbq_item
*lcbq
= data
;
111 int debug
= BGP_DEBUG(labelpool
, LABELPOOL
);
114 zlog_debug("%s: calling callback with labelid=%p label=%u allocated=%d",
115 __func__
, lcbq
->labelid
, lcbq
->label
, lcbq
->allocated
);
117 if (lcbq
->label
== MPLS_LABEL_NONE
) {
118 /* shouldn't happen */
119 flog_err(EC_BGP_LABEL
, "%s: error: label==MPLS_LABEL_NONE",
124 rc
= (*(lcbq
->cbfunc
))(lcbq
->label
, lcbq
->labelid
, lcbq
->allocated
);
126 if (lcbq
->allocated
&& rc
) {
128 * Callback rejected allocation. This situation could arise
129 * if there was a label request followed by the requestor
130 * deciding it didn't need the assignment (e.g., config
131 * change) while the reply to the original request (with
132 * label) was in the work queue.
135 zlog_debug("%s: callback rejected allocation, releasing labelid=%p label=%u",
136 __func__
, lcbq
->labelid
, lcbq
->label
);
138 uintptr_t lbl
= lcbq
->label
;
143 * If the rejected label was marked inuse by this labelid,
144 * release the label back to the pool.
146 * Further, if the rejected label was still assigned to
147 * this labelid in the LCB, delete the LCB.
149 if (!skiplist_search(lp
->inuse
, (void *)lbl
, &labelid
)) {
150 if (labelid
== lcbq
->labelid
) {
151 if (!skiplist_search(lp
->ledger
, labelid
,
153 if (lcbq
->label
== lcb
->label
)
154 skiplist_delete(lp
->ledger
,
157 skiplist_delete(lp
->inuse
, (void *)lbl
, NULL
);
165 static void lp_cbq_item_free(struct work_queue
*wq
, void *data
)
167 XFREE(MTYPE_BGP_LABEL_CBQ
, data
);
170 static void lp_lcb_free(void *goner
)
172 XFREE(MTYPE_BGP_LABEL_CB
, goner
);
175 static void lp_chunk_free(void *goner
)
177 struct lp_chunk
*chunk
= (struct lp_chunk
*)goner
;
179 bf_free(chunk
->allocated_map
);
180 XFREE(MTYPE_BGP_LABEL_CHUNK
, goner
);
183 void bgp_lp_init(struct event_loop
*master
, struct labelpool
*pool
)
185 if (BGP_DEBUG(labelpool
, LABELPOOL
))
186 zlog_debug("%s: entry", __func__
);
188 lp
= pool
; /* Set module pointer to pool data */
190 lp
->ledger
= skiplist_new(0, NULL
, lp_lcb_free
);
191 lp
->inuse
= skiplist_new(0, NULL
, NULL
);
192 lp
->chunks
= list_new();
193 lp
->chunks
->del
= lp_chunk_free
;
194 lp_fifo_init(&lp
->requests
);
195 lp
->callback_q
= work_queue_new(master
, "label callbacks");
197 lp
->callback_q
->spec
.workfunc
= lp_cbq_docallback
;
198 lp
->callback_q
->spec
.del_item_data
= lp_cbq_item_free
;
199 lp
->callback_q
->spec
.max_retries
= 0;
201 lp
->next_chunksize
= LP_CHUNK_SIZE_MIN
;
203 #if BGP_LABELPOOL_ENABLE_TESTS
208 /* check if a label callback was for a BGP LU node, and if so, unlock it */
209 static void check_bgp_lu_cb_unlock(struct lp_lcb
*lcb
)
211 if (lcb
->type
== LP_TYPE_BGP_LU
)
212 bgp_dest_unlock_node(lcb
->labelid
);
215 /* check if a label callback was for a BGP LU node, and if so, lock it */
216 static void check_bgp_lu_cb_lock(struct lp_lcb
*lcb
)
218 if (lcb
->type
== LP_TYPE_BGP_LU
)
219 bgp_dest_lock_node(lcb
->labelid
);
222 void bgp_lp_finish(void)
225 struct work_queue_item
*item
, *titem
;
227 #if BGP_LABELPOOL_ENABLE_TESTS
233 skiplist_free(lp
->ledger
);
236 skiplist_free(lp
->inuse
);
239 list_delete(&lp
->chunks
);
241 while ((lf
= lp_fifo_pop(&lp
->requests
))) {
242 check_bgp_lu_cb_unlock(&lf
->lcb
);
243 XFREE(MTYPE_BGP_LABEL_FIFO
, lf
);
245 lp_fifo_fini(&lp
->requests
);
247 /* we must unlock path infos for LU callbacks; but we cannot do that
248 * in the deletion callback of the workqueue, as that is also called
249 * to remove an element from the queue after it has been run, resulting
250 * in a double unlock. Hence we need to iterate over our queues and
251 * lists and manually perform the unlocking (ugh)
253 STAILQ_FOREACH_SAFE (item
, &lp
->callback_q
->items
, wq
, titem
)
254 check_bgp_lu_cb_unlock(item
->data
);
256 work_queue_free_and_null(&lp
->callback_q
);
261 static mpls_label_t
get_label_from_pool(void *labelid
)
263 struct listnode
*node
;
264 struct lp_chunk
*chunk
;
265 int debug
= BGP_DEBUG(labelpool
, LABELPOOL
);
270 for (ALL_LIST_ELEMENTS_RO(lp
->chunks
, node
, chunk
)) {
275 zlog_debug("%s: chunk first=%u last=%u",
276 __func__
, chunk
->first
, chunk
->last
);
279 * don't look in chunks with no available labels
285 * roll through bitfield starting where we stopped
288 index
= bf_find_next_clear_bit_wrap(
289 &chunk
->allocated_map
, chunk
->idx_last_allocated
+ 1,
293 * since chunk->nfree is non-zero, we should always get
296 assert(index
!= WORD_MAX
);
298 lbl
= chunk
->first
+ index
;
299 if (skiplist_insert(lp
->inuse
, (void *)lbl
, labelid
)) {
300 /* something is very wrong */
301 zlog_err("%s: unable to insert inuse label %u (id %p)",
302 __func__
, (uint32_t)lbl
, labelid
);
303 return MPLS_LABEL_NONE
;
309 bf_set_bit(chunk
->allocated_map
, index
);
310 chunk
->idx_last_allocated
= index
;
316 return MPLS_LABEL_NONE
;
320 * Success indicated by value of "label" field in returned LCB
322 static struct lp_lcb
*lcb_alloc(
325 int (*cbfunc
)(mpls_label_t label
, void *labelid
, bool allocated
))
328 * Set up label control block
330 struct lp_lcb
*new = XCALLOC(MTYPE_BGP_LABEL_CB
,
331 sizeof(struct lp_lcb
));
333 new->label
= get_label_from_pool(labelid
);
335 new->labelid
= labelid
;
336 new->cbfunc
= cbfunc
;
342 * Callers who need labels must supply a type, labelid, and callback.
343 * The type is a value defined in bgp_labelpool.h (add types as needed).
344 * The callback is for asynchronous notification of label allocation.
345 * The labelid is passed as an argument to the callback. It should be unique
346 * to the requested label instance.
348 * If zebra is not connected, callbacks with labels will be delayed
349 * until connection is established. If zebra connection is lost after
350 * labels have been assigned, existing assignments via this labelpool
351 * module will continue until reconnection.
353 * When connection to zebra is reestablished, previous label assignments
354 * will be invalidated (via callbacks having the "allocated" parameter unset)
355 * and new labels will be automatically reassigned by this labelpool module
356 * (that is, a requestor does not need to call bgp_lp_get() again if it is
357 * notified via callback that its label has been lost: it will eventually
358 * get another callback with a new label assignment).
360 * The callback function should return 0 to accept the allocation
361 * and non-zero to refuse it. The callback function return value is
362 * ignored for invalidations (i.e., when the "allocated" parameter is false)
364 * Prior requests for a given labelid are detected so that requests and
365 * assignments are not duplicated.
370 int (*cbfunc
)(mpls_label_t label
, void *labelid
, bool allocated
))
374 int debug
= BGP_DEBUG(labelpool
, LABELPOOL
);
377 zlog_debug("%s: labelid=%p", __func__
, labelid
);
380 * Have we seen this request before?
382 if (!skiplist_search(lp
->ledger
, labelid
, (void **)&lcb
)) {
385 lcb
= lcb_alloc(type
, labelid
, cbfunc
);
387 zlog_debug("%s: inserting lcb=%p label=%u",
388 __func__
, lcb
, lcb
->label
);
389 int rc
= skiplist_insert(lp
->ledger
, labelid
, lcb
);
392 /* shouldn't happen */
393 flog_err(EC_BGP_LABEL
,
394 "%s: can't insert new LCB into ledger list",
396 XFREE(MTYPE_BGP_LABEL_CB
, lcb
);
401 if (lcb
->label
!= MPLS_LABEL_NONE
) {
403 * Fast path: we filled the request from local pool (or
404 * this is a duplicate request that we filled already).
405 * Enqueue response work item with new label.
407 struct lp_cbq_item
*q
;
409 q
= XCALLOC(MTYPE_BGP_LABEL_CBQ
, sizeof(struct lp_cbq_item
));
411 q
->cbfunc
= lcb
->cbfunc
;
413 q
->label
= lcb
->label
;
414 q
->labelid
= lcb
->labelid
;
417 /* if this is a LU request, lock node before queueing */
418 check_bgp_lu_cb_lock(lcb
);
420 work_queue_add(lp
->callback_q
, q
);
429 zlog_debug("%s: slow path. lcb=%p label=%u",
430 __func__
, lcb
, lcb
->label
);
433 * Slow path: we are out of labels in the local pool,
434 * so remember the request and also get another chunk from
437 * We track number of outstanding label requests: don't
438 * need to get a chunk for each one.
441 struct lp_fifo
*lf
= XCALLOC(MTYPE_BGP_LABEL_FIFO
,
442 sizeof(struct lp_fifo
));
445 /* if this is a LU request, lock node before queueing */
446 check_bgp_lu_cb_lock(lcb
);
448 lp_fifo_add_tail(&lp
->requests
, lf
);
450 if (lp_fifo_count(&lp
->requests
) > lp
->pending_count
) {
451 if (!zclient
|| zclient
->sock
< 0)
453 if (zclient_send_get_label_chunk(zclient
, 0, lp
->next_chunksize
,
454 MPLS_LABEL_BASE_ANY
) !=
455 ZCLIENT_SEND_FAILURE
) {
456 lp
->pending_count
+= lp
->next_chunksize
;
457 if ((lp
->next_chunksize
<< 1) <= LP_CHUNK_SIZE_MAX
)
458 lp
->next_chunksize
<<= 1;
470 if (!skiplist_search(lp
->ledger
, labelid
, (void **)&lcb
)) {
471 if (label
== lcb
->label
&& type
== lcb
->type
) {
472 struct listnode
*node
;
473 struct lp_chunk
*chunk
;
474 uintptr_t lbl
= label
;
475 bool deallocated
= false;
477 /* no longer in use */
478 skiplist_delete(lp
->inuse
, (void *)lbl
, NULL
);
480 /* no longer requested */
481 skiplist_delete(lp
->ledger
, labelid
, NULL
);
484 * Find the chunk this label belongs to and
485 * deallocate the label
487 for (ALL_LIST_ELEMENTS_RO(lp
->chunks
, node
, chunk
)) {
490 if ((label
< chunk
->first
) ||
491 (label
> chunk
->last
))
494 index
= label
- chunk
->first
;
495 assert(bf_test_index(chunk
->allocated_map
,
497 bf_release_index(chunk
->allocated_map
, index
);
507 * zebra response giving us a chunk of labels
509 void bgp_lp_event_chunk(uint8_t keep
, uint32_t first
, uint32_t last
)
511 struct lp_chunk
*chunk
;
512 int debug
= BGP_DEBUG(labelpool
, LABELPOOL
);
517 flog_err(EC_BGP_LABEL
,
518 "%s: zebra label chunk invalid: first=%u, last=%u",
519 __func__
, first
, last
);
523 chunk
= XCALLOC(MTYPE_BGP_LABEL_CHUNK
, sizeof(struct lp_chunk
));
525 labelcount
= last
- first
+ 1;
527 chunk
->first
= first
;
529 chunk
->nfree
= labelcount
;
530 bf_init(chunk
->allocated_map
, labelcount
);
533 * Optimize for allocation by adding the new (presumably larger)
534 * chunk at the head of the list so it is examined first.
536 listnode_add_head(lp
->chunks
, chunk
);
538 lp
->pending_count
-= labelcount
;
541 zlog_debug("%s: %zu pending requests", __func__
,
542 lp_fifo_count(&lp
->requests
));
545 while (labelcount
&& (lf
= lp_fifo_first(&lp
->requests
))) {
548 void *labelid
= lf
->lcb
.labelid
;
550 if (skiplist_search(lp
->ledger
, labelid
, (void **)&lcb
)) {
551 /* request no longer in effect */
554 zlog_debug("%s: labelid %p: request no longer in effect",
557 /* if this was a BGP_LU request, unlock node
559 check_bgp_lu_cb_unlock(lcb
);
560 goto finishedrequest
;
564 if (lcb
->label
!= MPLS_LABEL_NONE
) {
565 /* request already has a label */
567 zlog_debug("%s: labelid %p: request already has a label: %u=0x%x, lcb=%p",
569 lcb
->label
, lcb
->label
, lcb
);
571 /* if this was a BGP_LU request, unlock node
573 check_bgp_lu_cb_unlock(lcb
);
575 goto finishedrequest
;
578 lcb
->label
= get_label_from_pool(lcb
->labelid
);
580 if (lcb
->label
== MPLS_LABEL_NONE
) {
582 * Out of labels in local pool, await next chunk
585 zlog_debug("%s: out of labels, await more",
594 * we filled the request from local pool.
595 * Enqueue response work item with new label.
597 struct lp_cbq_item
*q
= XCALLOC(MTYPE_BGP_LABEL_CBQ
,
598 sizeof(struct lp_cbq_item
));
600 q
->cbfunc
= lcb
->cbfunc
;
602 q
->label
= lcb
->label
;
603 q
->labelid
= lcb
->labelid
;
607 zlog_debug("%s: assigning label %u to labelid %p",
608 __func__
, q
->label
, q
->labelid
);
610 work_queue_add(lp
->callback_q
, q
);
613 lp_fifo_del(&lp
->requests
, lf
);
614 XFREE(MTYPE_BGP_LABEL_FIFO
, lf
);
619 * continue using allocated labels until zebra returns
621 void bgp_lp_event_zebra_down(void)
627 * Inform owners of previously-allocated labels that their labels
628 * are not valid. Request chunk from zebra large enough to satisfy
629 * previously-allocated labels plus any outstanding requests.
631 void bgp_lp_event_zebra_up(void)
633 unsigned int labels_needed
;
634 unsigned int chunks_needed
;
639 lp
->reconnect_count
++;
641 * Get label chunk allocation request dispatched to zebra
643 labels_needed
= lp_fifo_count(&lp
->requests
) +
644 skiplist_count(lp
->inuse
);
646 if (labels_needed
> lp
->next_chunksize
) {
647 while ((lp
->next_chunksize
< labels_needed
) &&
648 (lp
->next_chunksize
<< 1 <= LP_CHUNK_SIZE_MAX
))
650 lp
->next_chunksize
<<= 1;
654 chunks_needed
= (labels_needed
/ lp
->next_chunksize
) + 1;
655 labels_needed
= chunks_needed
* lp
->next_chunksize
;
657 lm_init_ok
= lm_label_manager_connect(zclient
, 1) == 0;
660 zlog_err("%s: label manager connection error", __func__
);
664 zclient_send_get_label_chunk(zclient
, 0, labels_needed
,
665 MPLS_LABEL_BASE_ANY
);
666 lp
->pending_count
= labels_needed
;
669 * Invalidate current list of chunks
671 list_delete_all_node(lp
->chunks
);
674 * Invalidate any existing labels and requeue them as requests
676 while (!skiplist_first(lp
->inuse
, NULL
, &labelid
)) {
681 if (!skiplist_search(lp
->ledger
, labelid
, (void **)&lcb
)) {
683 if (lcb
->label
!= MPLS_LABEL_NONE
) {
687 struct lp_cbq_item
*q
;
689 q
= XCALLOC(MTYPE_BGP_LABEL_CBQ
,
690 sizeof(struct lp_cbq_item
));
691 q
->cbfunc
= lcb
->cbfunc
;
693 q
->label
= lcb
->label
;
694 q
->labelid
= lcb
->labelid
;
695 q
->allocated
= false;
696 check_bgp_lu_cb_lock(lcb
);
697 work_queue_add(lp
->callback_q
, q
);
699 lcb
->label
= MPLS_LABEL_NONE
;
705 struct lp_fifo
*lf
= XCALLOC(MTYPE_BGP_LABEL_FIFO
,
706 sizeof(struct lp_fifo
));
709 check_bgp_lu_cb_lock(lcb
);
710 lp_fifo_add_tail(&lp
->requests
, lf
);
713 skiplist_delete_first(lp
->inuse
);
717 DEFUN(show_bgp_labelpool_summary
, show_bgp_labelpool_summary_cmd
,
718 "show bgp labelpool summary [json]",
720 "BGP Labelpool information\n"
721 "BGP Labelpool summary\n" JSON_STR
)
723 bool uj
= use_json(argc
, argv
);
724 json_object
*json
= NULL
;
728 vty_out(vty
, "{}\n");
730 vty_out(vty
, "No existing BGP labelpool\n");
731 return (CMD_WARNING
);
735 json
= json_object_new_object();
736 json_object_int_add(json
, "ledger", skiplist_count(lp
->ledger
));
737 json_object_int_add(json
, "inUse", skiplist_count(lp
->inuse
));
738 json_object_int_add(json
, "requests",
739 lp_fifo_count(&lp
->requests
));
740 json_object_int_add(json
, "labelChunks", listcount(lp
->chunks
));
741 json_object_int_add(json
, "pending", lp
->pending_count
);
742 json_object_int_add(json
, "reconnects", lp
->reconnect_count
);
745 vty_out(vty
, "Labelpool Summary\n");
746 vty_out(vty
, "-----------------\n");
747 vty_out(vty
, "%-13s %d\n",
748 "Ledger:", skiplist_count(lp
->ledger
));
749 vty_out(vty
, "%-13s %d\n", "InUse:", skiplist_count(lp
->inuse
));
750 vty_out(vty
, "%-13s %zu\n",
751 "Requests:", lp_fifo_count(&lp
->requests
));
752 vty_out(vty
, "%-13s %d\n",
753 "LabelChunks:", listcount(lp
->chunks
));
754 vty_out(vty
, "%-13s %d\n", "Pending:", lp
->pending_count
);
755 vty_out(vty
, "%-13s %d\n", "Reconnects:", lp
->reconnect_count
);
760 DEFUN(show_bgp_labelpool_ledger
, show_bgp_labelpool_ledger_cmd
,
761 "show bgp labelpool ledger [json]",
763 "BGP Labelpool information\n"
764 "BGP Labelpool ledger\n" JSON_STR
)
766 bool uj
= use_json(argc
, argv
);
767 json_object
*json
= NULL
, *json_elem
= NULL
;
768 struct lp_lcb
*lcb
= NULL
;
769 struct bgp_dest
*dest
;
771 const struct prefix
*p
;
776 vty_out(vty
, "{}\n");
778 vty_out(vty
, "No existing BGP labelpool\n");
779 return (CMD_WARNING
);
783 count
= skiplist_count(lp
->ledger
);
785 vty_out(vty
, "{}\n");
788 json
= json_object_new_array();
790 vty_out(vty
, "Prefix Label\n");
791 vty_out(vty
, "---------------------------\n");
794 for (rc
= skiplist_next(lp
->ledger
, (void **)&dest
, (void **)&lcb
,
796 !rc
; rc
= skiplist_next(lp
->ledger
, (void **)&dest
, (void **)&lcb
,
799 json_elem
= json_object_new_object();
800 json_object_array_add(json
, json_elem
);
804 if (!CHECK_FLAG(dest
->flags
, BGP_NODE_LABEL_REQUESTED
))
806 json_object_string_add(
807 json_elem
, "prefix", "INVALID");
808 json_object_int_add(json_elem
, "label",
811 vty_out(vty
, "%-18s %u\n",
812 "INVALID", lcb
->label
);
814 p
= bgp_dest_get_prefix(dest
);
816 json_object_string_addf(
817 json_elem
, "prefix", "%pFX", p
);
818 json_object_int_add(json_elem
, "label",
821 vty_out(vty
, "%-18pFX %u\n", p
,
827 json_object_string_add(json_elem
, "prefix",
829 json_object_int_add(json_elem
, "label",
832 vty_out(vty
, "%-18s %u\n", "VRF",
836 case LP_TYPE_NEXTHOP
:
838 json_object_string_add(json_elem
, "prefix",
840 json_object_int_add(json_elem
, "label",
843 vty_out(vty
, "%-18s %u\n", "nexthop",
853 DEFUN(show_bgp_labelpool_inuse
, show_bgp_labelpool_inuse_cmd
,
854 "show bgp labelpool inuse [json]",
856 "BGP Labelpool information\n"
857 "BGP Labelpool inuse\n" JSON_STR
)
859 bool uj
= use_json(argc
, argv
);
860 json_object
*json
= NULL
, *json_elem
= NULL
;
861 struct bgp_dest
*dest
;
865 const struct prefix
*p
;
869 vty_out(vty
, "No existing BGP labelpool\n");
870 return (CMD_WARNING
);
874 vty_out(vty
, "{}\n");
876 vty_out(vty
, "No existing BGP labelpool\n");
877 return (CMD_WARNING
);
881 count
= skiplist_count(lp
->inuse
);
883 vty_out(vty
, "{}\n");
886 json
= json_object_new_array();
888 vty_out(vty
, "Prefix Label\n");
889 vty_out(vty
, "---------------------------\n");
891 for (rc
= skiplist_next(lp
->inuse
, (void **)&label
, (void **)&dest
,
893 !rc
; rc
= skiplist_next(lp
->ledger
, (void **)&label
,
894 (void **)&dest
, &cursor
)) {
895 if (skiplist_search(lp
->ledger
, dest
, (void **)&lcb
))
899 json_elem
= json_object_new_object();
900 json_object_array_add(json
, json_elem
);
905 if (!CHECK_FLAG(dest
->flags
, BGP_NODE_LABEL_REQUESTED
))
907 json_object_string_add(
908 json_elem
, "prefix", "INVALID");
909 json_object_int_add(json_elem
, "label",
912 vty_out(vty
, "INVALID %u\n",
915 p
= bgp_dest_get_prefix(dest
);
917 json_object_string_addf(
918 json_elem
, "prefix", "%pFX", p
);
919 json_object_int_add(json_elem
, "label",
922 vty_out(vty
, "%-18pFX %u\n", p
,
928 json_object_string_add(json_elem
, "prefix",
930 json_object_int_add(json_elem
, "label", label
);
932 vty_out(vty
, "%-18s %u\n", "VRF",
935 case LP_TYPE_NEXTHOP
:
937 json_object_string_add(json_elem
, "prefix",
939 json_object_int_add(json_elem
, "label", label
);
941 vty_out(vty
, "%-18s %u\n", "nexthop",
951 DEFUN(show_bgp_labelpool_requests
, show_bgp_labelpool_requests_cmd
,
952 "show bgp labelpool requests [json]",
954 "BGP Labelpool information\n"
955 "BGP Labelpool requests\n" JSON_STR
)
957 bool uj
= use_json(argc
, argv
);
958 json_object
*json
= NULL
, *json_elem
= NULL
;
959 struct bgp_dest
*dest
;
960 const struct prefix
*p
;
961 struct lp_fifo
*item
, *next
;
966 vty_out(vty
, "{}\n");
968 vty_out(vty
, "No existing BGP labelpool\n");
969 return (CMD_WARNING
);
973 count
= lp_fifo_count(&lp
->requests
);
975 vty_out(vty
, "{}\n");
978 json
= json_object_new_array();
980 vty_out(vty
, "Prefix \n");
981 vty_out(vty
, "----------------\n");
984 for (item
= lp_fifo_first(&lp
->requests
); item
; item
= next
) {
985 next
= lp_fifo_next_safe(&lp
->requests
, item
);
986 dest
= item
->lcb
.labelid
;
988 json_elem
= json_object_new_object();
989 json_object_array_add(json
, json_elem
);
991 switch (item
->lcb
.type
) {
993 if (!CHECK_FLAG(dest
->flags
,
994 BGP_NODE_LABEL_REQUESTED
)) {
996 json_object_string_add(
997 json_elem
, "prefix", "INVALID");
999 vty_out(vty
, "INVALID\n");
1001 p
= bgp_dest_get_prefix(dest
);
1003 json_object_string_addf(
1004 json_elem
, "prefix", "%pFX", p
);
1006 vty_out(vty
, "%-18pFX\n", p
);
1011 json_object_string_add(json_elem
, "prefix",
1014 vty_out(vty
, "VRF\n");
1016 case LP_TYPE_NEXTHOP
:
1018 json_object_string_add(json_elem
, "prefix",
1021 vty_out(vty
, "Nexthop\n");
1026 vty_json(vty
, json
);
1030 DEFUN(show_bgp_labelpool_chunks
, show_bgp_labelpool_chunks_cmd
,
1031 "show bgp labelpool chunks [json]",
1033 "BGP Labelpool information\n"
1034 "BGP Labelpool chunks\n" JSON_STR
)
1036 bool uj
= use_json(argc
, argv
);
1037 json_object
*json
= NULL
, *json_elem
;
1038 struct listnode
*node
;
1039 struct lp_chunk
*chunk
;
1044 vty_out(vty
, "{}\n");
1046 vty_out(vty
, "No existing BGP labelpool\n");
1047 return (CMD_WARNING
);
1051 count
= listcount(lp
->chunks
);
1053 vty_out(vty
, "{}\n");
1056 json
= json_object_new_array();
1058 vty_out(vty
, "%10s %10s %10s %10s\n", "First", "Last", "Size",
1060 vty_out(vty
, "-------------------------------------------\n");
1063 for (ALL_LIST_ELEMENTS_RO(lp
->chunks
, node
, chunk
)) {
1066 size
= chunk
->last
- chunk
->first
+ 1;
1069 json_elem
= json_object_new_object();
1070 json_object_array_add(json
, json_elem
);
1071 json_object_int_add(json_elem
, "first", chunk
->first
);
1072 json_object_int_add(json_elem
, "last", chunk
->last
);
1073 json_object_int_add(json_elem
, "size", size
);
1074 json_object_int_add(json_elem
, "numberFree",
1077 vty_out(vty
, "%10u %10u %10u %10u\n", chunk
->first
,
1078 chunk
->last
, size
, chunk
->nfree
);
1081 vty_json(vty
, json
);
1085 static void show_bgp_nexthop_label_afi(struct vty
*vty
, afi_t afi
,
1086 struct bgp
*bgp
, bool detail
)
1088 struct bgp_label_per_nexthop_cache_head
*tree
;
1089 struct bgp_label_per_nexthop_cache
*iter
;
1092 char buf
[PREFIX2STR_BUFFER
];
1093 char labelstr
[MPLS_LABEL_STRLEN
];
1094 struct bgp_dest
*dest
;
1095 struct bgp_path_info
*path
;
1096 struct bgp
*bgp_path
;
1097 struct bgp_table
*table
;
1100 vty_out(vty
, "Current BGP label nexthop cache for %s, VRF %s\n",
1101 afi2str(afi
), bgp
->name_pretty
);
1103 tree
= &bgp
->mpls_labels_per_nexthop
[afi
];
1104 frr_each (bgp_label_per_nexthop_cache
, tree
, iter
) {
1105 if (afi2family(afi
) == AF_INET
)
1106 src
= (void *)&iter
->nexthop
.u
.prefix4
;
1108 src
= (void *)&iter
->nexthop
.u
.prefix6
;
1110 vty_out(vty
, " %s, label %s #paths %u\n",
1111 inet_ntop(afi2family(afi
), src
, buf
, sizeof(buf
)),
1112 mpls_label2str(1, &iter
->label
, labelstr
,
1113 sizeof(labelstr
), 0, true),
1116 vty_out(vty
, " if %s\n",
1117 ifindex2ifname(iter
->nh
->ifindex
,
1119 tbuf
= time(NULL
) - (monotime(NULL
) - iter
->last_update
);
1120 vty_out(vty
, " Last update: %s", ctime(&tbuf
));
1123 vty_out(vty
, " Paths:\n");
1124 LIST_FOREACH (path
, &(iter
->paths
), label_nh_thread
) {
1126 table
= bgp_dest_table(dest
);
1127 assert(dest
&& table
);
1128 afi
= family2afi(bgp_dest_get_prefix(dest
)->family
);
1130 bgp_path
= table
->bgp
;
1133 vty_out(vty
, " %d/%d %pBD RD ", afi
, safi
,
1136 vty_out(vty
, BGP_RD_AS_FORMAT(bgp
->asnotation
),
1137 (struct prefix_rd
*)bgp_dest_get_prefix(
1139 vty_out(vty
, " %s flags 0x%x\n",
1140 bgp_path
->name_pretty
, path
->flags
);
1142 vty_out(vty
, " %d/%d %pBD %s flags 0x%x\n",
1143 afi
, safi
, dest
, bgp_path
->name_pretty
,
1149 DEFPY(show_bgp_nexthop_label
, show_bgp_nexthop_label_cmd
,
1150 "show bgp [<view|vrf> VIEWVRFNAME] label-nexthop [detail]",
1151 SHOW_STR BGP_STR BGP_INSTANCE_HELP_STR
1152 "BGP label per-nexthop table\n"
1153 "Show detailed information\n")
1158 bool detail
= false;
1161 if (argv_find(argv
, argc
, "vrf", &idx
)) {
1162 vrf
= argv
[++idx
]->arg
;
1163 bgp
= bgp_lookup_by_name(vrf
);
1165 bgp
= bgp_get_default();
1170 if (argv_find(argv
, argc
, "detail", &idx
))
1173 for (afi
= AFI_IP
; afi
<= AFI_IP6
; afi
++)
1174 show_bgp_nexthop_label_afi(vty
, afi
, bgp
, detail
);
1178 #if BGP_LABELPOOL_ENABLE_TESTS
1179 /*------------------------------------------------------------------------
1180 * Testing code start
1181 *------------------------------------------------------------------------*/
1183 DEFINE_MTYPE_STATIC(BGPD
, LABELPOOL_TEST
, "Label pool test");
1185 #define LPT_STAT_INSERT_FAIL 0
1186 #define LPT_STAT_DELETE_FAIL 1
1187 #define LPT_STAT_ALLOCATED 2
1188 #define LPT_STAT_DEALLOCATED 3
1189 #define LPT_STAT_MAX 4
1191 const char *lpt_counter_names
[] = {
1192 "sl insert failures",
1193 "sl delete failures",
1195 "labels deallocated",
1198 static uint8_t lpt_generation
;
1199 static bool lpt_inprogress
;
1200 static struct skiplist
*lp_tests
;
1201 static unsigned int lpt_test_cb_tcb_lookup_fails
;
1202 static unsigned int lpt_release_tcb_lookup_fails
;
1203 static unsigned int lpt_test_event_tcb_lookup_fails
;
1204 static unsigned int lpt_stop_tcb_lookup_fails
;
1208 unsigned int request_maximum
;
1209 unsigned int request_blocksize
;
1210 uintptr_t request_count
; /* match type of labelid */
1212 struct skiplist
*labels
;
1213 struct timeval starttime
;
1214 struct skiplist
*timestamps_alloc
;
1215 struct skiplist
*timestamps_dealloc
;
1216 struct event
*event_thread
;
1217 unsigned int counter
[LPT_STAT_MAX
];
1220 /* test parameters */
1221 #define LPT_MAX_COUNT 500000 /* get this many labels in all */
1222 #define LPT_BLKSIZE 10000 /* this many at a time, then yield */
1223 #define LPT_TS_INTERVAL 10000 /* timestamp every this many labels */
1226 static int test_cb(mpls_label_t label
, void *labelid
, bool allocated
)
1228 uintptr_t generation
;
1229 struct lp_test
*tcb
;
1231 generation
= ((uintptr_t)labelid
>> 24) & 0xff;
1233 if (skiplist_search(lp_tests
, (void *)generation
, (void **)&tcb
)) {
1235 /* couldn't find current test in progress */
1236 ++lpt_test_cb_tcb_lookup_fails
;
1237 return -1; /* reject allocation */
1241 ++tcb
->counter
[LPT_STAT_ALLOCATED
];
1242 if (!(tcb
->counter
[LPT_STAT_ALLOCATED
] % LPT_TS_INTERVAL
)) {
1245 time_ms
= monotime_since(&tcb
->starttime
, NULL
) / 1000;
1246 skiplist_insert(tcb
->timestamps_alloc
,
1247 (void *)(uintptr_t)tcb
1248 ->counter
[LPT_STAT_ALLOCATED
],
1251 if (skiplist_insert(tcb
->labels
, labelid
,
1252 (void *)(uintptr_t)label
)) {
1253 ++tcb
->counter
[LPT_STAT_INSERT_FAIL
];
1257 ++tcb
->counter
[LPT_STAT_DEALLOCATED
];
1258 if (!(tcb
->counter
[LPT_STAT_DEALLOCATED
] % LPT_TS_INTERVAL
)) {
1261 time_ms
= monotime_since(&tcb
->starttime
, NULL
) / 1000;
1262 skiplist_insert(tcb
->timestamps_dealloc
,
1263 (void *)(uintptr_t)tcb
1264 ->counter
[LPT_STAT_ALLOCATED
],
1267 if (skiplist_delete(tcb
->labels
, labelid
, 0)) {
1268 ++tcb
->counter
[LPT_STAT_DELETE_FAIL
];
1275 static void labelpool_test_event_handler(struct event
*thread
)
1277 struct lp_test
*tcb
;
1279 if (skiplist_search(lp_tests
, (void *)(uintptr_t)(lpt_generation
),
1282 /* couldn't find current test in progress */
1283 ++lpt_test_event_tcb_lookup_fails
;
1288 * request a bunch of labels
1290 for (unsigned int i
= 0; (i
< tcb
->request_blocksize
) &&
1291 (tcb
->request_count
< tcb
->request_maximum
);
1296 ++tcb
->request_count
;
1299 * construct 32-bit id from request_count and generation
1301 id
= ((uintptr_t)tcb
->generation
<< 24) |
1302 (tcb
->request_count
& 0x00ffffff);
1303 bgp_lp_get(LP_TYPE_VRF
, (void *)id
, test_cb
);
1306 if (tcb
->request_count
< tcb
->request_maximum
)
1307 thread_add_event(bm
->master
, labelpool_test_event_handler
, NULL
,
1308 0, &tcb
->event_thread
);
1311 static void lptest_stop(void)
1313 struct lp_test
*tcb
;
1315 if (!lpt_inprogress
)
1318 if (skiplist_search(lp_tests
, (void *)(uintptr_t)(lpt_generation
),
1321 /* couldn't find current test in progress */
1322 ++lpt_stop_tcb_lookup_fails
;
1326 if (tcb
->event_thread
)
1327 event_cancel(&tcb
->event_thread
);
1329 lpt_inprogress
= false;
1332 static int lptest_start(struct vty
*vty
)
1334 struct lp_test
*tcb
;
1336 if (lpt_inprogress
) {
1337 vty_out(vty
, "test already in progress\n");
1341 if (skiplist_count(lp_tests
) >=
1342 (1 << (8 * sizeof(lpt_generation
))) - 1) {
1344 * Too many test runs
1346 vty_out(vty
, "too many tests: clear first\n");
1351 * We pack the generation and request number into the labelid;
1352 * make sure they fit.
1354 unsigned int n1
= LPT_MAX_COUNT
;
1355 unsigned int sh
= 0;
1356 unsigned int label_bits
;
1358 label_bits
= 8 * (sizeof(tcb
->request_count
) - sizeof(lpt_generation
));
1360 /* n1 should be same type as tcb->request_maximum */
1361 assert(sizeof(n1
) == sizeof(tcb
->request_maximum
));
1365 sh
+= 1; /* number of bits needed to hold LPT_MAX_COUNT */
1367 if (sh
> label_bits
) {
1369 "Sorry, test iteration count too big on this platform (LPT_MAX_COUNT %u, need %u bits, but label_bits is only %u)\n",
1370 LPT_MAX_COUNT
, sh
, label_bits
);
1374 lpt_inprogress
= true;
1377 tcb
= XCALLOC(MTYPE_LABELPOOL_TEST
, sizeof(*tcb
));
1379 tcb
->generation
= lpt_generation
;
1380 tcb
->label_type
= LP_TYPE_VRF
;
1381 tcb
->request_maximum
= LPT_MAX_COUNT
;
1382 tcb
->request_blocksize
= LPT_BLKSIZE
;
1383 tcb
->labels
= skiplist_new(0, NULL
, NULL
);
1384 tcb
->timestamps_alloc
= skiplist_new(0, NULL
, NULL
);
1385 tcb
->timestamps_dealloc
= skiplist_new(0, NULL
, NULL
);
1386 thread_add_event(bm
->master
, labelpool_test_event_handler
, NULL
, 0,
1387 &tcb
->event_thread
);
1388 monotime(&tcb
->starttime
);
1390 skiplist_insert(lp_tests
, (void *)(uintptr_t)tcb
->generation
, tcb
);
1394 DEFPY(start_labelpool_perf_test
, start_labelpool_perf_test_cmd
,
1395 "debug bgp lptest start",
1404 static void lptest_print_stats(struct vty
*vty
, struct lp_test
*tcb
)
1408 vty_out(vty
, "Global Lookup Failures in test_cb: %5u\n",
1409 lpt_test_cb_tcb_lookup_fails
);
1410 vty_out(vty
, "Global Lookup Failures in release: %5u\n",
1411 lpt_release_tcb_lookup_fails
);
1412 vty_out(vty
, "Global Lookup Failures in event: %5u\n",
1413 lpt_test_event_tcb_lookup_fails
);
1414 vty_out(vty
, "Global Lookup Failures in stop: %5u\n",
1415 lpt_stop_tcb_lookup_fails
);
1419 if (skiplist_search(lp_tests
, (void *)(uintptr_t)lpt_generation
,
1421 vty_out(vty
, "Error: can't find test %u\n",
1427 vty_out(vty
, "Test Generation %u:\n", tcb
->generation
);
1429 vty_out(vty
, "Counter Value\n");
1430 for (i
= 0; i
< LPT_STAT_MAX
; ++i
) {
1431 vty_out(vty
, "%20s: %10u\n", lpt_counter_names
[i
],
1436 if (tcb
->timestamps_alloc
) {
1443 vty_out(vty
, "%10s %10s\n", "Count", "Seconds");
1446 while (!skiplist_next(tcb
->timestamps_alloc
, &Key
, &Value
,
1449 elapsed
= ((float)(uintptr_t)Value
) / 1000;
1451 vty_out(vty
, "%10llu %10.3f\n",
1452 (unsigned long long)(uintptr_t)Key
, elapsed
);
1458 DEFPY(show_labelpool_perf_test
, show_labelpool_perf_test_cmd
,
1459 "debug bgp lptest show",
1471 while (!skiplist_next(lp_tests
, &Key
, &Value
, &cursor
)) {
1472 lptest_print_stats(vty
, (struct lp_test
*)Value
);
1475 vty_out(vty
, "no test results\n");
1480 DEFPY(stop_labelpool_perf_test
, stop_labelpool_perf_test_cmd
,
1481 "debug bgp lptest stop",
1487 if (lpt_inprogress
) {
1489 lptest_print_stats(vty
, NULL
);
1491 vty_out(vty
, "no test in progress\n");
1496 DEFPY(clear_labelpool_perf_test
, clear_labelpool_perf_test_cmd
,
1497 "debug bgp lptest clear",
1503 if (lpt_inprogress
) {
1507 while (!skiplist_first(lp_tests
, NULL
, NULL
))
1508 /* del function of skiplist cleans up tcbs */
1509 skiplist_delete_first(lp_tests
);
1515 * With the "release" command, we can release labels at intervals through
1516 * the ID space. Thus we can to exercise the bitfield-wrapping behavior
1517 * of the allocator in a subsequent test.
1519 /* clang-format off */
1520 DEFPY(release_labelpool_perf_test
, release_labelpool_perf_test_cmd
,
1521 "debug bgp lptest release test GENERATION$generation every (1-5)$every_nth",
1529 "label fraction denominator\n")
1531 /* clang-format on */
1533 unsigned long testnum
;
1535 struct lp_test
*tcb
;
1537 testnum
= strtoul(generation
, &end
, 0);
1539 vty_out(vty
, "Invalid test number: \"%s\"\n", generation
);
1542 if (lpt_inprogress
&& (testnum
== lpt_generation
)) {
1544 "Error: Test %lu is still in progress (stop first)\n",
1549 if (skiplist_search(lp_tests
, (void *)(uintptr_t)testnum
,
1552 /* couldn't find current test in progress */
1553 vty_out(vty
, "Error: Can't look up test number: \"%lu\"\n",
1555 ++lpt_release_tcb_lookup_fails
;
1560 void *Value
, *cValue
;
1562 unsigned int iteration
;
1567 rc
= skiplist_next(tcb
->labels
, &Key
, &Value
, &cursor
);
1573 /* find next item before we delete this one */
1574 rc
= skiplist_next(tcb
->labels
, &Key
, &Value
, &cursor
);
1576 if (!(iteration
% every_nth
)) {
1577 bgp_lp_release(tcb
->label_type
, cKey
,
1578 (mpls_label_t
)(uintptr_t)cValue
);
1579 skiplist_delete(tcb
->labels
, cKey
, NULL
);
1580 ++tcb
->counter
[LPT_STAT_DEALLOCATED
];
1588 static void lptest_delete(void *val
)
1590 struct lp_test
*tcb
= (struct lp_test
*)val
;
1597 while (!skiplist_next(tcb
->labels
, &Key
, &Value
, &cursor
))
1598 bgp_lp_release(tcb
->label_type
, Key
,
1599 (mpls_label_t
)(uintptr_t)Value
);
1600 skiplist_free(tcb
->labels
);
1603 if (tcb
->timestamps_alloc
) {
1605 skiplist_free(tcb
->timestamps_alloc
);
1606 tcb
->timestamps_alloc
= NULL
;
1609 if (tcb
->timestamps_dealloc
) {
1611 skiplist_free(tcb
->timestamps_dealloc
);
1612 tcb
->timestamps_dealloc
= NULL
;
1615 if (tcb
->event_thread
)
1616 event_cancel(&tcb
->event_thread
);
1618 memset(tcb
, 0, sizeof(*tcb
));
1620 XFREE(MTYPE_LABELPOOL_TEST
, tcb
);
1623 static void lptest_init(void)
1625 lp_tests
= skiplist_new(0, NULL
, lptest_delete
);
1628 static void lptest_finish(void)
1631 skiplist_free(lp_tests
);
1636 /*------------------------------------------------------------------------
1638 *------------------------------------------------------------------------*/
1639 #endif /* BGP_LABELPOOL_ENABLE_TESTS */
1641 void bgp_lp_vty_init(void)
1643 install_element(VIEW_NODE
, &show_bgp_labelpool_summary_cmd
);
1644 install_element(VIEW_NODE
, &show_bgp_labelpool_ledger_cmd
);
1645 install_element(VIEW_NODE
, &show_bgp_labelpool_inuse_cmd
);
1646 install_element(VIEW_NODE
, &show_bgp_labelpool_requests_cmd
);
1647 install_element(VIEW_NODE
, &show_bgp_labelpool_chunks_cmd
);
1649 #if BGP_LABELPOOL_ENABLE_TESTS
1650 install_element(ENABLE_NODE
, &start_labelpool_perf_test_cmd
);
1651 install_element(ENABLE_NODE
, &show_labelpool_perf_test_cmd
);
1652 install_element(ENABLE_NODE
, &stop_labelpool_perf_test_cmd
);
1653 install_element(ENABLE_NODE
, &release_labelpool_perf_test_cmd
);
1654 install_element(ENABLE_NODE
, &clear_labelpool_perf_test_cmd
);
1655 #endif /* BGP_LABELPOOL_ENABLE_TESTS */
1658 DEFINE_MTYPE_STATIC(BGPD
, LABEL_PER_NEXTHOP_CACHE
,
1659 "BGP Label Per Nexthop entry");
1661 /* The nexthops values are compared to
1662 * find in the tree the appropriate cache entry
1664 int bgp_label_per_nexthop_cache_cmp(const struct bgp_label_per_nexthop_cache
*a
,
1665 const struct bgp_label_per_nexthop_cache
*b
)
1667 return prefix_cmp(&a
->nexthop
, &b
->nexthop
);
1670 struct bgp_label_per_nexthop_cache
*
1671 bgp_label_per_nexthop_new(struct bgp_label_per_nexthop_cache_head
*tree
,
1672 struct prefix
*nexthop
)
1674 struct bgp_label_per_nexthop_cache
*blnc
;
1676 blnc
= XCALLOC(MTYPE_LABEL_PER_NEXTHOP_CACHE
,
1677 sizeof(struct bgp_label_per_nexthop_cache
));
1679 blnc
->label
= MPLS_INVALID_LABEL
;
1680 prefix_copy(&blnc
->nexthop
, nexthop
);
1681 LIST_INIT(&(blnc
->paths
));
1682 bgp_label_per_nexthop_cache_add(tree
, blnc
);
1687 struct bgp_label_per_nexthop_cache
*
1688 bgp_label_per_nexthop_find(struct bgp_label_per_nexthop_cache_head
*tree
,
1689 struct prefix
*nexthop
)
1691 struct bgp_label_per_nexthop_cache blnc
= {};
1696 memcpy(&blnc
.nexthop
, nexthop
, sizeof(struct prefix
));
1697 return bgp_label_per_nexthop_cache_find(tree
, &blnc
);
1700 void bgp_label_per_nexthop_free(struct bgp_label_per_nexthop_cache
*blnc
)
1702 if (blnc
->label
!= MPLS_INVALID_LABEL
) {
1703 bgp_zebra_send_nexthop_label(ZEBRA_MPLS_LABELS_DELETE
,
1704 blnc
->label
, blnc
->nh
->ifindex
,
1705 blnc
->nh
->vrf_id
, ZEBRA_LSP_BGP
,
1707 bgp_lp_release(LP_TYPE_NEXTHOP
, blnc
, blnc
->label
);
1709 bgp_label_per_nexthop_cache_del(blnc
->tree
, blnc
);
1711 nexthop_free(blnc
->nh
);
1713 XFREE(MTYPE_LABEL_PER_NEXTHOP_CACHE
, blnc
);
1716 void bgp_label_per_nexthop_init(void)
1718 install_element(VIEW_NODE
, &show_bgp_nexthop_label_cmd
);