diff options
| author | Kyrylo Yatsenko <hedrok@gmail.com> | 2026-05-02 23:19:42 +0300 |
|---|---|---|
| committer | Kyrylo Yatsenko <hedrok@gmail.com> | 2026-05-07 22:44:00 +0300 |
| commit | 3962d4e69eae9669a0d3355f1b6c5d6c1dbbe994 (patch) | |
| tree | a0156b0394f30442f2a49220566e4a78ed490c36 /scripts/package-build | |
| parent | 2413b09291341031d77066db5f84509a7def54cd (diff) | |
| download | vyos-build-3962d4e69eae9669a0d3355f1b6c5d6c1dbbe994.tar.gz vyos-build-3962d4e69eae9669a0d3355f1b6c5d6c1dbbe994.zip | |
T8612: Upgrade FRR to 10.6.1
Rework BGP-LS megapatch to include all latest work on BGP-LS
Rebase patches:
* 0021-pathd-add-optional-protocol-to-no-mpls-te-import.patch
* 0022-pathd-add-optional-parameters-to-no-index-cmd.patch
Delete patches already merged into 10.6.1:
* 0008-isis-fix-advertise-passive-only-routes-install.patch
* 0009-T7909-eigrp-malformed-update-fix.patch
* 0010-bgp-Support-multiple-labels-in-BGP-LU.patch
* 0011-zebra-add-CLI-no-versions-for-max-bw-and-others.patch
* 0014-Simplify-and-cleanup-mgmtd.patch
Diffstat (limited to 'scripts/package-build')
10 files changed, 5976 insertions, 16115 deletions
diff --git a/scripts/package-build/frr/package.toml b/scripts/package-build/frr/package.toml index 2a2a72a5..183358e0 100644 --- a/scripts/package-build/frr/package.toml +++ b/scripts/package-build/frr/package.toml @@ -6,7 +6,7 @@ build_cmd = "pipx run apkg build -i && find pkg/pkgs -type f -name *.deb -exec m [[packages]] name = "frr" -commit_id = "168ade7da9" # from stable/10.5 branch +commit_id = "71da51baee6fb2a02b24262defc46591c86e8a81" # tag frr-10.6.1 scm_url = "https://github.com/FRRouting/frr.git" build_cmd = "sudo dpkg -i ../*.deb; dpkg-buildpackage -us -uc -tc -b -Ppkg.frr.rtrlib,pkg.frr.lua" diff --git a/scripts/package-build/frr/patches/frr/0008-isis-fix-advertise-passive-only-routes-install.patch b/scripts/package-build/frr/patches/frr/0008-isis-fix-advertise-passive-only-routes-install.patch deleted file mode 100644 index 4861c138..00000000 --- a/scripts/package-build/frr/patches/frr/0008-isis-fix-advertise-passive-only-routes-install.patch +++ /dev/null @@ -1,133 +0,0 @@ -From e2dbe69dd830d01af5bf0202b347524b3151e4e6 Mon Sep 17 00:00:00 2001 -From: Kyrylo Yatsenko <hedrok@gmail.com> -Date: Fri, 19 Sep 2025 09:17:03 +0300 -Subject: [PATCH] isis: fix advertise-passive-only routes install - -When advertise-passive-only is set don't ignore active circuits -completely - we still need to install routes from those interfaces. - -Update test to check this. - -Fixes #16325 - -Signed-off-by: Kyrylo Yatsenko <hedrok@gmail.com> ---- - isisd/isis_lsp.c | 43 ++++++++----------- - tests/topotests/isis_topo1/test_isis_topo1.py | 30 ++++++++++--- - 2 files changed, 42 insertions(+), 31 deletions(-) - -diff --git a/isisd/isis_lsp.c b/isisd/isis_lsp.c -index d588af314c..0aefba95a2 100644 ---- a/isisd/isis_lsp.c -+++ b/isisd/isis_lsp.c -@@ -1308,37 +1308,30 @@ static void lsp_build(struct isis_lsp *lsp, struct isis_area *area) - continue; - } - -- if (area->advertise_passive_only && !circuit->is_passive) { -- lsp_debug( -- "ISIS (%s): Circuit is not passive, ignoring.", -- area->area_tag); -- continue; -- } -- - uint32_t metric = area->oldmetric - ? circuit->metric[level - 1] - : circuit->te_metric[level - 1]; - -- if (circuit->ip_router && circuit->ip_addrs->count > 0) { -- lsp_debug( -- "ISIS (%s): Circuit has IPv4 active, adding respective TLVs.", -- area->area_tag); -- struct listnode *ipnode; -- struct prefix_ipv4 *ipv4; -- for (ALL_LIST_ELEMENTS_RO(circuit->ip_addrs, ipnode, -- ipv4)) -- lsp_build_internal_reach_ipv4(lsp, area, ipv4, -- metric); -- } -+ if (area->advertise_passive_only && !circuit->is_passive) { -+ lsp_debug("ISIS (%s): Circuit is not passive, don't add prefixes.", -+ area->area_tag); -+ } else { -+ if (circuit->ip_router && circuit->ip_addrs->count > 0) { -+ lsp_debug("ISIS (%s): Circuit has IPv4 active, adding respective TLVs.", -+ area->area_tag); -+ struct listnode *ipnode; -+ struct prefix_ipv4 *ipv4; -+ for (ALL_LIST_ELEMENTS_RO(circuit->ip_addrs, ipnode, ipv4)) -+ lsp_build_internal_reach_ipv4(lsp, area, ipv4, metric); -+ } - -- if (circuit->ipv6_router && circuit->ipv6_non_link->count > 0) { -- struct listnode *ipnode; -- struct prefix_ipv6 *ipv6; -+ if (circuit->ipv6_router && circuit->ipv6_non_link->count > 0) { -+ struct listnode *ipnode; -+ struct prefix_ipv6 *ipv6; - -- for (ALL_LIST_ELEMENTS_RO(circuit->ipv6_non_link, -- ipnode, ipv6)) -- lsp_build_internal_reach_ipv6(lsp, area, ipv6, -- metric); -+ for (ALL_LIST_ELEMENTS_RO(circuit->ipv6_non_link, ipnode, ipv6)) -+ lsp_build_internal_reach_ipv6(lsp, area, ipv6, metric); -+ } - } - - switch (circuit->circ_type) { -diff --git a/tests/topotests/isis_topo1/test_isis_topo1.py b/tests/topotests/isis_topo1/test_isis_topo1.py -index 1cec2f16f0..5d65b353cf 100644 ---- a/tests/topotests/isis_topo1/test_isis_topo1.py -+++ b/tests/topotests/isis_topo1/test_isis_topo1.py -@@ -148,12 +148,9 @@ def test_isis_route_installation(): - filename = "{0}/{1}/{1}_route.json".format(CWD, rname) - expected = json.loads(open(filename, "r").read()) - -- def compare_isis_installed_routes(router, expected): -- "Helper function to test ISIS routes installed in rib." -- actual = router.vtysh_cmd("show ip route json", isjson=True) -- return topotest.json_cmp(actual, expected) -- -- test_func = functools.partial(compare_isis_installed_routes, router, expected) -+ test_func = functools.partial( -+ _helper_compare_isis_installed_routes, router, expected -+ ) - (result, _) = topotest.run_and_expect(test_func, None, wait=1, count=10) - assertmsg = "Router '{}' routes mismatch".format(rname) - assert result, assertmsg -@@ -565,6 +562,21 @@ def test_isis_advertise_passive_only(): - ) - assert result is True, result - -+ logger.info("Checking router for installed ISIS routes with advertise-passive-only") -+ -+ # routes must be installed -+ rname = "r1" -+ router = r1 -+ filename = "{0}/{1}/{1}_route.json".format(CWD, rname) -+ expected = json.loads(open(filename, "r").read()) -+ -+ test_func = functools.partial( -+ _helper_compare_isis_installed_routes, router, expected -+ ) -+ (result, _) = topotest.run_and_expect(test_func, None, wait=1, count=10) -+ assertmsg = "Router '{}' routes mismatch:\n{}".format(rname, _) -+ assert result, assertmsg -+ - - def test_isis_hello_padding_during_adjacency_formation(): - """Check that IIH packets is only padded when adjacency is still being formed -@@ -842,3 +854,9 @@ def parse_topology(lines, level): - continue - - return areas -+ -+ -+def _helper_compare_isis_installed_routes(router, expected): -+ "Helper function to test ISIS routes installed in rib." -+ actual = router.vtysh_cmd("show ip route json", isjson=True) -+ return topotest.json_cmp(actual, expected) --- -2.50.1 - diff --git a/scripts/package-build/frr/patches/frr/0009-T7909-eigrp-malformed-update-fix.patch b/scripts/package-build/frr/patches/frr/0009-T7909-eigrp-malformed-update-fix.patch deleted file mode 100644 index 8aaa2c16..00000000 --- a/scripts/package-build/frr/patches/frr/0009-T7909-eigrp-malformed-update-fix.patch +++ /dev/null @@ -1,101 +0,0 @@ -From d5777f8c38a9c61d8992d6ede9a6ea9697fd5ba0 Mon Sep 17 00:00:00 2001 -From: Ritika Chopra <ritika0313@gmail.com> -Date: Sun, 5 Oct 2025 21:41:46 -0700 -Subject: [PATCH] eigrpd: Handling for malformed update packets - --EIGRP daemon was crashing when the code was attempting to read more data from EIGRP update malformed packets than is available in the packets stream. --Safety checks have been added before reading from the stream to prevent any crashes --This patch addresses the Update packets carrying routes other than IPv4 Internal routes - -Signed-off-by: Ritika Chopra <ritika0313@gmail.com> ---- - eigrpd/eigrp_packet.c | 6 ++++++ - eigrpd/eigrp_update.c | 46 ++++++++++++++++++++++++++++++++++++++++--- - 2 files changed, 49 insertions(+), 3 deletions(-) - -diff --git a/eigrpd/eigrp_packet.c b/eigrpd/eigrp_packet.c -index e930e8f3c1..f4c16efca5 100644 ---- a/eigrpd/eigrp_packet.c -+++ b/eigrpd/eigrp_packet.c -@@ -551,6 +551,12 @@ void eigrp_read(struct event *thread) - /* Advance from IP header to EIGRP header (iph->ip_hl has been verified - by eigrp_recv_packet() to be correct). */ - -+ if ((iph->ip_hl * 4) + EIGRP_HEADER_LEN > stream_get_endp(ibuf)) { -+ zlog_warn("Malformed packet: IP header extends beyond packet data. IP header len = %u endp = %zu", -+ iph->ip_hl * 4, stream_get_endp(ibuf)); -+ return; -+ } -+ - stream_forward_getp(ibuf, (iph->ip_hl * 4)); - eigrph = (struct eigrp_header *)stream_pnt(ibuf); - -diff --git a/eigrpd/eigrp_update.c b/eigrpd/eigrp_update.c -index 7348231c3b..df03ee2d90 100644 ---- a/eigrpd/eigrp_update.c -+++ b/eigrpd/eigrp_update.c -@@ -279,6 +279,13 @@ void eigrp_update_receive(struct eigrp *eigrp, struct ip *iph, - - /*If there is topology information*/ - while (s->endp > s->getp) { -+ /* Ensure we have at least 4 bytes for TLV header */ -+ if (STREAM_READABLE(s) < 4) { -+ zlog_warn("Malformed packet: Unexpected early end of packet reached, stopping TLV processing"); -+ stream_forward_getp(s, STREAM_READABLE(s)); -+ break; -+ } -+ - type = stream_getw(s); - switch (type) { - case EIGRP_TLV_IPv4_INT: -@@ -369,11 +376,44 @@ void eigrp_update_receive(struct eigrp *eigrp, struct ip *iph, - * for now, lets just not creash the box - */ - default: -+ /* Handle unknown TLV types gracefully */ -+ zlog_warn("Unknown TLV type: 0x%04x", type); -+ -+ /* Validate we have enough data for TLV length */ -+ if (STREAM_READABLE(s) < 2) { -+ zlog_warn("Malformed packet: insufficient data for TLV length, skipping to end"); -+ stream_forward_getp(s, STREAM_READABLE(s)); -+ break; -+ } -+ - length = stream_getw(s); -- // -2 for type, -2 for len -- for (length -= 4; length; length--) { -- (void)stream_getc(s); -+ -+ /* Validate TLV length */ -+ if (length < 4) { -+ zlog_warn("Malformed packet: TLV length too small (%u), skipping to end", length); -+ stream_forward_getp(s, STREAM_READABLE(s)); -+ break; - } -+ -+ /* Check for reasonable TLV length */ -+ if (length > 1024) { -+ zlog_warn("Malformed packet: TLV length too large (%u), skipping to end", length); -+ stream_forward_getp(s, STREAM_READABLE(s)); -+ break; -+ } -+ -+ /* Check if TLV extends beyond packet */ -+ if (length > STREAM_READABLE(s) + 4) { -+ zlog_warn("Malformed packet: TLV length (%u) exceeds remaining data (%zu) + 4, skipping to end", -+ length, STREAM_READABLE(s)); -+ break; -+ } -+ /* Skip current TLV data safely to move on to next TLV */ -+ if (IS_DEBUG_EIGRP_PACKET(0, RECV)) -+ zlog_debug("Skipping unknown TLV: type=0x%04x, length=%u", type, length); -+ stream_forward_getp(s, length - 4); -+ -+ - } - } - --- -2.47.3 - diff --git a/scripts/package-build/frr/patches/frr/0010-bgp-Support-multiple-labels-in-BGP-LU.patch b/scripts/package-build/frr/patches/frr/0010-bgp-Support-multiple-labels-in-BGP-LU.patch deleted file mode 100644 index 3e94d7a6..00000000 --- a/scripts/package-build/frr/patches/frr/0010-bgp-Support-multiple-labels-in-BGP-LU.patch +++ /dev/null @@ -1,596 +0,0 @@ -From 60664bb4d86102f4786ce3882e686d6dfd6c998a Mon Sep 17 00:00:00 2001 -From: Kyrylo Yatsenko <hedrok@gmail.com> -Date: Fri, 31 Oct 2025 21:20:20 +0200 -Subject: [PATCH] bgpd: Support multiple labels in BGP-LU - -Updated behaviour for SAFI_LABELED_UNICAST to support multiple labels: - -* Change BGP_MAX_LABELS 2 -> 10 (maximum possible number of labels for - BGP-LU), previously for SAFI_LABELED_UNICAST only one label was - supported -* Save all labels when receiving an update event in - `bgp_nlri_get_labels`, send them all to zebra in - `bgp_zebra_announce_parse_nexthop` - -One of uses of multiple labels is SR-TE with BGP-LU. - -Resolves #19506 - -Signed-off-by: Kyrylo Yatsenko <hedrok@gmail.com> -(cherry picked from commit 17a082ee3a5d1295c27323773c523c945af12c3d) ---- - bgpd/bgp_attr.c | 13 ++++- - bgpd/bgp_debug.c | 6 +- - bgpd/bgp_label.c | 120 ++++++++++++++++++++++++++++----------- - bgpd/bgp_label.h | 28 +++++++-- - bgpd/bgp_route.c | 56 +++++++++++------- - bgpd/bgp_updgrp_packet.c | 14 ++--- - bgpd/bgp_zebra.c | 47 +++++++++++---- - 7 files changed, 197 insertions(+), 87 deletions(-) - -diff --git a/bgpd/bgp_attr.c b/bgpd/bgp_attr.c -index 3b154c28a3..0af57bfae2 100644 ---- a/bgpd/bgp_attr.c -+++ b/bgpd/bgp_attr.c -@@ -4721,6 +4721,8 @@ void bgp_packet_mpattr_prefix(struct stream *s, afi_t afi, safi_t safi, - uint8_t num_labels, bool addpath_capable, - uint32_t addpath_tx_id, struct attr *attr) - { -+ mpls_label_t restore_label; -+ - switch (safi) { - case SAFI_UNSPEC: - case SAFI_MAX: -@@ -4745,9 +4747,16 @@ void bgp_packet_mpattr_prefix(struct stream *s, afi_t afi, safi_t safi, - assert(!"Add encoding bits here for other AFI's"); - break; - case SAFI_LABELED_UNICAST: -+ if (num_labels > 1) { -+ zlog_warn("%s: stream_put_labeled_prefix currently supports only one label, ignoring rest (num_labels=%d)", -+ __func__, num_labels); -+ restore_label = label[0]; -+ label_set_bos(&label[0]); -+ } - /* Prefix write with label. */ -- stream_put_labeled_prefix(s, p, label, addpath_capable, -- addpath_tx_id); -+ stream_put_labeled_prefix(s, p, label, addpath_capable, addpath_tx_id); -+ if (num_labels > 1) -+ label[0] = restore_label; - break; - case SAFI_FLOWSPEC: - stream_putc(s, p->u.prefix_flowspec.prefixlen); -diff --git a/bgpd/bgp_debug.c b/bgpd/bgp_debug.c -index 530203d860..aaabd26ee0 100644 ---- a/bgpd/bgp_debug.c -+++ b/bgpd/bgp_debug.c -@@ -2924,11 +2924,7 @@ const char *bgp_debug_rdpfxpath2str(afi_t afi, safi_t safi, - snprintf(tag_buf, sizeof(tag_buf), " label %s", - tag_buf2); - } else { -- uint32_t label_value; -- -- label_value = decode_label(label); -- snprintf(tag_buf, sizeof(tag_buf), " label %u", -- label_value); -+ mpls_labels2str(label, num_labels, " label ", tag_buf, sizeof(tag_buf)); - } - } - -diff --git a/bgpd/bgp_label.c b/bgpd/bgp_label.c -index ae521a5db6..4a13d4b395 100644 ---- a/bgpd/bgp_label.c -+++ b/bgpd/bgp_label.c -@@ -198,19 +198,20 @@ int bgp_parse_fec_update(void) - return 1; - } - --mpls_label_t bgp_adv_label(struct bgp_dest *dest, struct bgp_path_info *pi, -- struct peer *to, afi_t afi, safi_t safi) -+void bgp_adv_label(struct bgp_dest *dest, struct bgp_path_info *pi, struct peer *to, afi_t afi, -+ safi_t safi, mpls_label_t *labels, uint8_t *num_labels) - { - struct peer *from; -- mpls_label_t remote_label; - int reflect; -+ uint8_t i; -+ bool use_local_label = true; - -- if (!dest || !pi || !to) -- return MPLS_INVALID_LABEL; -+ labels[0] = MPLS_INVALID_LABEL; -+ if (!dest || !pi || !to) { -+ *num_labels = 0; -+ return; -+ } - -- remote_label = BGP_PATH_INFO_NUM_LABELS(pi) -- ? pi->extra->labels->label[0] -- : MPLS_INVALID_LABEL; - from = pi->peer; - reflect = - ((from->sort == BGP_PEER_IBGP) && (to->sort == BGP_PEER_IBGP)); -@@ -218,12 +219,19 @@ mpls_label_t bgp_adv_label(struct bgp_dest *dest, struct bgp_path_info *pi, - if (reflect - && !CHECK_FLAG(to->af_flags[afi][safi], - PEER_FLAG_FORCE_NEXTHOP_SELF)) -- return remote_label; -+ use_local_label = false; - - if (CHECK_FLAG(to->af_flags[afi][safi], PEER_FLAG_NEXTHOP_UNCHANGED)) -- return remote_label; -+ use_local_label = false; - -- return dest->local_label; -+ if (use_local_label) { -+ *num_labels = 1; -+ labels[0] = dest->local_label; -+ } else { -+ *num_labels = BGP_PATH_INFO_NUM_LABELS(pi); -+ for (i = 0; i < *num_labels; i++) -+ labels[i] = pi->extra->labels->label[i]; -+ } - } - - static void bgp_send_fec_register_label_msg(struct bgp_dest *dest, bool reg, -@@ -414,35 +422,50 @@ void bgp_reg_dereg_for_label(struct bgp_dest *dest, struct bgp_path_info *pi, - dest, reg, with_label_index ? pi->attr->label_index : 0); - } - --static int bgp_nlri_get_labels(struct peer *peer, uint8_t *pnt, uint8_t plen, -- mpls_label_t *label) -+/* Labels must have BGP_MAX_LABELS elements */ -+static int bgp_nlri_get_labels(struct peer *peer, uint8_t *pnt, uint8_t plen, mpls_label_t *labels, -+ uint8_t *num_labels) - { - uint8_t *data = pnt; - uint8_t *lim = pnt + plen; - uint8_t llen = 0; - uint8_t label_depth = 0; -+ mpls_label_t *label_pnt; -+ mpls_label_t label; - - if (plen < BGP_LABEL_BYTES) - return 0; - -- for (; data < lim; data += BGP_LABEL_BYTES) { -- memcpy(label, data, BGP_LABEL_BYTES); -+ label_pnt = &labels[0]; -+ for (; (data + BGP_LABEL_BYTES) <= lim; data += BGP_LABEL_BYTES, label_pnt++) { -+ /* -+ * Support only BGP_MAX_LABELS, read rest to local variable and -+ * discard, shouldn't be possible - see comment to BGP_MAX_LABELS -+ */ -+ if (label_depth >= BGP_MAX_LABELS) -+ label_pnt = &label; -+ -+ memcpy(label_pnt, data, BGP_LABEL_BYTES); - llen += BGP_LABEL_BYTES; - -- bgp_set_valid_label(label); -+ bgp_set_valid_label(label_pnt); -+ - label_depth += 1; - -- if (bgp_is_withdraw_label(label) || label_bos(label)) -+ if (bgp_is_withdraw_label(label_pnt) || label_bos(label_pnt)) - break; - } - -- /* If we RX multiple labels we will end up keeping only the last -- * one. We do not yet support a label stack greater than 1. */ -- if (label_depth > 1) -- zlog_info("%pBP rcvd UPDATE with label stack %d deep", peer, -- label_depth); -+ *num_labels = label_depth; -+ -+ if (label_depth > BGP_MAX_LABELS) { -+ *num_labels = BGP_MAX_LABELS; -+ label_set_bos(&labels[*num_labels - 1]); -+ zlog_info("%pBP rcvd UPDATE with label stack %d deep, using only first %d labels", -+ peer, label_depth, BGP_MAX_LABELS); -+ } - -- if (!(bgp_is_withdraw_label(label) || label_bos(label))) -+ if (!(bgp_is_withdraw_label(label_pnt) || label_bos(label_pnt))) - flog_warn( - EC_BGP_INVALID_LABEL_STACK, - "%pBP rcvd UPDATE with invalid label stack - no bottom of stack", -@@ -463,7 +486,8 @@ int bgp_nlri_parse_label(struct peer *peer, struct attr *attr, - safi_t safi; - bool addpath_capable; - uint32_t addpath_id; -- mpls_label_t label = MPLS_INVALID_LABEL; -+ mpls_label_t labels[MPLS_MAX_LABELS] = { MPLS_INVALID_LABEL }; -+ uint8_t num_labels = 0; - uint8_t llen; - - pnt = packet->nlri; -@@ -507,7 +531,7 @@ int bgp_nlri_parse_label(struct peer *peer, struct attr *attr, - } - - /* Fill in the labels */ -- llen = bgp_nlri_get_labels(peer, pnt, psize, &label); -+ llen = bgp_nlri_get_labels(peer, pnt, psize, labels, &num_labels); - if (llen == 0) { - flog_err( - EC_BGP_UPDATE_RCV, -@@ -574,13 +598,11 @@ int bgp_nlri_parse_label(struct peer *peer, struct attr *attr, - } - - if (attr) { -- bgp_update(peer, &p, addpath_id, attr, packet->afi, -- safi, ZEBRA_ROUTE_BGP, BGP_ROUTE_NORMAL, -- NULL, &label, 1, 0, NULL); -+ bgp_update(peer, &p, addpath_id, attr, packet->afi, safi, ZEBRA_ROUTE_BGP, -+ BGP_ROUTE_NORMAL, NULL, labels, num_labels, 0, NULL); - } else { -- bgp_withdraw(peer, &p, addpath_id, packet->afi, -- SAFI_UNICAST, ZEBRA_ROUTE_BGP, -- BGP_ROUTE_NORMAL, NULL, &label, 1); -+ bgp_withdraw(peer, &p, addpath_id, packet->afi, SAFI_UNICAST, -+ ZEBRA_ROUTE_BGP, BGP_ROUTE_NORMAL, NULL, labels, num_labels); - } - } - -@@ -607,7 +629,7 @@ uint32_t decode_label(mpls_label_t *label_pnt) - return l; - } - --void encode_label(mpls_label_t label, mpls_label_t *label_pnt) -+void encode_label_bos(mpls_label_t label, mpls_label_t *label_pnt, bool bos) - { - uint8_t *pnt = (uint8_t *)label_pnt; - -@@ -619,7 +641,12 @@ void encode_label(mpls_label_t label, mpls_label_t *label_pnt) - } - *pnt++ = (label >> 12) & 0xff; - *pnt++ = (label >> 4) & 0xff; -- *pnt++ = ((label << 4) + 1) & 0xff; /* S=1 */ -+ *pnt++ = ((label << 4) + (bos ? 1 : 0)) & 0xff; -+} -+ -+void encode_label(mpls_label_t label, mpls_label_t *label_pnt) -+{ -+ encode_label_bos(label, label_pnt, true); - } - - bool bgp_labels_same(const mpls_label_t *tbl_a, const uint8_t num_labels_a, -@@ -647,3 +674,30 @@ bool bgp_labels_is_implicit_null(struct bgp_path_info *pi) - return true; - return false; - } -+ -+char *mpls_labels2str(mpls_label_t *labels, uint8_t num_labels, const char *prefix, char *buf, -+ int size) -+{ -+ uint32_t label_value; -+ uint32_t len = 0; -+ uint8_t i; -+ -+ buf[0] = '\0'; -+ if (!num_labels) -+ return buf; -+ -+ if (prefix) { -+ strlcat(buf + len, prefix, size - len); -+ len = strlen(buf); -+ } -+ -+ label_value = decode_label(&labels[0]); -+ len += snprintf(buf + len, size - len, "%u", label_value); -+ -+ for (i = 1; i < num_labels; i++) { -+ label_value = decode_label(&labels[i]); -+ len += snprintf(buf + len, size - len, "/%u", label_value); -+ } -+ -+ return buf; -+} -diff --git a/bgpd/bgp_label.h b/bgpd/bgp_label.h -index a467bc1130..2793aef1c8 100644 ---- a/bgpd/bgp_label.h -+++ b/bgpd/bgp_label.h -@@ -14,9 +14,15 @@ struct bgp_path_info; - struct peer; - - /* Maximum number of labels we can process or send with a prefix. We -- * really do only 1 for MPLS (BGP-LU) but we can do 2 for EVPN-VxLAN. -+ * support 10 for MPLS (BGP-LU) and need only 2 for EVPN-VxLAN. -+ * -+ * According to RFC 3107/RFC 8277 label stack is distributed in NLRI, length is given in -+ * one byte in bits! (prefix length + whole number of label stack bits), so even if -+ * prefix is 0 length, (each label takes 3 bytes) floor(256/24) = 10 -+ * -+ * It is impossible to pass more than 10 labels by BGP-LU - */ --#define BGP_MAX_LABELS 2 -+#define BGP_MAX_LABELS 10 - - /* MPLS label(s) - VNI(s) for EVPN-VxLAN */ - struct bgp_labels { -@@ -38,19 +44,22 @@ extern int bgp_reg_for_label_callback(mpls_label_t new_label, void *labelid, - extern void bgp_reg_dereg_for_label(struct bgp_dest *dest, - struct bgp_path_info *pi, bool reg); - extern int bgp_parse_fec_update(void); --extern mpls_label_t bgp_adv_label(struct bgp_dest *dest, -- struct bgp_path_info *pi, struct peer *to, -- afi_t afi, safi_t safi); -+extern void bgp_adv_label(struct bgp_dest *dest, struct bgp_path_info *pi, struct peer *to, -+ afi_t afi, safi_t safi, mpls_label_t *labels, uint8_t *num_labels); - - extern int bgp_nlri_parse_label(struct peer *peer, struct attr *attr, - struct bgp_nlri *packet); - extern uint32_t decode_label(mpls_label_t *label); -+extern void encode_label_bos(mpls_label_t label_in, mpls_label_t *label_out, bool bos); - extern void encode_label(mpls_label_t label_in, mpls_label_t *label_out); - extern bool bgp_labels_same(const mpls_label_t *tbl_a, - const uint8_t num_labels_a, - const mpls_label_t *tbl_b, - const uint8_t num_labels_b); - extern bool bgp_labels_is_implicit_null(struct bgp_path_info *pi); -+/* Write labels to str of format "prefixLABEL1/LABEL2/.../LABELN", prefix may be NULL */ -+extern char *mpls_labels2str(mpls_label_t *labels, uint8_t num_labels, const char *prefix, -+ char *buf, int size); - - static inline int bgp_labeled_safi(safi_t safi) - { -@@ -115,4 +124,13 @@ static inline uint8_t label_bos(mpls_label_t *label) - return (t[2] & 0x01); - }; - -+/* Set BOS to 1 */ -+static inline void label_set_bos(mpls_label_t *label) -+{ -+ uint8_t *t = (uint8_t *)label; -+ -+ if (t) -+ t[2] |= 0x01; -+} -+ - #endif /* _BGP_LABEL_H */ -diff --git a/bgpd/bgp_route.c b/bgpd/bgp_route.c -index 307e6fe94d..1857e2f2c0 100644 ---- a/bgpd/bgp_route.c -+++ b/bgpd/bgp_route.c -@@ -2170,8 +2170,9 @@ bool subgroup_announce_check(struct bgp_dest *dest, struct bgp_path_info *pi, - int samepeer_safe = 0; /* for synthetic mplsvpns routes */ - bool nh_reset = false; - uint64_t cum_bw; -- mpls_label_t label; - bool global_and_ll = false; -+ uint8_t num_labels; -+ mpls_label_t labels[BGP_MAX_LABELS] = { MPLS_INVALID_LABEL }; - - if (DISABLE_BGP_ANNOUNCE) - return false; -@@ -2256,13 +2257,11 @@ bool subgroup_announce_check(struct bgp_dest *dest, struct bgp_path_info *pi, - - /* If it's labeled safi, make sure the route has a valid label. */ - if (safi == SAFI_LABELED_UNICAST) { -- label = bgp_adv_label(dest, pi, peer, afi, safi); -- if (!bgp_is_valid_label(&label)) { -+ bgp_adv_label(dest, pi, peer, afi, safi, labels, &num_labels); -+ if (!bgp_is_valid_label(&labels[0])) { - if (bgp_debug_update(NULL, p, subgrp->update_group, 0)) -- zlog_debug("u%" PRIu64 ":s%" PRIu64 -- " %pFX is filtered - no label (%p)", -- subgrp->update_group->id, subgrp->id, -- p, &label); -+ zlog_debug("u%" PRIu64 ":s%" PRIu64 " %pFX is filtered - no label", -+ subgrp->update_group->id, subgrp->id, p); - return false; - } - } else if (safi == SAFI_MPLS_VPN && -@@ -2279,8 +2278,8 @@ bool subgroup_announce_check(struct bgp_dest *dest, struct bgp_path_info *pi, - * then get appropriate mpls local label - * and check its validity - */ -- label = bgp_mplsvpn_nh_label_bind_get_label(pi); -- if (!bgp_is_valid_label(&label)) { -+ labels[0] = bgp_mplsvpn_nh_label_bind_get_label(pi); -+ if (!bgp_is_valid_label(&labels[0])) { - if (bgp_debug_update(NULL, p, subgrp->update_group, 0)) - zlog_debug("u%" PRIu64 ":s%" PRIu64 - " %pFX is filtered - no valid label", -@@ -11553,6 +11552,7 @@ void route_vty_out_detail(struct vty *vty, struct bgp *bgp, struct bgp_dest *bn, - struct attr *pattr, uint16_t show_opts) - { - char buf[INET6_ADDRSTRLEN]; -+ char labels_buf[9 * BGP_MAX_LABELS]; /* 8 per label + / or \0 for each */ - char vni_buf[30] = {}; - struct attr *attr = pattr ? pattr : path->attr; - time_t tbuf; -@@ -11560,6 +11560,7 @@ void route_vty_out_detail(struct vty *vty, struct bgp *bgp, struct bgp_dest *bn, - json_object *json_bestpath = NULL; - json_object *json_cluster_list = NULL; - json_object *json_cluster_list_list = NULL; -+ json_object *json_labels = NULL; - json_object *json_ext_community = NULL; - json_object *json_ext_ipv6_community = NULL; - json_object *json_last_update = NULL; -@@ -11570,6 +11571,7 @@ void route_vty_out_detail(struct vty *vty, struct bgp *bgp, struct bgp_dest *bn, - json_object *json_path = NULL; - json_object *json_peer = NULL; - json_object *json_string = NULL; -+ json_object *json_int = NULL; - json_object *json_adv_to = NULL; - int first = 0; - struct listnode *node, *nnode; -@@ -11580,12 +11582,7 @@ void route_vty_out_detail(struct vty *vty, struct bgp *bgp, struct bgp_dest *bn, - bool nexthop_self = - CHECK_FLAG(path->flags, BGP_PATH_ANNC_NH_SELF) ? true : false; - int i; -- char *nexthop_hostname = -- bgp_nexthop_hostname(path->peer, path->nexthop); -- uint32_t ttl = 0; -- uint32_t bos = 0; -- uint32_t exp = 0; -- mpls_label_t label = MPLS_INVALID_LABEL; -+ char *nexthop_hostname = bgp_nexthop_hostname(path->peer, path->nexthop); - struct bgp_path_info *bpi_ultimate = - bgp_get_imported_bpi_ultimate(path); - struct bgp_route_evpn *bre = bgp_attr_get_evpn_overlay(attr); -@@ -12418,13 +12415,28 @@ void route_vty_out_detail(struct vty *vty, struct bgp *bgp, struct bgp_dest *bn, - /* Remote Label */ - if (bgp_path_info_has_valid_label(path) && - (safi != SAFI_EVPN && !is_route_parent_evpn(path))) { -- mpls_lse_decode(path->extra->labels->label[0], &label, &ttl, -- &exp, &bos); -- -- if (json_paths) -- json_object_int_add(json_path, "remoteLabel", label); -- else -- vty_out(vty, " Remote label: %d\n", label); -+ if (json_paths) { -+#if CONFDATE > 20261107 -+ CPP_NOTICE("Remove 'remoteLabel' field"); -+#endif -+ /* For backward compatibility write top label to remoteLabel */ -+ json_object_int_add(json_path, "remoteLabel", -+ decode_label(&path->extra->labels->label[0])); -+ -+ /* Write full stack to remoteLabels */ -+ json_labels = json_object_new_array(); -+ for (int label_index = 0; label_index < path->extra->labels->num_labels; -+ label_index++) { -+ json_int = json_object_new_int( -+ decode_label(&path->extra->labels->label[label_index])); -+ json_object_array_add(json_labels, json_int); -+ } -+ json_object_object_add(json_path, "remoteLabels", json_labels); -+ } else { -+ mpls_labels2str(path->extra->labels->label, path->extra->labels->num_labels, -+ NULL, labels_buf, sizeof(labels_buf)); -+ vty_out(vty, " Remote labels: %s\n", labels_buf); -+ } - } - - /* Remote SID */ -diff --git a/bgpd/bgp_updgrp_packet.c b/bgpd/bgp_updgrp_packet.c -index 141cb81efc..449173d93f 100644 ---- a/bgpd/bgp_updgrp_packet.c -+++ b/bgpd/bgp_updgrp_packet.c -@@ -681,7 +681,8 @@ struct bpacket *subgroup_update_packet(struct update_subgroup *subgrp) - int addpath_overhead = 0; - uint32_t addpath_tx_id = 0; - struct prefix_rd *prd = NULL; -- mpls_label_t label = MPLS_INVALID_LABEL, *label_pnt = NULL; -+ mpls_label_t *label_pnt = NULL; -+ mpls_label_t labels[BGP_MAX_LABELS] = { MPLS_INVALID_LABEL }; - uint8_t num_labels = 0; - - if (!subgrp) -@@ -804,10 +805,8 @@ struct bpacket *subgroup_update_packet(struct update_subgroup *subgrp) - dest->pdest); - - if (safi == SAFI_LABELED_UNICAST) { -- label = bgp_adv_label(dest, path, peer, afi, -- safi); -- label_pnt = &label; -- num_labels = 1; -+ bgp_adv_label(dest, path, peer, afi, safi, labels, &num_labels); -+ label_pnt = &labels[0]; - } else if (safi == SAFI_MPLS_VPN && path && - CHECK_FLAG(path->flags, - BGP_PATH_MPLSVPN_NH_LABEL_BIND) && -@@ -827,9 +826,8 @@ struct bpacket *subgroup_update_packet(struct update_subgroup *subgrp) - * called here, 'get_label()' returns a valid - * label. - */ -- label = bgp_mplsvpn_nh_label_bind_get_label( -- path); -- label_pnt = &label; -+ labels[0] = bgp_mplsvpn_nh_label_bind_get_label(path); -+ label_pnt = &labels[0]; - num_labels = 1; - } else { - num_labels = BGP_PATH_INFO_NUM_LABELS(path); -diff --git a/bgpd/bgp_zebra.c b/bgpd/bgp_zebra.c -index 0658beffa6..1d33883e87 100644 ---- a/bgpd/bgp_zebra.c -+++ b/bgpd/bgp_zebra.c -@@ -1314,11 +1314,10 @@ static void bgp_zebra_announce_parse_nexthop( - - if (bgp_debug_zebra(&api->prefix)) { - if (BGP_PATH_INFO_NUM_LABELS(mpinfo)) { -- zlog_debug("%s: p=%pFX, bgp_is_valid_label: %d", -+ zlog_debug("%s: p=%pFX, bgp_is_valid_label: %d, num_labels: %d", - __func__, p, -- bgp_is_valid_label( -- &mpinfo->extra->labels -- ->label[0])); -+ bgp_is_valid_label(&mpinfo->extra->labels->label[0]), -+ BGP_PATH_INFO_NUM_LABELS(mpinfo)); - } else { - zlog_debug("%s: p=%pFX, no label", __func__, p); - } -@@ -1397,15 +1396,28 @@ static void bgp_zebra_announce_parse_nexthop( - nh_label = *bgp_evpn_path_info_labels_get_l3vni( - labels, num_labels); - nh_label_type = ZEBRA_LSP_EVPN; -+ -+ api_nh->label_num = 1; -+ api_nh->labels[0] = nh_label; - } else { -- mpls_lse_decode(labels[0], &nh_label, &ttl, -- &exp, &bos); -+ uint8_t i; -+ int max_elements = sizeof(api_nh->labels) / -+ sizeof(api_nh->labels[0]); -+ -+ api_nh->label_num = num_labels; -+ if (api_nh->label_num > max_elements) { -+ zlog_warn("%s: too many labels provided for zebra (%d), copying first %d", -+ __func__, num_labels, max_elements); -+ api_nh->label_num = max_elements; -+ } -+ for (i = 0; i < api_nh->label_num; i++) { -+ mpls_lse_decode(labels[i], &api_nh->labels[i], &ttl, &exp, -+ &bos); -+ } - } - - SET_FLAG(api_nh->flags, ZAPI_NEXTHOP_FLAG_LABEL); -- api_nh->label_num = 1; - api_nh->label_type = nh_label_type; -- api_nh->labels[0] = nh_label; - } - - if (is_evpn && !(bre && bre->type == OVERLAY_INDEX_GATEWAY_IP)) -@@ -1462,7 +1474,8 @@ static void bgp_debug_zebra_nh(struct zapi_route *api) - char nh_buf[INET6_ADDRSTRLEN]; - char eth_buf[ETHER_ADDR_STRLEN + 7] = { '\0' }; - char buf1[ETHER_ADDR_STRLEN]; -- char label_buf[20]; -+ /* strlen("label ") + 8 chars per label + '/' or '\0' for each label */ -+ char label_buf[6 + 9 * BGP_MAX_LABELS]; - char sid_buf[20]; - char segs_buf[256]; - struct zapi_nexthop *api_nh; -@@ -1500,9 +1513,19 @@ static void bgp_debug_zebra_nh(struct zapi_route *api) - eth_buf[0] = '\0'; - segs_buf[0] = '\0'; - if (CHECK_FLAG(api_nh->flags, ZAPI_NEXTHOP_FLAG_LABEL) && -- !CHECK_FLAG(api_nh->flags, ZAPI_NEXTHOP_FLAG_EVPN)) -- snprintf(label_buf, sizeof(label_buf), "label %u", -- api_nh->labels[0]); -+ !CHECK_FLAG(api_nh->flags, ZAPI_NEXTHOP_FLAG_EVPN)) { -+ int label_len; -+ int j; -+ -+ label_len = snprintf(label_buf, sizeof(label_buf), "label %u", -+ api_nh->labels[0]); -+ -+ for (j = 1; j < api_nh->label_num; j++) { -+ label_len += snprintf(label_buf + label_len, -+ sizeof(label_buf) - label_len, "/%u", -+ api_nh->labels[j]); -+ } -+ } - if (CHECK_FLAG(api_nh->flags, ZAPI_NEXTHOP_FLAG_SEG6) && - !CHECK_FLAG(api_nh->flags, ZAPI_NEXTHOP_FLAG_EVPN)) { - inet_ntop(AF_INET6, &api_nh->seg6_segs[0], sid_buf, --- -2.50.1 - diff --git a/scripts/package-build/frr/patches/frr/0011-zebra-add-CLI-no-versions-for-max-bw-and-others.patch b/scripts/package-build/frr/patches/frr/0011-zebra-add-CLI-no-versions-for-max-bw-and-others.patch deleted file mode 100644 index 07d2d18d..00000000 --- a/scripts/package-build/frr/patches/frr/0011-zebra-add-CLI-no-versions-for-max-bw-and-others.patch +++ /dev/null @@ -1,222 +0,0 @@ -From 31a0930f89c6de17e8dcc83543f75dab96cd99cf Mon Sep 17 00:00:00 2001 -From: Kyrylo Yatsenko <hedrok@gmail.com> -Date: Fri, 19 Dec 2025 10:40:46 +0200 -Subject: [PATCH] zebra: add CLI 'no' versions for max-bw and others - -Add '[no]' versions for commands: - -* max-bw -* max-rsv-bw -* unrsv-bw - -Without these frr-reload failed after deletion of link-params with these commands. - -For them to work update: - -* lib_interface_zebra_link_params_max_bandwidth_destroy -* lib_interface_zebra_link_params_max_reservable_bandwidth_destroy -* lib_interface_zebra_link_params_unreserved_bandwidths_unreserved_bandwidth_destroy - -All three forbid to destroy these parameters. New code allows it and -sets value of these parameters to default_bw on removal. - -Fixes: #20310 -Signed-off-by: Kyrylo Yatsenko <hedrok@gmail.com> ---- - zebra/zebra_cli.c | 66 +++++++++++++++++++++++++---------------- - zebra/zebra_nb_config.c | 49 ++++++++++++++++++++---------- - 2 files changed, 75 insertions(+), 40 deletions(-) - -diff --git a/zebra/zebra_cli.c b/zebra/zebra_cli.c -index befb3ec6be..3d04c1513d 100644 ---- a/zebra/zebra_cli.c -+++ b/zebra/zebra_cli.c -@@ -379,21 +379,25 @@ static void lib_interface_zebra_link_params_metric_cli_write( - - DEFPY_YANG (link_params_maxbw, - link_params_maxbw_cmd, -- "max-bw BANDWIDTH", -+ "[no] max-bw BANDWIDTH", -+ NO_STR - "Maximum bandwidth that can be used\n" - "Bytes/second (IEEE floating point format)\n") - { - char value[YANG_VALUE_MAXLEN]; - float bw; - -- if (sscanf(bandwidth, "%g", &bw) != 1) { -- vty_out(vty, "Invalid bandwidth value\n"); -- return CMD_WARNING_CONFIG_FAILED; -- } -+ if (!no) { -+ if (sscanf(bandwidth, "%g", &bw) != 1) { -+ vty_out(vty, "Invalid bandwidth value\n"); -+ return CMD_WARNING_CONFIG_FAILED; -+ } - -- snprintf(value, sizeof(value), "%a", bw); -+ snprintf(value, sizeof(value), "%a", bw); - -- nb_cli_enqueue_change(vty, "./max-bandwidth", NB_OP_MODIFY, value); -+ nb_cli_enqueue_change(vty, "./max-bandwidth", NB_OP_MODIFY, value); -+ } else -+ nb_cli_enqueue_change(vty, "./max-bandwidth", NB_OP_DESTROY, NULL); - - return nb_cli_apply_changes(vty, NULL); - } -@@ -408,22 +412,25 @@ static void lib_interface_zebra_link_params_max_bandwidth_cli_write( - - DEFPY_YANG (link_params_max_rsv_bw, - link_params_max_rsv_bw_cmd, -- "max-rsv-bw BANDWIDTH", -+ "[no] max-rsv-bw BANDWIDTH", -+ NO_STR - "Maximum bandwidth that may be reserved\n" - "Bytes/second (IEEE floating point format)\n") - { - char value[YANG_VALUE_MAXLEN]; - float bw; - -- if (sscanf(bandwidth, "%g", &bw) != 1) { -- vty_out(vty, "Invalid bandwidth value\n"); -- return CMD_WARNING_CONFIG_FAILED; -- } -+ if (!no) { -+ if (sscanf(bandwidth, "%g", &bw) != 1) { -+ vty_out(vty, "Invalid bandwidth value\n"); -+ return CMD_WARNING_CONFIG_FAILED; -+ } - -- snprintf(value, sizeof(value), "%a", bw); -+ snprintf(value, sizeof(value), "%a", bw); - -- nb_cli_enqueue_change(vty, "./max-reservable-bandwidth", NB_OP_MODIFY, -- value); -+ nb_cli_enqueue_change(vty, "./max-reservable-bandwidth", NB_OP_MODIFY, value); -+ } else -+ nb_cli_enqueue_change(vty, "./max-reservable-bandwidth", NB_OP_DESTROY, NULL); - - return nb_cli_apply_changes(vty, NULL); - } -@@ -439,7 +446,8 @@ static void lib_interface_zebra_link_params_max_reservable_bandwidth_cli_write( - - DEFPY_YANG (link_params_unrsv_bw, - link_params_unrsv_bw_cmd, -- "unrsv-bw (0-7)$priority BANDWIDTH", -+ "[no] unrsv-bw (0-7)$priority BANDWIDTH", -+ NO_STR - "Unreserved bandwidth at each priority level\n" - "Priority\n" - "Bytes/second (IEEE floating point format)\n") -@@ -448,17 +456,25 @@ DEFPY_YANG (link_params_unrsv_bw, - char value[YANG_VALUE_MAXLEN]; - float bw; - -- if (sscanf(bandwidth, "%g", &bw) != 1) { -- vty_out(vty, "Invalid bandwidth value\n"); -- return CMD_WARNING_CONFIG_FAILED; -- } -+ if (!no) { -+ if (sscanf(bandwidth, "%g", &bw) != 1) { -+ vty_out(vty, "Invalid bandwidth value\n"); -+ return CMD_WARNING_CONFIG_FAILED; -+ } - -- snprintf(xpath, sizeof(xpath), -- "./unreserved-bandwidths/unreserved-bandwidth[priority='%s']/unreserved-bandwidth", -- priority_str); -- snprintf(value, sizeof(value), "%a", bw); -+ snprintf(xpath, sizeof(xpath), -+ "./unreserved-bandwidths/unreserved-bandwidth[priority='%s']/unreserved-bandwidth", -+ priority_str); -+ snprintf(value, sizeof(value), "%a", bw); - -- nb_cli_enqueue_change(vty, xpath, NB_OP_MODIFY, value); -+ nb_cli_enqueue_change(vty, xpath, NB_OP_MODIFY, value); -+ } else { -+ snprintf(xpath, sizeof(xpath), -+ "./unreserved-bandwidths/unreserved-bandwidth[priority='%s']", -+ priority_str); -+ -+ nb_cli_enqueue_change(vty, xpath, NB_OP_DESTROY, NULL); -+ } - - return nb_cli_apply_changes(vty, NULL); - } -diff --git a/zebra/zebra_nb_config.c b/zebra/zebra_nb_config.c -index 38533eb24b..5a2e8fe0e7 100644 ---- a/zebra/zebra_nb_config.c -+++ b/zebra/zebra_nb_config.c -@@ -1459,11 +1459,16 @@ int lib_interface_zebra_link_params_max_bandwidth_modify( - int lib_interface_zebra_link_params_max_bandwidth_destroy( - struct nb_cb_destroy_args *args) - { -- if (args->event == NB_EV_VALIDATE) { -- snprintfrr(args->errmsg, args->errmsg_len, -- "Removing max-bandwidth is not allowed"); -- return NB_ERR_VALIDATION; -- } -+ struct interface *ifp; -+ struct if_link_params *iflp; -+ -+ if (args->event != NB_EV_APPLY) -+ return NB_OK; -+ -+ ifp = nb_running_get_entry(args->dnode, NULL, true); -+ iflp = if_link_params_get(ifp); -+ if (iflp) -+ link_param_cmd_set_float(ifp, &iflp->max_bw, LP_MAX_BW, iflp->default_bw); - - return NB_OK; - } -@@ -1494,11 +1499,16 @@ int lib_interface_zebra_link_params_max_reservable_bandwidth_modify( - int lib_interface_zebra_link_params_max_reservable_bandwidth_destroy( - struct nb_cb_destroy_args *args) - { -- if (args->event == NB_EV_VALIDATE) { -- snprintfrr(args->errmsg, args->errmsg_len, -- "Removing max-reservable-bandwidth is not allowed"); -- return NB_ERR_VALIDATION; -- } -+ struct interface *ifp; -+ struct if_link_params *iflp; -+ -+ if (args->event != NB_EV_APPLY) -+ return NB_OK; -+ -+ ifp = nb_running_get_entry(args->dnode, NULL, true); -+ iflp = if_link_params_get(ifp); -+ if (iflp) -+ link_param_cmd_set_float(ifp, &iflp->max_rsv_bw, LP_MAX_RSV_BW, iflp->default_bw); - - return NB_OK; - } -@@ -1532,11 +1542,20 @@ int lib_interface_zebra_link_params_unreserved_bandwidths_unreserved_bandwidth_c - int lib_interface_zebra_link_params_unreserved_bandwidths_unreserved_bandwidth_destroy( - struct nb_cb_destroy_args *args) - { -- if (args->event == NB_EV_VALIDATE) { -- snprintfrr(args->errmsg, args->errmsg_len, -- "Removing unreserved-bandwidth is not allowed"); -- return NB_ERR_VALIDATION; -- } -+ struct interface *ifp; -+ struct if_link_params *iflp; -+ uint8_t priority; -+ -+ if (args->event != NB_EV_APPLY) -+ return NB_OK; -+ -+ priority = yang_dnode_get_uint8(args->dnode, "priority"); -+ -+ ifp = nb_running_get_entry(args->dnode, NULL, true); -+ iflp = if_link_params_get(ifp); -+ if (iflp) -+ link_param_cmd_set_float(ifp, &iflp->unrsv_bw[priority], LP_UNRSV_BW, -+ iflp->default_bw); - - return NB_OK; - } --- -2.50.1 - diff --git a/scripts/package-build/frr/patches/frr/0014-Simplify-and-cleanup-mgmtd.patch b/scripts/package-build/frr/patches/frr/0014-Simplify-and-cleanup-mgmtd.patch deleted file mode 100644 index 3e646328..00000000 --- a/scripts/package-build/frr/patches/frr/0014-Simplify-and-cleanup-mgmtd.patch +++ /dev/null @@ -1,12048 +0,0 @@ -From acc1741e372a209a2c0583b11eeee8409f07e264 Mon Sep 17 00:00:00 2001 -From: Christian Hopps <chopps@labn.net> -Date: Sun, 16 Nov 2025 07:57:08 +0000 -Subject: [PATCH] Simplify and cleanup mgmtd - -PR https://github.com/FRRouting/frr/pull/20088 - -Squashed all 18 commits for easier patch manipulation. - -Used commits: - -* d241d414ea mgmtd: fixes post reorganization -* 47f5704bc2 mgmtd: reorganize and cleanup the code -* 91f71c916d mgmtd: txn: remove copies of globals and simplify init/cleanup -* ec273b53c8 mgmtd: move mgmt vty code out of share lib and into mgmtd -* db9dc758c3 mgmtd: backend cleanup, remove locks and unused functions -* a9d106ce9d mgmtd: remove unneeded lock/unlock (ref-count) on front-end adapters -* 0c0a438ef4 mgmtd: front-end locking and candidate restore cleanup -* 51351b8e23 mgmtd: rename MGMT_TXN_LOCK to TXN_INCREF -* 5357a1befe mgmtd: standardize the clients sent and clients wait logic -* fc7fc581e2 mgmtd: txn: refactor txn req structs -* b7e995f72a mgmtd: simplify the config code/state machine -* 9c0a02d598 lib: adapt mgmtd backend client code to simplification changes -* 26190c2a4b lib: mgmtd: improve debug statement in front-end client code -* bbc2792605 lib: mgmt_msg: add msg code to string function -* 0dbc0c73bf lib: mgmt_msg: turn down the debug output -* 8fde52f524 lib: change define so it can be passed as function "ptr". -* 2345e038ab tools: recognize new FOREACH_BE_ADAPTER_BITS macro -* 3e379b2a89 lib: add the `vty` arg to the start/end config callbacks ---- - .clang-format | 1 + - bgpd/bgp_vty.c | 4 +- - isisd/isis_main.c | 4 +- - lib/command.h | 4 +- - lib/lib_vty.c | 22 +- - lib/mgmt_be_client.c | 353 +++--- - lib/mgmt_be_client.h | 8 +- - lib/mgmt_fe_client.c | 16 +- - lib/mgmt_fe_client.h | 8 +- - lib/mgmt_msg.c | 95 +- - lib/mgmt_msg_native.c | 64 +- - lib/mgmt_msg_native.h | 12 +- - lib/northbound_cli.c | 43 +- - lib/vty.c | 780 +----------- - lib/vty.h | 41 +- - mgmtd/mgmt.c | 6 +- - mgmtd/mgmt.h | 33 +- - mgmtd/mgmt_be_adapter.c | 973 +++++++-------- - mgmtd/mgmt_be_adapter.h | 118 +- - mgmtd/mgmt_ds.c | 98 +- - mgmtd/mgmt_ds.h | 26 +- - mgmtd/mgmt_fe_adapter.c | 1848 ++++++++++++++-------------- - mgmtd/mgmt_fe_adapter.h | 87 +- - mgmtd/mgmt_history.c | 16 +- - mgmtd/mgmt_main.c | 2 + - mgmtd/mgmt_memory.c | 3 - - mgmtd/mgmt_memory.h | 3 - - mgmtd/mgmt_txn.c | 2421 ++++++++----------------------------- - mgmtd/mgmt_txn.h | 282 ++--- - mgmtd/mgmt_txn_cfg.c | 1096 +++++++++++++++++ - mgmtd/mgmt_txn_priv.h | 80 ++ - mgmtd/mgmt_vty.c | 66 +- - mgmtd/mgmt_vty_frontend.c | 898 ++++++++++++++ - mgmtd/subdir.am | 3 + - ospf6d/ospf6_main.c | 4 +- - ospfd/ospf_main.c | 4 +- - sharpd/sharp_main.c | 4 +- - tools/checkpatch.pl | 2 +- - watchfrr/watchfrr.c | 4 +- - 39 files changed, 4604 insertions(+), 4928 deletions(-) - create mode 100644 mgmtd/mgmt_txn_cfg.c - create mode 100644 mgmtd/mgmt_txn_priv.h - create mode 100644 mgmtd/mgmt_vty_frontend.c - -diff --git a/.clang-format b/.clang-format -index 89f554508a..f92767967a 100644 ---- a/.clang-format -+++ b/.clang-format -@@ -105,6 +105,7 @@ ForEachMacros: - - 'FOREACH_AFI_SAFI' - - 'FOREACH_AFI_SAFI_NSF' - - 'FOREACH_BE_APPLY_BATCH_IN_LIST' -+ - 'FOREACH_BE_ADAPTER_BITS' - - 'FOREACH_BE_CLIENT_BITS' - - 'FOREACH_BE_TXN_BATCH_IN_LIST' - - 'FOREACH_BE_TXN_IN_LIST' -diff --git a/bgpd/bgp_vty.c b/bgpd/bgp_vty.c -index e21eeae317..a953790112 100644 ---- a/bgpd/bgp_vty.c -+++ b/bgpd/bgp_vty.c -@@ -20668,7 +20668,7 @@ static void bgp_config_end_timeout(struct event *t) - bgp_config_finish(t); - } - --static void bgp_config_start(void) -+static void bgp_config_start(struct vty *vty) - { - event_cancel(&t_bgp_cfg); - event_add_timer(bm->master, bgp_config_end_timeout, NULL, -@@ -20680,7 +20680,7 @@ static void bgp_config_start(void) - * EoR before route-maps are processed. - * This is especially valid if using `bgp route-map delay-timer`. - */ --static void bgp_config_end(void) -+static void bgp_config_end(struct vty *vty) - { - #define BGP_POST_CONFIG_DELAY_SECONDS 1 - uint32_t bgp_post_config_delay = -diff --git a/isisd/isis_main.c b/isisd/isis_main.c -index a38e4ae54e..042cfd7466 100644 ---- a/isisd/isis_main.c -+++ b/isisd/isis_main.c -@@ -214,14 +214,14 @@ static void isis_config_end_timeout(struct event *t) - isis_config_finish(t); - } - --static void isis_config_start(void) -+static void isis_config_start(struct vty *vty) - { - event_cancel(&t_isis_cfg); - event_add_timer(im->master, isis_config_end_timeout, NULL, - ISIS_PRE_CONFIG_MAX_WAIT_SECONDS, &t_isis_cfg); - } - --static void isis_config_end(void) -+static void isis_config_end(struct vty *vty) - { - /* If ISIS config processing thread isn't running, then - * we can return and rely it's properly handled. -diff --git a/lib/command.h b/lib/command.h -index ea08a74d6a..d5119fd97c 100644 ---- a/lib/command.h -+++ b/lib/command.h -@@ -611,8 +611,8 @@ extern int cmd_execute_command(vector, struct vty *, - extern int cmd_execute_command_strict(vector, struct vty *, - const struct cmd_element **); - extern void cmd_init(int terminal); --extern void cmd_init_config_callbacks(void (*start_config_cb)(void), -- void (*end_config_cb)(void)); -+extern void cmd_init_config_callbacks(void (*start_config_cb)(struct vty *vty), -+ void (*end_config_cb)(struct vty *vty)); - extern void cmd_terminate(void); - extern void cmd_exit(struct vty *vty); - extern int cmd_list_cmds(struct vty *vty, int do_permute); -diff --git a/lib/lib_vty.c b/lib/lib_vty.c -index 66dda7d0b4..1b476bca8b 100644 ---- a/lib/lib_vty.c -+++ b/lib/lib_vty.c -@@ -207,8 +207,8 @@ DEFUN (frr_version, - static struct call_back { - time_t readin_time; - -- void (*start_config)(void); -- void (*end_config)(void); -+ void (*start_config)(struct vty *vty); -+ void (*end_config)(struct vty *vty); - } callback; - - -@@ -220,7 +220,7 @@ DEFUN_NOSH(start_config, start_config_cmd, "XFRR_start_configuration", - vty->pending_allowed = 1; - - if (callback.start_config) -- (*callback.start_config)(); -+ (*callback.start_config)(vty); - - return CMD_SUCCESS; - } -@@ -238,6 +238,7 @@ DEFUN_NOSH(end_config, end_config_cmd, "XFRR_end_configuration", - frrtime_to_interval(readin_time, readin_time_str, - sizeof(readin_time_str)); - -+ /* This is also getting cleared in the config node exit */ - vty->pending_allowed = 0; - ret = nb_cli_pending_commit_check(vty); - -@@ -245,23 +246,14 @@ DEFUN_NOSH(end_config, end_config_cmd, "XFRR_end_configuration", - zlog_debug("%s: VTY:%p, pending SET-CFG: %u", __func__, vty, - (uint32_t)vty->mgmt_num_pending_setcfg); - -- /* -- * If (and only if) we have sent any CLI config commands to MGMTd -- * FE interface using vty_mgmt_send_config_data() without implicit -- * commit before, should we need to send an explicit COMMIT-REQ now -- * to apply all those commands at once. -- */ -- if (vty->mgmt_num_pending_setcfg && vty_mgmt_fe_enabled()) -- vty_mgmt_send_commit_config(vty, false, false, false); -- - if (callback.end_config) -- (*callback.end_config)(); -+ (*callback.end_config)(vty); - - return ret; - } - --void cmd_init_config_callbacks(void (*start_config_cb)(void), -- void (*end_config_cb)(void)) -+void cmd_init_config_callbacks(void (*start_config_cb)(struct vty *vty), -+ void (*end_config_cb)(struct vty *vty)) - { - callback.start_config = start_config_cb; - callback.end_config = end_config_cb; -diff --git a/lib/mgmt_be_client.c b/lib/mgmt_be_client.c -index 6a5ca6649f..6a611d7611 100644 ---- a/lib/mgmt_be_client.c -+++ b/lib/mgmt_be_client.c -@@ -33,7 +33,6 @@ struct be_oper_iter_arg { - struct lyd_node *hint; /* last node added */ - }; - --PREDECL_LIST(mgmt_be_txns); - struct mgmt_be_txn_ctx { - /* Txn-Id as assigned by MGMTD */ - uint64_t txn_id; -@@ -42,21 +41,17 @@ struct mgmt_be_txn_ctx { - struct mgmt_be_client_txn_ctx client_data; - struct mgmt_be_client *client; - -- struct mgmt_be_txns_item list_linkage; -- -+ struct nb_config *candidate_config; - struct nb_transaction *nb_txn; - uint32_t nb_txn_id; - }; - #define MGMTD_BE_TXN_FLAGS_CFGPREP_FAILED (1U << 1) - --DECLARE_LIST(mgmt_be_txns, struct mgmt_be_txn_ctx, list_linkage); -- - struct mgmt_be_client { - struct msg_client client; - - char *name; - -- struct nb_config *candidate_config; - struct nb_config *running_config; - - uint64_t num_edit_nb_cfg; -@@ -66,15 +61,12 @@ struct mgmt_be_client { - uint64_t num_apply_nb_cfg; - uint64_t avg_apply_nb_cfg_tm; - -- struct mgmt_be_txns_head txn_head; -+ struct mgmt_be_txn_ctx *config_txn; - - struct mgmt_be_client_cbs cbs; - uintptr_t user_data; - }; - --#define FOREACH_BE_TXN_IN_LIST(client_ctx, txn) \ -- frr_each_safe (mgmt_be_txns, &(client_ctx)->txn_head, (txn)) -- - struct debug mgmt_dbg_be_client = { - .conf = "debug mgmt client backend", - .desc = "Management backend client operations", -@@ -96,15 +88,10 @@ static struct mgmt_be_txn_ctx * - mgmt_be_find_txn_by_id(struct mgmt_be_client *client_ctx, uint64_t txn_id, - bool warn) - { -- struct mgmt_be_txn_ctx *txn = NULL; -- -- FOREACH_BE_TXN_IN_LIST (client_ctx, txn) -- if (txn->txn_id == txn_id) -- return txn; -+ if (client_ctx->config_txn && client_ctx->config_txn->txn_id == txn_id) -+ return client_ctx->config_txn; - if (warn) -- log_err_be_client("client %s unkonwn txn-id: %" PRIu64, -- client_ctx->name, txn_id); -- -+ log_err_be_client("client %s unkonwn txn-id: %Lu", client_ctx->name, txn_id); - return NULL; - } - -@@ -123,53 +110,54 @@ mgmt_be_txn_create(struct mgmt_be_client *client_ctx, uint64_t txn_id) - txn = XCALLOC(MTYPE_MGMTD_BE_TXN, sizeof(struct mgmt_be_txn_ctx)); - txn->txn_id = txn_id; - txn->client = client_ctx; -- mgmt_be_txns_add_tail(&client_ctx->txn_head, txn); -+ /* -+ * NOTE: If this is ends up being expensive then we should just offload -+ * creating one in a thread whenever updating running_config. -+ */ -+ txn->candidate_config = nb_config_dup(client_ctx->running_config); -+ if (!txn->candidate_config) { -+ log_err_be_client("Failed to create candidate config for txn-id: %Lu", txn_id); -+ XFREE(MTYPE_MGMTD_BE_TXN, txn); -+ return NULL; -+ } -+ /* record that we have one going */ -+ client_ctx->config_txn = txn; - - debug_be_client("Created new txn-id: %" PRIu64, txn_id); - - return txn; - } - --static void mgmt_be_txn_delete(struct mgmt_be_client *client_ctx, -- struct mgmt_be_txn_ctx **txn) -+static void mgmt_be_txn_delete(struct mgmt_be_txn_ctx *txn) - { -+ struct mgmt_be_client *client_ctx; - char err_msg[] = "MGMT Transaction Delete"; - - if (!txn) - return; -- - /* -- * Remove the transaction from the list of transactions -- * so that future lookups with the same transaction id -- * does not return this one. -+ * Stop tracking this transaction from the client context - */ -- mgmt_be_txns_del(&client_ctx->txn_head, *txn); -+ client_ctx = txn->client; -+ assert(client_ctx->config_txn == txn); -+ client_ctx->config_txn = NULL; - - /* -- * Time to delete the transaction which should also take care of -- * cleaning up all batches created via CFG_REQs. But first notify the -- * client about the transaction delete. -+ * Delete the northbound transaction which should also take care -+ * of cleaning up any allocations made so far. - */ -- if (client_ctx->cbs.txn_notify) -- (void)(*client_ctx->cbs.txn_notify)(client_ctx, -- client_ctx->user_data, -- &(*txn)->client_data, true); -+ if (txn->nb_txn) -+ nb_candidate_commit_abort(txn->nb_txn, err_msg, sizeof(err_msg)); - -- if ((*txn)->nb_txn) -- nb_candidate_commit_abort((*txn)->nb_txn, err_msg, -- sizeof(err_msg)); -- XFREE(MTYPE_MGMTD_BE_TXN, *txn); -+ nb_config_free(txn->candidate_config); - -- *txn = NULL; -+ XFREE(MTYPE_MGMTD_BE_TXN, txn); - } - - static void mgmt_be_cleanup_all_txns(struct mgmt_be_client *client_ctx) - { -- struct mgmt_be_txn_ctx *txn = NULL; -- -- FOREACH_BE_TXN_IN_LIST (client_ctx, txn) { -- mgmt_be_txn_delete(client_ctx, &txn); -- } -+ mgmt_be_txn_delete(client_ctx->config_txn); -+ assert(client_ctx->config_txn == NULL); - } - - -@@ -308,65 +296,10 @@ static int mgmt_be_send_notification(void *__client, const char *path, const str - return 0; - } - --static int mgmt_be_send_txn_reply(struct mgmt_be_client *client, uint64_t txn_id, bool create) --{ -- struct mgmt_msg_txn_reply *msg; -- int ret; -- -- msg = mgmt_msg_native_alloc_msg(struct mgmt_msg_txn_reply, 0, MTYPE_MSG_NATIVE_TXN_REPLY); -- msg->code = MGMT_MSG_CODE_TXN_REPLY; -- msg->refer_id = txn_id; -- msg->created = create; -- -- debug_be_client("Sending TXN_REPLY txn-id %Lu", txn_id); -- -- ret = be_client_send_native_msg(client, msg, mgmt_msg_native_get_msg_len(msg), false); -- mgmt_msg_native_free_msg(msg); -- return ret; --} -- - /* ===================== */ - /* CONFIGURATION PROCESS */ - /* ===================== */ - --/* ------------------ */ --/* Create Transaction */ --/* ------------------ */ -- --/* -- * Process transaction create message -- */ --static void be_client_handle_txn_req(struct mgmt_be_client *client, uint64_t txn_id, void *msgbuf, -- size_t msg_len) --{ -- struct mgmt_msg_txn_req *msg = msgbuf; -- struct mgmt_be_txn_ctx *txn; -- -- debug_be_client("Got TXN_REQ for client %s txn-id %Lu req-id %Lu", client->name, txn_id, -- msg->req_id); -- -- if (msg->create) { -- debug_be_client("Creating new txn-id %" PRIu64, txn_id); -- -- txn = mgmt_be_txn_create(client, txn_id); -- if (!txn) { -- msg_conn_disconnect(&client->client.conn, true); -- return; -- } -- -- if (client->cbs.txn_notify) -- (*client->cbs.txn_notify)(client, client->user_data, &txn->client_data, -- false); -- } else { -- debug_be_client("Deleting txn-id: %" PRIu64, txn_id); -- txn = mgmt_be_find_txn_by_id(client, txn_id, false); -- if (txn) -- mgmt_be_txn_delete(client, &txn); -- } -- -- mgmt_be_send_txn_reply(client, txn_id, msg->create); --} -- - /* ------------------------- */ - /* Receive Config from MgmtD */ - /* ------------------------- */ -@@ -405,17 +338,6 @@ static void mgmt_be_txn_cfg_abort(struct mgmt_be_txn_ctx *txn) - nb_candidate_commit_abort(txn->nb_txn, errmsg, sizeof(errmsg)); - txn->nb_txn = 0; - } -- -- /* -- * revert candidate back to running -- * -- * This is one txn ctx but the candidate_config is per client ctx, how -- * does that work? -- */ -- debug_be_client("Reset candidate configurations after abort of txn-id: %" PRIu64, -- txn->txn_id); -- nb_config_replace(txn->client->candidate_config, -- txn->client->running_config, true); - } - - /* ------------------------------------------------ */ -@@ -470,10 +392,29 @@ static bool be_client_change_edit_config(struct nb_config *candidate_config, con - return error; - } - --static int mgmt_be_txn_cfg_prepare(struct mgmt_be_txn_ctx *txn, const char **config) -+/** -+ * be_txn_config_prepare() - Prepare to apply the given configuration. -+ * @client_ctx: The backend client context. -+ * @txn_id: The transaction id for this configuration change. -+ * -+ * The configuration changes are applied to the candidate configuration and -+ * validated. If the configuration is valid it is saved in a northbound -+ * transaction that is saved in a mgmtd txn associated with the @txn_id. -+ * -+ * If there is a config validation error the error is returned to mgmtd. For any -+ * other error the connection is dropped. -+ * -+ * Currently the global candidate configuration is used. This will be changed to -+ * a per TXN candidate eventually. For now it is assumed that we only have one -+ * change TXN at a time. -+ * -+ * Return: true if the connection should be dropped. -+ */ -+static bool mgmt_be_txn_cfg_prepare(struct mgmt_be_client *client_ctx, uint64_t txn_id, -+ const char **config) - { -- struct mgmt_be_client *client_ctx; -- struct nb_context nb_ctx = {0}; -+ struct mgmt_be_txn_ctx *txn; -+ struct nb_context nb_ctx = { 0 }; - struct timeval edit_nb_cfg_start; - struct timeval edit_nb_cfg_end; - unsigned long edit_nb_cfg_tm; -@@ -483,58 +424,76 @@ static int mgmt_be_txn_cfg_prepare(struct mgmt_be_txn_ctx *txn, const char **con - bool error; - char err_buf[BUFSIZ]; - size_t num_processed; -+ bool disconnect = false; - int err; - -- assert(txn && txn->client); -- client_ctx = txn->client; -- - num_processed = 0; -- error = false; - nb_ctx.client = NB_CLIENT_CLI; - nb_ctx.user = (void *)client_ctx->user_data; - err_buf[0] = 0; - -+ debug_be_client("Creating new txn-id %Lu", txn_id); -+ - /* -- * this is always true, right?! -+ * First validate there's no other config txn right now. Eventually we -+ * may want to support multiple CFG_REQ prioer to a CFG_APPLY. - */ -- assert(!txn->nb_txn); -+ if (client_ctx->config_txn) { -+ log_err_be_client("Cannot prepare cfg for txn-id: %Lu, another txn-id: %Lu was in progress", -+ txn_id, client_ctx->config_txn->txn_id); -+ return true; -+ } - -- if (!txn->nb_txn) { -- /* -- * XXX, is this just an old crusty comment? -- * -- * This happens when the current backend client is only -- * interested in consuming the config items but is not -- * interested in validating it. -- */ -- gettimeofday(&edit_nb_cfg_start, NULL); -- -- error = be_client_change_edit_config(client_ctx->candidate_config, config, err_buf, -- sizeof(err_buf)); -- if (error) { -- log_err_be_client("Failed to update configs for txn-id: %" PRIu64 -- " to candidate, err: '%s'", -- txn->txn_id, err_buf); -- return -1; -- } -+ /* Create a txn to track the config changes for later apply */ -+ txn = mgmt_be_txn_create(client_ctx, txn_id); -+ if (!txn) { -+ log_err_be_client("Failed to create txn for txn_id: %Lu", txn_id); -+ return true; -+ } - -- gettimeofday(&edit_nb_cfg_end, NULL); -- edit_nb_cfg_tm = timeval_elapsed(edit_nb_cfg_end, edit_nb_cfg_start); -- client_ctx->avg_edit_nb_cfg_tm = -- ((client_ctx->avg_edit_nb_cfg_tm * client_ctx->num_edit_nb_cfg) + -- edit_nb_cfg_tm) / -- (client_ctx->num_edit_nb_cfg + 1); -- client_ctx->num_edit_nb_cfg++; -+ /* Apply the list of config changes to the candidate config */ -+ gettimeofday(&edit_nb_cfg_start, NULL); -+ error = be_client_change_edit_config(txn->candidate_config, config, err_buf, -+ sizeof(err_buf)); -+ if (error) { -+ log_err_be_client("Failed to update configs for txn-id: %" PRIu64 -+ " to candidate, err: '%s'", -+ txn->txn_id, err_buf); -+ disconnect = true; -+ goto done; - } - -+ gettimeofday(&edit_nb_cfg_end, NULL); -+ edit_nb_cfg_tm = timeval_elapsed(edit_nb_cfg_end, edit_nb_cfg_start); -+ client_ctx->avg_edit_nb_cfg_tm = -+ ((client_ctx->avg_edit_nb_cfg_tm * client_ctx->num_edit_nb_cfg) + edit_nb_cfg_tm) / -+ (client_ctx->num_edit_nb_cfg + 1); -+ client_ctx->num_edit_nb_cfg++; -+ -+ /* Create a northbound transaction and prepare for the changes */ -+ -+ /* -+ * XXX this takes the candidate config we just applied a list of config -+ * changes to, creates a diff, then creates a new list of changes, -+ * again, so that we can use that to call the callbacks. Would should -+ * just use the original list! -+ * -+ * We do need the changes applied to the candidate config for validation -+ * and to eventually replace the running config, but we should be able -+ * to go back and just use the original list. The only possible issue is -+ * if the list includes any new changes. We should maybe build a new -+ * list and then validate that it is the same as the original lsit (or a -+ * subset of it) in development mode, but not in production build. -+ * -+ * Anyway we should time it to see if this is a big deal or not. -+ */ - gettimeofday(&prep_nb_cfg_start, NULL); -- err = nb_candidate_commit_prepare(nb_ctx, client_ctx->candidate_config, -- "MGMTD Backend Txn", &txn->nb_txn, -- false, true, -- err_buf, sizeof(err_buf) - 1); -+ err = nb_candidate_commit_prepare(nb_ctx, txn->candidate_config, "MGMTD Backend Txn", -+ &txn->nb_txn, false, true, err_buf, sizeof(err_buf) - 1); - if (err != NB_OK) { - err_buf[sizeof(err_buf) - 1] = 0; - if (err == NB_ERR_VALIDATION) { -+ /* maybe symantic validation error isn't a LOGERR? */ - log_err_be_client("Failed to validate configs txn-id: %" PRIu64 - " %zu batches, err: '%s'", - txn->txn_id, num_processed, err_buf); -@@ -556,38 +515,39 @@ static int mgmt_be_txn_cfg_prepare(struct mgmt_be_txn_ctx *txn, const char **con - - gettimeofday(&prep_nb_cfg_end, NULL); - prep_nb_cfg_tm = timeval_elapsed(prep_nb_cfg_end, prep_nb_cfg_start); -- client_ctx->avg_prep_nb_cfg_tm = ((client_ctx->avg_prep_nb_cfg_tm * -- client_ctx->num_prep_nb_cfg) + -- prep_nb_cfg_tm) / -- (client_ctx->num_prep_nb_cfg + 1); -+ client_ctx->avg_prep_nb_cfg_tm = -+ ((client_ctx->avg_prep_nb_cfg_tm * client_ctx->num_prep_nb_cfg) + prep_nb_cfg_tm) / -+ (client_ctx->num_prep_nb_cfg + 1); - client_ctx->num_prep_nb_cfg++; - -- mgmt_be_send_cfg_reply(client_ctx, txn->txn_id, !error, error ? err_buf : NULL); -+ /* -+ * Replying to mgmtd -+ */ -+ disconnect = !!mgmt_be_send_cfg_reply(client_ctx, txn->txn_id, !error, -+ error ? err_buf : NULL); - - debug_be_client("Avg-nb-edit-duration %Lu uSec, nb-prep-duration %lu (avg: %Lu) uSec, batch size %u", - client_ctx->avg_edit_nb_cfg_tm, prep_nb_cfg_tm, - client_ctx->avg_prep_nb_cfg_tm, (uint32_t)num_processed); -- -- if (error) -+done: -+ /* -+ * If we have any error we abort the new transaction and delete the txn -+ */ -+ if (error || disconnect) { - mgmt_be_txn_cfg_abort(txn); -+ mgmt_be_txn_delete(txn); -+ } - -- return 0; -+ return disconnect; - } - - static void be_client_handle_cfg(struct mgmt_be_client *client, uint64_t txn_id, void *msgbuf, - size_t msg_len) - { - struct mgmt_msg_cfg_req *msg = msgbuf; -- struct mgmt_be_txn_ctx *txn; - const char **config = NULL; - -- debug_be_client("Got CFG_DATA_REQ txn-id: %Lu", txn_id); -- -- txn = mgmt_be_find_txn_by_id(client, txn_id, true); -- if (!txn) { -- log_err_be_client("Can't find TXN for txn_id: %Lu", txn_id); -- goto failed; -- } -+ debug_be_client("Got CFG_REQ txn-id: %Lu", txn_id); - - config = mgmt_msg_native_strings_decode(msg, msg_len, msg->config); - if (darr_len(config) == 0) { -@@ -595,9 +555,9 @@ static void be_client_handle_cfg(struct mgmt_be_client *client, uint64_t txn_id, - goto failed; - } - -- debug_be_client("End of data; CFG_PREPARE_REQ processing"); -+ debug_be_client("CFG_REQ processing"); - -- if (mgmt_be_txn_cfg_prepare(txn, config)) -+ if (mgmt_be_txn_cfg_prepare(client, txn_id, config)) - goto failed; - - darr_free_free(config); -@@ -605,6 +565,7 @@ static void be_client_handle_cfg(struct mgmt_be_client *client, uint64_t txn_id, - - failed: - darr_free_free(config); -+ log_err_be_client("Disconnecting client %s due to error handling CFG_REQ", client->name); - msg_conn_disconnect(&client->client.conn, true); - } - -@@ -631,16 +592,18 @@ static int mgmt_be_send_apply_reply(struct mgmt_be_client *client_ctx, - return ret; - } - --static int mgmt_be_txn_proc_cfgapply(struct mgmt_be_txn_ctx *txn) -+static bool mgmt_be_txn_proc_cfgapply(struct mgmt_be_txn_ctx *txn) - { - struct mgmt_be_client *client_ctx; - struct timeval apply_nb_cfg_start; - struct timeval apply_nb_cfg_end; - unsigned long apply_nb_cfg_tm; - char err_buf[BUFSIZ]; -+ bool disconnect; - - assert(txn && txn->client); - client_ctx = txn->client; -+ assert(client_ctx->config_txn == txn); - - assert(txn->nb_txn); - -@@ -648,8 +611,7 @@ static int mgmt_be_txn_proc_cfgapply(struct mgmt_be_txn_ctx *txn) - * Now apply all the batches we have applied in one go. - */ - gettimeofday(&apply_nb_cfg_start, NULL); -- (void)nb_candidate_commit_apply(txn->nb_txn, true, &txn->nb_txn_id, -- err_buf, sizeof(err_buf) - 1); -+ nb_candidate_commit_apply(txn->nb_txn, true, &txn->nb_txn_id, err_buf, sizeof(err_buf) - 1); - gettimeofday(&apply_nb_cfg_end, NULL); - - apply_nb_cfg_tm = timeval_elapsed(apply_nb_cfg_end, apply_nb_cfg_start); -@@ -659,25 +621,54 @@ static int mgmt_be_txn_proc_cfgapply(struct mgmt_be_txn_ctx *txn) - client_ctx->num_apply_nb_cfg++; - txn->nb_txn = NULL; - -- mgmt_be_send_apply_reply(client_ctx, txn->txn_id, true, NULL); -+ disconnect = !!mgmt_be_send_apply_reply(client_ctx, txn->txn_id, true, NULL); - - debug_be_client("Nb-apply-duration %lu (avg: %Lu) uSec", apply_nb_cfg_tm, - client_ctx->avg_apply_nb_cfg_tm); - -- return 0; -+ mgmt_be_txn_delete(txn); -+ -+ return disconnect; - } - - static void be_client_handle_cfg_apply(struct mgmt_be_client *client, uint64_t txn_id) - { -- struct mgmt_be_txn_ctx *txn; -+ struct mgmt_be_txn_ctx *txn = NULL; - - debug_be_client("Got CFG_APPLY_REQ for client %s txn-id %Lu", client->name, txn_id); - -- txn = mgmt_be_find_txn_by_id(client, txn_id, true); -+ if (client->config_txn && client->config_txn->txn_id == txn_id) -+ txn = client->config_txn; -+ else -+ log_err_be_client("client %s unkonwn config txn-id: %Lu", client->name, txn_id); -+ - if (!txn || mgmt_be_txn_proc_cfgapply(txn)) - msg_conn_disconnect(&client->client.conn, true); - } - -+/* -+ * This currently serves as the CFG_ABORT -+ */ -+static void be_client_handle_txn_req(struct mgmt_be_client *client, uint64_t txn_id, void *msgbuf, -+ size_t msg_len) -+{ -+ struct mgmt_msg_txn_req *msg = msgbuf; -+ -+ debug_be_client("Got TXN_DELETE txn-id: %Lu", txn_id); -+ if (msg->create) { -+ log_err_be_client("Unsupported TXN_REQ create for txn-id: %Lu", txn_id); -+ goto failed; -+ } -+ if (!client->config_txn || client->config_txn->txn_id != txn_id) { -+ log_err_be_client("Cannot find TXN for TXN_DELETE txn-id: %Lu", txn_id); -+ goto failed; -+ } -+ mgmt_be_txn_delete(client->config_txn); -+ return; -+failed: -+ msg_conn_disconnect(&client->client.conn, true); -+} -+ - - struct be_client_tree_data_batch_args { - struct mgmt_be_client *client; -@@ -1009,6 +1000,7 @@ static void be_client_handle_native_msg(struct mgmt_be_client *client, - - switch (msg->code) { - case MGMT_MSG_CODE_TXN_REQ: -+ /* XXX used as a CFG_ABORT_REQ */ - be_client_handle_txn_req(client, txn_id, msg, msg_len); - break; - case MGMT_MSG_CODE_CFG_REQ: -@@ -1119,8 +1111,12 @@ static int _notify_conenct_disconnect(struct msg_client *msg_client, - if (connected) { - assert(msg_client->conn.fd != -1); - ret = mgmt_be_send_subscribe(client); -- if (ret) -+ if (ret) { -+ log_err_be_client("Failed to send subscribe on connect: %s", client->name); - return ret; -+ } -+ debug_be_client("Sent subscribe on connect: %s: fd: %d", client->name, -+ msg_client->conn.fd); - } - - /* Notify BE client through registered callback (if any) */ -@@ -1227,7 +1223,12 @@ static enum nb_error clients_client_state_candidate_config_version_get( - const struct nb_node *nb_node, const void *parent_list_entry, struct lyd_node *parent) - { - const struct lysc_node *snode = nb_node->snode; -- uint64_t value = __be_client->candidate_config->version; -+ uint64_t value; -+ -+ if (__be_client->config_txn) -+ value = __be_client->config_txn->candidate_config->version; -+ else -+ value = __be_client->running_config->version; - - if (lyd_new_term_bin(parent, snode->module, snode->name, &value, sizeof(value), - LYD_NEW_PATH_UPDATE, NULL)) -@@ -1376,10 +1377,9 @@ struct mgmt_be_client *mgmt_be_client_create(const char *client_name, - - client->name = XSTRDUP(MTYPE_MGMTD_BE_CLIENT_NAME, client_name); - client->running_config = running_config; -- client->candidate_config = vty_shared_candidate_config; -+ // client->candidate_config = vty_shared_candidate_config; - if (cbs) - client->cbs = *cbs; -- mgmt_be_txns_init(&client->txn_head); - - snprintf(server_path, sizeof(server_path), MGMTD_BE_SOCK_NAME); - -@@ -1417,7 +1417,6 @@ void mgmt_be_client_destroy(struct mgmt_be_client *client) - nb_oper_cancel_all_walks(); - msg_client_cleanup(&client->client); - mgmt_be_cleanup_all_txns(client); -- mgmt_be_txns_fini(&client->txn_head); - - XFREE(MTYPE_MGMTD_BE_CLIENT_NAME, client->name); - XFREE(MTYPE_MGMTD_BE_CLIENT, client); -diff --git a/lib/mgmt_be_client.h b/lib/mgmt_be_client.h -index d0b1f12ceb..5d1aeb7b97 100644 ---- a/lib/mgmt_be_client.h -+++ b/lib/mgmt_be_client.h -@@ -54,12 +54,8 @@ struct mgmt_be_client_txn_ctx { - * - */ - struct mgmt_be_client_cbs { -- void (*client_connect_notify)(struct mgmt_be_client *client, -- uintptr_t usr_data, bool connected); -- void (*txn_notify)(struct mgmt_be_client *client, uintptr_t usr_data, -- struct mgmt_be_client_txn_ctx *txn_ctx, -- bool destroyed); -- -+ void (*client_connect_notify)(struct mgmt_be_client *client, uintptr_t usr_data, -+ bool connected); - const char **config_xpaths; - uint nconfig_xpaths; - const char **oper_xpaths; -diff --git a/lib/mgmt_fe_client.c b/lib/mgmt_fe_client.c -index 4fed87f0d3..95d0bafa6b 100644 ---- a/lib/mgmt_fe_client.c -+++ b/lib/mgmt_fe_client.c -@@ -266,9 +266,9 @@ int mgmt_fe_send_lockds_req(struct mgmt_fe_client *client, uint64_t session_id, - return ret; - } - --int mgmt_fe_send_commitcfg_req(struct mgmt_fe_client *client, uint64_t session_id, uint64_t req_id, -- enum mgmt_ds_id src_ds_id, enum mgmt_ds_id dest_ds_id, -- bool validate_only, bool abort, bool unlock) -+int mgmt_fe_send_commit_req(struct mgmt_fe_client *client, uint64_t session_id, uint64_t req_id, -+ enum mgmt_ds_id src_ds_id, enum mgmt_ds_id dest_ds_id, -+ bool validate_only, bool abort, bool unlock) - { - struct mgmt_msg_commit *msg; - int ret; -@@ -287,8 +287,12 @@ int mgmt_fe_send_commitcfg_req(struct mgmt_fe_client *client, uint64_t session_i - msg->action = MGMT_MSG_COMMIT_APPLY; - msg->unlock = unlock; - -- debug_fe_client("Sending COMMIT message for Src-DS:%s, Dst-DS:%s session-id %Lu", -- dsid2name(src_ds_id), dsid2name(dest_ds_id), session_id); -+ debug_fe_client("Sending COMMIT message for src: %s dst: %s session-id %Lu action: %s unlock: %d", -+ dsid2name(src_ds_id), dsid2name(dest_ds_id), session_id, -+ msg->action == MGMT_MSG_COMMIT_VALIDATE ? "validate" -+ : msg->action == MGMT_MSG_COMMIT_ABORT ? "abort" -+ : "apply", -+ unlock); - - ret = mgmt_msg_native_send_msg(&client->client.conn, msg, false); - ret = fe_client_push_sent_msg(client, (struct mgmt_msg_header *)msg, ret); -@@ -695,7 +699,7 @@ static void mgmt_fe_client_process_msg(uint8_t version, uint8_t *data, - return; - } - -- log_err_fe_client("Protobuf no longer used in backend API"); -+ log_err_fe_client("Protobuf no longer used in frontend API"); - msg_conn_disconnect(&client->client.conn, true); - } - -diff --git a/lib/mgmt_fe_client.h b/lib/mgmt_fe_client.h -index 22b167453a..8ff08b566a 100644 ---- a/lib/mgmt_fe_client.h -+++ b/lib/mgmt_fe_client.h -@@ -232,10 +232,10 @@ extern int mgmt_fe_send_lockds_req(struct mgmt_fe_client *client, uint64_t sessi - * Returns: - * 0 on success, otherwise msg_conn_send_msg() return values. - */ --extern int mgmt_fe_send_commitcfg_req(struct mgmt_fe_client *client, uint64_t session_id, -- uint64_t req_id, enum mgmt_ds_id src_ds_id, -- enum mgmt_ds_id dst_ds_id, bool validate_only, bool abort, -- bool unlock); -+extern int mgmt_fe_send_commit_req(struct mgmt_fe_client *client, uint64_t session_id, -+ uint64_t req_id, enum mgmt_ds_id src_ds_id, -+ enum mgmt_ds_id dst_ds_id, bool validate_only, bool abort, -+ bool unlock); - - /* - * Send GET-DATA to MGMTD daemon. -diff --git a/lib/mgmt_msg.c b/lib/mgmt_msg.c -index 8f22b31231..615e353217 100644 ---- a/lib/mgmt_msg.c -+++ b/lib/mgmt_msg.c -@@ -17,6 +17,14 @@ - #include "mgmt_msg.h" - #include "mgmt_msg_native.h" - -+static bool trace; -+ -+#define MGMT_MSG_TRACE(dbgtag, fmt, ...) \ -+ do { \ -+ if (dbgtag && trace) \ -+ zlog_debug("%s: %s: " fmt, dbgtag, __func__, ##__VA_ARGS__); \ -+ } while (0) -+ - - #define MGMT_MSG_DBG(dbgtag, fmt, ...) \ - do { \ -@@ -56,6 +64,7 @@ enum mgmt_msg_rsched mgmt_msg_read(struct mgmt_msg_state *ms, int fd, - ssize_t n, left; - - assert(ms && fd != -1); -+ MGMT_MSG_TRACE(dbgtag, "enter with %zu bytes available to read on fd %d", avail, fd); - - /* - * Read as much as we can into the stream. -@@ -65,19 +74,18 @@ enum mgmt_msg_rsched mgmt_msg_read(struct mgmt_msg_state *ms, int fd, - - /* -2 is normal nothing read, and to retry */ - if (n == -2) { -- MGMT_MSG_DBG(dbgtag, "nothing more to read"); -+ MGMT_MSG_TRACE(dbgtag, "nothing more to read on fd %d", fd); - break; - } - if (n <= 0) { - if (n == 0) -- MGMT_MSG_ERR(ms, "got EOF/disconnect"); -+ MGMT_MSG_ERR(ms, "got EOF/disconnect on fd %d", fd); - else -- MGMT_MSG_ERR(ms, -- "got error while reading: '%s'", -+ MGMT_MSG_ERR(ms, "got error while reading on fd %d: '%s'", fd, - safe_strerror(errno)); - return MSR_DISCONNECT; - } -- MGMT_MSG_DBG(dbgtag, "read %zd bytes", n); -+ MGMT_MSG_TRACE(dbgtag, "read %zd bytes on fd %d", n, fd); - ms->nrxb += n; - avail -= n; - } -@@ -90,13 +98,13 @@ enum mgmt_msg_rsched mgmt_msg_read(struct mgmt_msg_state *ms, int fd, - while (left > (ssize_t)sizeof(struct mgmt_msg_hdr)) { - mhdr = (struct mgmt_msg_hdr *)(STREAM_DATA(ms->ins) + total); - if (!MGMT_MSG_IS_MARKER(mhdr->marker)) { -- MGMT_MSG_DBG(dbgtag, "recv corrupt buffer, disconnect"); -+ MGMT_MSG_DBG(dbgtag, "recv corrupt buffer on fd %d, disconnect", fd); - return MSR_DISCONNECT; - } - if ((ssize_t)mhdr->len > left) - break; - -- MGMT_MSG_DBG(dbgtag, "read full message len %u", mhdr->len); -+ MGMT_MSG_TRACE(dbgtag, "read full message on fd %d len %u", fd, mhdr->len); - total += mhdr->len; - left -= mhdr->len; - mcount++; -@@ -166,7 +174,7 @@ bool mgmt_msg_procbufs(struct mgmt_msg_state *ms, - uint8_t *data; - size_t left, nproc; - -- MGMT_MSG_DBG(dbgtag, "Have %zu streams to process", ms->inq.count); -+ MGMT_MSG_TRACE(dbgtag, "Have %zu streams to process", ms->inq.count); - - nproc = 0; - while (nproc < ms->max_read_buf) { -@@ -176,7 +184,7 @@ bool mgmt_msg_procbufs(struct mgmt_msg_state *ms, - - data = STREAM_DATA(work); - left = stream_get_endp(work); -- MGMT_MSG_DBG(dbgtag, "Processing stream of len %zu", left); -+ MGMT_MSG_TRACE(dbgtag, "Processing stream of len %zu", left); - - for (; left > sizeof(struct mgmt_msg_hdr); - left -= mhdr->len, data += mhdr->len) { -@@ -185,10 +193,11 @@ bool mgmt_msg_procbufs(struct mgmt_msg_state *ms, - assert(MGMT_MSG_IS_MARKER(mhdr->marker)); - assert(left >= mhdr->len); - -- handle_msg(MGMT_MSG_MARKER_VERSION(mhdr->marker), -- (uint8_t *)(mhdr + 1), -- mhdr->len - sizeof(struct mgmt_msg_hdr), -- user); -+ /* -+ * Q: if the handler disconnects should stop/flush? -+ */ -+ handle_msg(MGMT_MSG_MARKER_VERSION(mhdr->marker), (uint8_t *)(mhdr + 1), -+ mhdr->len - sizeof(struct mgmt_msg_hdr), user); - ms->nrxm++; - nproc++; - } -@@ -230,9 +239,8 @@ enum mgmt_msg_wsched mgmt_msg_write(struct mgmt_msg_state *ms, int fd, - ssize_t n; - - if (ms->outs) { -- MGMT_MSG_DBG(dbgtag, -- "found unqueued stream with %zu bytes, queueing", -- stream_get_endp(ms->outs)); -+ MGMT_MSG_TRACE(dbgtag, "found unqueued stream with %zu bytes on fd %d, queueing", -+ stream_get_endp(ms->outs), fd); - stream_fifo_push(&ms->outq, ms->outs); - ms->outs = NULL; - } -@@ -245,46 +253,40 @@ enum mgmt_msg_wsched mgmt_msg_write(struct mgmt_msg_state *ms, int fd, - n = stream_flush(s, fd); - if (n <= 0) { - if (n == 0) -- MGMT_MSG_ERR(ms, -- "connection closed while writing"); -+ MGMT_MSG_ERR(ms, "connection closed while writing on fd %d", fd); - else if (ERRNO_IO_RETRY(errno)) { -- MGMT_MSG_DBG( -- dbgtag, -- "retry error while writing %zd bytes: %s (%d)", -- left, safe_strerror(errno), errno); -+ MGMT_MSG_DBG(dbgtag, -+ "retry error while writing %zd bytes on fd %d: %s (%d)", -+ left, fd, safe_strerror(errno), errno); - return MSW_SCHED_STREAM; - } else -- MGMT_MSG_ERR( -- ms, -- "error while writing %zd bytes: %s (%d)", -- left, safe_strerror(errno), errno); -+ MGMT_MSG_ERR(ms, "error while writing %zd bytes on fd %d: %s (%d)", -+ left, fd, safe_strerror(errno), errno); - - n = mgmt_msg_reset_writes(ms); -- MGMT_MSG_DBG(dbgtag, "drop and freed %zd streams", n); -+ MGMT_MSG_ERR(ms, "drop, freed %zd streams on fd %d for disconnect", n, fd); - - return MSW_DISCONNECT; - } - - ms->ntxb += n; - if (n != left) { -- MGMT_MSG_DBG(dbgtag, "short stream write %zd of %zd", n, -- left); -+ MGMT_MSG_DBG(dbgtag, "short stream write %zd of %zd on fd %d", n, left, fd); - stream_forward_getp(s, n); - return MSW_SCHED_STREAM; - } - - stream_free(stream_fifo_pop(&ms->outq)); -- MGMT_MSG_DBG(dbgtag, "wrote stream of %zd bytes", n); -+ MGMT_MSG_TRACE(dbgtag, "wrote stream of %zd bytes on fd %d", n, fd); - nproc++; - } - if (s) { -- MGMT_MSG_DBG( -- dbgtag, -- "reached %zu buffer writes, pausing with %zu streams left", -- ms->max_write_buf, ms->outq.count); -+ MGMT_MSG_DBG(dbgtag, -+ "reached %zu buffer writes on fd %d, pausing with %zu streams left", -+ ms->max_write_buf, fd, ms->outq.count); - return MSW_SCHED_STREAM; - } -- MGMT_MSG_DBG(dbgtag, "flushed all streams from output q"); -+ MGMT_MSG_TRACE(dbgtag, "flushed all streams from output q for fd %d", fd); - return MSW_SCHED_NONE; - } - -@@ -317,30 +319,27 @@ int mgmt_msg_send_msg(struct mgmt_msg_state *ms, uint8_t version, void *msg, - size_t mlen = len + sizeof(*mhdr); - - if (mlen > ms->max_msg_sz) -- MGMT_MSG_DBG(dbgtag, "Sending large msg size %zu > max size %zu", -- mlen, ms->max_msg_sz); -+ MGMT_MSG_TRACE(dbgtag, "Sending large msg size %zu > max size %zu", mlen, -+ ms->max_msg_sz); - - if (!ms->outs) { -- MGMT_MSG_DBG(dbgtag, "creating new stream for msg len %zu", mlen); -+ MGMT_MSG_TRACE(dbgtag, "creating new stream for msg len %zu", mlen); - ms->outs = stream_new(MAX(ms->max_msg_sz, mlen)); - } else if (mlen > ms->max_msg_sz && ms->outs->endp == 0) { - /* msg is larger than stream max size get a fit-to-size stream */ -- MGMT_MSG_DBG(dbgtag, -- "replacing old stream with fit-to-size for msg len %zu", -- mlen); -+ MGMT_MSG_TRACE(dbgtag, "replacing old stream with fit-to-size for msg len %zu", -+ mlen); - stream_free(ms->outs); - ms->outs = stream_new(mlen); - } else if (STREAM_WRITEABLE(ms->outs) < mlen) { -- MGMT_MSG_DBG(dbgtag, -- "enq existing stream len %zu and creating new stream for msg len %zu", -- STREAM_WRITEABLE(ms->outs), mlen); -+ MGMT_MSG_TRACE(dbgtag, -+ "enq existing stream len %zu and creating new stream for msg len %zu", -+ STREAM_WRITEABLE(ms->outs), mlen); - stream_fifo_push(&ms->outq, ms->outs); - ms->outs = stream_new(MAX(ms->max_msg_sz, mlen)); - } else { -- MGMT_MSG_DBG( -- dbgtag, -- "using existing stream with avail %zu for msg len %zu", -- STREAM_WRITEABLE(ms->outs), mlen); -+ MGMT_MSG_TRACE(dbgtag, "using existing stream with avail %zu for msg len %zu", -+ STREAM_WRITEABLE(ms->outs), mlen); - } - s = ms->outs; - -diff --git a/lib/mgmt_msg_native.c b/lib/mgmt_msg_native.c -index 21ca54c3a4..763dbcc73f 100644 ---- a/lib/mgmt_msg_native.c -+++ b/lib/mgmt_msg_native.c -@@ -69,9 +69,67 @@ size_t mgmt_msg_get_min_size(uint code) - return mgmt_msg_min_sizes[code]; - } - --int vmgmt_msg_native_send_error(struct msg_conn *conn, uint64_t sess_or_txn_id, -- uint64_t req_id, bool short_circuit_ok, -- int16_t error, const char *errfmt, va_list ap) -+const char *mgmt_msg_code_name(uint code) -+{ -+ static char buf[48]; -+ -+ switch (code) { -+ case MGMT_MSG_CODE_ERROR: -+ return "ERROR"; -+ case MGMT_MSG_CODE_GET_TREE: -+ return "GET_TREE"; -+ case MGMT_MSG_CODE_TREE_DATA: -+ return "TREE_DATA"; -+ case MGMT_MSG_CODE_GET_DATA: -+ return "GET_DATA"; -+ case MGMT_MSG_CODE_NOTIFY: -+ return "NOTIFY"; -+ case MGMT_MSG_CODE_EDIT: -+ return "EDIT"; -+ case MGMT_MSG_CODE_EDIT_REPLY: -+ return "EDIT_REPLY"; -+ case MGMT_MSG_CODE_RPC: -+ return "RPC"; -+ case MGMT_MSG_CODE_RPC_REPLY: -+ return "RPC_REPLY"; -+ case MGMT_MSG_CODE_NOTIFY_SELECT: -+ return "NOTIFY_SELECT"; -+ case MGMT_MSG_CODE_SESSION_REQ: -+ return "SESSION_REQ"; -+ case MGMT_MSG_CODE_SESSION_REPLY: -+ return "SESSION_REPLY"; -+ case MGMT_MSG_CODE_LOCK: -+ return "LOCK"; -+ case MGMT_MSG_CODE_LOCK_REPLY: -+ return "LOCK_REPLY"; -+ case MGMT_MSG_CODE_COMMIT: -+ return "COMMIT"; -+ case MGMT_MSG_CODE_COMMIT_REPLY: -+ return "COMMIT_REPLY"; -+ case MGMT_MSG_CODE_SUBSCRIBE: -+ return "SUBSCRIBE"; -+ case MGMT_MSG_CODE_TXN_REQ: -+ return "TXN_REQ"; -+ case MGMT_MSG_CODE_TXN_REPLY: -+ return "TXN_REPLY"; -+ case MGMT_MSG_CODE_CFG_REQ: -+ return "CFG_REQ"; -+ case MGMT_MSG_CODE_CFG_REPLY: -+ return "CFG_REPLY"; -+ case MGMT_MSG_CODE_CFG_APPLY_REQ: -+ return "CFG_APPLY_REQ"; -+ case MGMT_MSG_CODE_CFG_APPLY_REPLY: -+ return "CFG_APPLY_REPLY"; -+ default: -+ snprintf(buf, sizeof(buf), "UNKNOWN(%u)", code); -+ return buf; -+ } -+} -+ -+ -+int vmgmt_msg_native_send_error(struct msg_conn *conn, uint64_t sess_or_txn_id, uint64_t req_id, -+ bool short_circuit_ok, int16_t error, const char *errfmt, -+ va_list ap) - { - struct mgmt_msg_error *msg; - char *errstr; -diff --git a/lib/mgmt_msg_native.h b/lib/mgmt_msg_native.h -index 99ce1e17fa..f6a0ce4d1a 100644 ---- a/lib/mgmt_msg_native.h -+++ b/lib/mgmt_msg_native.h -@@ -215,6 +215,8 @@ DECLARE_MTYPE(MSG_NATIVE_COMMIT_REPLY); - #define MGMT_MSG_DATASTORE_CANDIDATE 2 - #define MGMT_MSG_DATASTORE_OPERATIONAL 3 - -+const char *mgmt_msg_code_name(uint code); -+ - /* - * Formats - */ -@@ -375,7 +377,13 @@ struct mgmt_msg_notify_data { - _Static_assert(sizeof(struct mgmt_msg_notify_data) == offsetof(struct mgmt_msg_notify_data, data), - "Size mismatch"); - --#define EDIT_FLAG_IMPLICIT_LOCK 0x01 -+/* -+ * deprecated -- can simply do the locks as needed, if the session holds the -+ * locks then they aren't needed, otherwise an attempt to acquire them is made -+ * and if that fails the edit fails. -+ */ -+#define EDIT_FLAG_IMPLICIT_LOCK 0x01 -+/* also deprecated -- unneeded, if DS is candidate, then no "commit", if running then "commit" */ - #define EDIT_FLAG_IMPLICIT_COMMIT 0x02 - - #define EDIT_OP_CREATE 0 -@@ -777,7 +785,7 @@ extern int vmgmt_msg_native_send_error(struct msg_conn *conn, - * mgmt_msg_free_native_msg() - Free a native msg. - * @msg: pointer to message allocated by mgmt_msg_create_native_msg(). - */ --#define mgmt_msg_native_free_msg(msg) darr_free(msg) -+#define mgmt_msg_native_free_msg darr_free - - /** - * mgmt_msg_native_get_msg_len() - Get the total length of the msg. -diff --git a/lib/northbound_cli.c b/lib/northbound_cli.c -index 81ed51e518..348cf2cc41 100644 ---- a/lib/northbound_cli.c -+++ b/lib/northbound_cli.c -@@ -218,35 +218,12 @@ static void create_xpath_base_abs(struct vty *vty, char *xpath_base_abs, - static int _nb_cli_apply_changes(struct vty *vty, const char *xpath_base, bool clear_pending) - { - char xpath_base_abs[XPATH_MAXLEN] = {}; -- bool implicit_commit; - - create_xpath_base_abs(vty, xpath_base_abs, sizeof(xpath_base_abs), - xpath_base); - -- if (vty_mgmt_should_process_cli_apply_changes(vty)) { -- VTY_CHECK_XPATH; -- -- assert(vty->type != VTY_FILE); -- /* -- * The legacy user wanted to clear pending (i.e., perform a -- * commit immediately) due to some non-yang compatible -- * functionality. This new mgmtd code however, continues to send -- * changes putting off the commit until XFRR_end is received -- * (i.e., end-of-config-file). This should be fine b/c all -- * conversions to mgmtd require full proper implementations. -- */ -- if (!vty->num_cfg_changes) -- return CMD_SUCCESS; -- -- implicit_commit = vty_needs_implicit_commit(vty); -- if (vty_mgmt_send_config_data(vty, xpath_base_abs, implicit_commit) < 0) { -- vty_out(vty, "%% Failed to apply configuration data.\n"); -- return CMD_WARNING_CONFIG_FAILED; -- } -- if (!implicit_commit) -- ++vty->mgmt_num_pending_setcfg; -- return CMD_SUCCESS; -- } -+ if (vty->type != VTY_FILE && nb_cli_apply_changes_mgmt_cb) -+ return nb_cli_apply_changes_mgmt_cb(vty, xpath_base_abs); - - return nb_cli_apply_changes_internal(vty, xpath_base_abs, clear_pending); - } -@@ -330,20 +307,10 @@ int nb_cli_rpc(struct vty *vty, const char *xpath, struct lyd_node **output_p) - assert(err == LY_SUCCESS); - } - -- if (vty_mgmt_fe_enabled()) { -- char *data = NULL; -- -- err = lyd_print_mem(&data, input, LYD_JSON, LYD_PRINT_SHRINK); -- assert(err == LY_SUCCESS); -- -- ret = vty_mgmt_send_rpc_req(vty, LYD_JSON, xpath, data); -- -- free(data); -+ if (nb_cli_rpc_mgmt_cb) { -+ ret = nb_cli_rpc_mgmt_cb(vty, xpath, input); - lyd_free_all(input); -- -- if (ret < 0) -- return CMD_WARNING; -- return CMD_SUCCESS; -+ return ret; - } - - /* validate input tree to create implicit defaults */ -diff --git a/lib/vty.c b/lib/vty.c -index b4e59a2a7b..41319afd0d 100644 ---- a/lib/vty.c -+++ b/lib/vty.c -@@ -73,9 +73,14 @@ enum vty_event { - - struct nb_config *vty_mgmt_candidate_config; - --static struct mgmt_fe_client *mgmt_fe_client; --static bool mgmt_fe_connected; --static uint64_t mgmt_client_id_next; -+void (*vty_new_mgmt_cb)(struct vty *vty); -+void (*vty_close_mgmt_cb)(struct vty *vty); -+int (*vty_config_enter_mgmt_cb)(struct vty *vty, bool private_config, bool exclusive, -+ bool file_lock); -+void (*vty_config_node_exit_mgmt_cb)(struct vty *vty); -+int (*nb_cli_apply_changes_mgmt_cb)(struct vty *vty, const char *xpath_base_abs); -+int (*nb_cli_rpc_mgmt_cb)(struct vty *vty, const char *xpath, const struct lyd_node *input); -+ - - PREDECL_DLIST(vtyservs); - -@@ -128,66 +133,10 @@ static bool vty_log_commands_perm; - /* Vty incremental flush threshold size */ - static const int VTY_MAX_INCREMENTAL_FLUSH = 1024 * 128; - --char const *const mgmt_daemons[] = { -- "zebra", --#ifdef HAVE_RIPD -- "ripd", --#endif --#ifdef HAVE_RIPNGD -- "ripngd", --#endif --#ifdef HAVE_STATICD -- "staticd", --#endif --}; --uint mgmt_daemons_count = array_size(mgmt_daemons); -- -- --static int vty_mgmt_lock_candidate_inline(struct vty *vty) --{ -- assert(!vty->mgmt_locked_candidate_ds); -- (void)vty_mgmt_send_lockds_req(vty, MGMTD_DS_CANDIDATE, true, true); -- return vty->mgmt_locked_candidate_ds ? 0 : -1; --} -- --static int vty_mgmt_unlock_candidate_inline(struct vty *vty) --{ -- assert(vty->mgmt_locked_candidate_ds); -- (void)vty_mgmt_send_lockds_req(vty, MGMTD_DS_CANDIDATE, false, true); -- return vty->mgmt_locked_candidate_ds ? -1 : 0; --} -- --static int vty_mgmt_lock_running_inline(struct vty *vty) --{ -- assert(!vty->mgmt_locked_running_ds); -- (void)vty_mgmt_send_lockds_req(vty, MGMTD_DS_RUNNING, true, true); -- return vty->mgmt_locked_running_ds ? 0 : -1; --} -- --static int vty_mgmt_unlock_running_inline(struct vty *vty) --{ -- assert(vty->mgmt_locked_running_ds); -- (void)vty_mgmt_send_lockds_req(vty, MGMTD_DS_RUNNING, false, true); -- return vty->mgmt_locked_running_ds ? -1 : 0; --} -- --void vty_mgmt_resume_response(struct vty *vty, int ret) -+void vty_resume_response(struct vty *vty, int ret) - { - uint8_t header[4] = {0, 0, 0, 0}; - -- if (!vty->mgmt_req_pending_cmd) { -- zlog_err( -- "vty resume response called without mgmt_req_pending_cmd"); -- return; -- } -- -- debug_fe_client("resuming CLI cmd after %s on vty session-id: %" PRIu64 -- " with '%s'", -- vty->mgmt_req_pending_cmd, vty->mgmt_session_id, -- ret == CMD_SUCCESS ? "success" : "failed"); -- -- vty->mgmt_req_pending_cmd = NULL; -- - if (vty->type != VTY_FILE) { - header[3] = ret; - buffer_put(vty->obuf, header, 4); -@@ -1724,17 +1673,8 @@ struct vty *vty_new(void) - new->max = VTY_BUFSIZ; - new->pass_fd = -1; - -- if (mgmt_fe_client) { -- if (!mgmt_client_id_next) -- mgmt_client_id_next++; -- new->mgmt_client_id = mgmt_client_id_next++; -- new->mgmt_session_id = 0; -- mgmt_fe_create_client_session( -- mgmt_fe_client, new->mgmt_client_id, (uintptr_t) new); -- /* we short-circuit create the session so it must be set now */ -- assertf(new->mgmt_session_id != 0, -- "Failed to create client session for VTY"); -- } -+ if (vty_new_mgmt_cb) -+ vty_new_mgmt_cb(new); - - return new; - } -@@ -2297,77 +2237,6 @@ void vty_pass_fd(struct vty *vty, int fd) - vty->pass_fd = fd; - } - --bool mgmt_vty_read_configs(void) --{ -- char path[PATH_MAX]; -- struct vty *vty; -- FILE *confp; -- uint line_num = 0; -- uint count = 0; -- uint index; -- -- vty = vty_new(); -- vty->wfd = STDERR_FILENO; -- vty->type = VTY_FILE; -- vty->node = CONFIG_NODE; -- vty->config = true; -- vty->pending_allowed = true; -- -- vty->candidate_config = vty_shared_candidate_config; -- -- vty_mgmt_lock_candidate_inline(vty); -- vty_mgmt_lock_running_inline(vty); -- -- for (index = 0; index < array_size(mgmt_daemons); index++) { -- snprintf(path, sizeof(path), "%s/%s.conf", frr_sysconfdir, -- mgmt_daemons[index]); -- -- confp = vty_open_config(path, config_default); -- if (!confp) -- continue; -- -- zlog_info("mgmtd: reading config file: %s", path); -- -- /* Execute configuration file */ -- line_num = 0; -- (void)config_from_file(vty, confp, &line_num); -- count++; -- -- fclose(confp); -- } -- -- snprintf(path, sizeof(path), "%s/mgmtd.conf", frr_sysconfdir); -- confp = vty_open_config(path, config_default); -- if (confp) { -- zlog_info("mgmtd: reading config file: %s", path); -- -- line_num = 0; -- (void)config_from_file(vty, confp, &line_num); -- count++; -- -- fclose(confp); -- } -- -- /* Conditionally unlock as the config file may have "exit"d early which -- * would then have unlocked things. -- */ -- if (vty->mgmt_locked_running_ds) -- vty_mgmt_unlock_running_inline(vty); -- if (vty->mgmt_locked_candidate_ds) -- vty_mgmt_unlock_candidate_inline(vty); -- -- vty->pending_allowed = false; -- -- if (!count) -- vty_close(vty); -- else -- vty_read_file_finish(vty, NULL); -- -- zlog_info("mgmtd: finished reading config files"); -- -- return true; --} -- - static void vtysh_read(struct event *thread) - { - int ret; -@@ -2568,12 +2437,8 @@ void vty_close(struct vty *vty) - /* Drop out of configure / transaction if needed. */ - vty_config_exit(vty); - -- if (mgmt_fe_client && vty->mgmt_session_id) { -- debug_fe_client("closing vty session"); -- mgmt_fe_destroy_client_session(mgmt_fe_client, -- vty->mgmt_client_id); -- vty->mgmt_session_id = 0; -- } -+ if (vty_close_mgmt_cb) -+ vty_close_mgmt_cb(vty); - - /* Cancel threads.*/ - event_cancel(&vty->t_read); -@@ -2936,39 +2801,17 @@ bool vty_read_config(struct nb_config *config, const char *config_file, - int vty_config_enter(struct vty *vty, bool private_config, bool exclusive, - bool file_lock) - { -- if (exclusive && !vty_mgmt_fe_enabled() && -- nb_running_lock(NB_CLIENT_CLI, vty)) { -+ int ret = CMD_SUCCESS; -+ -+ if (vty_config_enter_mgmt_cb) -+ ret = vty_config_enter_mgmt_cb(vty, private_config, exclusive, file_lock); -+ else if (exclusive && nb_running_lock(NB_CLIENT_CLI, vty)) { - vty_out(vty, "%% Configuration is locked by other client\n"); -- return CMD_WARNING; -+ ret = CMD_WARNING; - } - -- /* -- * We only need to do a lock when reading a config file as we will be -- * sending a batch of setcfg changes followed by a single commit -- * message. For user interactive mode we are doing implicit commits -- * those will obtain the lock (or not) when they try and commit. -- */ -- if (file_lock && vty_mgmt_fe_enabled() && !private_config) { -- if (vty_mgmt_lock_candidate_inline(vty)) { -- vty_out(vty, -- "%% Can't enter config; candidate datastore locked by another session\n"); -- return CMD_WARNING_CONFIG_FAILED; -- } -- if (vty_mgmt_lock_running_inline(vty)) { -- vty_out(vty, -- "%% Can't enter config; running datastore locked by another session\n"); -- vty_mgmt_unlock_candidate_inline(vty); -- return CMD_WARNING_CONFIG_FAILED; -- } -- assert(vty->mgmt_locked_candidate_ds); -- assert(vty->mgmt_locked_running_ds); -- -- /* -- * As datastores are locked explicitly, we don't need implicit -- * commits and should allow pending changes. -- */ -- vty->pending_allowed = true; -- } -+ if (ret != CMD_SUCCESS) -+ return ret; - - vty->node = CONFIG_NODE; - vty->config = true; -@@ -3026,11 +2869,8 @@ int vty_config_node_exit(struct vty *vty) - - vty->pending_allowed = false; - -- if (vty->mgmt_locked_running_ds) -- vty_mgmt_unlock_running_inline(vty); -- -- if (vty->mgmt_locked_candidate_ds) -- vty_mgmt_unlock_candidate_inline(vty); -+ if (vty_config_node_exit_mgmt_cb) -+ vty_config_node_exit_mgmt_cb(vty); - - /* Perform any pending commits. */ - (void)nb_cli_pending_commit_check(vty); -@@ -3539,575 +3379,6 @@ void vty_init_vtysh(void) - /* currently nothing to do, but likely to have future use */ - } - -- --/* -- * These functions allow for CLI handling to be placed inside daemons; however, -- * currently they are only used by mgmtd, with mgmtd having each daemons CLI -- * functionality linked into it. This design choice was taken for efficiency. -- */ -- --static void vty_mgmt_server_connected(struct mgmt_fe_client *client, -- uintptr_t usr_data, bool connected) --{ -- debug_fe_client("Got %sconnected %s MGMTD Frontend Server", -- !connected ? "dis: " : "", !connected ? "from" : "to"); -- -- /* -- * We should not have any sessions for connecting or disconnecting case. -- * The fe client library will delete all session on disconnect before -- * calling us. -- */ -- assert(mgmt_fe_client_session_count(client) == 0); -- -- mgmt_fe_connected = connected; -- -- /* Start or stop listening for vty connections */ -- if (connected) -- frr_vty_serv_start(true); -- else -- frr_vty_serv_stop(); --} -- --/* -- * A session has successfully been created for a vty. -- */ --static void vty_mgmt_session_notify(struct mgmt_fe_client *client, -- uintptr_t usr_data, uint64_t client_id, -- bool create, bool success, -- uintptr_t session_id, uintptr_t session_ctx) --{ -- struct vty *vty; -- -- vty = (struct vty *)session_ctx; -- -- if (!success) { -- zlog_err("%s session for client %" PRIu64 " failed!", -- create ? "Creating" : "Destroying", client_id); -- return; -- } -- -- debug_fe_client("%s session for client %" PRIu64 " successfully", -- create ? "Created" : "Destroyed", client_id); -- -- if (create) { -- assert(session_id != 0); -- vty->mgmt_session_id = session_id; -- } else { -- vty->mgmt_session_id = 0; -- /* We may come here by way of vty_close() and short-circuits */ -- if (vty->status != VTY_CLOSE) -- vty_close(vty); -- } --} -- --static void vty_mgmt_ds_lock_notified(struct mgmt_fe_client *client, uintptr_t usr_data, -- uint64_t client_id, uintptr_t session_id, -- uintptr_t session_ctx, uint64_t req_id, bool lock_ds, -- bool success, enum mgmt_ds_id ds_id, char *errmsg_if_any) --{ -- struct vty *vty; -- bool is_short_circuit = mgmt_fe_client_current_msg_short_circuit(client); -- -- vty = (struct vty *)session_ctx; -- -- assert(ds_id == MGMTD_DS_CANDIDATE || ds_id == MGMTD_DS_RUNNING); -- if (!success) -- zlog_err("%socking for DS %u failed, Err: '%s' vty %p", -- lock_ds ? "L" : "Unl", ds_id, errmsg_if_any, vty); -- else { -- debug_fe_client("%socked DS %u successfully", -- lock_ds ? "L" : "Unl", ds_id); -- if (ds_id == MGMTD_DS_CANDIDATE) -- vty->mgmt_locked_candidate_ds = lock_ds; -- else -- vty->mgmt_locked_running_ds = lock_ds; -- } -- -- if (!is_short_circuit && vty->mgmt_req_pending_cmd) { -- assert(!strcmp(vty->mgmt_req_pending_cmd, "MESSAGE_LOCKDS_REQ")); -- vty_mgmt_resume_response(vty, -- success ? CMD_SUCCESS : CMD_WARNING); -- } --} -- --static void vty_mgmt_commit_config_result_notified(struct mgmt_fe_client *client, -- uintptr_t usr_data, uint64_t client_id, -- uintptr_t session_id, uintptr_t session_ctx, -- uint64_t req_id, bool success, -- enum mgmt_ds_id src_ds_id, -- enum mgmt_ds_id dst_ds_id, bool validate_only, -- bool unlock, char *errmsg_if_any) --{ -- struct vty *vty; -- -- vty = (struct vty *)session_ctx; -- -- if (!success) { -- zlog_err("COMMIT_CONFIG request for client 0x%" PRIx64 -- " failed, Error: '%s'", -- client_id, errmsg_if_any ? errmsg_if_any : "Unknown"); -- vty_out(vty, "%% Configuration failed.\n\n"); -- if (errmsg_if_any) -- vty_out(vty, "%s\n", errmsg_if_any); -- } else { -- debug_fe_client("COMMIT_CONFIG request for client 0x%" PRIx64 -- " req-id %" PRIu64 " was successfull%s%s", -- client_id, req_id, errmsg_if_any ? ": " : "", -- errmsg_if_any ?: ""); -- if (!unlock && errmsg_if_any) -- vty_out(vty, "MGMTD: %s\n", errmsg_if_any); -- } -- -- if (unlock) { -- /* we locked these when we sent the commit, unlock now */ -- vty_mgmt_unlock_candidate_inline(vty); -- vty_mgmt_unlock_running_inline(vty); -- } -- -- vty_mgmt_resume_response(vty, success ? CMD_SUCCESS -- : CMD_WARNING_CONFIG_FAILED); --} -- --static ssize_t vty_mgmt_libyang_print(void *user_data, const void *buf, -- size_t count) --{ -- struct vty *vty = user_data; -- -- vty_out(vty, "%.*s", (int)count, (const char *)buf); -- return count; --} -- --static void vty_out_yang_error(struct vty *vty, LYD_FORMAT format, -- const struct ly_err_item *ei) --{ --#if (LY_VERSION_MAJOR < 3) --#define data_path path --#else --#define data_path data_path --#endif -- bool have_apptag = ei->apptag && ei->apptag[0] != 0; -- bool have_path = ei->data_path && ei->data_path[0] != 0; -- bool have_msg = ei->msg && ei->msg[0] != 0; -- const char *severity = NULL; -- const char *evalid = NULL; -- const char *ecode = NULL; --#if (LY_VERSION_MAJOR < 3) -- LY_ERR err = ei->no; --#else -- LY_ERR err = ei->err; --#endif -- -- if (ei->level == LY_LLERR) -- severity = "error"; -- else if (ei->level == LY_LLWRN) -- severity = "warning"; -- -- ecode = yang_ly_strerrcode(err); -- if (err == LY_EVALID && ei->vecode != LYVE_SUCCESS) -- evalid = yang_ly_strvecode(ei->vecode); -- -- switch (format) { -- case LYD_XML: -- vty_out(vty, -- "<rpc-error xmlns=\"urn:ietf:params:xml:ns:netconf:base:1.0\">"); -- vty_out(vty, "<error-type>application</error-type>"); -- if (severity) -- vty_out(vty, "<error-severity>%s</error-severity>", -- severity); -- if (ecode) -- vty_out(vty, "<error-code>%s</error-code>", ecode); -- if (evalid) -- vty_out(vty, "<error-validation>%s</error-validation>\n", -- evalid); -- if (have_path) -- vty_out(vty, "<error-path>%s</error-path>\n", -- ei->data_path); -- if (have_apptag) -- vty_out(vty, "<error-app-tag>%s</error-app-tag>\n", -- ei->apptag); -- if (have_msg) -- vty_out(vty, "<error-message>%s</error-message>\n", -- ei->msg); -- -- vty_out(vty, "</rpc-error>"); -- break; -- case LYD_JSON: -- vty_out(vty, "{ \"error-type\": \"application\""); -- if (severity) -- vty_out(vty, ", \"error-severity\": \"%s\"", severity); -- if (ecode) -- vty_out(vty, ", \"error-code\": \"%s\"", ecode); -- if (evalid) -- vty_out(vty, ", \"error-validation\": \"%s\"", evalid); -- if (have_path) -- vty_out(vty, ", \"error-path\": \"%s\"", ei->data_path); -- if (have_apptag) -- vty_out(vty, ", \"error-app-tag\": \"%s\"", ei->apptag); -- if (have_msg) -- vty_out(vty, ", \"error-message\": \"%s\"", ei->msg); -- -- vty_out(vty, "}"); -- break; -- case LYD_UNKNOWN: -- case LYD_LYB: -- default: -- vty_out(vty, "%% error"); -- if (severity) -- vty_out(vty, " severity: %s", severity); -- if (evalid) -- vty_out(vty, " invalid: %s", evalid); -- if (have_path) -- vty_out(vty, " path: %s", ei->data_path); -- if (have_apptag) -- vty_out(vty, " app-tag: %s", ei->apptag); -- if (have_msg) -- vty_out(vty, " msg: %s", ei->msg); -- break; -- } --#undef data_path --} -- --static uint vty_out_yang_errors(struct vty *vty, LYD_FORMAT format) --{ -- const struct ly_err_item *ei = ly_err_first(ly_native_ctx); -- uint count; -- -- if (!ei) -- return 0; -- -- if (format == LYD_JSON) -- vty_out(vty, "\"ietf-restconf:errors\": [ "); -- -- for (count = 0; ei; count++, ei = ei->next) { -- if (count) -- vty_out(vty, ", "); -- vty_out_yang_error(vty, format, ei); -- } -- -- if (format == LYD_JSON) -- vty_out(vty, " ]"); -- -- ly_err_clean(ly_native_ctx, NULL); -- -- return count; --} -- -- --static int vty_mgmt_get_tree_result_notified(struct mgmt_fe_client *client, uintptr_t user_data, -- uint64_t client_id, uint64_t session_id, -- uintptr_t session_ctx, uint64_t req_id, -- enum mgmt_ds_id ds_id, LYD_FORMAT result_type, -- void *result, size_t len, int partial_error) --{ -- struct vty *vty; -- struct lyd_node *dnode; -- int ret = CMD_SUCCESS; -- LY_ERR err; -- -- vty = (struct vty *)session_ctx; -- -- debug_fe_client("GET_TREE request %ssucceeded, client 0x%" PRIx64 -- " req-id %" PRIu64, -- partial_error ? "partially " : "", client_id, req_id); -- -- assert(result_type == LYD_LYB || -- result_type == vty->mgmt_req_pending_data); -- -- if (vty->mgmt_req_pending_data == LYD_XML && partial_error) -- vty_out(vty, -- "<!-- some errors occurred gathering results -->\n"); -- -- if (result_type == LYD_LYB) { -- /* -- * parse binary into tree and print in the specified format -- */ -- result_type = vty->mgmt_req_pending_data; -- -- err = lyd_parse_data_mem(ly_native_ctx, result, LYD_LYB, 0, 0, -- &dnode); -- if (!err) -- err = lyd_print_clb(vty_mgmt_libyang_print, vty, dnode, -- result_type, LYD_PRINT_WITHSIBLINGS); -- lyd_free_all(dnode); -- -- if (vty_out_yang_errors(vty, result_type) || err) -- ret = CMD_WARNING; -- } else { -- /* -- * Print the in-format result -- */ -- assert(result_type == LYD_XML || result_type == LYD_JSON); -- vty_out(vty, "%.*s\n", (int)len - 1, (const char *)result); -- } -- -- vty_mgmt_resume_response(vty, ret); -- -- return 0; --} -- --static int vty_mgmt_edit_result_notified(struct mgmt_fe_client *client, -- uintptr_t user_data, -- uint64_t client_id, uint64_t session_id, -- uintptr_t session_ctx, uint64_t req_id, -- const char *xpath) --{ -- struct vty *vty = (struct vty *)session_ctx; -- -- debug_fe_client("EDIT request for client 0x%" PRIx64 " req-id %" PRIu64 -- " was successful, xpath: %s", -- client_id, req_id, xpath); -- -- vty_mgmt_resume_response(vty, CMD_SUCCESS); -- -- return 0; --} -- --static int vty_mgmt_rpc_result_notified(struct mgmt_fe_client *client, -- uintptr_t user_data, uint64_t client_id, -- uint64_t session_id, -- uintptr_t session_ctx, uint64_t req_id, -- const char *result) --{ -- struct vty *vty = (struct vty *)session_ctx; -- -- debug_fe_client("RPC request for client 0x%" PRIx64 " req-id %" PRIu64 -- " was successful", -- client_id, req_id); -- -- if (result) -- vty_out(vty, "%s\n", result); -- -- vty_mgmt_resume_response(vty, CMD_SUCCESS); -- -- return 0; --} -- --static int vty_mgmt_error_notified(struct mgmt_fe_client *client, -- uintptr_t user_data, uint64_t client_id, -- uint64_t session_id, uintptr_t session_ctx, -- uint64_t req_id, int error, -- const char *errstr) --{ -- struct vty *vty = (struct vty *)session_ctx; -- const char *cname = mgmt_fe_client_name(client); -- -- if (!vty->mgmt_req_pending_cmd) { -- debug_fe_client("Error with no pending command: %d returned for client %s 0x%" PRIx64 -- " session-id %" PRIu64 " req-id %" PRIu64 -- "error-str %s", -- error, cname, client_id, session_id, req_id, -- errstr); -- vty_out(vty, -- "%% Error %d from MGMTD for %s with no pending command: %s\n", -- error, cname, errstr); -- return CMD_WARNING; -- } -- -- debug_fe_client("Error %d returned for client %s 0x%" PRIx64 -- " session-id %" PRIu64 " req-id %" PRIu64 "error-str %s", -- error, cname, client_id, session_id, req_id, errstr); -- -- vty_out(vty, "%% %s (for %s, client %s)\n", errstr, -- vty->mgmt_req_pending_cmd, cname); -- -- vty_mgmt_resume_response(vty, error ? CMD_WARNING : CMD_SUCCESS); -- -- return 0; --} -- --static struct mgmt_fe_client_cbs mgmt_cbs = { -- .client_connect_notify = vty_mgmt_server_connected, -- .client_session_notify = vty_mgmt_session_notify, -- .lock_ds_notify = vty_mgmt_ds_lock_notified, -- .commit_config_notify = vty_mgmt_commit_config_result_notified, -- .get_tree_notify = vty_mgmt_get_tree_result_notified, -- .edit_notify = vty_mgmt_edit_result_notified, -- .rpc_notify = vty_mgmt_rpc_result_notified, -- .error_notify = vty_mgmt_error_notified, -- --}; -- --void vty_init_mgmt_fe(void) --{ -- char name[40]; -- -- assert(vty_master); -- assert(!mgmt_fe_client); -- snprintf(name, sizeof(name), "vty-%s-%ld", frr_get_progname(), -- (long)getpid()); -- mgmt_fe_client = mgmt_fe_client_create(name, &mgmt_cbs, 0, vty_master); -- assert(mgmt_fe_client); --} -- --bool vty_mgmt_fe_enabled(void) --{ -- return mgmt_fe_client && mgmt_fe_connected; --} -- --bool vty_mgmt_should_process_cli_apply_changes(struct vty *vty) --{ -- return vty->type != VTY_FILE && vty_mgmt_fe_enabled(); --} -- --int vty_mgmt_send_lockds_req(struct vty *vty, enum mgmt_ds_id ds_id, bool lock, bool scok) --{ -- assert(mgmt_fe_client); -- assert(vty->mgmt_session_id); -- -- vty->mgmt_req_id++; -- if (mgmt_fe_send_lockds_req(mgmt_fe_client, vty->mgmt_session_id, -- vty->mgmt_req_id, ds_id, lock, scok)) { -- zlog_err("Failed sending %sLOCK-DS-REQ req-id %" PRIu64, -- lock ? "" : "UN", vty->mgmt_req_id); -- vty_out(vty, "Failed to send %sLOCK-DS-REQ to MGMTD!\n", -- lock ? "" : "UN"); -- return -1; -- } -- -- if (!scok) -- vty->mgmt_req_pending_cmd = "MESSAGE_LOCKDS_REQ"; -- -- return 0; --} -- --int vty_mgmt_send_config_data(struct vty *vty, const char *xpath_base, -- bool implicit_commit) --{ -- char err_buf[BUFSIZ]; -- bool error = false; -- -- /* ADD this: candidate is locked or error */ -- -- if (implicit_commit) { -- assert(vty->mgmt_client_id && vty->mgmt_session_id); -- if (vty_mgmt_lock_candidate_inline(vty)) { -- vty_out(vty, "%% could not lock candidate DS\n"); -- return CMD_WARNING_CONFIG_FAILED; -- } else if (vty_mgmt_lock_running_inline(vty)) { -- vty_out(vty, "%% could not lock running DS\n"); -- vty_mgmt_unlock_candidate_inline(vty); -- return CMD_WARNING_CONFIG_FAILED; -- } -- } -- -- nb_candidate_edit_config_changes(vty->candidate_config, vty->cfg_changes, -- vty->num_cfg_changes, xpath_base, false, err_buf, -- sizeof(err_buf), &error); -- if (error) { -- /* -- * Failure to edit the candidate configuration should never -- * happen in practice, unless there's a bug in the code. When -- * that happens, log the error but otherwise ignore it. -- */ -- vty_out(vty, "%% Couldn't apply changes: %s", err_buf); -- --#if 0 // or do we expect the caller to do this? -- /* the candidate is no longer valid so restore it */ -- nb_config_replace(vty->candidate_config, running_config, true); --#endif --error: -- if (implicit_commit) { -- vty_mgmt_unlock_running_inline(vty); -- vty_mgmt_unlock_candidate_inline(vty); -- } -- return CMD_WARNING_CONFIG_FAILED; -- } -- -- if (!implicit_commit) -- return CMD_SUCCESS; -- -- assert(vty->mgmt_client_id && vty->mgmt_session_id); -- if (vty_mgmt_send_commit_config(vty, false, false, true) < 0) -- goto error; -- -- return CMD_SUCCESS; --} -- --int vty_mgmt_send_commit_config(struct vty *vty, bool validate_only, bool abort, bool unlock) --{ -- if (mgmt_fe_client && vty->mgmt_session_id) { -- vty->mgmt_req_id++; -- if (mgmt_fe_send_commitcfg_req(mgmt_fe_client, vty->mgmt_session_id, -- vty->mgmt_req_id, MGMTD_DS_CANDIDATE, -- MGMTD_DS_RUNNING, validate_only, abort, unlock)) { -- zlog_err("Failed sending COMMIT-REQ req-id %" PRIu64, -- vty->mgmt_req_id); -- vty_out(vty, "Failed to send COMMIT-REQ to MGMTD!\n"); -- return -1; -- } -- -- vty->mgmt_req_pending_cmd = "MESSAGE_COMMCFG_REQ"; -- vty->mgmt_num_pending_setcfg = 0; -- } -- -- return 0; --} -- --int vty_mgmt_send_get_data_req(struct vty *vty, uint8_t datastore, -- LYD_FORMAT result_type, uint8_t flags, -- uint8_t defaults, const char *xpath) --{ -- LYD_FORMAT intern_format = result_type; -- -- vty->mgmt_req_id++; -- -- if (mgmt_fe_send_get_data_req(mgmt_fe_client, vty->mgmt_session_id, -- vty->mgmt_req_id, datastore, -- intern_format, flags, defaults, xpath)) { -- zlog_err("Failed to send GET-DATA to MGMTD session-id: %" PRIu64 -- " req-id %" PRIu64 ".", -- vty->mgmt_session_id, vty->mgmt_req_id); -- vty_out(vty, "Failed to send GET-DATA to MGMTD!\n"); -- return -1; -- } -- -- vty->mgmt_req_pending_cmd = "MESSAGE_GET_DATA_REQ"; -- vty->mgmt_req_pending_data = result_type; -- -- return 0; --} -- --int vty_mgmt_send_edit_req(struct vty *vty, uint8_t datastore, -- LYD_FORMAT request_type, uint8_t flags, -- uint8_t operation, const char *xpath, -- const char *data) --{ -- vty->mgmt_req_id++; -- -- if (mgmt_fe_send_edit_req(mgmt_fe_client, vty->mgmt_session_id, -- vty->mgmt_req_id, datastore, request_type, -- flags, operation, xpath, data)) { -- zlog_err("Failed to send EDIT to MGMTD session-id: %" PRIu64 -- " req-id %" PRIu64 ".", -- vty->mgmt_session_id, vty->mgmt_req_id); -- vty_out(vty, "Failed to send EDIT to MGMTD!\n"); -- return -1; -- } -- -- vty->mgmt_req_pending_cmd = "MESSAGE_EDIT_REQ"; -- -- return 0; --} -- --int vty_mgmt_send_rpc_req(struct vty *vty, LYD_FORMAT request_type, -- const char *xpath, const char *data) --{ -- vty->mgmt_req_id++; -- -- if (mgmt_fe_send_rpc_req(mgmt_fe_client, vty->mgmt_session_id, -- vty->mgmt_req_id, request_type, xpath, data)) { -- zlog_err("Failed to send RPC to MGMTD session-id: %" PRIu64 -- " req-id %" PRIu64 ".", -- vty->mgmt_session_id, vty->mgmt_req_id); -- vty_out(vty, "Failed to send RPC to MGMTD!\n"); -- return -1; -- } -- -- vty->mgmt_req_pending_cmd = "MESSAGE_RPC_REQ"; -- -- return 0; --} -- - /* Install vty's own commands like `who' command. */ - void vty_init(struct event_loop *master_thread, bool do_command_logging) - { -@@ -4173,10 +3444,5 @@ void vty_terminate(void) - vtys_fini(vtysh_sessions); - vtys_init(vtysh_sessions); - -- if (mgmt_fe_client) { -- mgmt_fe_client_destroy(mgmt_fe_client); -- mgmt_fe_client = NULL; -- } -- - vty_serv_stop(); - } -diff --git a/lib/vty.h b/lib/vty.h -index 83185c7b8f..5caa412ef7 100644 ---- a/lib/vty.h -+++ b/lib/vty.h -@@ -415,30 +415,6 @@ extern void vty_stdio_suspend(void); - extern void vty_stdio_resume(void); - extern void vty_stdio_close(void); - --extern void vty_init_mgmt_fe(void); --extern bool vty_mgmt_fe_enabled(void); --extern bool vty_mgmt_should_process_cli_apply_changes(struct vty *vty); -- --extern bool mgmt_vty_read_configs(void); --extern int vty_mgmt_send_config_data(struct vty *vty, const char *xpath_base, -- bool implicit_commit); --extern int vty_mgmt_send_commit_config(struct vty *vty, bool validate_only, bool abort, bool unlock); --extern int vty_mgmt_send_get_data_req(struct vty *vty, uint8_t datastore, -- LYD_FORMAT result_type, uint8_t flags, -- uint8_t defaults, const char *xpath); --extern int vty_mgmt_send_edit_req(struct vty *vty, uint8_t datastore, -- LYD_FORMAT request_type, uint8_t flags, -- uint8_t operation, const char *xpath, -- const char *data); --extern int vty_mgmt_send_rpc_req(struct vty *vty, LYD_FORMAT request_type, -- const char *xpath, const char *data); --extern int vty_mgmt_send_lockds_req(struct vty *vty, enum mgmt_ds_id ds_id, bool lock, bool scok); --extern void vty_mgmt_resume_response(struct vty *vty, int ret); -- --static inline bool vty_needs_implicit_commit(const struct vty *vty) --{ -- return frr_get_cli_mode() == FRR_CLI_CLASSIC && !vty->pending_allowed; --} - - /* Applications can check vty status */ - static inline bool vty_is_closed(const struct vty *vty) -@@ -447,6 +423,23 @@ static inline bool vty_is_closed(const struct vty *vty) - vty->wfd < 0); - } - -+/* -+ * Semi-private APIs for use in mgmtd-vty code -+ */ -+extern void vty_resume_response(struct vty *vty, int ret); -+ -+/* --------------------------------------------------- */ -+/* Callbacks for Mgmtd front-end CLI vty modifications */ -+/* --------------------------------------------------- */ -+extern void (*vty_new_mgmt_cb)(struct vty *vty); -+extern void (*vty_close_mgmt_cb)(struct vty *vty); -+extern int (*vty_config_enter_mgmt_cb)(struct vty *vty, bool private_config, bool exclusive, -+ bool file_lock); -+extern void (*vty_config_node_exit_mgmt_cb)(struct vty *vty); -+extern int (*nb_cli_apply_changes_mgmt_cb)(struct vty *vty, const char *xpath_base_abs); -+extern int (*nb_cli_rpc_mgmt_cb)(struct vty *vty, const char *xpath, const struct lyd_node *input); -+ -+ - #ifdef __cplusplus - } - #endif -diff --git a/mgmtd/mgmt.c b/mgmtd/mgmt.c -index 7f8a142171..d882fa2ed0 100644 ---- a/mgmtd/mgmt.c -+++ b/mgmtd/mgmt.c -@@ -57,7 +57,7 @@ void mgmt_init(void) - mgmt_history_init(); - - /* Initialize MGMTD Transaction module */ -- mgmt_txn_init(mm, mm->master); -+ mgmt_txn_init(); - - /* Initialize the MGMTD Frontend Adapter Module */ - mgmt_fe_adapter_init(mm->master); -@@ -66,7 +66,7 @@ void mgmt_init(void) - * Initialize the CLI frontend client -- this queues an event for the - * client to short-circuit connect to the server (ourselves). - */ -- vty_init_mgmt_fe(); -+ vty_mgmt_init(); - - /* - * MGMTD VTY commands installation -- the frr lib code will queue an -@@ -91,9 +91,9 @@ void mgmt_init(void) - void mgmt_terminate(void) - { - mgmt_be_client_destroy(mgmt_be_client); -+ mgmt_txn_destroy(); - mgmt_fe_adapter_destroy(); - mgmt_be_adapter_destroy(); -- mgmt_txn_destroy(); - mgmt_history_destroy(); - mgmt_ds_destroy(); - } -diff --git a/mgmtd/mgmt.h b/mgmtd/mgmt.h -index 665e8d8fd8..592b993ad5 100644 ---- a/mgmtd/mgmt.h -+++ b/mgmtd/mgmt.h -@@ -33,7 +33,7 @@ extern struct debug mgmt_debug_txn; - #define MGMT_DEBUG_FE_CHECK() DEBUG_MODE_CHECK(&mgmt_debug_fe, DEBUG_MODE_ALL) - #define MGMT_DEBUG_TXN_CHECK() DEBUG_MODE_CHECK(&mgmt_debug_tx, DEBUG_MODE_ALL) - --struct mgmt_txn_ctx; -+struct mgmt_txn; - - /* - * MGMTD master for system wide configurations and variables. -@@ -44,16 +44,6 @@ struct mgmt_master { - /* How big should we set the socket buffer size */ - uint32_t socket_buffer; - -- /* The single instance of config transaction allowed at any time */ -- struct mgmt_txns_head txn_list; -- -- /* Map of Transactions and its ID */ -- struct hash *txn_hash; -- uint64_t next_txn_id; -- -- /* The single instance of config transaction allowed at any time */ -- struct mgmt_txn_ctx *cfg_txn; -- - /* Datastores */ - struct mgmt_ds_ctx *running_ds; - struct mgmt_ds_ctx *candidate_ds; -@@ -66,6 +56,7 @@ struct mgmt_master { - struct mgmt_cmt_infos_head cmts; /* List of last 10 commits executed. */ - }; - -+extern struct frr_daemon_info *mgmt_daemon_info; - extern struct mgmt_master *mm; - - /* Inline functions */ -@@ -101,4 +92,24 @@ extern void mgmt_master_init(struct event_loop *master, const int buffer_size); - extern void mgmt_init(void); - extern void mgmt_vty_init(void); - -+/* -+ * mgmt vty -+ */ -+extern void vty_mgmt_init(void); -+extern void vty_mgmt_terminate(void); -+ -+extern int vty_mgmt_send_commit_config(struct vty *vty, bool validate_only, bool abort, -+ bool unlock); -+extern int vty_mgmt_send_get_data_req(struct vty *vty, uint8_t datastore, LYD_FORMAT result_type, -+ uint8_t flags, uint8_t defaults, const char *xpath); -+extern int vty_mgmt_send_edit_req(struct vty *vty, uint8_t datastore, LYD_FORMAT request_type, -+ uint8_t flags, uint8_t operation, const char *xpath, -+ const char *data); -+extern void vty_mgmt_resume_response(struct vty *vty, int ret); -+ -+ -+extern int vty_mgmt_send_config_data(struct vty *vty, const char *xpath_base, bool implicit_commit); -+extern int vty_mgmt_send_rpc_req(struct vty *vty, LYD_FORMAT request_type, const char *xpath, -+ const char *data); -+ - #endif /* _FRR_MGMTD_H */ -diff --git a/mgmtd/mgmt_be_adapter.c b/mgmtd/mgmt_be_adapter.c -index 6d0e303c9a..ff31d29051 100644 ---- a/mgmtd/mgmt_be_adapter.c -+++ b/mgmtd/mgmt_be_adapter.c -@@ -4,7 +4,7 @@ - * - * Copyright (C) 2021 Vmware, Inc. - * Pushpasis Sarkar <spushpasis@vmware.com> -- * Copyright (c) 2023, LabN Consulting, L.L.C. -+ * Copyright (c) 2023-2025, LabN Consulting, L.L.C. - */ - - #include <zebra.h> -@@ -22,15 +22,35 @@ - #include "mgmtd/mgmt_be_adapter.h" - - #define _dbg(fmt, ...) DEBUGD(&mgmt_debug_be, "BE-ADAPTER: %s: " fmt, __func__, ##__VA_ARGS__) -+#define _log_warn(fmt, ...) zlog_warn("BE-ADAPTER: %s: WARNING: " fmt, __func__, ##__VA_ARGS__) - #define _log_err(fmt, ...) zlog_err("BE-ADAPTER: %s: ERROR: " fmt, __func__, ##__VA_ARGS__) - --#define FOREACH_ADAPTER_IN_LIST(adapter) \ -- frr_each_safe (mgmt_be_adapters, &mgmt_be_adapters, (adapter)) - - /* ---------- */ --/* Client IDs */ -+/* Prototypes */ - /* ---------- */ - -+/* Forward declarations */ -+static void be_adapter_sched_init_event(struct mgmt_be_client_adapter *adapter); -+ -+static void be_adapter_delete(struct mgmt_be_client_adapter *adapter); -+ -+static enum mgmt_be_client_id mgmt_be_client_name2id(const char *name); -+ -+ -+/* --------- */ -+/* Constants */ -+/* --------- */ -+ -+/* -+ * Client IDs -+ */ -+ -+/* -+ * NOTE: This mapping is more trouble than it's worth. Just convert to a dynamic -+ * allocation as backends subscribe/register. -+ */ -+ - const char *mgmt_be_client_names[MGMTD_BE_CLIENT_ID_MAX + 1] = { - [MGMTD_BE_CLIENT_ID_TESTC] = "mgmtd-testc", /* always first */ - [MGMTD_BE_CLIENT_ID_MGMTD] = "mgmtd", /* loopback */ -@@ -47,9 +67,15 @@ const char *mgmt_be_client_names[MGMTD_BE_CLIENT_ID_MAX + 1] = { - [MGMTD_BE_CLIENT_ID_MAX] = "Unknown/Invalid", - }; - --/* ------------- */ --/* XPATH MAPPING */ --/* ------------- */ -+ -+/* ------------------------------- */ -+/* Const XPath Mappings (for init) */ -+/* ------------------------------- */ -+ -+/* -+ * NOTE: These mappings are more trouble than they are worth. Just convert to -+ * use the subscribe message exclusively. -+ */ - - /* - * Mapping of YANG XPath prefixes to their corresponding backend clients. -@@ -228,6 +254,10 @@ static const char *const *be_client_rpc_xpaths[MGMTD_BE_CLIENT_ID_MAX] = { - [MGMTD_BE_CLIENT_ID_ZEBRA] = zebra_rpc_xpaths, - }; - -+/* ---------------- */ -+/* Global Variables */ -+/* ---------------- */ -+ - /* - * We would like to have a better ADT than one with O(n) comparisons - * -@@ -245,7 +275,7 @@ static struct mgmt_be_xpath_map *be_rpc_xpath_map; - static struct event_loop *mgmt_loop; - static struct msg_server mgmt_be_server = {.fd = -1}; - --static struct mgmt_be_adapters_head mgmt_be_adapters; -+LIST_HEAD(be_adapter_list_head, mgmt_be_client_adapter) be_adapters; - - static struct mgmt_be_client_adapter - *mgmt_be_adapters_by_id[MGMTD_BE_CLIENT_ID_MAX]; -@@ -259,14 +289,11 @@ static const char *const mgmtd_config_xpaths[] = { - }; - - --/* Forward declarations */ --static void --mgmt_be_adapter_sched_init_event(struct mgmt_be_client_adapter *adapter); -- --static bool be_is_client_interested(const char *xpath, enum mgmt_be_client_id id, -- enum mgmt_be_xpath_subscr_type type); -+/* ---------------- */ -+/* Lookup Functions */ -+/* ---------------- */ - --const char *mgmt_be_client_id2name(enum mgmt_be_client_id id) -+static const char *be_adapter_id_name(enum mgmt_be_client_id id) - { - if (id > MGMTD_BE_CLIENT_ID_MAX) - return "invalid client id"; -@@ -278,43 +305,173 @@ static enum mgmt_be_client_id mgmt_be_client_name2id(const char *name) - enum mgmt_be_client_id id; - - FOREACH_MGMTD_BE_CLIENT_ID (id) { -- if (!strncmp(mgmt_be_client_names[id], name, -- MGMTD_CLIENT_NAME_MAX_LEN)) -+ if (!strncmp(mgmt_be_client_names[id], name, MGMTD_CLIENT_NAME_MAX_LEN)) - return id; - } - - return MGMTD_BE_CLIENT_ID_MAX; - } - --static struct mgmt_be_client_adapter * --mgmt_be_find_adapter_by_fd(int conn_fd) -+static struct mgmt_be_client_adapter *mgmt_be_find_adapter_by_fd(int conn_fd) - { - struct mgmt_be_client_adapter *adapter; - -- FOREACH_ADAPTER_IN_LIST (adapter) { -+ LIST_FOREACH (adapter, &be_adapters, link) - if (adapter->conn->fd == conn_fd) - return adapter; -+ return NULL; -+} -+ -+struct mgmt_be_client_adapter *mgmt_be_get_adapter_by_id(enum mgmt_be_client_id id) -+{ -+ return (id < MGMTD_BE_CLIENT_ID_MAX ? mgmt_be_adapters_by_id[id] : NULL); -+} -+ -+ -+/* ======================= */ -+/* XPath Mapping Functions */ -+/* ======================= */ -+ -+/* -+ * Check if either path or xpath is a prefix of the other. Before checking the -+ * xpath is converted to a regular path string (e..g, removing key value -+ * specifiers). -+ */ -+static bool mgmt_be_xpath_prefix(const char *path, const char *xpath) -+{ -+ int xc, pc; -+ -+ while ((xc = *xpath++)) { -+ if (xc == '[') { -+ xpath = frrstr_skip_over_char(xpath, ']'); -+ if (!xpath) -+ return false; -+ continue; -+ } -+ pc = *path++; -+ if (!pc) -+ return true; -+ if (pc != xc) -+ return false; - } -+ return true; -+} - -- return NULL; -+/* -+ * Get the mask of clients interested in an xpath. -+ */ -+uint64_t mgmt_be_interested_clients(const char *xpath, enum mgmt_be_xpath_subscr_type type) -+{ -+ struct mgmt_be_xpath_map *maps = NULL, *map; -+ uint64_t clients = 0; -+ bool wild_root; -+ -+ switch (type) { -+ case MGMT_BE_XPATH_SUBSCR_TYPE_CFG: -+ maps = be_cfg_xpath_map; -+ break; -+ case MGMT_BE_XPATH_SUBSCR_TYPE_OPER: -+ maps = be_oper_xpath_map; -+ break; -+ case MGMT_BE_XPATH_SUBSCR_TYPE_NOTIF: -+ maps = be_notif_xpath_map; -+ break; -+ case MGMT_BE_XPATH_SUBSCR_TYPE_RPC: -+ maps = be_rpc_xpath_map; -+ break; -+ } -+ -+ /* wild_root will select all clients that advertise op-state */ -+ wild_root = !strcmp(xpath, "/") || !strcmp(xpath, "/*"); -+ darr_foreach_p (maps, map) -+ if (wild_root || mgmt_be_xpath_prefix(map->xpath_prefix, xpath)) -+ clients |= map->clients; -+ -+ _dbg("xpath: '%s' subscribed clients: 0x%Lx", xpath, clients); -+ -+ return clients; - } - --static struct mgmt_be_client_adapter * --mgmt_be_find_adapter_by_name(const char *name) -+/* -+ * Test for interest by mgmtd in xpath. -+ * -+ * Mgmtd handles it's own config directly vs as a backend client of -+ * itself. This function supports that. -+ */ -+bool mgmt_is_mgmtd_interested(const char *xpath) - { -- struct mgmt_be_client_adapter *adapter; -+ const char *const *match = mgmtd_config_xpaths; -+ const char *const *ematch = match + array_size(mgmtd_config_xpaths); - -- FOREACH_ADAPTER_IN_LIST (adapter) { -- if (!strncmp(adapter->name, name, sizeof(adapter->name))) -- return adapter; -+ for (; match < ematch; match++) { -+ if (mgmt_be_xpath_prefix(*match, xpath)) { -+ _dbg("mgmtd: subscribed to %s", xpath); -+ return true; -+ } -+ } -+ return false; -+} -+ -+ -+/* -+ * This function is inefficient. For each xpath it walks the global map list. -+ * We should keep a separate per-client map and use that here. -+ * -+ * NOTE: Fix this when removing the global constant maps used for bootstrapping. -+ */ -+static bool be_is_client_interested(const char *xpath, enum mgmt_be_client_id id, -+ enum mgmt_be_xpath_subscr_type type) -+{ -+ uint64_t clients; -+ -+ assert(id < MGMTD_BE_CLIENT_ID_MAX); -+ -+ _dbg("Checking client: %s for xpath: '%s'", be_adapter_id_name(id), xpath); -+ -+ clients = mgmt_be_interested_clients(xpath, type); -+ if (IS_IDBIT_SET(clients, id)) { -+ _dbg("client: %s: interested", be_adapter_id_name(id)); -+ return true; - } - -- return NULL; -+ _dbg("client: %s: not interested", be_adapter_id_name(id)); -+ return false; -+} -+ -+/* -+ * Get full config changes for adapter. -+ * -+ * Walk the entire running config building a set of create-changes to send to -+ * the adapter and return a set of config changes. -+ */ -+struct nb_config_cbs mgmt_be_adapter_get_config(struct mgmt_be_client_adapter *adapter) -+{ -+ struct nb_config_cbs changes = { 0 }; -+ const struct lyd_node *root, *dnode; -+ uint32_t seq = 0; -+ char *xpath; -+ -+ LY_LIST_FOR (running_config->dnode, root) { -+ LYD_TREE_DFS_BEGIN (root, dnode) { -+ if (lysc_is_key(dnode->schema)) -+ goto walk_cont; -+ -+ xpath = lyd_path(dnode, LYD_PATH_STD, NULL, 0); -+ if (be_is_client_interested(xpath, adapter->id, -+ MGMT_BE_XPATH_SUBSCR_TYPE_CFG)) -+ nb_config_diff_add_change(&changes, NB_CB_CREATE, &seq, dnode); -+ else -+ LYD_TREE_DFS_continue = 1; /* skip any subtree */ -+ free(xpath); -+walk_cont: -+ LYD_TREE_DFS_END(root, dnode); -+ } -+ } -+ return changes; - } - --static void mgmt_register_client_xpath(enum mgmt_be_client_id id, -- const char *xpath, -- enum mgmt_be_xpath_subscr_type type) -+static void be_adapter_register_client_xpath(enum mgmt_be_client_id id, const char *xpath, -+ enum mgmt_be_xpath_subscr_type type) - { - struct mgmt_be_xpath_map **maps, *map; - -@@ -337,7 +494,7 @@ static void mgmt_register_client_xpath(enum mgmt_be_client_id id, - - darr_foreach_p (*maps, map) { - if (!strcmp(xpath, map->xpath_prefix)) { -- map->clients |= (1u << id); -+ SET_IDBIT(map->clients, id); - return; - } - } -@@ -348,9 +505,15 @@ static void mgmt_register_client_xpath(enum mgmt_be_client_id id, - } - - /* -- * initial the combined maps from per client maps -+ * Use the global per-client constants to initialize the xpath maps. -+ * -+ * NOTE: This system was used to bootstrap the mgmtd work, and has proved to add -+ * complexity to the process converting daemons to use mgmtd with no real -+ * performance requirement, the daemon can send it's xpath regsitrations in it's -+ * subscribe message so we should just use that. Please convert to do that and -+ * remove this function at first convenience. - */ --static void mgmt_be_xpath_map_init(void) -+static void be_adapter_xpath_maps_init(void) - { - enum mgmt_be_client_id id; - const char *const *init; -@@ -361,29 +524,26 @@ static void mgmt_be_xpath_map_init(void) - /* Initialize the common config init map */ - for (init = be_client_config_xpaths[id]; init && *init; init++) { - _dbg(" - CFG XPATH: '%s'", *init); -- mgmt_register_client_xpath(id, *init, -- MGMT_BE_XPATH_SUBSCR_TYPE_CFG); -+ be_adapter_register_client_xpath(id, *init, MGMT_BE_XPATH_SUBSCR_TYPE_CFG); - } - - /* Initialize the common oper init map */ - for (init = be_client_oper_xpaths[id]; init && *init; init++) { - _dbg(" - OPER XPATH: '%s'", *init); -- mgmt_register_client_xpath(id, *init, -- MGMT_BE_XPATH_SUBSCR_TYPE_OPER); -+ be_adapter_register_client_xpath(id, *init, MGMT_BE_XPATH_SUBSCR_TYPE_OPER); - } - - /* Initialize the common NOTIF init map */ - for (init = be_client_notif_xpaths[id]; init && *init; init++) { - _dbg(" - NOTIF XPATH: '%s'", *init); -- mgmt_register_client_xpath(id, *init, -- MGMT_BE_XPATH_SUBSCR_TYPE_NOTIF); -+ be_adapter_register_client_xpath(id, *init, -+ MGMT_BE_XPATH_SUBSCR_TYPE_NOTIF); - } - - /* Initialize the common RPC init map */ - for (init = be_client_rpc_xpaths[id]; init && *init; init++) { - _dbg(" - RPC XPATH: '%s'", *init); -- mgmt_register_client_xpath(id, *init, -- MGMT_BE_XPATH_SUBSCR_TYPE_RPC); -+ be_adapter_register_client_xpath(id, *init, MGMT_BE_XPATH_SUBSCR_TYPE_RPC); - } - } - -@@ -393,7 +553,10 @@ static void mgmt_be_xpath_map_init(void) - _dbg("Total RPC XPath Maps: %u", darr_len(be_rpc_xpath_map)); - } - --static void mgmt_be_xpath_map_cleanup(void) -+/* -+ * Cleanup the xpath maps. -+ */ -+static void be_adapter_xpath_maps_cleanup(void) - { - struct mgmt_be_xpath_map *map; - -@@ -415,138 +578,32 @@ static void mgmt_be_xpath_map_cleanup(void) - } - - -+/* ============================== */ -+/* Backend Message (API) Handling */ -+/* ============================== */ -+ - /* -- * Check if either path or xpath is a prefix of the other. Before checking the -- * xpath is converted to a regular path string (e..g, removing key value -- * specifiers). -+ * The TXN module is the primary producer/consumer of messages to/from backend -+ * clients so that is where you will find most of the backend message handling -+ * functions. - */ --static bool mgmt_be_xpath_prefix(const char *path, const char *xpath) --{ -- int xc, pc; -- -- while ((xc = *xpath++)) { -- if (xc == '[') { -- xpath = frrstr_skip_over_char(xpath, ']'); -- if (!xpath) -- return false; -- continue; -- } -- pc = *path++; -- if (!pc) -- return true; -- if (pc != xc) -- return false; -- } -- return true; --} -- --static void mgmt_be_adapter_delete(struct mgmt_be_client_adapter *adapter) --{ -- _dbg("deleting client adapter '%s'", adapter->name); -- -- /* -- * Notify about disconnect for appropriate cleanup -- */ -- mgmt_txn_notify_be_adapter_conn(adapter, false); -- if (adapter->id < MGMTD_BE_CLIENT_ID_MAX) { -- mgmt_be_adapters_by_id[adapter->id] = NULL; -- adapter->id = MGMTD_BE_CLIENT_ID_MAX; -- } -- -- assert(adapter->refcount == 1); -- mgmt_be_adapter_unlock(&adapter); --} -- --static int mgmt_be_adapter_notify_disconnect(struct msg_conn *conn) --{ -- struct mgmt_be_client_adapter *adapter = conn->user; -- -- _dbg("notify disconnect for client adapter '%s'", adapter->name); -- -- mgmt_be_adapter_delete(adapter); -- -- return 0; --} -- --static void --mgmt_be_adapter_cleanup_old_conn(struct mgmt_be_client_adapter *adapter) --{ -- struct mgmt_be_client_adapter *old; - -- FOREACH_ADAPTER_IN_LIST (old) { -- if (old != adapter && -- !strncmp(adapter->name, old->name, sizeof(adapter->name))) { -- /* -- * We have a Zombie lingering around -- */ -- _dbg("Client '%s' (FD:%d) seems to have reconnected. Removing old connection (FD:%d)!", -- adapter->name, adapter->conn->fd, old->conn->fd); -- /* this will/should delete old */ -- msg_conn_disconnect(old->conn, false); -- } -- } --} -- --int mgmt_be_send_txn_req(struct mgmt_be_client_adapter *adapter, uint64_t txn_id, bool create) --{ -- struct mgmt_msg_txn_req *msg; -- int ret; -- -- msg = mgmt_msg_native_alloc_msg(struct mgmt_msg_txn_req, 0, MTYPE_MSG_NATIVE_TXN_REQ); -- msg->code = MGMT_MSG_CODE_TXN_REQ; -- msg->refer_id = txn_id; -- msg->create = create; -- -- _dbg("Sending TXN_REQ to '%s' to %s txn-id: %Lu", adapter->name, -- create ? "create" : "delete", txn_id); -- -- ret = mgmt_msg_native_send_msg(adapter->conn, msg, false); -- mgmt_msg_native_free_msg(msg); -- return ret; --} -- --int mgmt_be_send_cfgdata_req(struct mgmt_be_client_adapter *adapter, struct mgmt_msg_cfg_req *msg) -+int mgmt_be_adapter_send(struct mgmt_be_client_adapter *adapter, void *_msg) - { -+ struct mgmt_msg_header *msg = (struct mgmt_msg_header *)_msg; -+ uint64_t txn_id = msg->refer_id; - int ret; - -- _dbg("Sending CFG_REQ to '%s' txn-id: %Lu req-id: %Lu", adapter->name, msg->refer_id, -- msg->req_id); -+ _dbg("Sending %s to '%s' txn-id: %Lu", mgmt_msg_code_name(msg->code), adapter->name, -+ txn_id); - - ret = mgmt_msg_native_send_msg(adapter->conn, msg, false); - if (ret) -- _log_err("Could not send CFG_REQ txn-id: %Lu to client '%s", msg->refer_id, -- adapter->name); -- return ret; --} -- --int mgmt_be_send_cfgapply_req(struct mgmt_be_client_adapter *adapter, -- uint64_t txn_id) --{ -- struct mgmt_msg_cfg_apply_req *msg; -- int ret; -- -- msg = mgmt_msg_native_alloc_msg(struct mgmt_msg_cfg_apply_req, 0, -- MTYPE_MSG_NATIVE_CFG_APPLY_REQ); -- msg->code = MGMT_MSG_CODE_CFG_APPLY_REQ; -- msg->refer_id = txn_id; -- -- _dbg("Sending CFG_APPLY_REQ to '%s' txn-id: %Lu", adapter->name, txn_id); -- -- ret = mgmt_msg_native_send_msg(adapter->conn, msg, false); -- mgmt_msg_native_free_msg(msg); -+ _log_err("Failed sending %s to '%s' txn-id: %Lu", mgmt_msg_code_name(msg->code), -+ adapter->name, txn_id); - return ret; - } - --int mgmt_be_send_native(enum mgmt_be_client_id id, void *msg) --{ -- struct mgmt_be_client_adapter *adapter = mgmt_be_get_adapter_by_id(id); -- -- if (!adapter) -- return -1; -- -- return mgmt_msg_native_send_msg(adapter->conn, msg, false); --} -- - /* - * Send notification to back-ends that subscribed for them. - */ -@@ -599,9 +656,13 @@ static void mgmt_be_adapter_send_notify(struct mgmt_msg_notify_data *msg, size_t - static void be_adapter_handle_subscribe(struct mgmt_msg_subscribe *msg, size_t msg_len, - struct mgmt_be_client_adapter *adapter) - { -+ struct mgmt_be_client_adapter *old; - const char **s = NULL; - uint i = 0; - -+ _dbg("SUBSCRIBE '%s' to register xpaths config: %u oper: %u notif: %u rpc: %u", -+ adapter->name, msg->nconfig, msg->noper, msg->nnotify, msg->nrpc); -+ - s = mgmt_msg_native_strings_decode(msg, msg_len, msg->strings); - if (!s) { - _log_err("Corrupt subscribe msg from '%s'", adapter->name); -@@ -621,498 +682,336 @@ static void be_adapter_handle_subscribe(struct mgmt_msg_subscribe *msg, size_t m - if (adapter->id >= MGMTD_BE_CLIENT_ID_MAX) { - _log_err("Unable to resolve adapter '%s' to a valid ID. Disconnecting!", - adapter->name); -- /* this will/should delete old */ -- msg_conn_disconnect(adapter->conn, false); -+ be_adapter_delete(adapter); - goto done; - } -+ -+ old = mgmt_be_adapters_by_id[adapter->id]; -+ if (old) { -+ _dbg("be-client: %s using fd: %d reconnected with fd: %d", old->name, -+ old->conn->fd, adapter->conn->fd); -+ be_adapter_delete(old); -+ } - mgmt_be_adapters_by_id[adapter->id] = adapter; -- mgmt_be_adapter_cleanup_old_conn(adapter); - - /* schedule INIT sequence now that it is registered */ -- mgmt_be_adapter_sched_init_event(adapter); -+ be_adapter_sched_init_event(adapter); - - for (uint j = 0; j < msg->nconfig; j++) -- mgmt_register_client_xpath(adapter->id, s[i++], MGMT_BE_XPATH_SUBSCR_TYPE_CFG); -+ be_adapter_register_client_xpath(adapter->id, s[i++], -+ MGMT_BE_XPATH_SUBSCR_TYPE_CFG); - - for (uint j = 0; j < msg->noper; j++) -- mgmt_register_client_xpath(adapter->id, s[i++], MGMT_BE_XPATH_SUBSCR_TYPE_OPER); -+ be_adapter_register_client_xpath(adapter->id, s[i++], -+ MGMT_BE_XPATH_SUBSCR_TYPE_OPER); - - for (uint j = 0; j < msg->nnotify; j++) -- mgmt_register_client_xpath(adapter->id, s[i++], MGMT_BE_XPATH_SUBSCR_TYPE_NOTIF); -+ be_adapter_register_client_xpath(adapter->id, s[i++], -+ MGMT_BE_XPATH_SUBSCR_TYPE_NOTIF); - - for (uint j = 0; j < msg->nrpc; j++) -- mgmt_register_client_xpath(adapter->id, s[i++], MGMT_BE_XPATH_SUBSCR_TYPE_RPC); -+ be_adapter_register_client_xpath(adapter->id, s[i++], -+ MGMT_BE_XPATH_SUBSCR_TYPE_RPC); - done: - darr_free_free(s); - } - --/* -- * Handle a native encoded message -- */ --static void be_adapter_handle_native_msg(struct mgmt_be_client_adapter *adapter, -- struct mgmt_msg_header *msg, -- size_t msg_len) -+ -+static void be_adapter_process_msg(uint8_t version, uint8_t *data, size_t msg_len, -+ struct msg_conn *conn) - { -- struct mgmt_msg_subscribe *subr_msg; -+ struct mgmt_be_client_adapter *adapter = conn->user; -+ struct mgmt_msg_header *msg = (typeof(msg))data; - struct mgmt_msg_notify_data *notify_msg; -- struct mgmt_msg_txn_reply *txn_msg; - struct mgmt_msg_error *error_msg; - -- /* get the transaction */ -+ if (version != MGMT_MSG_VERSION_NATIVE) { -+ _log_err("Protobuf not supported for backend messages (adapter: %s)", -+ adapter->name); -+ return; -+ } -+ if (msg_len < sizeof(*msg)) { -+ _log_err("native message to adapter %s too short %zu", adapter->name, msg_len); -+ return; -+ } - -- switch (msg->code) { -- case MGMT_MSG_CODE_SUBSCRIBE: -- subr_msg = (typeof(subr_msg))msg; -- _dbg("Got SUBSCRIBE from '%s' to register xpaths config: %u oper: %u notif: %u rpc: %u", -- adapter->name, subr_msg->nconfig, subr_msg->noper, subr_msg->nnotify, -- subr_msg->nrpc); -+ /* -+ * Most messages are sent to the TXN module for processing. -+ * -+ * NOTE: Handling the config messages may lead to disconnect and -+ * deletion of the adapater. So don't do with it after calling the txn -+ * function. -+ */ - -- be_adapter_handle_subscribe(subr_msg, msg_len, adapter); -- break; -+ _dbg("Got %s from '%s' txn-id %Lu", mgmt_msg_code_name(msg->code), adapter->name, -+ msg->refer_id); - -+ switch (msg->code) { -+ case MGMT_MSG_CODE_SUBSCRIBE: -+ be_adapter_handle_subscribe((struct mgmt_msg_subscribe *)msg, msg_len, adapter); -+ return; - case MGMT_MSG_CODE_TXN_REPLY: -- txn_msg = (typeof(txn_msg))msg; -- _dbg("Got TXN_REPLY from '%s' txn-id %Lu successfully '%s'", adapter->name, -- txn_msg->refer_id, txn_msg->created ? "Created" : "Deleted"); -- /* -- * Forward the TXN_REPLY to txn module. -- */ -- mgmt_txn_notify_be_txn_reply(txn_msg->refer_id, txn_msg->created, true, adapter); -- break; -+ assert(0); -+ return; - case MGMT_MSG_CODE_CFG_REPLY: -- _dbg("Got successful CFG_REPLY from '%s' txn-id %Lu", adapter->name, msg->refer_id); -- /* -- * Forward the CGFData-create reply to txn module. -- */ -- mgmt_txn_notify_be_cfg_reply(msg->refer_id, true, NULL, adapter); -- break; -+ mgmt_txn_handle_cfg_reply(msg->refer_id, adapter); -+ return; - case MGMT_MSG_CODE_CFG_APPLY_REPLY: -- _dbg("Got successful CFG_APPLY_REPLY from '%s' txn-id %Lu", adapter->name, -- msg->refer_id); -- /* -- * Forward the CGFData-apply reply to txn module. -- */ -- mgmt_txn_notify_be_cfg_apply_reply(msg->refer_id, true, NULL, adapter); -- break; -- -+ mgmt_txn_handle_cfg_apply_reply(msg->refer_id, adapter); -+ return; - case MGMT_MSG_CODE_ERROR: - error_msg = (typeof(error_msg))msg; -- _dbg("Got ERROR from '%s' txn-id %Lu", adapter->name, msg->refer_id); -- -- /* Forward the reply to the txn module */ -- mgmt_txn_notify_error(adapter, msg->refer_id, msg->req_id, -- error_msg->error, error_msg->errstr); -- /* We may have lost our connection and adapter at this point */ -- break; -+ mgmt_txn_handle_error_reply(adapter, msg->refer_id, msg->req_id, error_msg->error, -+ error_msg->errstr); -+ return; - case MGMT_MSG_CODE_TREE_DATA: -- /* tree data from a backend client */ -- _dbg("Got TREE_DATA from '%s' txn-id %" PRIu64, adapter->name, msg->refer_id); -- -- /* Forward the reply to the txn module */ -- mgmt_txn_notify_tree_data_reply(adapter, (struct mgmt_msg_tree_data *)msg, msg_len); -- break; -+ mgmt_txn_handle_tree_data_reply(adapter, (struct mgmt_msg_tree_data *)msg, msg_len); -+ return; - case MGMT_MSG_CODE_RPC_REPLY: -- /* RPC reply from a backend client */ -- _dbg("Got RPC_REPLY from '%s' txn-id %" PRIu64, adapter->name, msg->refer_id); -- -- /* Forward the reply to the txn module */ -- mgmt_txn_notify_rpc_reply(adapter, (struct mgmt_msg_rpc_reply *)msg, msg_len); -- break; -+ mgmt_txn_handle_rpc_reply(adapter, (struct mgmt_msg_rpc_reply *)msg, msg_len); -+ return; - case MGMT_MSG_CODE_NOTIFY: - /* -- * Handle notify message from a back-end client -+ * Handle notify message from a back-end client no TXN for this. - */ - notify_msg = (typeof(notify_msg))msg; -- _dbg("Got NOTIFY from '%s'", adapter->name); - mgmt_be_adapter_send_notify(notify_msg, msg_len, adapter); - mgmt_fe_adapter_send_notify(notify_msg, msg_len); -- break; -+ return; - default: - _log_err("unknown native message txn-id %" PRIu64 " req-id %" PRIu64 - " code %u from BE client for adapter %s", - msg->refer_id, msg->req_id, msg->code, adapter->name); -- break; -- } --} -- -- --static void mgmt_be_adapter_process_msg(uint8_t version, uint8_t *data, -- size_t len, struct msg_conn *conn) --{ -- struct mgmt_be_client_adapter *adapter = conn->user; -- -- if (version == MGMT_MSG_VERSION_NATIVE) { -- struct mgmt_msg_header *msg = (typeof(msg))data; -- -- if (len >= sizeof(*msg)) -- be_adapter_handle_native_msg(adapter, msg, len); -- else -- _log_err("native message to adapter %s too short %zu", adapter->name, len); - return; - } -- -- _log_err("Protobuf not supported for backend messages (adapter: %s)", adapter->name); - } - --/* -- * Args for callback -- */ --struct mgmt_be_get_adapter_config_params { -- struct mgmt_be_client_adapter *adapter; -- struct nb_config_cbs *cfg_chgs; -- uint32_t seq; --}; -+/* =================== */ -+/* Backend VTY support */ -+/* =================== */ - --/* -- * Initialize a BE client over a new connection -- */ --static void mgmt_be_adapter_conn_init(struct event *thread) -+void mgmt_be_adapter_status_write(struct vty *vty) - { - struct mgmt_be_client_adapter *adapter; -+ uint count = 0; - -- adapter = (struct mgmt_be_client_adapter *)EVENT_ARG(thread); -- assert(adapter && adapter->conn->fd >= 0); -+ vty_out(vty, "MGMTD Backend Adapters\n"); - -- /* -- * Notify TXN module to create a CONFIG transaction and -- * download the CONFIGs identified for this new client. -- * If the TXN module fails to initiate the CONFIG transaction -- * retry a bit later. It only fails if there's an existing config -- * transaction in progress. -- */ -- if (mgmt_txn_notify_be_adapter_conn(adapter, true) != 0) { -- zlog_err("XXX txn in progress, retry init"); -- mgmt_be_adapter_sched_init_event(adapter); -- return; -+ LIST_FOREACH (adapter, &be_adapters, link) { -+ vty_out(vty, " Client: \t\t\t%s\n", adapter->name); -+ vty_out(vty, " Conn-FD: \t\t\t%d\n", adapter->conn->fd); -+ vty_out(vty, " Client-Id: \t\t\t%d\n", adapter->id); -+ vty_out(vty, " Msg-Recvd: \t\t\t%Lu\n", adapter->conn->mstate.nrxm); -+ vty_out(vty, " Bytes-Recvd: \t\t%Lu\n", adapter->conn->mstate.nrxb); -+ vty_out(vty, " Msg-Sent: \t\t\t%Lu\n", adapter->conn->mstate.ntxm); -+ vty_out(vty, " Bytes-Sent: \t\t%Lu\n", adapter->conn->mstate.ntxb); -+ count++; - } -+ vty_out(vty, " Total: %u\n", count); - } - --/* -- * Schedule the initialization of the BE client connection. -- */ --static void --mgmt_be_adapter_sched_init_event(struct mgmt_be_client_adapter *adapter) --{ -- event_add_timer_msec(mgmt_loop, mgmt_be_adapter_conn_init, adapter, -- MGMTD_BE_CONN_INIT_DELAY_MSEC, -- &adapter->conn_init_ev); --} -- --void mgmt_be_adapter_lock(struct mgmt_be_client_adapter *adapter) -+static void _show_xpath_map(struct vty *vty, struct mgmt_be_xpath_map *map) - { -- adapter->refcount++; --} -+ enum mgmt_be_client_id id; -+ const char *astr; - --extern void mgmt_be_adapter_unlock(struct mgmt_be_client_adapter **adapter) --{ -- struct mgmt_be_client_adapter *a = *adapter; -- assert(a && a->refcount); -- -- if (!--a->refcount) { -- mgmt_be_adapters_del(&mgmt_be_adapters, a); -- event_cancel(&a->conn_init_ev); -- msg_server_conn_delete(a->conn); -- XFREE(MTYPE_MGMTD_BE_ADPATER, a); -+ vty_out(vty, " - xpath: '%s'\n", map->xpath_prefix); -+ FOREACH_BE_CLIENT_BITS (id, map->clients) { -+ astr = mgmt_be_get_adapter_by_id(id) ? "active" : "inactive"; -+ vty_out(vty, " -- %s-client: '%s'\n", astr, be_adapter_id_name(id)); - } -- -- *adapter = NULL; - } - --/* -- * Initialize the BE adapter module -- */ --void mgmt_be_adapter_init(struct event_loop *tm) -+void mgmt_be_xpath_register_write(struct vty *vty) - { -- char server_path[MAXPATHLEN]; -+ struct mgmt_be_xpath_map *map; - -- assert(!mgmt_loop); -- mgmt_loop = tm; -+ vty_out(vty, "MGMTD Backend CFG XPath Registry: Count: %u\n", darr_len(be_oper_xpath_map)); -+ darr_foreach_p (be_cfg_xpath_map, map) -+ _show_xpath_map(vty, map); - -- mgmt_be_adapters_init(&mgmt_be_adapters); -- mgmt_be_xpath_map_init(); -+ vty_out(vty, "\nMGMTD Backend OPER XPath Registry: Count: %u\n", -+ darr_len(be_oper_xpath_map)); -+ darr_foreach_p (be_oper_xpath_map, map) -+ _show_xpath_map(vty, map); - -- snprintf(server_path, sizeof(server_path), MGMTD_BE_SOCK_NAME); -+ vty_out(vty, "\nMGMTD Backend NOTIFY XPath Registry: Count: %u\n", -+ darr_len(be_notif_xpath_map)); -+ darr_foreach_p (be_notif_xpath_map, map) -+ _show_xpath_map(vty, map); - -- if (msg_server_init(&mgmt_be_server, server_path, tm, -- mgmt_be_create_adapter, "backend", &mgmt_debug_be)) { -- zlog_err("cannot initialize backend server"); -- exit(1); -- } -+ vty_out(vty, "\nMGMTD Backend RPC XPath Registry: Count: %u\n", darr_len(be_rpc_xpath_map)); -+ darr_foreach_p (be_rpc_xpath_map, map) -+ _show_xpath_map(vty, map); - } - - /* -- * Destroy the BE adapter module -+ * Should replace this with proper YANG module - */ --void mgmt_be_adapter_destroy(void) -+void mgmt_be_adapter_show_xpath_registries(struct vty *vty, const char *xpath) - { -+ enum mgmt_be_client_id id; - struct mgmt_be_client_adapter *adapter; -+ uint64_t cclients, nclients, oclients, rclients, combined; - -- msg_server_cleanup(&mgmt_be_server); -- FOREACH_ADAPTER_IN_LIST (adapter) { -- mgmt_be_adapter_delete(adapter); -- } -- mgmt_be_xpath_map_cleanup(); --} -- --/* -- * The server accepted a new connection -- */ --struct msg_conn *mgmt_be_create_adapter(int conn_fd, union sockunion *from) --{ -- struct mgmt_be_client_adapter *adapter = NULL; -- -- assert(!mgmt_be_find_adapter_by_fd(conn_fd)); -- -- adapter = XCALLOC(MTYPE_MGMTD_BE_ADPATER, -- sizeof(struct mgmt_be_client_adapter)); -- adapter->id = MGMTD_BE_CLIENT_ID_MAX; -- snprintf(adapter->name, sizeof(adapter->name), "Unknown-FD-%d", -- conn_fd); -- -- mgmt_be_adapter_lock(adapter); -- mgmt_be_adapters_add_tail(&mgmt_be_adapters, adapter); -- RB_INIT(nb_config_cbs, &adapter->cfg_chgs); -- -- adapter->conn = msg_server_conn_create(mgmt_loop, conn_fd, -- mgmt_be_adapter_notify_disconnect, -- mgmt_be_adapter_process_msg, -- MGMTD_BE_MAX_NUM_MSG_PROC, -- MGMTD_BE_MAX_NUM_MSG_WRITE, -- MGMTD_BE_MAX_MSG_LEN, adapter, -- "BE-adapter"); -- -- adapter->conn->debug = DEBUG_MODE_CHECK(&mgmt_debug_be, DEBUG_MODE_ALL); -- -- _dbg("Added new MGMTD Backend adapter '%s'", adapter->name); -- -- return adapter->conn; --} -+ cclients = mgmt_be_interested_clients(xpath, MGMT_BE_XPATH_SUBSCR_TYPE_CFG); -+ oclients = mgmt_be_interested_clients(xpath, MGMT_BE_XPATH_SUBSCR_TYPE_OPER); -+ nclients = mgmt_be_interested_clients(xpath, MGMT_BE_XPATH_SUBSCR_TYPE_NOTIF); -+ rclients = mgmt_be_interested_clients(xpath, MGMT_BE_XPATH_SUBSCR_TYPE_RPC); -+ combined = cclients | nclients | oclients | rclients; - --struct mgmt_be_client_adapter * --mgmt_be_get_adapter_by_id(enum mgmt_be_client_id id) --{ -- return (id < MGMTD_BE_CLIENT_ID_MAX ? mgmt_be_adapters_by_id[id] : NULL); -+ vty_out(vty, "XPath: '%s'\n", xpath); -+ FOREACH_BE_CLIENT_BITS (id, combined) { -+ vty_out(vty, " -- Client: '%s'\tconfig:%d notify:%d oper:%d rpc:%d\n", -+ be_adapter_id_name(id), IS_IDBIT_SET(cclients, id), -+ IS_IDBIT_SET(nclients, id), IS_IDBIT_SET(oclients, id), -+ IS_IDBIT_SET(rclients, id)); -+ adapter = mgmt_be_get_adapter_by_id(id); -+ if (adapter) -+ vty_out(vty, " -- Adapter: %p\n", adapter); -+ } - } - --struct mgmt_be_client_adapter * --mgmt_be_get_adapter_by_name(const char *name) --{ -- return mgmt_be_find_adapter_by_name(name); --} -+/* ========================== */ -+/* Backend Adapter Management */ -+/* ========================== */ - - void mgmt_be_adapter_toggle_client_debug(bool set) - { - struct mgmt_be_client_adapter *adapter; - -- FOREACH_ADAPTER_IN_LIST (adapter) -+ LIST_FOREACH (adapter, &be_adapters, link) - adapter->conn->debug = set; - } - - /* -- * Get a full set of changes for all the config that an adapter is subscribed to -- * receive. -+ * Delete a BE client adapter - */ --void mgmt_be_get_adapter_config(struct mgmt_be_client_adapter *adapter, -- struct nb_config_cbs **changes) -+static void be_adapter_delete(struct mgmt_be_client_adapter *adapter) - { -- const struct lyd_node *root, *dnode; -- uint32_t seq = 0; -- char *xpath; -- -- /* We can't be in the middle of sending other chgs when here. */ -- assert(RB_EMPTY(nb_config_cbs, &adapter->cfg_chgs)); -- -- *changes = &adapter->cfg_chgs; -- LY_LIST_FOR (running_config->dnode, root) { -- LYD_TREE_DFS_BEGIN (root, dnode) { -- if (lysc_is_key(dnode->schema)) -- goto walk_cont; -+ _dbg("deleting client adapter '%s'", adapter->name); - -- xpath = lyd_path(dnode, LYD_PATH_STD, NULL, 0); -- if (be_is_client_interested(xpath, adapter->id, -- MGMT_BE_XPATH_SUBSCR_TYPE_CFG)) -- nb_config_diff_add_change(*changes, NB_CB_CREATE, &seq, dnode); -- else -- LYD_TREE_DFS_continue = 1; /* skip any subtree */ -- free(xpath); -- walk_cont: -- LYD_TREE_DFS_END(root, dnode); -- } -+ /* -+ * Notify about disconnect for appropriate cleanup -+ */ -+ mgmt_txn_handle_be_adapter_connect(adapter, false); -+ if (adapter->id < MGMTD_BE_CLIENT_ID_MAX) { -+ mgmt_be_adapters_by_id[adapter->id] = NULL; -+ adapter->id = MGMTD_BE_CLIENT_ID_MAX; - } -+ -+ LIST_REMOVE(adapter, link); -+ event_cancel(&adapter->conn_init_ev); -+ msg_server_conn_delete(adapter->conn); -+ XFREE(MTYPE_MGMTD_BE_ADPATER, adapter); - } - --uint64_t mgmt_be_interested_clients(const char *xpath, -- enum mgmt_be_xpath_subscr_type type) -+static int mgmt_be_adapter_notify_disconnect(struct msg_conn *conn) - { -- struct mgmt_be_xpath_map *maps = NULL, *map; -- enum mgmt_be_client_id id; -- uint64_t clients; -- bool wild_root; -+ struct mgmt_be_client_adapter *adapter = conn->user; - -- switch (type) { -- case MGMT_BE_XPATH_SUBSCR_TYPE_CFG: -- maps = be_cfg_xpath_map; -- break; -- case MGMT_BE_XPATH_SUBSCR_TYPE_OPER: -- maps = be_oper_xpath_map; -- break; -- case MGMT_BE_XPATH_SUBSCR_TYPE_NOTIF: -- maps = be_notif_xpath_map; -- break; -- case MGMT_BE_XPATH_SUBSCR_TYPE_RPC: -- maps = be_rpc_xpath_map; -- break; -- } -+ _dbg("notify disconnect for client adapter '%s'", adapter->name); - -- clients = 0; -+ be_adapter_delete(adapter); - -- _dbg("XPATH: '%s'", xpath); -+ return 0; -+} - -- /* wild_root will select all clients that advertise op-state */ -- wild_root = !strcmp(xpath, "/") || !strcmp(xpath, "/*"); -- darr_foreach_p (maps, map) -- if (wild_root || mgmt_be_xpath_prefix(map->xpath_prefix, xpath)) -- clients |= map->clients; -+/* -+ * Initialize a BE client over a new connection -+ */ -+static void be_adapter_conn_init(struct event *thread) -+{ -+ struct mgmt_be_client_adapter *adapter; -+ enum mgmt_be_client_id id; - -- if (DEBUG_MODE_CHECK(&mgmt_debug_be, DEBUG_MODE_ALL)) { -- FOREACH_BE_CLIENT_BITS (id, clients) -- _dbg("Cient: %s: subscribed", mgmt_be_client_id2name(id)); -+ adapter = (struct mgmt_be_client_adapter *)EVENT_ARG(thread); -+ assert(adapter && adapter->conn->fd >= 0); -+ id = adapter->id; -+ -+ /* -+ * Notify TXN module to create a CONFIG transaction and -+ * download the CONFIGs identified for this new client. -+ * If the TXN module fails to initiate the CONFIG transaction -+ * retry a bit later. It only fails if there's an existing config -+ * transaction in progress. -+ */ -+ if (mgmt_txn_handle_be_adapter_connect(adapter, true) != 0) { -+ /* Deal with a disconnect happening */ -+ if (!mgmt_be_adapters_by_id[id]) -+ return; -+ _log_err("Couldn't send intial config to adapter: %s", adapter->name); -+ be_adapter_sched_init_event(adapter); - } -- return clients; - } - --bool mgmt_is_mgmtd_interested(const char *xpath) -+/* -+ * Schedule the initialization of the BE client connection. -+ */ -+static void be_adapter_sched_init_event(struct mgmt_be_client_adapter *adapter) - { -- const char *const *match = mgmtd_config_xpaths; -- const char *const *ematch = match + array_size(mgmtd_config_xpaths); -- -- for (; match < ematch; match++) { -- if (mgmt_be_xpath_prefix(*match, xpath)) { -- _dbg("mgmtd: subscribed to %s", xpath); -- return true; -- } -- } -- return false; -+ event_add_timer_msec(mgmt_loop, be_adapter_conn_init, adapter, -+ MGMTD_BE_CONN_INIT_DELAY_MSEC, &adapter->conn_init_ev); - } - --/** -- * Return true if `client_id` is interested in `xpath` for `config` -- * or oper (!`config`). -- * -- * Args: -- * xpath - the xpath to check for interest. -- * client_id - the BE client being checked for. -- * bool - check for config (vs oper) subscription. -- * -- * Returns: -- * Interested or not. -+/* -+ * The server accepted a new connection - */ --static bool be_is_client_interested(const char *xpath, enum mgmt_be_client_id id, -- enum mgmt_be_xpath_subscr_type type) -+static struct msg_conn *be_adapter_create(int conn_fd, union sockunion *from) - { -- uint64_t clients; -+ struct mgmt_be_client_adapter *adapter = NULL; - -- assert(id < MGMTD_BE_CLIENT_ID_MAX); -+ assert(!mgmt_be_find_adapter_by_fd(conn_fd)); - -- _dbg("Checking client: %s for xpath: '%s'", mgmt_be_client_id2name(id), xpath); -+ adapter = XCALLOC(MTYPE_MGMTD_BE_ADPATER, sizeof(struct mgmt_be_client_adapter)); -+ adapter->id = MGMTD_BE_CLIENT_ID_MAX; -+ snprintf(adapter->name, sizeof(adapter->name), "Unknown-FD-%d", conn_fd); - -- clients = mgmt_be_interested_clients(xpath, type); -- if (IS_IDBIT_SET(clients, id)) { -- _dbg("client: %s: interested", mgmt_be_client_id2name(id)); -- return true; -- } -+ LIST_INSERT_HEAD(&be_adapters, adapter, link); - -- _dbg("client: %s: not interested", mgmt_be_client_id2name(id)); -- return false; --} -+ adapter->conn = msg_server_conn_create(mgmt_loop, conn_fd, -+ mgmt_be_adapter_notify_disconnect, -+ be_adapter_process_msg, MGMTD_BE_MAX_NUM_MSG_PROC, -+ MGMTD_BE_MAX_NUM_MSG_WRITE, MGMTD_BE_MAX_MSG_LEN, -+ adapter, "BE-adapter"); - --void mgmt_be_adapter_status_write(struct vty *vty) --{ -- struct mgmt_be_client_adapter *adapter; -+ adapter->conn->debug = DEBUG_MODE_CHECK(&mgmt_debug_be, DEBUG_MODE_ALL); - -- vty_out(vty, "MGMTD Backend Adapters\n"); -+ _dbg("Added new MGMTD Backend adapter '%s'", adapter->name); - -- FOREACH_ADAPTER_IN_LIST (adapter) { -- vty_out(vty, " Client: \t\t\t%s\n", adapter->name); -- vty_out(vty, " Conn-FD: \t\t\t%d\n", adapter->conn->fd); -- vty_out(vty, " Client-Id: \t\t\t%d\n", adapter->id); -- vty_out(vty, " Ref-Count: \t\t\t%u\n", adapter->refcount); -- vty_out(vty, " Msg-Recvd: \t\t\t%" PRIu64 "\n", -- adapter->conn->mstate.nrxm); -- vty_out(vty, " Bytes-Recvd: \t\t%" PRIu64 "\n", -- adapter->conn->mstate.nrxb); -- vty_out(vty, " Msg-Sent: \t\t\t%" PRIu64 "\n", -- adapter->conn->mstate.ntxm); -- vty_out(vty, " Bytes-Sent: \t\t%" PRIu64 "\n", -- adapter->conn->mstate.ntxb); -- } -- vty_out(vty, " Total: %d\n", -- (int)mgmt_be_adapters_count(&mgmt_be_adapters)); -+ return adapter->conn; - } - --static void be_show_xpath_register(struct vty *vty, -- struct mgmt_be_xpath_map *map) --{ -- enum mgmt_be_client_id id; -- const char *astr; -- -- vty_out(vty, " - xpath: '%s'\n", map->xpath_prefix); -- FOREACH_BE_CLIENT_BITS (id, map->clients) { -- astr = mgmt_be_get_adapter_by_id(id) ? "active" : "inactive"; -- vty_out(vty, " -- %s-client: '%s'\n", astr, -- mgmt_be_client_id2name(id)); -- } --} --void mgmt_be_xpath_register_write(struct vty *vty) -+/* -+ * Initialize the BE adapter module -+ */ -+void mgmt_be_adapter_init(struct event_loop *tm) - { -- struct mgmt_be_xpath_map *map; -+ char server_path[MAXPATHLEN]; - -- vty_out(vty, "MGMTD Backend CFG XPath Registry: Count: %u\n", -- darr_len(be_oper_xpath_map)); -- darr_foreach_p (be_cfg_xpath_map, map) -- be_show_xpath_register(vty, map); -+ assert(!mgmt_loop); -+ mgmt_loop = tm; - -- vty_out(vty, "\nMGMTD Backend OPER XPath Registry: Count: %u\n", -- darr_len(be_oper_xpath_map)); -- darr_foreach_p (be_oper_xpath_map, map) -- be_show_xpath_register(vty, map); -+ be_adapter_xpath_maps_init(); - -- vty_out(vty, "\nMGMTD Backend NOTIFY XPath Registry: Count: %u\n", -- darr_len(be_notif_xpath_map)); -- darr_foreach_p (be_notif_xpath_map, map) -- be_show_xpath_register(vty, map); -+ snprintf(server_path, sizeof(server_path), MGMTD_BE_SOCK_NAME); - -- vty_out(vty, "\nMGMTD Backend RPC XPath Registry: Count: %u\n", -- darr_len(be_rpc_xpath_map)); -- darr_foreach_p (be_rpc_xpath_map, map) -- be_show_xpath_register(vty, map); -+ if (msg_server_init(&mgmt_be_server, server_path, tm, be_adapter_create, "backend", -+ &mgmt_debug_be)) { -+ zlog_err("cannot initialize backend server"); -+ exit(1); -+ } - } - --void mgmt_be_show_xpath_registries(struct vty *vty, const char *xpath) -+/* -+ * Destroy the BE adapter module -+ */ -+void mgmt_be_adapter_destroy(void) - { -- enum mgmt_be_client_id id; -- struct mgmt_be_client_adapter *adapter; -- uint64_t cclients, nclients, oclients, rclients, combined; -- -- cclients = mgmt_be_interested_clients(xpath, -- MGMT_BE_XPATH_SUBSCR_TYPE_CFG); -- oclients = mgmt_be_interested_clients(xpath, -- MGMT_BE_XPATH_SUBSCR_TYPE_OPER); -- nclients = mgmt_be_interested_clients(xpath, -- MGMT_BE_XPATH_SUBSCR_TYPE_NOTIF); -- rclients = mgmt_be_interested_clients(xpath, -- MGMT_BE_XPATH_SUBSCR_TYPE_RPC); -- combined = cclients | nclients | oclients | rclients; -+ struct mgmt_be_client_adapter *adapter, *next; - -- vty_out(vty, "XPath: '%s'\n", xpath); -- FOREACH_BE_CLIENT_BITS (id, combined) { -- vty_out(vty, -- " -- Client: '%s'\tconfig:%d notify:%d oper:%d rpc:%d\n", -- mgmt_be_client_id2name(id), IS_IDBIT_SET(cclients, id), -- IS_IDBIT_SET(nclients, id), IS_IDBIT_SET(oclients, id), -- IS_IDBIT_SET(rclients, id)); -- adapter = mgmt_be_get_adapter_by_id(id); -- if (adapter) -- vty_out(vty, " -- Adapter: %p\n", adapter); -- } -+ msg_server_cleanup(&mgmt_be_server); -+ LIST_FOREACH_SAFE (adapter, &be_adapters, link, next) -+ be_adapter_delete(adapter); -+ be_adapter_xpath_maps_cleanup(); - } -diff --git a/mgmtd/mgmt_be_adapter.h b/mgmtd/mgmt_be_adapter.h -index 26dd56ac62..390ae078e9 100644 ---- a/mgmtd/mgmt_be_adapter.h -+++ b/mgmtd/mgmt_be_adapter.h -@@ -14,6 +14,7 @@ - #include "mgmt_msg.h" - #include "mgmt_defines.h" - #include "mgmtd/mgmt_ds.h" -+#include "queue.h" - - #define MGMTD_BE_CONN_INIT_DELAY_MSEC 50 - -@@ -45,7 +46,6 @@ enum mgmt_be_client_id { - - - PREDECL_LIST(mgmt_be_adapters); --PREDECL_LIST(mgmt_txn_badapters); - - struct mgmt_be_client_adapter { - struct msg_conn *conn; -@@ -53,38 +53,13 @@ struct mgmt_be_client_adapter { - struct event *conn_init_ev; - - enum mgmt_be_client_id id; -- uint32_t flags; - char name[MGMTD_CLIENT_NAME_MAX_LEN]; - -- int refcount; -- -- /* -- * List of config items that should be sent to the -- * backend during re/connect. This is temporarily -- * created and then freed-up as soon as the initial -- * config items has been applied onto the backend. -- */ -- struct nb_config_cbs cfg_chgs; - struct mgmt_commit_stats cfg_stats; -- - struct mgmt_be_adapters_item list_linkage; --}; -- --#define MGMTD_BE_ADAPTER_FLAGS_CFG_SYNCED (1U << 0) - --DECLARE_LIST(mgmt_be_adapters, struct mgmt_be_client_adapter, list_linkage); -- --/* -- * MGMT_SUBSCR_xxx - flags for subscription types for xpaths registrations -- * -- * MGMT_SUBSCR_VALIDATE_CFG :: the client should be asked to validate config -- * MGMT_SUBSCR_NOTIFY_CFG :: the client should be notified of config changes -- * MGMT_SUBSCR_OPER_OWN :: the client owns the given oeprational state -- */ --#define MGMT_SUBSCR_VALIDATE_CFG 0x1 --#define MGMT_SUBSCR_NOTIFY_CFG 0x2 --#define MGMT_SUBSCR_OPER_OWN 0x4 --#define MGMT_SUBSCR_ALL 0x7 -+ LIST_ENTRY(mgmt_be_client_adapter) link; -+}; - - /* --------- */ - /* CLIENT-ID */ -@@ -94,8 +69,11 @@ DECLARE_LIST(mgmt_be_adapters, struct mgmt_be_client_adapter, list_linkage); - for ((id) = MGMTD_BE_CLIENT_ID_MIN; (id) < MGMTD_BE_CLIENT_ID_MAX; \ - (id)++) - -+#define IDBIT_MASK(id) (1ull << (id)) -+#define IS_IDBIT_UNSET(v, id) (!((v)&IDBIT_MASK(id))) - #define IS_IDBIT_SET(v, id) (!IS_IDBIT_UNSET(v, id)) --#define IS_IDBIT_UNSET(v, id) (!((v) & (1ull << (id)))) -+#define SET_IDBIT(v, id) ((v) |= IDBIT_MASK(id)) -+#define UNSET_IDBIT(v, id) ((v) &= ~IDBIT_MASK(id)) - - #define _GET_NEXT_SET(id, bits) \ - ({ \ -@@ -111,6 +89,26 @@ DECLARE_LIST(mgmt_be_adapters, struct mgmt_be_client_adapter, list_linkage); - for ((id) = _GET_NEXT_SET(MGMTD_BE_CLIENT_ID_MIN, bits); (id) < MGMTD_BE_CLIENT_ID_MAX; \ - (id) = _GET_NEXT_SET((id) + 1, bits)) - -+/* This is required to avoid assignment in if conditional in FOREACH_BE_ADAPTER_BITS :( */ -+#define _GET_NEXT_SET_ADAPTER(id, adapter, bits) \ -+ ({ \ -+ enum mgmt_be_client_id _gnsa_id = (id); \ -+ \ -+ for (; _gnsa_id < MGMTD_BE_CLIENT_ID_MAX; _gnsa_id++) { \ -+ if (IS_IDBIT_SET(bits, _gnsa_id)) { \ -+ (adapter) = mgmt_be_get_adapter_by_id(_gnsa_id); \ -+ if ((adapter)) \ -+ break; \ -+ } \ -+ } \ -+ _gnsa_id; \ -+ }) -+ -+#define FOREACH_BE_ADAPTER_BITS(id, adapter, bits) \ -+ for ((id) = _GET_NEXT_SET_ADAPTER(MGMTD_BE_CLIENT_ID_MIN, (adapter), (bits)); \ -+ (id) < MGMTD_BE_CLIENT_ID_MAX; \ -+ (id) = _GET_NEXT_SET_ADAPTER((id) + 1, (adapter), (bits))) -+ - /* ---------- */ - /* Prototypes */ - /* ---------- */ -@@ -121,20 +119,6 @@ extern void mgmt_be_adapter_init(struct event_loop *tm); - /* Destroy the backend adapter module. */ - extern void mgmt_be_adapter_destroy(void); - --/* Acquire lock for backend adapter. */ --extern void mgmt_be_adapter_lock(struct mgmt_be_client_adapter *adapter); -- --/* Remove lock from backend adapter. */ --extern void mgmt_be_adapter_unlock(struct mgmt_be_client_adapter **adapter); -- --/* Create backend adapter. */ --extern struct msg_conn *mgmt_be_create_adapter(int conn_fd, -- union sockunion *su); -- --/* Fetch backend adapter given an adapter name. */ --extern struct mgmt_be_client_adapter * --mgmt_be_get_adapter_by_name(const char *name); -- - /* Fetch backend adapter given an client ID. */ - extern struct mgmt_be_client_adapter * - mgmt_be_get_adapter_by_id(enum mgmt_be_client_id id); -@@ -146,42 +130,7 @@ extern const char *mgmt_be_client_id2name(enum mgmt_be_client_id id); - extern void mgmt_be_adapter_toggle_client_debug(bool set); - - /* Fetch backend adapter config. */ --extern void mgmt_be_get_adapter_config(struct mgmt_be_client_adapter *adapter, -- struct nb_config_cbs **changes); -- --/* Create/destroy a transaction. */ --extern int mgmt_be_send_txn_req(struct mgmt_be_client_adapter *adapter, -- uint64_t txn_id, bool create); -- --/* -- * Send config data create request to backend client. -- * -- * adaptr -- * Backend adapter information. -- * -- * msg -- * The already constructed message to send. -- * -- * Returns: -- * 0 on success, -1 on failure. -- */ --extern int mgmt_be_send_cfgdata_req(struct mgmt_be_client_adapter *adapter, -- struct mgmt_msg_cfg_req *msg); -- --/* -- * Send config apply request to backend client. -- * -- * adapter -- * Backend adapter information. -- * -- * txn_id -- * Unique transaction identifier. -- * -- * Returns: -- * 0 on success, -1 on failure. -- */ --extern int mgmt_be_send_cfgapply_req(struct mgmt_be_client_adapter *adapter, -- uint64_t txn_id); -+extern struct nb_config_cbs mgmt_be_adapter_get_config(struct mgmt_be_client_adapter *adapter); - - /* - * Dump backend adapter status to vty. -@@ -204,7 +153,7 @@ extern void mgmt_be_xpath_register_write(struct vty *vty); - * Return: - * Any return value from msg_conn_send_msg(). - */ --extern int mgmt_be_send_native(enum mgmt_be_client_id id, void *msg); -+extern int mgmt_be_adapter_send(struct mgmt_be_client_adapter *adapter, void *msg); - - enum mgmt_be_xpath_subscr_type { - MGMT_BE_XPATH_SUBSCR_TYPE_CFG, -@@ -224,17 +173,10 @@ enum mgmt_be_xpath_subscr_type { - extern uint64_t mgmt_be_interested_clients(const char *xpath, - enum mgmt_be_xpath_subscr_type type); - --/** -- * mgmt_fe_adapter_send_notify() - notify FE clients of a notification. -- * @msg: the notify message from the backend client. -- * @msglen: the length of the notify message. -- */ --extern void mgmt_fe_adapter_send_notify(struct mgmt_msg_notify_data *msg, -- size_t msglen); - /* - * Dump backend client information for a given xpath to vty. - */ --extern void mgmt_be_show_xpath_registries(struct vty *vty, const char *xpath); -+extern void mgmt_be_adapter_show_xpath_registries(struct vty *vty, const char *xpath); - - /* - * Specials for mgmtd internally handling BE like behaviors -diff --git a/mgmtd/mgmt_ds.c b/mgmtd/mgmt_ds.c -index a0e6c83393..4dae8f5222 100644 ---- a/mgmtd/mgmt_ds.c -+++ b/mgmtd/mgmt_ds.c -@@ -16,12 +16,14 @@ - #include "libyang/libyang.h" - - #define _dbg(fmt, ...) DEBUGD(&mgmt_debug_ds, "DS: %s: " fmt, __func__, ##__VA_ARGS__) -+#define _log_warn(fmt, ...) zlog_warn("%s: ERROR: " fmt, __func__, ##__VA_ARGS__) - #define _log_err(fmt, ...) zlog_err("%s: ERROR: " fmt, __func__, ##__VA_ARGS__) - - struct mgmt_ds_ctx { - enum mgmt_ds_id ds_id; - - bool locked; -+ uint64_t txn_locked; /* lock held by txn */ - uint64_t vty_session_id; /* Owner of the lock or 0 */ - - bool config_ds; -@@ -222,14 +224,17 @@ bool mgmt_ds_is_config(struct mgmt_ds_ctx *ds_ctx) - return ds_ctx->config_ds; - } - --bool mgmt_ds_is_locked(struct mgmt_ds_ctx *ds_ctx, uint64_t *session_id) -+bool mgmt_ds_is_locked(struct mgmt_ds_ctx *ds_ctx, uint64_t *session_id, uint64_t *txn_id) - { -- if (!ds_ctx || !ds_ctx->locked) -+ if (!ds_ctx) - return false; - -- if (session_id) -+ if (ds_ctx->locked && session_id) - *session_id = ds_ctx->vty_session_id; -- return true; -+ if (ds_ctx->txn_locked && txn_id) -+ *txn_id = ds_ctx->txn_locked; -+ -+ return ds_ctx->locked || ds_ctx->txn_locked; - } - - int mgmt_ds_lock(struct mgmt_ds_ctx *ds_ctx, uint64_t session_id) -@@ -237,31 +242,93 @@ int mgmt_ds_lock(struct mgmt_ds_ctx *ds_ctx, uint64_t session_id) - assert(ds_ctx); - - if (ds_ctx->locked) { -- _log_err("lock already taken on DS:%s by session-id %Lu", -+ _log_err("LOCK already taken on DS:%s by session-id %Lu", - mgmt_ds_id2name(ds_ctx->ds_id), ds_ctx->vty_session_id); -- return EBUSY; -+ return -EBUSY; - } -- -- _dbg("obtaining lock on DS:%s for session-id %Lu", mgmt_ds_id2name(ds_ctx->ds_id), -- session_id); -+ if (ds_ctx->txn_locked) { -+ _log_err("LOCK already taken on DS:%s by session-less txn-id %Lu", -+ mgmt_ds_id2name(ds_ctx->ds_id), ds_ctx->txn_locked); -+ return -EBUSY; -+ } -+ _dbg("LOCK on DS:%s for session-id %Lu", mgmt_ds_id2name(ds_ctx->ds_id), session_id); - - ds_ctx->locked = true; - ds_ctx->vty_session_id = session_id; - return 0; - } - --void mgmt_ds_unlock(struct mgmt_ds_ctx *ds_ctx) -+void mgmt_ds_unlock(struct mgmt_ds_ctx *ds_ctx, uint64_t session_id) - { - assert(ds_ctx); - if (!ds_ctx->locked) - _log_err("unlock on unlocked in DS:%s last session-id %Lu", - mgmt_ds_id2name(ds_ctx->ds_id), ds_ctx->vty_session_id); -- else -+ else { -+ assert(ds_ctx->vty_session_id == session_id); - _dbg("releasing lock on DS:%s for session-id %Lu", mgmt_ds_id2name(ds_ctx->ds_id), - ds_ctx->vty_session_id); -+ } - - ds_ctx->locked = 0; - ds_ctx->vty_session_id = MGMTD_SESSION_ID_NONE; -+ -+ if (ds_ctx->txn_locked) -+ _dbg("TXN-LOCK remains on DS:%s for txn-id %Lu", mgmt_ds_id2name(ds_ctx->ds_id), -+ ds_ctx->txn_locked); -+} -+ -+bool mgmt_ds_is_txn_locked(struct mgmt_ds_ctx *ds_ctx, uint64_t *txn_id) -+{ -+ if (ds_ctx && ds_ctx->txn_locked) { -+ *txn_id = ds_ctx->txn_locked; -+ return true; -+ } -+ return false; -+} -+ -+uint64_t mgmt_ds_txn_lock(struct mgmt_ds_ctx *ds_ctx, uint64_t txn_id) -+{ -+ assert(ds_ctx); -+ -+ if (ds_ctx->txn_locked && ds_ctx->txn_locked != txn_id) { -+ _log_err("TXN-LOCK txn lock held on DS:%s by txn-id: %Lu not txn-id: %Lu", -+ mgmt_ds_id2name(ds_ctx->ds_id), ds_ctx->txn_locked, txn_id); -+ return ds_ctx->txn_locked; -+ } -+ if (ds_ctx->txn_locked == txn_id) { -+ _log_warn("TXN-LOCK double lock on DS:%s txn-id: %Lu", -+ mgmt_ds_id2name(ds_ctx->ds_id), ds_ctx->txn_locked); -+ return 0; -+ } -+ _dbg("TXN-LOCK on DS:%s session-id: %Lu txn-id: %Lu", mgmt_ds_id2name(ds_ctx->ds_id), -+ ds_ctx->vty_session_id, txn_id); -+ ds_ctx->txn_locked = txn_id; -+ return 0; -+} -+ -+void mgmt_ds_txn_unlock(struct mgmt_ds_ctx *ds_ctx, uint64_t txn_id) -+{ -+ assert(ds_ctx); -+ if (ds_ctx->txn_locked && ds_ctx->txn_locked != txn_id) { -+ _log_err("TXN-UNLOCK: txn lock held on DS:%s by session-id: %Lu txn-id: %Lu not txn-id: %Lu", -+ mgmt_ds_id2name(ds_ctx->ds_id), ds_ctx->vty_session_id, -+ ds_ctx->txn_locked, txn_id); -+ return; -+ } -+ if (!ds_ctx->txn_locked) -+ _log_warn("TXN-UNLOCK of unlocked on DS:%s session-id: %Lu txn-id: %Lu", -+ mgmt_ds_id2name(ds_ctx->ds_id), ds_ctx->vty_session_id, txn_id); -+ else -+ _dbg("TXN-UNLOCK on DS:%s session-id: %Lu txn-id: %Lu", -+ mgmt_ds_id2name(ds_ctx->ds_id), ds_ctx->vty_session_id, txn_id); -+ ds_ctx->txn_locked = 0; -+} -+ -+void mgmt_ds_restore_nb_config(struct mgmt_ds_ctx *ds_ctx, struct nb_config *backup) -+{ -+ assert(ds_ctx->config_ds); -+ nb_config_replace(ds_ctx->root.cfg_root, backup, false); - } - - int mgmt_ds_copy_dss(struct mgmt_ds_ctx *dst, struct mgmt_ds_ctx *src, bool updt_cmt_rec) -@@ -514,7 +581,8 @@ void mgmt_ds_dump_tree(struct vty *vty, struct mgmt_ds_ctx *ds_ctx, - - void mgmt_ds_status_write_one(struct vty *vty, struct mgmt_ds_ctx *ds_ctx) - { -- uint64_t session_id; -+ uint64_t session_id = 0; -+ uint64_t txn_id = 0; - bool locked; - - if (!ds_ctx) { -@@ -522,13 +590,13 @@ void mgmt_ds_status_write_one(struct vty *vty, struct mgmt_ds_ctx *ds_ctx) - return; - } - -- locked = mgmt_ds_is_locked(ds_ctx, &session_id); -+ locked = mgmt_ds_is_locked(ds_ctx, &session_id, &txn_id); - vty_out(vty, " DS: %s\n", mgmt_ds_id2name(ds_ctx->ds_id)); - vty_out(vty, " DS-Hndl: \t\t\t%p\n", ds_ctx); - vty_out(vty, " Config: \t\t\t%s\n", - ds_ctx->config_ds ? "True" : "False"); -- vty_out(vty, " Locked: \t\t\t%s Session-ID: %Lu\n", locked ? "True" : "False", -- locked ? session_id : 0); -+ vty_out(vty, " Locked: \t\t\t%s Session-ID: %Lu Txn-ID: %Lu\n", -+ locked ? "True" : "False", session_id, txn_id); - } - - void mgmt_ds_status_write(struct vty *vty) -diff --git a/mgmtd/mgmt_ds.h b/mgmtd/mgmt_ds.h -index 0849674acd..a509426ef1 100644 ---- a/mgmtd/mgmt_ds.h -+++ b/mgmtd/mgmt_ds.h -@@ -34,6 +34,7 @@ - - extern struct nb_config *running_config; - -+struct mgmt_master; - struct mgmt_ds_ctx; - - /*************************************************************** -@@ -178,10 +179,10 @@ extern struct mgmt_ds_ctx *mgmt_ds_get_ctx_by_id(struct mgmt_master *mm, enum mg - extern bool mgmt_ds_is_config(struct mgmt_ds_ctx *ds_ctx); - - /* -- * Check if a given datastore is locked by a session, if so return that session -- * ID if session_id is not NULL. -+ * Check if a given datastore is locked. If so return that session -+ * ID if session_id is not NULL. Also return txn_id if locked by a config txn. - */ --extern bool mgmt_ds_is_locked(struct mgmt_ds_ctx *ds_ctx, uint64_t *session_id); -+extern bool mgmt_ds_is_locked(struct mgmt_ds_ctx *ds_ctx, uint64_t *session_id, uint64_t *txn_id); - - /* - * Acquire write lock to a ds given a ds_handle -@@ -190,8 +191,11 @@ extern int mgmt_ds_lock(struct mgmt_ds_ctx *ds_ctx, uint64_t session_id); - - /* - * Remove a lock from ds given a ds_handle -+ * -+ * session_id -+ * Session ID that holds the lock. - */ --extern void mgmt_ds_unlock(struct mgmt_ds_ctx *ds_ctx); -+extern void mgmt_ds_unlock(struct mgmt_ds_ctx *ds_ctx, uint64_t session_id); - - /* - * Copy from source to destination datastore. -@@ -215,6 +219,11 @@ extern int mgmt_ds_copy_dss(struct mgmt_ds_ctx *dst, struct mgmt_ds_ctx *src, bo - */ - extern struct nb_config *mgmt_ds_get_nb_config(struct mgmt_ds_ctx *ds_ctx); - -+/* -+ * Restore configuration for a given datastore context from a backup. -+ */ -+extern void mgmt_ds_restore_nb_config(struct mgmt_ds_ctx *ds_ctx, struct nb_config *backup); -+ - /* - * Find YANG data node given a datastore handle YANG xpath. - */ -@@ -329,4 +338,13 @@ extern void mgmt_ds_status_write(struct vty *vty); - */ - void mgmt_ds_reset_candidate(void); - -+ -+/* -+ * Private TXN-Lock functions. -+ */ -+ -+extern bool mgmt_ds_is_txn_locked(struct mgmt_ds_ctx *ds_ctx, uint64_t *txn_id); -+extern uint64_t mgmt_ds_txn_lock(struct mgmt_ds_ctx *ds_ctx, uint64_t txn_id); -+extern void mgmt_ds_txn_unlock(struct mgmt_ds_ctx *ds_ctx, uint64_t txn_id); -+ - #endif /* _FRR_MGMTD_DS_H_ */ -diff --git a/mgmtd/mgmt_fe_adapter.c b/mgmtd/mgmt_fe_adapter.c -index 36f37eb1f0..fcc8c61fac 100644 ---- a/mgmtd/mgmt_fe_adapter.c -+++ b/mgmtd/mgmt_fe_adapter.c -@@ -26,10 +26,6 @@ - #define _dbg(fmt, ...) DEBUGD(&mgmt_debug_fe, "FE-ADAPTER: %s: " fmt, __func__, ##__VA_ARGS__) - #define _log_err(fmt, ...) zlog_err("FE-ADAPTER: %s: ERROR: " fmt, __func__, ##__VA_ARGS__) - --#define FOREACH_ADAPTER_IN_LIST(adapter) \ -- frr_each_safe (mgmt_fe_adapters, &mgmt_fe_adapters, (adapter)) -- -- - enum mgmt_session_event { - MGMTD_FE_SESSION_CFG_TXN_CLNUP = 1, - MGMTD_FE_SESSION_SHOW_TXN_CLNUP, -@@ -47,13 +43,21 @@ struct mgmt_fe_session_ctx { - struct event *proc_cfg_txn_clnp; - struct event *proc_show_txn_clnp; - -- struct mgmt_fe_sessions_item list_linkage; -+ LIST_ENTRY(mgmt_fe_session_ctx) link; - }; - --DECLARE_LIST(mgmt_fe_sessions, struct mgmt_fe_session_ctx, list_linkage); -+struct mgmt_fe_client_adapter { -+ struct msg_conn *conn; -+ char name[MGMTD_CLIENT_NAME_MAX_LEN]; - --#define FOREACH_SESSION_IN_LIST(adapter, session) \ -- frr_each_safe (mgmt_fe_sessions, &(adapter)->fe_sessions, (session)) -+ LIST_ENTRY(mgmt_fe_client_adapter) link; -+ -+ /* List of sessions created and being maintained for this client. */ -+ LIST_HEAD(fe_session_list_head, mgmt_fe_session_ctx) sessions; -+ -+ /* NOTE: shared by all sessions, only works b/c one session configuring at a time */ -+ struct mgmt_commit_stats cmt_stats; -+}; - - /* - * A tree for storing unique notify-select strings. -@@ -67,6 +71,13 @@ struct ns_string { - static uint32_t ns_string_compare(const struct ns_string *ns1, const struct ns_string *ns2); - DECLARE_RBTREE_UNIQ(ns_string, struct ns_string, link, ns_string_compare); - -+/* ---------- */ -+/* Prototypes */ -+/* ---------- */ -+ -+static struct msg_conn *fe_adapter_create(int conn_fd, union sockunion *from); -+static void fe_session_compute_commit_timers(struct mgmt_commit_stats *cmt_stats); -+ - /* ---------------- */ - /* Global variables */ - /* ---------------- */ -@@ -74,7 +85,7 @@ DECLARE_RBTREE_UNIQ(ns_string, struct ns_string, link, ns_string_compare); - static struct event_loop *mgmt_loop; - static struct msg_server mgmt_fe_server = {.fd = -1}; - --static struct mgmt_fe_adapters_head mgmt_fe_adapters; -+LIST_HEAD(fe_adapter_list_head, mgmt_fe_client_adapter) fe_adapters; - - static struct hash *mgmt_fe_sessions; - static uint64_t mgmt_fe_next_session_id; -@@ -164,11 +175,36 @@ char **mgmt_fe_get_all_selectors(void) - return selectors; - } - -+enum mgmt_result nb_error_to_mgmt_result(enum nb_error error) -+{ -+ switch (error) { -+ case NB_OK: -+ return MGMTD_SUCCESS; -+ case NB_ERR: -+ return MGMTD_INTERNAL_ERROR; -+ case NB_ERR_NO_CHANGES: -+ return MGMTD_NO_CFG_CHANGES; -+ case NB_ERR_NOT_FOUND: -+ return MGMTD_INVALID_PARAM; -+ case NB_ERR_EXISTS: -+ return MGMTD_INVALID_PARAM; -+ case NB_ERR_LOCKED: -+ return MGMTD_DS_LOCK_FAILED; -+ case NB_ERR_VALIDATION: -+ return MGMTD_VALIDATION_ERROR; -+ case NB_ERR_RESOURCE: -+ return MGMTD_INTERNAL_ERROR; -+ case NB_ERR_INCONSISTENCY: -+ return MGMTD_INTERNAL_ERROR; -+ case NB_YIELD: -+ return MGMTD_UNKNOWN_FAILURE; -+ } -+ return MGMTD_UNKNOWN_FAILURE; -+} - --/* Forward declarations */ --static void --mgmt_fe_session_register_event(struct mgmt_fe_session_ctx *session, -- enum mgmt_session_event event); -+/* =========================== */ -+/* Frontend Session Management */ -+/* =========================== */ - - static int mgmt_fe_session_write_lock_ds(enum mgmt_ds_id ds_id, struct mgmt_ds_ctx *ds_ctx, - struct mgmt_fe_session_ctx *session) -@@ -201,80 +237,17 @@ static void mgmt_fe_session_unlock_ds(enum mgmt_ds_id ds_id, struct mgmt_ds_ctx - session->session_id, mgmt_ds_id2name(ds_id)); - - session->ds_locked[ds_id] = false; -- mgmt_ds_unlock(ds_ctx); -+ mgmt_ds_unlock(ds_ctx, session->session_id); - _dbg("Unlocked DS:%s write-locked earlier by session-id: %" PRIu64 " from %s", - mgmt_ds_id2name(ds_id), session->session_id, session->adapter->name); - } - --static void --mgmt_fe_session_cfg_txn_cleanup(struct mgmt_fe_session_ctx *session) --{ -- /* -- * Destroy the actual transaction created earlier. -- */ -- if (session->cfg_txn_id != MGMTD_TXN_ID_NONE) -- mgmt_destroy_txn(&session->cfg_txn_id); --} -- --static void --mgmt_fe_session_show_txn_cleanup(struct mgmt_fe_session_ctx *session) --{ -- /* -- * Destroy the transaction created recently. -- */ -- if (session->txn_id != MGMTD_TXN_ID_NONE) -- mgmt_destroy_txn(&session->txn_id); --} -- --static void --mgmt_fe_session_compute_commit_timers(struct mgmt_commit_stats *cmt_stats) --{ -- cmt_stats->last_exec_tm = -- timeval_elapsed(cmt_stats->last_end, cmt_stats->last_start); -- if (cmt_stats->last_exec_tm > cmt_stats->max_tm) { -- cmt_stats->max_tm = cmt_stats->last_exec_tm; -- cmt_stats->max_batch_cnt = cmt_stats->last_batch_cnt; -- } -- -- if (cmt_stats->last_exec_tm < cmt_stats->min_tm) { -- cmt_stats->min_tm = cmt_stats->last_exec_tm; -- cmt_stats->min_batch_cnt = cmt_stats->last_batch_cnt; -- } --} -- --static void mgmt_fe_cleanup_session(struct mgmt_fe_session_ctx **sessionp) --{ -- enum mgmt_ds_id ds_id; -- struct mgmt_ds_ctx *ds_ctx; -- struct mgmt_fe_session_ctx *session = *sessionp; -- -- if (session->adapter) { -- mgmt_fe_session_cfg_txn_cleanup(session); -- mgmt_fe_session_show_txn_cleanup(session); -- for (ds_id = 0; ds_id < MGMTD_DS_MAX_ID; ds_id++) { -- ds_ctx = mgmt_ds_get_ctx_by_id(mm, ds_id); -- if (ds_ctx && session->ds_locked[ds_id]) -- mgmt_fe_session_unlock_ds(ds_id, ds_ctx, -- session); -- } -- mgmt_fe_sessions_del(&session->adapter->fe_sessions, session); -- assert(session->adapter->refcount > 1); -- mgmt_fe_adapter_unlock(&session->adapter); -- } -- mgmt_fe_ns_string_remove_session(&mgmt_fe_ns_strings, session); -- darr_free_free(session->notify_xpaths); -- hash_release(mgmt_fe_sessions, session); -- XFREE(MTYPE_MGMTD_FE_SESSION, session); -- *sessionp = NULL; --} -- - static struct mgmt_fe_session_ctx * --mgmt_fe_find_session_by_client_id(struct mgmt_fe_client_adapter *adapter, -- uint64_t client_id) -+fe_session_lookup_by_client_id(struct mgmt_fe_client_adapter *adapter, uint64_t client_id) - { - struct mgmt_fe_session_ctx *session; - -- FOREACH_SESSION_IN_LIST (adapter, session) { -+ LIST_FOREACH (session, &adapter->sessions, link) { - if (session->client_id == client_id) { - _dbg("Found session-id %" PRIu64 " using client-id %" PRIu64, - session->session_id, client_id); -@@ -285,7 +258,7 @@ mgmt_fe_find_session_by_client_id(struct mgmt_fe_client_adapter *adapter, - return NULL; - } - --static unsigned int mgmt_fe_session_hash_key(const void *data) -+static unsigned int fe_session_hash_key(const void *data) - { - const struct mgmt_fe_session_ctx *session = data; - -@@ -293,7 +266,7 @@ static unsigned int mgmt_fe_session_hash_key(const void *data) - sizeof(session->session_id) / sizeof(uint32_t), 0); - } - --static bool mgmt_fe_session_hash_cmp(const void *d1, const void *d2) -+static bool fe_session_hash_cmp(const void *d1, const void *d2) - { - const struct mgmt_fe_session_ctx *session1 = d1; - const struct mgmt_fe_session_ctx *session2 = d2; -@@ -301,12 +274,14 @@ static bool mgmt_fe_session_hash_cmp(const void *d1, const void *d2) - return (session1->session_id == session2->session_id); - } - --static inline struct mgmt_fe_session_ctx * --mgmt_session_id2ctx(uint64_t session_id) -+static inline struct mgmt_fe_session_ctx *fe_session_lookup(uint64_t session_id) - { - struct mgmt_fe_session_ctx key = {0}; - struct mgmt_fe_session_ctx *session; - -+ if (session_id == MGMTD_SESSION_ID_NONE) -+ return NULL; -+ - if (!mgmt_fe_sessions) - return NULL; - -@@ -316,31 +291,47 @@ mgmt_session_id2ctx(uint64_t session_id) - return session; - } - --void mgmt_fe_adapter_toggle_client_debug(bool set) -+static struct mgmt_fe_session_ctx *fe_session_by_txn_id(uint64_t txn_id) - { -- struct mgmt_fe_client_adapter *adapter; -+ uint64_t session_id = mgmt_txn_get_session_id(txn_id); - -- FOREACH_ADAPTER_IN_LIST (adapter) -- adapter->conn->debug = set; -+ if (session_id == MGMTD_SESSION_ID_NONE) -+ return NULL; -+ return fe_session_lookup(session_id); - } - --static struct mgmt_fe_session_ctx *fe_adapter_session_by_txn_id(uint64_t txn_id) -+static void fe_session_cleanup(struct mgmt_fe_session_ctx **sessionp) - { -- uint64_t session_id = mgmt_txn_get_session_id(txn_id); -+ enum mgmt_ds_id ds_id; -+ struct mgmt_ds_ctx *ds_ctx; -+ struct mgmt_fe_session_ctx *session = *sessionp; - -- if (session_id == MGMTD_SESSION_ID_NONE) -- return NULL; -- return mgmt_session_id2ctx(session_id); -+ /* XXXchopps what about RPC txns? */ -+ mgmt_destroy_txn(&session->cfg_txn_id); -+ mgmt_destroy_txn(&session->txn_id); -+ for (ds_id = 0; ds_id < MGMTD_DS_MAX_ID; ds_id++) { -+ ds_ctx = mgmt_ds_get_ctx_by_id(mm, ds_id); -+ if (ds_ctx && session->ds_locked[ds_id]) -+ mgmt_fe_session_unlock_ds(ds_id, ds_ctx, session); -+ } -+ -+ LIST_REMOVE(session, link); -+ -+ mgmt_fe_ns_string_remove_session(&mgmt_fe_ns_strings, session); -+ darr_free_free(session->notify_xpaths); -+ hash_release(mgmt_fe_sessions, session); -+ XFREE(MTYPE_MGMTD_FE_SESSION, session); -+ *sessionp = NULL; - } - --static struct mgmt_fe_session_ctx *mgmt_fe_create_session(struct mgmt_fe_client_adapter *adapter, -- uint8_t notify_format, uint64_t client_id) -+static struct mgmt_fe_session_ctx *fe_session_create(struct mgmt_fe_client_adapter *adapter, -+ uint8_t notify_format, uint64_t client_id) - { - struct mgmt_fe_session_ctx *session; - -- session = mgmt_fe_find_session_by_client_id(adapter, client_id); -+ session = fe_session_lookup_by_client_id(adapter, client_id); - if (session) -- mgmt_fe_cleanup_session(&session); -+ fe_session_cleanup(&session); - - session = XCALLOC(MTYPE_MGMTD_FE_SESSION, - sizeof(struct mgmt_fe_session_ctx)); -@@ -350,8 +341,7 @@ static struct mgmt_fe_session_ctx *mgmt_fe_create_session(struct mgmt_fe_client_ - session->notify_format = notify_format; - session->txn_id = MGMTD_TXN_ID_NONE; - session->cfg_txn_id = MGMTD_TXN_ID_NONE; -- mgmt_fe_adapter_lock(adapter); -- mgmt_fe_sessions_add_tail(&adapter->fe_sessions, session); -+ LIST_INSERT_HEAD(&adapter->sessions, session, link); - if (!mgmt_fe_next_session_id) - mgmt_fe_next_session_id++; - session->session_id = mgmt_fe_next_session_id++; -@@ -360,42 +350,49 @@ static struct mgmt_fe_session_ctx *mgmt_fe_create_session(struct mgmt_fe_client_ - return session; - } - --static int fe_adapter_send_native_msg(struct mgmt_fe_client_adapter *adapter, -- void *msg, size_t len, -- bool short_circuit_ok) --{ -- return msg_conn_send_msg(adapter->conn, MGMT_MSG_VERSION_NATIVE, msg, -- len, NULL, short_circuit_ok); --} - --static int fe_adapter_conn_send_error(struct msg_conn *conn, -- uint64_t session_id, uint64_t req_id, -- bool short_circuit_ok, int16_t error, -- const char *errfmt, ...) PRINTFRR(6, 7); --static int fe_adapter_conn_send_error(struct msg_conn *conn, uint64_t session_id, -- uint64_t req_id, bool short_circuit_ok, -- int16_t error, const char *errfmt, ...) --{ -- va_list ap; -- int ret; -+/* =============================== */ -+/* Frontend Message (API) Handling */ -+/* =============================== */ - -- va_start(ap, errfmt); -- -- ret = vmgmt_msg_native_send_error(conn, session_id, req_id, -- short_circuit_ok, error, errfmt, ap); -- va_end(ap); -+/* -+ * Code structure: 3 functions per message type: -+ * -+ * 1) send reply back to FE client -+ * 2) process result from Txn module to send back to FE client, call (1) -+ * 3) handle request from FE client, either create TXN or call (1) -+ * -+ * For message types that don't require a transaction then (2) is elided. -+ * -+ * Txn's are used by any message that requires a fan-out to multiple backends: -+ * -+ * 1) FE handler creates Txn and calls Txn module to process request -+ * 2) Txn module creates Txn request determines backends to contact and sends -+ * them each a message. -+ * 3) Each backend client responds and the BE adapter calls TXN module -+ * 4) Txn module aggregates results and calls FE reply function to process -+ * send results back to FE client. -+ * -+ * .-> (BE Daemon) -> [BE adapter/Txn Handler] -+ * (1) (2) / o (3) \\ (4) -+ * [FE handler] => [TXN] ... o [TXN] => [FE reply] -+ * session \ o // -+ * `-> (BE Daemon) -> [BE adapter/Txn Handler] -+ */ - -- return ret; -+static int fe_adapter_send_msg(struct mgmt_fe_client_adapter *adapter, void *msg, size_t len, -+ bool short_circuit_ok) -+{ -+ return msg_conn_send_msg(adapter->conn, MGMT_MSG_VERSION_NATIVE, msg, len, NULL, -+ short_circuit_ok); - } - --static int fe_adapter_send_error(struct mgmt_fe_session_ctx *session, -- uint64_t req_id, bool short_circuit_ok, -- int16_t error, const char *errfmt, ...) -+static int fe_session_send_error(struct mgmt_fe_session_ctx *session, uint64_t req_id, -+ bool short_circuit_ok, int16_t error, const char *errfmt, ...) - PRINTFRR(5, 6); - --static int fe_adapter_send_error(struct mgmt_fe_session_ctx *session, -- uint64_t req_id, bool short_circuit_ok, -- int16_t error, const char *errfmt, ...) -+static int fe_session_send_error(struct mgmt_fe_session_ctx *session, uint64_t req_id, -+ bool short_circuit_ok, int16_t error, const char *errfmt, ...) - { - va_list ap; - int ret; -@@ -409,300 +406,39 @@ static int fe_adapter_send_error(struct mgmt_fe_session_ctx *session, - return ret; - } - -- --static void mgmt_fe_session_cfg_txn_clnup(struct event *thread) --{ -- struct mgmt_fe_session_ctx *session; -- -- session = (struct mgmt_fe_session_ctx *)EVENT_ARG(thread); -- -- mgmt_fe_session_cfg_txn_cleanup(session); --} -- --static void mgmt_fe_session_show_txn_clnup(struct event *thread) --{ -- struct mgmt_fe_session_ctx *session; -- -- session = (struct mgmt_fe_session_ctx *)EVENT_ARG(thread); -- -- mgmt_fe_session_show_txn_cleanup(session); --} -- --static void --mgmt_fe_session_register_event(struct mgmt_fe_session_ctx *session, -- enum mgmt_session_event event) --{ -- struct timeval tv = {.tv_sec = 0, -- .tv_usec = MGMTD_FE_MSG_PROC_DELAY_USEC}; -- -- switch (event) { -- case MGMTD_FE_SESSION_CFG_TXN_CLNUP: -- event_add_timer_tv(mgmt_loop, mgmt_fe_session_cfg_txn_clnup, -- session, &tv, &session->proc_cfg_txn_clnp); -- break; -- case MGMTD_FE_SESSION_SHOW_TXN_CLNUP: -- event_add_timer_tv(mgmt_loop, mgmt_fe_session_show_txn_clnup, -- session, &tv, &session->proc_show_txn_clnp); -- break; -- } --} -- --static struct mgmt_fe_client_adapter * --mgmt_fe_find_adapter_by_fd(int conn_fd) --{ -- struct mgmt_fe_client_adapter *adapter; -- -- FOREACH_ADAPTER_IN_LIST (adapter) { -- if (adapter->conn->fd == conn_fd) -- return adapter; -- } -- -- return NULL; --} -- --static void mgmt_fe_adapter_delete(struct mgmt_fe_client_adapter *adapter) --{ -- struct mgmt_fe_session_ctx *session; -- _dbg("deleting client adapter '%s'", adapter->name); -- -- /* TODO: notify about client disconnect for appropriate cleanup */ -- FOREACH_SESSION_IN_LIST (adapter, session) -- mgmt_fe_cleanup_session(&session); -- mgmt_fe_sessions_fini(&adapter->fe_sessions); -- -- assert(adapter->refcount == 1); -- mgmt_fe_adapter_unlock(&adapter); --} -- --static int mgmt_fe_adapter_notify_disconnect(struct msg_conn *conn) --{ -- struct mgmt_fe_client_adapter *adapter = conn->user; -- -- _dbg("notify disconnect for client adapter '%s'", adapter->name); -- -- mgmt_fe_adapter_delete(adapter); -- -- return 0; --} -- --/** -- * Send result of get-tree request back to the FE client. -- * -- * Args: -- * session: the session. -- * req_id: the request ID. -- * short_circuit_ok: if allowed to short circuit the message. -- * result_format: LYD_FORMAT for the sent output. -- * tree: the tree to send, can be NULL which will send an empty tree. -- * partial_error: if an error occurred during gathering results. -+/* -+ * Transaction (txn) Error - send an error back to the FE client and cleanup any -+ * in-progress txn. - * -- * Return: -- * Any error that occurs -- the message is likely not sent if non-zero. -+ * XXXchopps this is used in 2 places for GET-DATA and RPC errors from TXN -+ * module just adapt those 2 normal reply fucntions to take an error result and -+ * get rid of this function. - */ --static int fe_adapter_send_tree_data(struct mgmt_fe_session_ctx *session, -- uint64_t req_id, bool short_circuit_ok, -- uint8_t result_type, uint32_t wd_options, -- const struct lyd_node *tree, -- int partial_error) -- -+int mgmt_fe_adapter_txn_error(uint64_t txn_id, uint64_t req_id, bool short_circuit_ok, -+ int16_t error, const char *errstr) - { -- struct mgmt_msg_tree_data *msg; -- uint8_t **darrp = NULL; -- int ret = 0; -- -- msg = mgmt_msg_native_alloc_msg(struct mgmt_msg_tree_data, 0, -- MTYPE_MSG_NATIVE_TREE_DATA); -- msg->refer_id = session->session_id; -- msg->req_id = req_id; -- msg->code = MGMT_MSG_CODE_TREE_DATA; -- msg->partial_error = partial_error; -- msg->result_type = result_type; -- -- darrp = mgmt_msg_native_get_darrp(msg); -- ret = yang_print_tree_append(darrp, tree, result_type, -- (wd_options | LYD_PRINT_WITHSIBLINGS)); -- if (ret != LY_SUCCESS) { -- _log_err("Error building get-tree result for client %s session-id %" PRIu64 -- " req-id %" PRIu64 " scok %d result type %u", -- session->adapter->name, session->session_id, req_id, short_circuit_ok, -- result_type); -- goto done; -- } -- -- _dbg("Sending get-tree result from adapter %s to session-id %" PRIu64 " req-id %" PRIu64 -- " scok %d result type %u len %u", -- session->adapter->name, session->session_id, req_id, short_circuit_ok, result_type, -- mgmt_msg_native_get_msg_len(msg)); -- -- ret = fe_adapter_send_native_msg(session->adapter, msg, -- mgmt_msg_native_get_msg_len(msg), -- short_circuit_ok); --done: -- mgmt_msg_native_free_msg(msg); -- -- return ret; --} -- --static int fe_adapter_send_rpc_reply(struct mgmt_fe_session_ctx *session, -- uint64_t req_id, uint8_t result_type, -- const struct lyd_node *result) --{ -- struct mgmt_msg_rpc_reply *msg; -- uint8_t **darrp = NULL; -+ struct mgmt_fe_session_ctx *session; - int ret; - -- msg = mgmt_msg_native_alloc_msg(struct mgmt_msg_rpc_reply, 0, -- MTYPE_MSG_NATIVE_RPC_REPLY); -- msg->refer_id = session->session_id; -- msg->req_id = req_id; -- msg->code = MGMT_MSG_CODE_RPC_REPLY; -- msg->result_type = result_type; -- -- if (result) { -- darrp = mgmt_msg_native_get_darrp(msg); -- ret = yang_print_tree_append(darrp, result, result_type, 0); -- if (ret != LY_SUCCESS) { -- _log_err("Error building rpc-reply result for client %s session-id %" PRIu64 -- " req-id %" PRIu64 " result type %u", -- session->adapter->name, session->session_id, req_id, result_type); -- goto done; -- } -+ session = fe_session_by_txn_id(txn_id); -+ if (!session) { -+ _log_err("failed sending error for txn-id %" PRIu64 " session not found", txn_id); -+ return -ENOENT; - } - -- _dbg("Sending rpc-reply from adapter %s to session-id %" PRIu64 " req-id %" PRIu64 " len %u", -- session->adapter->name, session->session_id, req_id, mgmt_msg_native_get_msg_len(msg)); -- -- ret = fe_adapter_send_native_msg(session->adapter, msg, -- mgmt_msg_native_get_msg_len(msg), -- false); --done: -- mgmt_msg_native_free_msg(msg); -- -- return ret; --} -- --static int fe_adapter_send_edit_reply(struct mgmt_fe_session_ctx *session, -- uint64_t req_id, bool changed, -- bool created, const char *xpath, -- const char *data) --{ -- struct mgmt_msg_edit_reply *msg; -- int ret; -+ ret = fe_session_send_error(session, req_id, false, error, "%s", errstr); - -- msg = mgmt_msg_native_alloc_msg(struct mgmt_msg_edit_reply, 0, -- MTYPE_MSG_NATIVE_EDIT_REPLY); -- msg->refer_id = session->session_id; -- msg->req_id = req_id; -- msg->changed = changed; -- msg->created = created; -- msg->code = MGMT_MSG_CODE_EDIT_REPLY; -- -- mgmt_msg_native_xpath_encode(msg, xpath); -- -- if (data) -- mgmt_msg_native_append(msg, data, strlen(data) + 1); -- -- _dbg("Sending edit-reply from adapter %s to session-id %" PRIu64 " req-id %" PRIu64 -- " changed %u created %u len %u", -- session->adapter->name, session->session_id, req_id, changed, created, -- mgmt_msg_native_get_msg_len(msg)); -- -- ret = fe_adapter_send_native_msg(session->adapter, msg, -- mgmt_msg_native_get_msg_len(msg), -- false); -- mgmt_msg_native_free_msg(msg); -- -- return ret; --} -- --static int fe_adapter_native_send_session_reply(struct mgmt_fe_client_adapter *adapter, -- uint64_t session_id, uint64_t req_id, bool created) --{ -- struct mgmt_msg_session_reply *msg; -- bool scok = adapter->conn->is_short_circuit; -- int ret; -- -- msg = mgmt_msg_native_alloc_msg(struct mgmt_msg_session_reply, 0, -- MTYPE_MSG_NATIVE_SESSION_REPLY); -- msg->refer_id = session_id; -- msg->req_id = req_id; -- msg->code = MGMT_MSG_CODE_SESSION_REPLY; -- msg->created = created; -- -- _dbg("Sending session-reply from adapter %s to session-id %Lu req-id %Lu created %u scok %u", -- adapter->name, session_id, req_id, created, scok); -- -- ret = fe_adapter_send_native_msg(adapter, msg, mgmt_msg_native_get_msg_len(msg), scok); -- mgmt_msg_native_free_msg(msg); -+ mgmt_destroy_txn(&session->txn_id); - - return ret; - } - --/** -- * fe_adapter_handle_session_req() - Handle a session-req message from a FE client. -- * @msg_raw: the message data. -- * @msg_len: the length of the message data. -- */ --static void fe_adapter_handle_session_req(struct mgmt_fe_client_adapter *adapter, void *_msg, -- size_t msg_len) --{ -- const struct mgmt_msg_session_req *msg = _msg; -- struct mgmt_fe_session_ctx *session; -- bool scok = adapter->conn->is_short_circuit; -- uint64_t client_id; - -- _dbg("Got session-req is create %u req-id %Lu for refer-id %Lu notify-fmt %u from '%s'", -- msg->refer_id == 0, msg->req_id, msg->refer_id, msg->notify_format, adapter->name); -+/* -------------- */ -+/* COMMIT Message */ -+/* -------------- */ - -- /* -- * It's important that any error has its refer_id set to 0 for create -- * case and set to the passed in msg->refer_id otherwise (the destroy -- * case). For non-error return pass the session_id for create or destroy. -- */ -- -- if (msg->refer_id) { -- uint64_t session_id = msg->refer_id; -- -- session = mgmt_session_id2ctx(session_id); -- if (!session) { -- fe_adapter_conn_send_error(adapter->conn, session_id, msg->req_id, scok, -- EINVAL, -- "No session to delete for session-id: %" PRIu64, -- session_id); -- return; -- } -- fe_adapter_native_send_session_reply(adapter, session_id, msg->req_id, false); -- mgmt_fe_cleanup_session(&session); -- return; -- } -- -- client_id = msg->req_id; -- -- /* Default notification format */ -- if (msg->notify_format && msg->notify_format > MGMT_MSG_FORMAT_LAST) { -- fe_adapter_conn_send_error(adapter->conn, 0, msg->req_id, scok, EINVAL, -- "Unrecognized notify format: %u", msg->notify_format); -- return; -- } -- -- /* See if we have a client name to register */ -- if (msg_len > sizeof(*msg)) { -- if (!MGMT_MSG_VALIDATE_NUL_TERM(msg, msg_len)) { -- fe_adapter_conn_send_error(adapter->conn, 0, msg->req_id, scok, EINVAL, -- "Corrupt session-req message rcvd from client-id: %" PRIu64, -- client_id); -- return; -- } -- _dbg("Set client-name to '%s'", msg->client_name); -- strlcpy(adapter->name, msg->client_name, sizeof(adapter->name)); -- } -- -- session = mgmt_fe_create_session(adapter, msg->notify_format ?: DEFAULT_NOTIFY_FORMAT, -- client_id); -- fe_adapter_native_send_session_reply(adapter, session->session_id, msg->req_id, true); --} -- --static void fe_adapter_send_commit_reply(struct mgmt_fe_session_ctx *session, uint64_t req_id, -+static void fe_session_send_commit_reply(struct mgmt_fe_session_ctx *session, uint64_t req_id, - uint8_t source, uint8_t target, uint8_t action, - bool unlock) - { -@@ -723,8 +459,7 @@ static void fe_adapter_send_commit_reply(struct mgmt_fe_session_ctx *session, ui - session->session_id, session->adapter->name, req_id, mgmt_ds_id2name(source), - mgmt_ds_id2name(target), action, unlock); - -- ret = fe_adapter_send_native_msg(session->adapter, msg, mgmt_msg_native_get_msg_len(msg), -- false); -+ ret = fe_adapter_send_msg(session->adapter, msg, mgmt_msg_native_get_msg_len(msg), false); - mgmt_msg_native_free_msg(msg); - if (ret) { - _log_err("Failed to send COMMIT_REPLY to session-id %Lu", session->session_id); -@@ -732,19 +467,89 @@ static void fe_adapter_send_commit_reply(struct mgmt_fe_session_ctx *session, ui - } - } - --/** -- * fe_adapter_handle_commit() - Handle a commit message from a FE client. -- * @msg_raw: the message data. -- * @msg_len: the length of the message data. -- */ --static void fe_adapter_handle_commit(struct mgmt_fe_session_ctx *session, void *_msg, -+int mgmt_fe_send_commit_cfg_reply(uint64_t session_id, uint64_t txn_id, enum mgmt_ds_id src_ds_id, -+ enum mgmt_ds_id dst_ds_id, uint64_t req_id, bool validate_only, -+ bool unlock, enum mgmt_result result, const char *error_if_any) -+{ -+ struct mgmt_fe_session_ctx *session; -+ uint8_t action; -+ int ret = 0; -+ -+ /* -+ * When a session is deleted (e.g., disconnects) while there's an active -+ * TXN we can get a NULL return for the session_id after the TXN -+ * completes. A commit message never implicitly locks datastores -- -+ * those locks are managed by the client (or cleaned up on session -+ * disconnect). -+ * -+ * However, for the implicit commit (legacy CLI/non-transactional) case -+ * the intention is that each change is made to the candidate and -+ * running at the same (i.e., as the user enters commands they take -+ * affect). So a failure to apply the change to running means we should -+ * back that change out of the candidate DS, and the user will be -+ * presented with an error message. -+ * -+ * So, if we have a failiure to apply an implicit commit, we -+ * should restore the candidate DS, and we can do this by copying -+ * running back over candidate. This doesn't work in the transactional -+ * case b/c the candidate may be made up from multiple changes and a -+ * failure of the latest change shouldn't invalidate all the previous -+ * valid changes which is what would happen if we copied running back -+ * over candidate. -+ */ -+ session = fe_session_lookup(session_id); -+ if (session && session->cfg_txn_id && session->cfg_txn_id != txn_id) -+ session = NULL; -+ -+ if (validate_only) -+ action = MGMT_MSG_COMMIT_VALIDATE; -+ else -+ action = MGMT_MSG_COMMIT_APPLY; -+ -+ /* -+ * Restore the source from the dest in case of error with an implicit commit. -+ * Currently we use the unlock feedback to identify an implicit commit. -+ */ -+ if (result != MGMTD_SUCCESS && result != MGMTD_NO_CFG_CHANGES && unlock) -+ mgmt_ds_copy_dss(mgmt_ds_get_ctx_by_id(mm, src_ds_id), -+ mgmt_ds_get_ctx_by_id(mm, dst_ds_id), false); -+ -+ if (!session) -+ return -ENOENT; -+ -+ if (mm->perf_stats_en) -+ gettimeofday(&session->adapter->cmt_stats.last_end, NULL); -+ fe_session_compute_commit_timers(&session->adapter->cmt_stats); -+ -+ if (result == MGMTD_SUCCESS || result == MGMTD_NO_CFG_CHANGES) -+ fe_session_send_commit_reply(session, req_id, src_ds_id, dst_ds_id, action, unlock); -+ else { -+ ret = fe_session_send_error( -+ session, req_id, false, EINVAL /* convert result */, -+ "commit failed session-id %Lu on %s req-id %Lu source-ds: %s target-ds: %s validate-only: %u: reason: '%s'", -+ session->session_id, session->adapter->name, req_id, -+ mgmt_ds_id2name(src_ds_id), mgmt_ds_id2name(dst_ds_id), validate_only, -+ error_if_any ?: ""); -+ } -+ -+ assert(session->cfg_txn_id == txn_id); -+ mgmt_destroy_txn(&session->cfg_txn_id); -+ -+ return ret; -+} -+ -+static void fe_session_handle_commit(struct mgmt_fe_session_ctx *session, void *_msg, - size_t msg_len) - { - struct mgmt_msg_commit *msg = _msg; - struct mgmt_ds_ctx *src_ds_ctx, *dst_ds_ctx; -+ uint64_t txn_id; - -- _dbg("Got COMMIT for source-ds: %s target-ds: %s action: %u on session-id %Lu from '%s'", -- mgmt_ds_id2name(msg->source), mgmt_ds_id2name(msg->target), msg->action, -+ _dbg("Got COMMIT for source-ds: %s target-ds: %s action: %s on session-id %Lu from '%s'", -+ mgmt_ds_id2name(msg->source), mgmt_ds_id2name(msg->target), -+ msg->action == MGMT_MSG_COMMIT_VALIDATE ? "validate" -+ : msg->action == MGMT_MSG_COMMIT_ABORT ? "abort" -+ : "apply", - session->session_id, session->adapter->name); - - if (mm->perf_stats_en) -@@ -753,29 +558,52 @@ static void fe_adapter_handle_commit(struct mgmt_fe_session_ctx *session, void * - - /* Validate source and dest DS */ - if (msg->source != MGMTD_DS_CANDIDATE || msg->target != MGMTD_DS_RUNNING) { -- fe_adapter_send_error(session, msg->req_id, false, EINVAL, -+ fe_session_send_error(session, msg->req_id, false, EINVAL, - "source/target for commit must be candidate/running"); - return; - } - -- /* User should have lock on both source and dest DS */ -- if (!session->ds_locked[msg->target] || !session->ds_locked[msg->source]) { -- fe_adapter_send_error(session, msg->req_id, false, EBUSY, -- "source/target not locked by session-id: %Lu on '%s'", -+ src_ds_ctx = mgmt_ds_get_ctx_by_id(mm, msg->source); -+ dst_ds_ctx = mgmt_ds_get_ctx_by_id(mm, msg->target); -+ -+ if (mgmt_ds_is_txn_locked(src_ds_ctx, &txn_id) || -+ mgmt_ds_is_txn_locked(dst_ds_ctx, &txn_id)) { -+ fe_session_send_error(session, msg->req_id, false, EBUSY, -+ "source/target datastore is locked by another transaction txn-id: %Lu", -+ txn_id); -+ return; -+ } -+ -+ /* User must always have lock on source */ -+ if (!session->ds_locked[msg->source]) { -+ fe_session_send_error(session, msg->req_id, false, EBUSY, -+ "source not locked by session-id: %Lu on '%s'", - session->session_id, session->adapter->name); - return; - } - -+ /* For apply/abort user must also have lock on target */ -+ if (msg->action != MGMT_MSG_COMMIT_VALIDATE && !session->ds_locked[msg->target]) { -+ fe_session_send_error(session, msg->req_id, false, EBUSY, -+ "target not locked for apply/abort by session-id: %Lu on '%s'", -+ session->session_id, session->adapter->name); -+ return; -+ } -+ -+ /* -+ * We get an exisitng cfg_txn_id if a check/validate was done first then -+ * an apply/abort later. -+ */ - if (session->cfg_txn_id == MGMTD_TXN_ID_NONE) { - /* as we have the lock no-one else should have a config txn */ -- assert(!mgmt_config_txn_in_progress()); -+ assert(!mgmt_txn_config_in_progress()); - - /* - * Start a CONFIG Transaction (if not started already) - */ - session->cfg_txn_id = mgmt_create_txn(session->session_id, MGMTD_TXN_TYPE_CONFIG); - if (session->cfg_txn_id == MGMTD_SESSION_ID_NONE) { -- fe_adapter_send_error(session, msg->req_id, false, ENOMEM, -+ fe_session_send_error(session, msg->req_id, false, ENOMEM, - "failed to create config transaction for session-id: %Lu on '%s'", - session->session_id, session->adapter->name); - return; -@@ -787,23 +615,19 @@ static void fe_adapter_handle_commit(struct mgmt_fe_session_ctx *session, void * - /* - * Create COMMIT Config request under the transaction - */ -- src_ds_ctx = mgmt_ds_get_ctx_by_id(mm, msg->source); -- dst_ds_ctx = mgmt_ds_get_ctx_by_id(mm, msg->target); -- assert(src_ds_ctx && dst_ds_ctx); -- if (mgmt_txn_send_commit_config_req(session->cfg_txn_id, msg->req_id, msg->source, -- src_ds_ctx, msg->target, dst_ds_ctx, -- msg->action == MGMT_MSG_COMMIT_VALIDATE, -- msg->action == MGMT_MSG_COMMIT_ABORT, -- false /* implicit */, msg->unlock, NULL) != 0) { -- fe_adapter_send_error(session, msg->req_id, false, EINVAL, -- "failed to create config request under txn-id: %Lu for session-id: %Lu on '%s'", -- session->cfg_txn_id, session->session_id, -- session->adapter->name); -- } -+ mgmt_txn_send_commit_config_req(session->cfg_txn_id, msg->req_id, msg->source, src_ds_ctx, -+ msg->target, dst_ds_ctx, -+ msg->action == MGMT_MSG_COMMIT_VALIDATE, -+ msg->action == MGMT_MSG_COMMIT_ABORT, false /* implicit */, -+ msg->unlock, NULL); - } - --static int fe_adapter_native_send_lock_reply(struct mgmt_fe_session_ctx *session, uint64_t req_id, -- uint8_t datastore, bool lock, bool short_circuit_ok) -+/* ------------ */ -+/* LOCK Message */ -+/* ------------ */ -+ -+static int fe_session_send_lock_reply(struct mgmt_fe_session_ctx *session, uint64_t req_id, -+ uint8_t datastore, bool lock, bool short_circuit_ok) - { - struct mgmt_msg_lock_reply *msg; - int ret; -@@ -819,25 +643,21 @@ static int fe_adapter_native_send_lock_reply(struct mgmt_fe_session_ctx *session - session->adapter->name, session->session_id, req_id, datastore, lock, - short_circuit_ok); - -- ret = fe_adapter_send_native_msg(session->adapter, msg, mgmt_msg_native_get_msg_len(msg), -- short_circuit_ok); -+ ret = fe_adapter_send_msg(session->adapter, msg, mgmt_msg_native_get_msg_len(msg), -+ short_circuit_ok); - mgmt_msg_native_free_msg(msg); - - return ret; - } - --/** -- * fe_adapter_handle_lock() - Handle a session-req message from a FE client. -- * @msg_raw: the message data. -- * @msg_len: the length of the message data. -- */ --static void fe_adapter_handle_lock(struct mgmt_fe_session_ctx *session, void *_msg, size_t msg_len) -+static void fe_session_handle_lock(struct mgmt_fe_session_ctx *session, void *_msg, size_t msg_len) - { - const struct mgmt_msg_lock *msg = _msg; - struct mgmt_ds_ctx *ds_ctx; - bool short_circuit_ok = session->adapter->conn->is_short_circuit; - uint8_t datastore = msg->datastore; - uint64_t lock_session; -+ uint64_t txn_id; - bool lock = msg->lock; - int ret; - -@@ -845,48 +665,111 @@ static void fe_adapter_handle_lock(struct mgmt_fe_session_ctx *session, void *_m - mgmt_ds_id2name(datastore), msg->refer_id, session->adapter->name); - - if (datastore != MGMTD_DS_CANDIDATE && datastore != MGMTD_DS_RUNNING) { -- fe_adapter_send_error(session, msg->req_id, short_circuit_ok, EINVAL, -+ fe_session_send_error(session, msg->req_id, short_circuit_ok, EINVAL, - "Lock/Unlock on DS other than candidate or running DS not supported"); - return; - } - -- ds_ctx = mgmt_ds_get_ctx_by_id(mm, datastore); -- assert(ds_ctx); -+ ds_ctx = mgmt_ds_get_ctx_by_id(mm, datastore); -+ assert(ds_ctx); -+ -+ if (lock && mgmt_ds_is_locked(ds_ctx, &lock_session, &txn_id) && -+ lock_session != session->session_id) { -+ fe_session_send_error(session, msg->req_id, short_circuit_ok, EBUSY, -+ "Lock already taken on datastore %s by session: %Lu txn-id: %Lu", -+ mgmt_ds_id2name(datastore), lock_session, txn_id); -+ return; -+ } else if (lock) { -+ ret = mgmt_fe_session_write_lock_ds(datastore, ds_ctx, session); -+ if (ret) { -+ fe_session_send_error(session, msg->req_id, short_circuit_ok, EBUSY, -+ "Unexpected error %d trying to lock datastore by session-id: %Lu", -+ ret, session->session_id); -+ return; -+ } -+ } else { -+ /* unlock even if one or both of the lock indicators is wrong */ -+ mgmt_fe_session_unlock_ds(datastore, ds_ctx, session); -+ } -+ -+ if (fe_session_send_lock_reply(session, msg->req_id, msg->datastore, msg->lock, -+ short_circuit_ok)) { -+ assert(!short_circuit_ok); -+ _log_err("Failed to send LOCK_REPLY to session-id %Lu", session->session_id); -+ msg_conn_disconnect(session->adapter->conn, false); -+ } -+} -+ -+/* ---------------- */ -+/* GET-DATA Message */ -+/* ---------------- */ -+ -+/* -+ * Send result of get-tree request back to the FE client. -+ */ -+static int fe_session_send_tree_data(struct mgmt_fe_session_ctx *session, uint64_t req_id, -+ bool short_circuit_ok, uint8_t result_type, -+ uint32_t wd_options, const struct lyd_node *tree, -+ int partial_error) -+ -+{ -+ struct mgmt_msg_tree_data *msg; -+ uint8_t **darrp = NULL; -+ int ret = 0; -+ -+ msg = mgmt_msg_native_alloc_msg(struct mgmt_msg_tree_data, 0, MTYPE_MSG_NATIVE_TREE_DATA); -+ msg->refer_id = session->session_id; -+ msg->req_id = req_id; -+ msg->code = MGMT_MSG_CODE_TREE_DATA; -+ msg->partial_error = partial_error; -+ msg->result_type = result_type; -+ -+ darrp = mgmt_msg_native_get_darrp(msg); -+ ret = yang_print_tree_append(darrp, tree, result_type, -+ (wd_options | LYD_PRINT_WITHSIBLINGS)); -+ if (ret != LY_SUCCESS) { -+ _log_err("Error building get-tree result for client %s session-id %" PRIu64 -+ " req-id %" PRIu64 " scok %d result type %u", -+ session->adapter->name, session->session_id, req_id, short_circuit_ok, -+ result_type); -+ goto done; -+ } -+ -+ _dbg("Sending get-tree result from adapter %s to session-id %" PRIu64 " req-id %" PRIu64 -+ " scok %d result type %u len %u", -+ session->adapter->name, session->session_id, req_id, short_circuit_ok, result_type, -+ mgmt_msg_native_get_msg_len(msg)); -+ -+ ret = fe_adapter_send_msg(session->adapter, msg, mgmt_msg_native_get_msg_len(msg), -+ short_circuit_ok); -+done: -+ mgmt_msg_native_free_msg(msg); -+ -+ return ret; -+} - -- if (lock && mgmt_ds_is_locked(ds_ctx, &lock_session) && -- lock_session != session->session_id) { -- fe_adapter_send_error(session, msg->req_id, short_circuit_ok, EBUSY, -- "Lock already taken on datastore by session: %Lu", -- lock_session); -+void mgmt_fe_adapter_send_tree_data(uint64_t session_id, uint64_t txn_id, uint64_t req_id, -+ LYD_FORMAT result_type, uint32_t wd_options, -+ const struct lyd_node *tree, int partial_error, -+ bool short_circuit_ok) -+{ -+ struct mgmt_fe_session_ctx *session; -+ -+ session = fe_session_lookup(session_id); -+ if (!session) - return; -- } else if (lock) { -- ret = mgmt_fe_session_write_lock_ds(datastore, ds_ctx, session); -- if (ret) { -- fe_adapter_send_error(session, msg->req_id, short_circuit_ok, EBUSY, -- "Unexpected error %d trying to lock datastore by session-id: %Lu", -- ret, session->session_id); -- return; -- } -- } else { -- /* unlock even if one or both of the lock indicators is wrong */ -- mgmt_fe_session_unlock_ds(datastore, ds_ctx, session); -- } - -- if (fe_adapter_native_send_lock_reply(session, msg->req_id, msg->datastore, msg->lock, -- short_circuit_ok)) { -- assert(!short_circuit_ok); -- _log_err("Failed to send LOCK_REPLY to session-id %Lu", session->session_id); -- msg_conn_disconnect(session->adapter->conn, false); -- } -+ /* XXXchopps why do we care about this? Why not allow multple? */ -+ assert(session->txn_id == txn_id); -+ -+ if (fe_session_send_tree_data(session, req_id, short_circuit_ok, result_type, wd_options, -+ tree, partial_error)) -+ fe_session_send_error(session, req_id, false, -EIO, -+ "Failed sending GET-DATA reply"); -+ mgmt_destroy_txn(&session->txn_id); - } - --/** -- * fe_adapter_handle_get_data() - Handle a get-tree message from a FE client. -- * @session: the client session. -- * @msg_raw: the message data. -- * @msg_len: the length of the message data. -- */ --static void fe_adapter_handle_get_data(struct mgmt_fe_session_ctx *session, void *_msg, -+static void fe_session_handle_get_data(struct mgmt_fe_session_ctx *session, void *_msg, - size_t msg_len) - { - struct mgmt_msg_get_data *msg = _msg; -@@ -905,14 +788,14 @@ static void fe_adapter_handle_get_data(struct mgmt_fe_session_ctx *session, void - session->adapter->name, session->session_id, msg->req_id); - - if (!MGMT_MSG_VALIDATE_NUL_TERM(msg, msg_len)) { -- fe_adapter_send_error(session, req_id, false, -EINVAL, -+ fe_session_send_error(session, req_id, false, -EINVAL, - "Invalid message rcvd from session-id: %" PRIu64, - session->session_id); - goto done; - } - - if (session->txn_id != MGMTD_TXN_ID_NONE) { -- fe_adapter_send_error(session, req_id, false, -EINPROGRESS, -+ fe_session_send_error(session, req_id, false, -EINPROGRESS, - "Transaction in progress txn-id: %" PRIu64 - " for session-id: %" PRIu64, - session->txn_id, session->session_id); -@@ -933,7 +816,7 @@ static void fe_adapter_handle_get_data(struct mgmt_fe_session_ctx *session, void - wd_options = LYD_PRINT_WD_IMPL_TAG; - break; - default: -- fe_adapter_send_error(session, req_id, false, -EINVAL, -+ fe_session_send_error(session, req_id, false, -EINVAL, - "Invalid defaults value %u for session-id: %" PRIu64, - msg->defaults, session->session_id); - goto done; -@@ -955,13 +838,13 @@ static void fe_adapter_handle_get_data(struct mgmt_fe_session_ctx *session, void - err = ly_ctx_get_yanglib_data(ly_native_ctx, &ylib, "%u", - ly_ctx_get_change_count(ly_native_ctx)); - if (err) { -- fe_adapter_send_error(session, req_id, false, err, -+ fe_session_send_error(session, req_id, false, err, - "Error getting yang-library data, session-id: %" PRIu64 - " error: %s", - session->session_id, ly_last_errmsg()); - } else if (nb_oper_is_yang_lib_query(msg->xpath)) { - yang_lyd_trim_xpath(&ylib, msg->xpath); -- (void)fe_adapter_send_tree_data(session, req_id, false, msg->result_type, -+ (void)fe_session_send_tree_data(session, req_id, false, msg->result_type, - wd_options, ylib, 0); - goto done; - } -@@ -978,7 +861,7 @@ static void fe_adapter_handle_get_data(struct mgmt_fe_session_ctx *session, void - ds_id = MGMTD_DS_OPERATIONAL; - break; - default: -- fe_adapter_send_error(session, req_id, false, -EINVAL, -+ fe_session_send_error(session, req_id, false, -EINVAL, - "Unsupported datastore %" PRIu8 - " requested from session-id: %" PRIu64, - msg->datastore, session->session_id); -@@ -988,7 +871,7 @@ static void fe_adapter_handle_get_data(struct mgmt_fe_session_ctx *session, void - err = yang_resolve_snode_xpath(ly_native_ctx, msg->xpath, &snodes, - &simple_xpath); - if (err) { -- fe_adapter_send_error(session, req_id, false, -EINPROGRESS, -+ fe_session_send_error(session, req_id, false, -EINPROGRESS, - "XPath doesn't resolve for session-id: %" PRIu64, - session->session_id); - goto done; -@@ -1002,8 +885,8 @@ static void fe_adapter_handle_get_data(struct mgmt_fe_session_ctx *session, void - _dbg("No backends provide xpath: %s for txn-id: %" PRIu64 " session-id: %" PRIu64, - msg->xpath, session->txn_id, session->session_id); - -- fe_adapter_send_tree_data(session, req_id, false, -- msg->result_type, wd_options, NULL, 0); -+ fe_session_send_tree_data(session, req_id, false, msg->result_type, wd_options, -+ NULL, 0); - goto done; - } - -@@ -1013,8 +896,8 @@ static void fe_adapter_handle_get_data(struct mgmt_fe_session_ctx *session, void - /* Start a SHOW Transaction */ - session->txn_id = mgmt_create_txn(session->session_id, - MGMTD_TXN_TYPE_SHOW); -- if (session->txn_id == MGMTD_SESSION_ID_NONE) { -- fe_adapter_send_error(session, req_id, false, -EINPROGRESS, -+ if (session->txn_id == MGMTD_TXN_ID_NONE) { -+ fe_session_send_error(session, req_id, false, -EINPROGRESS, - "failed to create a 'show' txn"); - goto done; - } -@@ -1028,7 +911,7 @@ static void fe_adapter_handle_get_data(struct mgmt_fe_session_ctx *session, void - if (ret) { - /* destroy the just created txn */ - mgmt_destroy_txn(&session->txn_id); -- fe_adapter_send_error(session, req_id, false, -EINPROGRESS, -+ fe_session_send_error(session, req_id, false, -EINPROGRESS, - "failed to create a 'show' txn"); - } - done: -@@ -1037,120 +920,215 @@ done: - darr_free(snodes); - } - --static void fe_adapter_handle_edit(struct mgmt_fe_session_ctx *session, void *_msg, size_t msg_len) -+/* ------------ */ -+/* EDIT Message */ -+/* ------------ */ -+ -+static int fe_session_send_edit_reply(struct mgmt_fe_session_ctx *session, uint64_t req_id, -+ bool changed, bool created, const char *xpath, -+ const char *data) -+{ -+ struct mgmt_msg_edit_reply *msg; -+ int ret; -+ -+ msg = mgmt_msg_native_alloc_msg(struct mgmt_msg_edit_reply, 0, MTYPE_MSG_NATIVE_EDIT_REPLY); -+ msg->refer_id = session->session_id; -+ msg->req_id = req_id; -+ msg->changed = changed; -+ msg->created = created; -+ msg->code = MGMT_MSG_CODE_EDIT_REPLY; -+ -+ mgmt_msg_native_xpath_encode(msg, xpath); -+ -+ if (data) -+ mgmt_msg_native_append(msg, data, strlen(data) + 1); -+ -+ _dbg("Sending edit-reply from adapter %s to session-id %" PRIu64 " req-id %" PRIu64 -+ " changed %u created %u len %u", -+ session->adapter->name, session->session_id, req_id, changed, created, -+ mgmt_msg_native_get_msg_len(msg)); -+ -+ ret = fe_adapter_send_msg(session->adapter, msg, mgmt_msg_native_get_msg_len(msg), false); -+ mgmt_msg_native_free_msg(msg); -+ -+ return ret; -+} -+ -+ -+int mgmt_fe_adapter_send_edit_reply(uint64_t session_id, uint64_t txn_id, uint64_t req_id, -+ bool unlock, bool commit, struct mgmt_edit_req **edit, -+ enum mgmt_result result, const char *errstr) -+{ -+ struct mgmt_fe_session_ctx *session; -+ const enum mgmt_ds_id can_id = MGMTD_DS_CANDIDATE; -+ const enum mgmt_ds_id run_id = MGMTD_DS_RUNNING; -+ struct mgmt_ds_ctx *can_ds = mgmt_ds_get_ctx_by_id(mm, can_id); -+ struct mgmt_ds_ctx *run_ds = mgmt_ds_get_ctx_by_id(mm, run_id); -+ int ret; -+ -+ /* -+ * When a session is deleted (e.g., disconnects) while there's an -+ * active TXN we can get a NULL return here when the TXN completes. We -+ * still want to do any cleanup that the session cleanup could not -+ * accomplish b/c of the outstanding TXN. -+ */ -+ session = fe_session_lookup(session_id); -+ if (session && session->cfg_txn_id && session->cfg_txn_id != txn_id) -+ session = NULL; -+ -+ /* -+ * Deal with the backup candidate config. If the edit was successful we -+ * free the backup. If it failed we restore the datastore from the -+ * backup. -+ */ -+ if (result == MGMTD_SUCCESS || result == MGMTD_NO_CFG_CHANGES) -+ nb_config_free((*edit)->nb_backup); -+ else -+ mgmt_ds_restore_nb_config(can_ds, (*edit)->nb_backup); -+ -+ if (!session) -+ return -ENOENT; -+ -+ if ((*edit)->unlock_running) -+ mgmt_fe_session_unlock_ds(run_id, run_ds, session); -+ if ((*edit)->unlock_candidate) -+ mgmt_fe_session_unlock_ds(can_id, can_ds, session); -+ -+ -+ if (result != MGMTD_SUCCESS && result != MGMTD_NO_CFG_CHANGES) -+ /* Fix the error */ -+ ret = fe_session_send_error(session, req_id, false, -EINVAL, "%s", errstr); -+ else -+ ret = fe_session_send_edit_reply(session, req_id, -+ result == MGMTD_SUCCESS /*changed*/, -+ (*edit)->created, (*edit)->xpath_created, errstr); -+ XFREE(MTYPE_MGMTD_TXN_REQ, *edit); -+ -+ assert(session->cfg_txn_id == txn_id); -+ mgmt_destroy_txn(&session->cfg_txn_id); -+ -+ return ret; -+} -+ -+static void fe_session_handle_edit(struct mgmt_fe_session_ctx *session, void *_msg, size_t msg_len) - { - struct mgmt_msg_edit *msg = _msg; -- enum mgmt_ds_id ds_id, rds_id; -- struct mgmt_ds_ctx *ds_ctx, *rds_ctx; -+ const enum mgmt_ds_id can_id = MGMTD_DS_CANDIDATE; -+ const enum mgmt_ds_id run_id = MGMTD_DS_RUNNING; -+ struct mgmt_ds_ctx *can_ds = mgmt_ds_get_ctx_by_id(mm, can_id); -+ struct mgmt_ds_ctx *run_ds = mgmt_ds_get_ctx_by_id(mm, run_id); -+ struct mgmt_edit_req *edit; -+ struct nb_config *nb_config; -+ uint64_t txn_id = MGMTD_TXN_ID_NONE; -+ bool implicit_can_lock = false; -+ bool implicit_run_lock = false; - const char *xpath, *data; -- bool lock, commit; -+ char errstr[BUFSIZ]; -+ bool commit; - int ret; - -- lock = CHECK_FLAG(msg->flags, EDIT_FLAG_IMPLICIT_LOCK); -+ /* grab the deprecated commit flag -- the lock flag isn't required at all */ - commit = CHECK_FLAG(msg->flags, EDIT_FLAG_IMPLICIT_COMMIT); - -- if (lock && commit && msg->datastore == MGMT_MSG_DATASTORE_RUNNING) -- ; -- else if (msg->datastore != MGMT_MSG_DATASTORE_CANDIDATE) { -- fe_adapter_send_error(session, msg->req_id, false, -EINVAL, -+ /* -+ * Validate input args and obtain any needed locks -+ */ -+ -+ commit = commit || msg->datastore == MGMT_MSG_DATASTORE_RUNNING; -+ if (!commit && msg->datastore != MGMT_MSG_DATASTORE_CANDIDATE) { -+ fe_session_send_error(session, msg->req_id, false, -EINVAL, - "Unsupported datastore"); - return; - } - -+ /* Decode the target xpath and config changes */ - xpath = mgmt_msg_native_xpath_data_decode(msg, msg_len, data); - if (!xpath) { -- fe_adapter_send_error(session, msg->req_id, false, -EINVAL, -- "Invalid message"); -+ fe_session_send_error(session, msg->req_id, false, -EINVAL, "Invalid message"); - return; - } - -- ds_id = MGMTD_DS_CANDIDATE; -- ds_ctx = mgmt_ds_get_ctx_by_id(mm, ds_id); -- assert(ds_ctx); -- -- rds_id = MGMTD_DS_RUNNING; -- rds_ctx = mgmt_ds_get_ctx_by_id(mm, rds_id); -- assert(rds_ctx); -+ /* If committing, make sure no other txn has locks on the datastores */ -+ if (commit && -+ (mgmt_ds_is_txn_locked(can_ds, &txn_id) || mgmt_ds_is_txn_locked(run_ds, &txn_id))) { -+ fe_session_send_error(session, msg->req_id, false, -EBUSY, -+ "datastores are locked by another transaction txn-id: %Lu", -+ txn_id); -+ return; -+ } - -- if (lock) { -- if (mgmt_fe_session_write_lock_ds(ds_id, ds_ctx, session)) { -- fe_adapter_send_error(session, msg->req_id, false, -- -EBUSY, -+ /* We always ensure the candidate DS is locked */ -+ if (!session->ds_locked[can_id]) { -+ if (mgmt_fe_session_write_lock_ds(can_id, can_ds, session)) { -+ fe_session_send_error(session, msg->req_id, false, -EBUSY, - "Candidate DS is locked by another session"); - return; - } -+ implicit_can_lock = true; -+ } - -- if (commit) { -- if (mgmt_fe_session_write_lock_ds(rds_id, rds_ctx, -- session)) { -- mgmt_fe_session_unlock_ds(ds_id, ds_ctx, -- session); -- fe_adapter_send_error( -- session, msg->req_id, false, -EBUSY, -- "Running DS is locked by another session"); -- return; -- } -- } -- } else { -- if (!session->ds_locked[ds_id]) { -- fe_adapter_send_error(session, msg->req_id, false, -- -EBUSY, -- "Candidate DS is not locked"); -+ /* And if modifying running, ensure it is locked too */ -+ if (commit && !session->ds_locked[run_id]) { -+ if (mgmt_fe_session_write_lock_ds(run_id, run_ds, session)) { -+ if (implicit_can_lock) -+ mgmt_fe_session_unlock_ds(can_id, can_ds, session); -+ fe_session_send_error(session, msg->req_id, false, -EBUSY, -+ "Running DS is locked by another session"); - return; - } -- -- if (commit) { -- if (!session->ds_locked[rds_id]) { -- fe_adapter_send_error(session, msg->req_id, -- false, -EBUSY, -- "Running DS is not locked"); -- return; -- } -- } -+ implicit_run_lock = true; - } - -- session->cfg_txn_id = mgmt_create_txn(session->session_id, -- MGMTD_TXN_TYPE_CONFIG); -- if (session->cfg_txn_id == MGMTD_SESSION_ID_NONE) { -- if (lock) { -- mgmt_fe_session_unlock_ds(ds_id, ds_ctx, session); -- if (commit) -- mgmt_fe_session_unlock_ds(rds_id, rds_ctx, -- session); -+ /* -+ * Everythign valid and setup, proceed to make the edit. -+ */ -+ -+ errstr[0] = '\0'; -+ nb_config = mgmt_ds_get_nb_config(can_ds); -+ -+ edit = XCALLOC(MTYPE_MGMTD_TXN_REQ, sizeof(struct mgmt_edit_req)); -+ edit->unlock_candidate = implicit_can_lock; -+ edit->unlock_running = implicit_run_lock; -+ edit->nb_backup = nb_config_dup(nb_config); /* keep a backup of candidate */ -+ -+ /* Make edits to the candidate DS */ -+ ret = nb_candidate_edit_tree(nb_config, msg->operation, msg->request_type, xpath, data, -+ &edit->created, edit->xpath_created, errstr, sizeof(errstr)); -+ if (ret && ret == NB_ERR_NO_CHANGES) -+ ret = NB_OK; -+ else if (ret) -+ _dbg("Edit failed for txn-id %Lu req-id %Lu: %s: restoring candidate", txn_id, -+ msg->req_id, errstr[0] ? errstr : "unknown reason"); -+ else if (commit) { -+ /* Get a TXN for the commit */ -+ txn_id = mgmt_create_txn(session->session_id, MGMTD_TXN_TYPE_CONFIG); -+ if (txn_id == MGMTD_SESSION_ID_NONE) { -+ ret = NB_ERR_EXISTS; /* should not happen as we have the locks */ -+ goto reply; - } -- fe_adapter_send_error(session, msg->req_id, false, -EBUSY, -- "Failed to create a configuration transaction"); -- return; -- } -+ session->cfg_txn_id = txn_id; - -- _dbg("Created new config txn-id: %" PRIu64 " for session-id: %" PRIu64, session->cfg_txn_id, -- session->session_id); -+ _dbg("Created new config txn-id: %Lu for session-id: %Lu", txn_id, -+ session->session_id); - -- ret = mgmt_txn_send_edit(session->cfg_txn_id, msg->req_id, ds_id, -- ds_ctx, rds_id, rds_ctx, lock, commit, -- msg->request_type, msg->flags, msg->operation, -- xpath, data); -- if (ret) { -- /* destroy the just created txn */ -- mgmt_destroy_txn(&session->cfg_txn_id); -- if (lock) { -- mgmt_fe_session_unlock_ds(ds_id, ds_ctx, session); -- if (commit) -- mgmt_fe_session_unlock_ds(rds_id, rds_ctx, -- session); -- } -- fe_adapter_send_error(session, msg->req_id, false, -EBUSY, -- "Failed to create a configuration transaction"); -+ /* And this is modifying the running */ -+ mgmt_txn_send_commit_config_req(txn_id, msg->req_id, can_id, can_ds, run_id, run_ds, -+ false, false, true /* implicit */, false, edit); -+ return; - } -+reply: -+ mgmt_fe_adapter_send_edit_reply(session->session_id, txn_id, msg->req_id, false, commit, -+ &edit, nb_error_to_mgmt_result(ret), errstr); - } - --/** -- * fe_adapter_handle_notify_select() - Handle an Notify Select message. -- * @session: the client session. -- * @_msg: the message data. -- * @msg_len: the length of the message data. -+/* --------------------- */ -+/* NOTIFY-SELECT Message */ -+/* --------------------- */ -+ -+/* -+ * Handle an Notify Select message - there's no reply for this message. - */ --static void fe_adapter_handle_notify_select(struct mgmt_fe_session_ctx *session, void *_msg, -+static void fe_session_handle_notify_select(struct mgmt_fe_session_ctx *session, void *_msg, - size_t msg_len) - { - struct mgmt_msg_notify_select *msg = _msg; -@@ -1162,13 +1140,12 @@ static void fe_adapter_handle_notify_select(struct mgmt_fe_session_ctx *session, - char *selstr = NULL; - uint64_t clients = 0; - uint64_t all_matched = 0, rm_clients = 0; -- uint ret = NB_OK; - - - if (msg_len >= sizeof(*msg)) { - selectors = mgmt_msg_native_strings_decode(msg, msg_len, msg->selectors); - if (!selectors) { -- fe_adapter_send_error(session, req_id, false, -EINVAL, "Invalid message"); -+ fe_session_send_error(session, req_id, false, -EINVAL, "Invalid message"); - return; - } - } -@@ -1177,7 +1154,7 @@ static void fe_adapter_handle_notify_select(struct mgmt_fe_session_ctx *session, - darr_foreach_p (selectors, sp) { - nb_nodes = nb_nodes_find(*sp); - if (!nb_nodes) { -- fe_adapter_send_error(session, req_id, false, -EINVAL, -+ fe_session_send_error(session, req_id, false, -EINVAL, - "Selector doesn't resolve to a node: %s", *sp); - darr_free_free(selectors); - return; -@@ -1236,35 +1213,22 @@ static void fe_adapter_handle_notify_select(struct mgmt_fe_session_ctx *session, - session->session_id); - goto done; - } -- if (!(clients | rm_clients)) { -+ if (!(clients | rm_clients)) - _dbg("No backends to newly notify for selectors: '%s' session-id: %Lu", selstr, - session->session_id); -- } else { -- /* -- * First send a message to set the selectors on the changed clients. -- */ -- ret = mgmt_txn_send_notify_selectors(req_id, MGMTD_SESSION_ID_NONE, -- (clients | rm_clients), -- msg->replace ? NULL : selectors); -- if (ret) { -- fe_adapter_send_error(session, req_id, false, -EINPROGRESS, -- "Failed to create a NOTIFY_SELECT transaction"); -- } -- } -+ else -+ /* Send a message to set the selectors on the changed clients */ -+ mgmt_txn_send_notify_selectors(req_id, MGMTD_SESSION_ID_NONE, -+ (clients | rm_clients), -+ msg->replace ? NULL : selectors); - -- if (ret != NB_OK || !all_matched || !selectors) -+ if (!all_matched || !selectors) - goto done; - - _dbg("Created new push for session-id: %Lu", session->session_id); - -- /* -- * Send a second message requesting a full state dump -- */ -- ret = mgmt_txn_send_notify_selectors(req_id, session->session_id, all_matched, selectors); -- if (ret) { -- fe_adapter_send_error(session, req_id, false, -EINPROGRESS, -- "Failed to create a NOTIFY_SELECT transaction"); -- } -+ /* Send a second message requesting a full state dump for the session */ -+ mgmt_txn_send_notify_selectors(req_id, session->session_id, all_matched, selectors); - done: - if (session->notify_xpaths != selectors) - darr_free(selectors); -@@ -1272,33 +1236,82 @@ done: - XFREE(MTYPE_TMP, selstr); - } - --/** -- * fe_adapter_handle_rpc() - Handle an RPC message from an FE client. -- * @session: the client session. -- * @_msg: the message data. -- * @msg_len: the length of the message data. -- */ --static void fe_adapter_handle_rpc(struct mgmt_fe_session_ctx *session, void *_msg, size_t msg_len) -+/* ----------- */ -+/* RPC Message */ -+/* ----------- */ -+ -+static int fe_session_send_rpc_reply(struct mgmt_fe_session_ctx *session, uint64_t req_id, -+ uint8_t result_type, const struct lyd_node *result) -+{ -+ struct mgmt_msg_rpc_reply *msg; -+ uint8_t **darrp = NULL; -+ int ret; -+ -+ msg = mgmt_msg_native_alloc_msg(struct mgmt_msg_rpc_reply, 0, MTYPE_MSG_NATIVE_RPC_REPLY); -+ msg->refer_id = session->session_id; -+ msg->req_id = req_id; -+ msg->code = MGMT_MSG_CODE_RPC_REPLY; -+ msg->result_type = result_type; -+ -+ if (result) { -+ darrp = mgmt_msg_native_get_darrp(msg); -+ ret = yang_print_tree_append(darrp, result, result_type, 0); -+ if (ret != LY_SUCCESS) { -+ _log_err("Error building rpc-reply result for client %s session-id %" PRIu64 -+ " req-id %" PRIu64 " result type %u", -+ session->adapter->name, session->session_id, req_id, result_type); -+ goto done; -+ } -+ } -+ -+ _dbg("Sending rpc-reply from adapter %s to session-id %" PRIu64 " req-id %" PRIu64 -+ " len %u", -+ session->adapter->name, session->session_id, req_id, mgmt_msg_native_get_msg_len(msg)); -+ -+ ret = fe_adapter_send_msg(session->adapter, msg, mgmt_msg_native_get_msg_len(msg), false); -+done: -+ mgmt_msg_native_free_msg(msg); -+ -+ return ret; -+} -+ -+void mgmt_fe_adapter_send_rpc_reply(uint64_t session_id, uint64_t txn_id, uint64_t req_id, -+ LYD_FORMAT result_type, const struct lyd_node *result) -+{ -+ struct mgmt_fe_session_ctx *session; -+ -+ session = fe_session_lookup(session_id); -+ if (!session) -+ return; -+ -+ /* XXXchopps why do we care about this? Why not allow multple? */ -+ assert(session->txn_id == txn_id); -+ -+ if (fe_session_send_rpc_reply(session, req_id, result_type, result)) -+ fe_session_send_error(session, req_id, false, -EIO, "Failed sending RPC reply"); -+ -+ mgmt_destroy_txn(&session->txn_id); -+} -+ -+static void fe_session_handle_rpc(struct mgmt_fe_session_ctx *session, void *_msg, size_t msg_len) - { - struct mgmt_msg_rpc *msg = _msg; - const struct lysc_node *snode; - const char *xpath, *data; - uint64_t req_id = msg->req_id; - uint64_t clients; -- int ret; - - _dbg("Received RPC request from client %s for session-id %" PRIu64 " req-id %" PRIu64, - session->adapter->name, session->session_id, msg->req_id); - - xpath = mgmt_msg_native_xpath_data_decode(msg, msg_len, data); - if (!xpath) { -- fe_adapter_send_error(session, req_id, false, -EINVAL, -- "Invalid message"); -+ fe_session_send_error(session, req_id, false, -EINVAL, "Invalid message"); - return; - } - - if (session->txn_id != MGMTD_TXN_ID_NONE) { -- fe_adapter_send_error(session, req_id, false, -EINPROGRESS, -+ fe_session_send_error(session, req_id, false, -EINPROGRESS, - "Transaction in progress txn-id: %" PRIu64 - " for session-id: %" PRIu64, - session->txn_id, session->session_id); -@@ -1307,13 +1320,12 @@ static void fe_adapter_handle_rpc(struct mgmt_fe_session_ctx *session, void *_ms - - snode = lys_find_path(ly_native_ctx, NULL, xpath, 0); - if (!snode) { -- fe_adapter_send_error(session, req_id, false, -ENOENT, -- "No such path: %s", xpath); -+ fe_session_send_error(session, req_id, false, -ENOENT, "No such path: %s", xpath); - return; - } - - if (snode->nodetype != LYS_RPC && snode->nodetype != LYS_ACTION) { -- fe_adapter_send_error(session, req_id, false, -EINVAL, -+ fe_session_send_error(session, req_id, false, -EINVAL, - "Not an RPC or action path: %s", xpath); - return; - } -@@ -1324,7 +1336,7 @@ static void fe_adapter_handle_rpc(struct mgmt_fe_session_ctx *session, void *_ms - _dbg("No backends implement xpath: %s for txn-id: %" PRIu64 " session-id: %" PRIu64, - xpath, session->txn_id, session->session_id); - -- fe_adapter_send_error(session, req_id, false, -ENOENT, -+ fe_session_send_error(session, req_id, false, -ENOENT, - "No backends implement xpath: %s", xpath); - return; - } -@@ -1333,7 +1345,7 @@ static void fe_adapter_handle_rpc(struct mgmt_fe_session_ctx *session, void *_ms - session->txn_id = mgmt_create_txn(session->session_id, - MGMTD_TXN_TYPE_RPC); - if (session->txn_id == MGMTD_SESSION_ID_NONE) { -- fe_adapter_send_error(session, req_id, false, -EINPROGRESS, -+ fe_session_send_error(session, req_id, false, -EINPROGRESS, - "Failed to create an RPC transaction"); - return; - } -@@ -1342,27 +1354,140 @@ static void fe_adapter_handle_rpc(struct mgmt_fe_session_ctx *session, void *_ms - session->session_id); - - /* Create an RPC request under the transaction */ -- ret = mgmt_txn_send_rpc(session->txn_id, req_id, clients, -- msg->request_type, xpath, data, -- mgmt_msg_native_data_len_decode(msg, msg_len)); -- if (ret) { -- /* destroy the just created txn */ -- mgmt_destroy_txn(&session->txn_id); -- fe_adapter_send_error(session, req_id, false, -EINPROGRESS, -- "Failed to create an RPC transaction"); -+ mgmt_txn_send_rpc(session->txn_id, req_id, clients, msg->request_type, xpath, data, -+ mgmt_msg_native_data_len_decode(msg, msg_len)); -+} -+ -+/* -------------------- */ -+/* New Session Requests */ -+/* -------------------- */ -+ -+static int fe_adapter_conn_send_error(struct msg_conn *conn, uint64_t session_id, uint64_t req_id, -+ bool short_circuit_ok, int16_t error, const char *errfmt, -+ ...) PRINTFRR(6, 7); -+static int fe_adapter_conn_send_error(struct msg_conn *conn, uint64_t session_id, uint64_t req_id, -+ bool short_circuit_ok, int16_t error, const char *errfmt, ...) -+{ -+ va_list ap; -+ int ret; -+ -+ va_start(ap, errfmt); -+ -+ ret = vmgmt_msg_native_send_error(conn, session_id, req_id, short_circuit_ok, error, -+ errfmt, ap); -+ va_end(ap); -+ -+ return ret; -+} -+ -+static int fe_adapter_send_session_reply(struct mgmt_fe_client_adapter *adapter, -+ uint64_t session_id, uint64_t req_id, bool created) -+{ -+ struct mgmt_msg_session_reply *msg; -+ bool scok = adapter->conn->is_short_circuit; -+ int ret; -+ -+ msg = mgmt_msg_native_alloc_msg(struct mgmt_msg_session_reply, 0, -+ MTYPE_MSG_NATIVE_SESSION_REPLY); -+ msg->refer_id = session_id; -+ msg->req_id = req_id; -+ msg->code = MGMT_MSG_CODE_SESSION_REPLY; -+ msg->created = created; -+ -+ _dbg("Sending session-reply from adapter %s to session-id %Lu req-id %Lu created %u scok %u", -+ adapter->name, session_id, req_id, created, scok); -+ -+ ret = fe_adapter_send_msg(adapter, msg, mgmt_msg_native_get_msg_len(msg), scok); -+ mgmt_msg_native_free_msg(msg); -+ -+ return ret; -+} -+ -+/* -+ * Handle a session-req message from a FE client. -+ */ -+static void fe_adapter_handle_session_req(struct mgmt_fe_client_adapter *adapter, void *_msg, -+ size_t msg_len) -+{ -+ const struct mgmt_msg_session_req *msg = _msg; -+ struct mgmt_fe_session_ctx *session; -+ bool scok = adapter->conn->is_short_circuit; -+ uint64_t client_id; -+ -+ _dbg("Got session-req is create %u req-id %Lu for refer-id %Lu notify-fmt %u from '%s'", -+ msg->refer_id == 0, msg->req_id, msg->refer_id, msg->notify_format, adapter->name); -+ -+ /* -+ * It's important that any error has its refer_id set to 0 for create -+ * case and set to the passed in msg->refer_id otherwise (the destroy -+ * case). For non-error return pass the session_id for create or destroy. -+ */ -+ -+ if (msg->refer_id) { -+ uint64_t session_id = msg->refer_id; -+ -+ session = fe_session_lookup(session_id); -+ if (!session) { -+ fe_adapter_conn_send_error(adapter->conn, session_id, msg->req_id, scok, -+ EINVAL, -+ "No session to delete for session-id: %" PRIu64, -+ session_id); -+ return; -+ } -+ fe_adapter_send_session_reply(adapter, session_id, msg->req_id, false); -+ fe_session_cleanup(&session); -+ return; -+ } -+ -+ client_id = msg->req_id; -+ -+ /* Default notification format */ -+ if (msg->notify_format && msg->notify_format > MGMT_MSG_FORMAT_LAST) { -+ fe_adapter_conn_send_error(adapter->conn, 0, msg->req_id, scok, EINVAL, -+ "Unrecognized notify format: %u", msg->notify_format); -+ return; -+ } -+ -+ /* See if we have a client name to register */ -+ if (msg_len > sizeof(*msg)) { -+ if (!MGMT_MSG_VALIDATE_NUL_TERM(msg, msg_len)) { -+ fe_adapter_conn_send_error(adapter->conn, 0, msg->req_id, scok, EINVAL, -+ "Corrupt session-req msg from client-id: %Lu", -+ client_id); -+ return; -+ } -+ _dbg("Set client-name to '%s'", msg->client_name); -+ strlcpy(adapter->name, msg->client_name, sizeof(adapter->name)); - } -+ -+ session = fe_session_create(adapter, msg->notify_format ?: DEFAULT_NOTIFY_FORMAT, -+ client_id); -+ fe_adapter_send_session_reply(adapter, session->session_id, msg->req_id, true); - } - --/** -- * Handle a native encoded message from the FE client. -+/* -+ * Handle a message from the FE client. - */ --static void fe_adapter_handle_native_msg(struct mgmt_fe_client_adapter *adapter, -- struct mgmt_msg_header *msg, -- size_t msg_len) -+static void fe_adapter_process_msg(uint8_t version, uint8_t *data, size_t msg_len, -+ struct msg_conn *conn) - { -+ struct mgmt_fe_client_adapter *adapter = conn->user; - struct mgmt_fe_session_ctx *session; -- size_t min_size = mgmt_msg_get_min_size(msg->code); -+ struct mgmt_msg_header *msg = (typeof(msg))data; -+ size_t min_size; -+ -+ if (version != MGMT_MSG_VERSION_NATIVE) { -+ _log_err("Protobuf not supported for frontend messages (adapter: %s)", -+ adapter->name); -+ return; -+ } -+ -+ if (msg_len < sizeof(*msg)) { -+ _log_err("native message to adapter %s too short %zu", adapter->name, msg_len); -+ return; -+ } - -+ min_size = mgmt_msg_get_min_size(msg->code); - if (msg_len < min_size) { - if (!min_size) - _log_err("adapter %s: recv msg refer-id %" PRIu64 " unknown message type %u", -@@ -1374,6 +1499,10 @@ static void fe_adapter_handle_native_msg(struct mgmt_fe_client_adapter *adapter, - return; - } - -+ /* -+ * Handle new session requests up front. -+ */ -+ - if (msg->code == MGMT_MSG_CODE_SESSION_REQ) { - _dbg("adapter %s: session-id %Lu received SESSION_REQ message", adapter->name, - msg->refer_id); -@@ -1381,7 +1510,11 @@ static void fe_adapter_handle_native_msg(struct mgmt_fe_client_adapter *adapter, - return; - } - -- session = mgmt_session_id2ctx(msg->refer_id); -+ /* -+ * Get session and handle all other message types. -+ */ -+ -+ session = fe_session_lookup(msg->refer_id); - if (!session) { - _log_err("adapter %s: recv msg unknown session-id %" PRIu64, adapter->name, - msg->refer_id); -@@ -1393,32 +1526,32 @@ static void fe_adapter_handle_native_msg(struct mgmt_fe_client_adapter *adapter, - case MGMT_MSG_CODE_COMMIT: - _dbg("adapter %s: session-id %Lu received COMMIT message", adapter->name, - msg->refer_id); -- fe_adapter_handle_commit(session, msg, msg_len); -+ fe_session_handle_commit(session, msg, msg_len); - break; - case MGMT_MSG_CODE_EDIT: - _dbg("adapter %s: session-id %" PRIu64 " received EDIT message", adapter->name, - msg->refer_id); -- fe_adapter_handle_edit(session, msg, msg_len); -+ fe_session_handle_edit(session, msg, msg_len); - break; - case MGMT_MSG_CODE_LOCK: - _dbg("adapter %s: session-id %Lu received LOCK message", adapter->name, - msg->refer_id); -- fe_adapter_handle_lock(session, msg, msg_len); -+ fe_session_handle_lock(session, msg, msg_len); - break; - case MGMT_MSG_CODE_NOTIFY_SELECT: - _dbg("adapter %s: session-id %" PRIu64 " received NOTIFY_SELECT message", - adapter->name, msg->refer_id); -- fe_adapter_handle_notify_select(session, msg, msg_len); -+ fe_session_handle_notify_select(session, msg, msg_len); - break; - case MGMT_MSG_CODE_GET_DATA: - _dbg("adapter %s: session-id %" PRIu64 " received GET_DATA message", adapter->name, - msg->refer_id); -- fe_adapter_handle_get_data(session, msg, msg_len); -+ fe_session_handle_get_data(session, msg, msg_len); - break; - case MGMT_MSG_CODE_RPC: - _dbg("adapter %s: session-id %" PRIu64 " received RPC message", adapter->name, - msg->refer_id); -- fe_adapter_handle_rpc(session, msg, msg_len); -+ fe_session_handle_rpc(session, msg, msg_len); - break; - default: - _log_err("unknown native message session-id %" PRIu64 " req-id %" PRIu64 -@@ -1428,25 +1561,9 @@ static void fe_adapter_handle_native_msg(struct mgmt_fe_client_adapter *adapter, - } - } - -- --static void mgmt_fe_adapter_process_msg(uint8_t version, uint8_t *data, -- size_t len, struct msg_conn *conn) --{ -- struct mgmt_fe_client_adapter *adapter = conn->user; -- -- if (version == MGMT_MSG_VERSION_NATIVE) { -- struct mgmt_msg_header *msg = (typeof(msg))data; -- -- if (len >= sizeof(*msg)) -- fe_adapter_handle_native_msg(adapter, msg, len); -- else -- _log_err("native message to adapter %s too short %zu", adapter->name, len); -- return; -- } -- -- _log_err("Protobuf not supported for frontend messages (adapter: %s)", adapter->name); --} -- -+/* ============================= */ -+/* Async Notification Processing */ -+/* ============================= */ - - static struct mgmt_msg_notify_data *assure_notify_msg_cache(const struct mgmt_msg_notify_data *msg, - size_t msglen, struct lyd_node **tree, -@@ -1559,7 +1676,7 @@ void mgmt_fe_adapter_send_notify(struct mgmt_msg_notify_data *msg, size_t msglen - * notifications it first gets a dump of all the subscribed state. - */ - if (msg->refer_id != MGMTD_SESSION_ID_NONE) { -- session = mgmt_session_id2ctx(msg->refer_id); -+ session = fe_session_lookup(msg->refer_id); - if (!session || !session->notify_xpaths) { - _dbg("No session listening for notify 'get' data: %Lu", msg->refer_id); - return; -@@ -1567,7 +1684,7 @@ void mgmt_fe_adapter_send_notify(struct mgmt_msg_notify_data *msg, size_t msglen - - send_msg = assure_notify_msg_cache(msg, msglen, &tree, session->notify_format, - cache); -- (void)fe_adapter_send_native_msg(session->adapter, send_msg, msglen, false); -+ (void)fe_adapter_send_msg(session->adapter, send_msg, msglen, false); - goto done; - } - -@@ -1602,7 +1719,7 @@ void mgmt_fe_adapter_send_notify(struct mgmt_msg_notify_data *msg, size_t msglen - send_msg = assure_notify_msg_cache(msg, msglen, &tree, sessions[i]->notify_format, - cache); - send_msg->refer_id = sessions[i]->session_id; -- (void)fe_adapter_send_native_msg(sessions[i]->adapter, send_msg, msglen, false); -+ (void)fe_adapter_send_msg(sessions[i]->adapter, send_msg, msglen, false); - } - darr_free(sessions); - -@@ -1612,14 +1729,14 @@ void mgmt_fe_adapter_send_notify(struct mgmt_msg_notify_data *msg, size_t msglen - * scheme). - */ - if (CHECK_FLAG(nb_node->snode->nodetype, LYS_NOTIF)) { -- FOREACH_ADAPTER_IN_LIST (adapter) { -- FOREACH_SESSION_IN_LIST (adapter, session) { -+ LIST_FOREACH (adapter, &fe_adapters, link) { -+ LIST_FOREACH (session, &adapter->sessions, link) { - if (session->notify_xpaths) - continue; - send_msg = assure_notify_msg_cache(msg, msglen, &tree, - session->notify_format, cache); - send_msg->refer_id = session->session_id; -- (void)fe_adapter_send_native_msg(adapter, send_msg, msglen, false); -+ (void)fe_adapter_send_msg(adapter, send_msg, msglen, false); - } - } - } -@@ -1630,275 +1747,37 @@ done: - cleanup_notify_msg_cache(msg, &tree, cache); - } - --void mgmt_fe_adapter_lock(struct mgmt_fe_client_adapter *adapter) --{ -- adapter->refcount++; --} -- --void mgmt_fe_adapter_unlock(struct mgmt_fe_client_adapter **adapter) --{ -- struct mgmt_fe_client_adapter *a = *adapter; -- -- assert(a && a->refcount); -- -- if (!--a->refcount) { -- mgmt_fe_adapters_del(&mgmt_fe_adapters, a); -- msg_server_conn_delete(a->conn); -- XFREE(MTYPE_MGMTD_FE_ADPATER, a); -- } -- *adapter = NULL; --} -- --/* -- * Initialize the FE adapter module -- */ --void mgmt_fe_adapter_init(struct event_loop *tm) --{ -- char server_path[MAXPATHLEN]; -- -- assert(!mgmt_loop); -- mgmt_loop = tm; -- -- mgmt_fe_adapters_init(&mgmt_fe_adapters); -- -- assert(!mgmt_fe_sessions); -- mgmt_fe_sessions = -- hash_create(mgmt_fe_session_hash_key, mgmt_fe_session_hash_cmp, -- "MGMT Frontend Sessions"); -- -- ns_string_init(&mgmt_fe_ns_strings); -- -- snprintf(server_path, sizeof(server_path), MGMTD_FE_SOCK_NAME); -- -- if (msg_server_init(&mgmt_fe_server, server_path, tm, -- mgmt_fe_create_adapter, "frontend", &mgmt_debug_fe)) { -- zlog_err("cannot initialize frontend server"); -- exit(1); -- } --} -- --static FRR_NORETURN void mgmt_fe_abort_if_session(void *data) --{ -- struct mgmt_fe_session_ctx *session = data; -- -- _log_err("found orphaned session id %" PRIu64 " client id %" PRIu64 " adapter %s", -- session->session_id, session->client_id, -- session->adapter ? session->adapter->name : "NULL"); -- abort(); --} -- --/* -- * Destroy the FE adapter module -- */ --void mgmt_fe_adapter_destroy(void) --{ -- struct mgmt_fe_client_adapter *adapter; -- -- msg_server_cleanup(&mgmt_fe_server); -- -- -- /* Deleting the adapters will delete all the sessions */ -- FOREACH_ADAPTER_IN_LIST (adapter) -- mgmt_fe_adapter_delete(adapter); -- -- mgmt_fe_free_ns_strings(&mgmt_fe_ns_strings); -- -- hash_clean_and_free(&mgmt_fe_sessions, mgmt_fe_abort_if_session); --} -- --/* -- * The server accepted a new connection -- */ --struct msg_conn *mgmt_fe_create_adapter(int conn_fd, union sockunion *from) --{ -- struct mgmt_fe_client_adapter *adapter = NULL; -- -- adapter = mgmt_fe_find_adapter_by_fd(conn_fd); -- if (!adapter) { -- adapter = XCALLOC(MTYPE_MGMTD_FE_ADPATER, sizeof(struct mgmt_fe_client_adapter)); -- snprintf(adapter->name, sizeof(adapter->name), "Unknown-FD-%d", -- conn_fd); -- -- mgmt_fe_sessions_init(&adapter->fe_sessions); -- mgmt_fe_adapter_lock(adapter); -- mgmt_fe_adapters_add_tail(&mgmt_fe_adapters, adapter); -- -- adapter->conn = msg_server_conn_create( -- mgmt_loop, conn_fd, mgmt_fe_adapter_notify_disconnect, -- mgmt_fe_adapter_process_msg, MGMTD_FE_MAX_NUM_MSG_PROC, -- MGMTD_FE_MAX_NUM_MSG_WRITE, MGMTD_FE_MAX_MSG_LEN, -- adapter, "FE-adapter"); -- -- adapter->conn->debug = DEBUG_MODE_CHECK(&mgmt_debug_fe, -- DEBUG_MODE_ALL); -- -- adapter->cmt_stats.min_tm = ULONG_MAX; -- _dbg("Added new MGMTD Frontend adapter '%s'", adapter->name); -- } -- return adapter->conn; --} -- --int mgmt_fe_send_commit_cfg_reply(uint64_t session_id, uint64_t txn_id, enum mgmt_ds_id src_ds_id, -- enum mgmt_ds_id dst_ds_id, uint64_t req_id, bool validate_only, -- bool unlock, enum mgmt_result result, const char *error_if_any) --{ -- struct mgmt_fe_session_ctx *session; -- uint8_t action; -- -- session = mgmt_session_id2ctx(session_id); -- if (!session || session->cfg_txn_id != txn_id) -- return -1; -- -- if (validate_only) -- action = MGMT_MSG_COMMIT_VALIDATE; -- else -- action = MGMT_MSG_COMMIT_APPLY; -- -- /* Cleanup the CONFIG transaction associated with this session. */ -- if (session->cfg_txn_id && -- ((result == MGMTD_SUCCESS && !validate_only) || (result == MGMTD_NO_CFG_CHANGES))) -- mgmt_fe_session_register_event(session, MGMTD_FE_SESSION_CFG_TXN_CLNUP); -- -- if (mm->perf_stats_en) -- gettimeofday(&session->adapter->cmt_stats.last_end, NULL); -- mgmt_fe_session_compute_commit_timers(&session->adapter->cmt_stats); -- -- if (result != MGMTD_SUCCESS && result != MGMTD_NO_CFG_CHANGES) -- return fe_adapter_send_error( -- session, req_id, false, EINVAL /* convert result */, -- "commit failed session-id %Lu on %s req-id %Lu source-ds: %s target-ds: %s validate-only: %u: reason: '%s'", -- session->session_id, session->adapter->name, req_id, -- mgmt_ds_id2name(src_ds_id), mgmt_ds_id2name(dst_ds_id), validate_only, -- error_if_any ?: ""); -- fe_adapter_send_commit_reply(session, req_id, src_ds_id, dst_ds_id, action, unlock); -- return 0; --} -- --int mgmt_fe_adapter_send_tree_data(uint64_t session_id, uint64_t txn_id, -- uint64_t req_id, LYD_FORMAT result_type, -- uint32_t wd_options, -- const struct lyd_node *tree, -- int partial_error, bool short_circuit_ok) --{ -- struct mgmt_fe_session_ctx *session; -- int ret; -- -- session = mgmt_session_id2ctx(session_id); -- if (!session || session->txn_id != txn_id) -- return -1; -- -- ret = fe_adapter_send_tree_data(session, req_id, short_circuit_ok, -- result_type, wd_options, tree, -- partial_error); -- -- mgmt_destroy_txn(&session->txn_id); -- -- return ret; --} -- --int mgmt_fe_adapter_send_rpc_reply(uint64_t session_id, uint64_t txn_id, -- uint64_t req_id, LYD_FORMAT result_type, -- const struct lyd_node *result) --{ -- struct mgmt_fe_session_ctx *session; -- int ret; -- -- session = mgmt_session_id2ctx(session_id); -- if (!session || session->txn_id != txn_id) -- return -1; - -- ret = fe_adapter_send_rpc_reply(session, req_id, result_type, result); -+/* =========================== */ -+/* Frontend VTY and Statistics */ -+/* =========================== */ - -- mgmt_destroy_txn(&session->txn_id); -- -- return ret; --} -- --int mgmt_fe_adapter_send_edit_reply(uint64_t session_id, uint64_t txn_id, -- uint64_t req_id, bool unlock, bool commit, -- bool created, const char *xpath, -- int16_t error, const char *errstr) -+static void fe_session_compute_commit_timers(struct mgmt_commit_stats *cmt_stats) - { -- struct mgmt_fe_session_ctx *session; -- enum mgmt_ds_id ds_id, rds_id; -- struct mgmt_ds_ctx *ds_ctx, *rds_ctx; -- int ret; -- -- session = mgmt_session_id2ctx(session_id); -- if (!session || session->cfg_txn_id != txn_id) -- return -1; -- -- if (session->cfg_txn_id != MGMTD_TXN_ID_NONE && commit) -- mgmt_fe_session_register_event(session, -- MGMTD_FE_SESSION_CFG_TXN_CLNUP); -- -- if (unlock) { -- ds_id = MGMTD_DS_CANDIDATE; -- ds_ctx = mgmt_ds_get_ctx_by_id(mm, ds_id); -- assert(ds_ctx); -- -- mgmt_fe_session_unlock_ds(ds_id, ds_ctx, session); -- -- if (commit) { -- rds_id = MGMTD_DS_RUNNING; -- rds_ctx = mgmt_ds_get_ctx_by_id(mm, rds_id); -- assert(rds_ctx); -- -- mgmt_fe_session_unlock_ds(rds_id, rds_ctx, session); -- } -+ cmt_stats->last_exec_tm = timeval_elapsed(cmt_stats->last_end, cmt_stats->last_start); -+ if (cmt_stats->last_exec_tm > cmt_stats->max_tm) { -+ cmt_stats->max_tm = cmt_stats->last_exec_tm; -+ cmt_stats->max_batch_cnt = cmt_stats->last_batch_cnt; - } - -- if (error != 0 && error != -EALREADY) -- ret = fe_adapter_send_error(session, req_id, false, error, "%s", -- errstr); -- else -- ret = fe_adapter_send_edit_reply(session, req_id, created, -- !error, xpath, errstr); -- -- if (session->cfg_txn_id != MGMTD_TXN_ID_NONE && !commit) -- mgmt_destroy_txn(&session->cfg_txn_id); -- -- return ret; --} -- --/** -- * Send an error back to the FE client and cleanup any in-progress txn. -- */ --int mgmt_fe_adapter_txn_error(uint64_t txn_id, uint64_t req_id, -- bool short_circuit_ok, int16_t error, -- const char *errstr) --{ -- struct mgmt_fe_session_ctx *session; -- int ret; -- -- session = fe_adapter_session_by_txn_id(txn_id); -- if (!session) { -- _log_err("failed sending error for txn-id %" PRIu64 " session not found", txn_id); -- return -ENOENT; -+ if (cmt_stats->last_exec_tm < cmt_stats->min_tm) { -+ cmt_stats->min_tm = cmt_stats->last_exec_tm; -+ cmt_stats->min_batch_cnt = cmt_stats->last_batch_cnt; - } -- -- ret = fe_adapter_send_error(session, req_id, false, error, "%s", errstr); -- -- mgmt_destroy_txn(&session->txn_id); -- -- return ret; - } - --struct mgmt_commit_stats * --mgmt_fe_get_session_commit_stats(uint64_t session_id) -+struct mgmt_commit_stats *mgmt_fe_get_session_commit_stats(uint64_t session_id) - { - struct mgmt_fe_session_ctx *session; - -- session = mgmt_session_id2ctx(session_id); -+ session = fe_session_lookup(session_id); - if (!session || !session->adapter) - return NULL; - - return &session->adapter->cmt_stats; - } - --static void --mgmt_fe_adapter_cmt_stats_write(struct vty *vty, -- struct mgmt_fe_client_adapter *adapter) -+static void _cmt_stats_write(struct vty *vty, struct mgmt_fe_client_adapter *adapter) - { - char buf[MGMT_LONG_TIME_MAX_LEN]; - -@@ -1968,17 +1847,20 @@ void mgmt_fe_adapter_status_write(struct vty *vty, bool detail) - struct mgmt_fe_session_ctx *session; - enum mgmt_ds_id ds_id; - bool locked = false; -+ uint acount = 0; -+ uint scount; - - vty_out(vty, "MGMTD Frontend Adpaters\n"); - -- FOREACH_ADAPTER_IN_LIST (adapter) { -+ LIST_FOREACH (adapter, &fe_adapters, link) { - vty_out(vty, " Client: \t\t\t\t%s\n", adapter->name); - vty_out(vty, " Conn-FD: \t\t\t\t%d\n", adapter->conn->fd); - if (detail) { -- mgmt_fe_adapter_cmt_stats_write(vty, adapter); -+ _cmt_stats_write(vty, adapter); - } -+ scount = 0; - vty_out(vty, " Sessions\n"); -- FOREACH_SESSION_IN_LIST (adapter, session) { -+ LIST_FOREACH (session, &adapter->sessions, link) { - vty_out(vty, " Session: \t\t\t\t%p\n", session); - vty_out(vty, " Client-Id: \t\t\t%" PRIu64 "\n", - session->client_id); -@@ -1994,9 +1876,9 @@ void mgmt_fe_adapter_status_write(struct vty *vty, bool detail) - } - if (!locked) - vty_out(vty, " None\n"); -+ scount++; - } -- vty_out(vty, " Total-Sessions: \t\t\t%d\n", -- (int)mgmt_fe_sessions_count(&adapter->fe_sessions)); -+ vty_out(vty, " Total-Sessions: \t\t\t%u\n", scount); - vty_out(vty, " Msg-Recvd: \t\t\t\t%" PRIu64 "\n", - adapter->conn->mstate.nrxm); - vty_out(vty, " Bytes-Recvd: \t\t\t%" PRIu64 "\n", -@@ -2006,8 +1888,7 @@ void mgmt_fe_adapter_status_write(struct vty *vty, bool detail) - vty_out(vty, " Bytes-Sent: \t\t\t%" PRIu64 "\n", - adapter->conn->mstate.ntxb); - } -- vty_out(vty, " Total: %d\n", -- (int)mgmt_fe_adapters_count(&mgmt_fe_adapters)); -+ vty_out(vty, " Total: %u\n", acount); - } - - void mgmt_fe_adapter_perf_measurement(struct vty *vty, bool config) -@@ -2018,12 +1899,127 @@ void mgmt_fe_adapter_perf_measurement(struct vty *vty, bool config) - void mgmt_fe_adapter_reset_perf_stats(struct vty *vty) - { - struct mgmt_fe_client_adapter *adapter; -- struct mgmt_fe_session_ctx *session; - -- FOREACH_ADAPTER_IN_LIST (adapter) { -- FOREACH_SESSION_IN_LIST (adapter, session) { -- memset(&adapter->cmt_stats, 0, -- sizeof(adapter->cmt_stats)); -- } -+ LIST_FOREACH (adapter, &fe_adapters, link) -+ memset(&adapter->cmt_stats, 0, sizeof(adapter->cmt_stats)); -+} -+ -+/* =================== */ -+/* Frontend Management */ -+/* =================== */ -+ -+void mgmt_fe_adapter_toggle_client_debug(bool set) -+{ -+ struct mgmt_fe_client_adapter *adapter; -+ -+ LIST_FOREACH (adapter, &fe_adapters, link) -+ adapter->conn->debug = set; -+} -+ -+static struct mgmt_fe_client_adapter *fe_adapter_lookup_by_fd(int conn_fd) -+{ -+ struct mgmt_fe_client_adapter *adapter; -+ -+ LIST_FOREACH (adapter, &fe_adapters, link) -+ if (adapter->conn->fd == conn_fd) -+ return adapter; -+ return NULL; -+} -+ -+static void fe_adapter_delete(struct mgmt_fe_client_adapter *adapter) -+{ -+ struct mgmt_fe_session_ctx *session, *next; -+ -+ _dbg("Deleting client adapter '%s'", adapter->name); -+ -+ /* TODO: notify about client disconnect for appropriate cleanup */ -+ LIST_FOREACH_SAFE (session, &adapter->sessions, link, next) -+ fe_session_cleanup(&session); -+ LIST_REMOVE(adapter, link); -+ msg_server_conn_delete(adapter->conn); -+ XFREE(MTYPE_MGMTD_FE_ADPATER, adapter); -+} -+ -+static int fe_adapter_notify_disconnect(struct msg_conn *conn) -+{ -+ struct mgmt_fe_client_adapter *adapter = conn->user; -+ -+ _dbg("notify disconnect for client adapter '%s'", adapter->name); -+ -+ fe_adapter_delete(adapter); -+ -+ return 0; -+} -+ -+/* -+ * The server accepted a new connection -+ */ -+static struct msg_conn *fe_adapter_create(int conn_fd, union sockunion *from) -+{ -+ struct mgmt_fe_client_adapter *adapter = NULL; -+ -+ adapter = fe_adapter_lookup_by_fd(conn_fd); -+ if (!adapter) { -+ adapter = XCALLOC(MTYPE_MGMTD_FE_ADPATER, sizeof(struct mgmt_fe_client_adapter)); -+ snprintf(adapter->name, sizeof(adapter->name), "Unknown-FD-%d", conn_fd); -+ -+ LIST_INSERT_HEAD(&fe_adapters, adapter, link); -+ -+ adapter->conn = -+ msg_server_conn_create(mgmt_loop, conn_fd, fe_adapter_notify_disconnect, -+ fe_adapter_process_msg, MGMTD_FE_MAX_NUM_MSG_PROC, -+ MGMTD_FE_MAX_NUM_MSG_WRITE, MGMTD_FE_MAX_MSG_LEN, -+ adapter, "FE-adapter"); -+ -+ adapter->conn->debug = DEBUG_MODE_CHECK(&mgmt_debug_fe, DEBUG_MODE_ALL); -+ -+ adapter->cmt_stats.min_tm = ULONG_MAX; -+ _dbg("Added new MGMTD Frontend adapter '%s'", adapter->name); -+ } -+ return adapter->conn; -+} -+ -+ -+/* -+ * Initialize the FE adapter module -+ */ -+void mgmt_fe_adapter_init(struct event_loop *tm) -+{ -+ char server_path[MAXPATHLEN]; -+ -+ assert(!mgmt_loop); -+ mgmt_loop = tm; -+ -+ assert(!mgmt_fe_sessions); -+ mgmt_fe_sessions = hash_create(fe_session_hash_key, fe_session_hash_cmp, -+ "MGMT Frontend Sessions"); -+ -+ ns_string_init(&mgmt_fe_ns_strings); -+ -+ snprintf(server_path, sizeof(server_path), MGMTD_FE_SOCK_NAME); -+ -+ if (msg_server_init(&mgmt_fe_server, server_path, tm, fe_adapter_create, "frontend", -+ &mgmt_debug_fe)) { -+ zlog_err("cannot initialize frontend server"); -+ exit(1); - } - } -+ -+/* -+ * Destroy the FE adapter module -+ */ -+void mgmt_fe_adapter_destroy(void) -+{ -+ struct mgmt_fe_client_adapter *adapter, *next; -+ -+ msg_server_cleanup(&mgmt_fe_server); -+ -+ -+ /* Deleting the adapters will delete all the sessions */ -+ LIST_FOREACH_SAFE (adapter, &fe_adapters, link, next) -+ fe_adapter_delete(adapter); -+ -+ mgmt_fe_free_ns_strings(&mgmt_fe_ns_strings); -+ -+ hash_free(mgmt_fe_sessions); -+} -diff --git a/mgmtd/mgmt_fe_adapter.h b/mgmtd/mgmt_fe_adapter.h -index c0ba95c5e7..bd3b4bad91 100644 ---- a/mgmtd/mgmt_fe_adapter.h -+++ b/mgmtd/mgmt_fe_adapter.h -@@ -14,9 +14,6 @@ - #include "mgmt_msg.h" - #include "mgmt_defines.h" - --struct mgmt_fe_client_adapter; --struct mgmt_master; -- - struct mgmt_commit_stats { - struct timeval last_start; - struct timeval prep_cfg_start; -@@ -36,44 +33,24 @@ struct mgmt_commit_stats { - unsigned long commit_cnt; - }; - --PREDECL_LIST(mgmt_fe_sessions); -- --PREDECL_LIST(mgmt_fe_adapters); -- --struct mgmt_fe_client_adapter { -- struct msg_conn *conn; -- char name[MGMTD_CLIENT_NAME_MAX_LEN]; -- -- /* List of sessions created and being maintained for this client. */ -- struct mgmt_fe_sessions_head fe_sessions; -- -- int refcount; -- struct mgmt_commit_stats cmt_stats; -- -- struct mgmt_fe_adapters_item list_linkage; -+struct mgmt_edit_req { -+ char xpath_created[XPATH_MAXLEN]; -+ struct nb_config *nb_backup; -+ bool created; -+ bool unlock_candidate; -+ bool unlock_running; - }; - --DECLARE_LIST(mgmt_fe_adapters, struct mgmt_fe_client_adapter, list_linkage); -- - /* Initialise frontend adapter module */ - extern void mgmt_fe_adapter_init(struct event_loop *tm); - - /* Destroy frontend adapter module */ - extern void mgmt_fe_adapter_destroy(void); - --/* Acquire lock for frontend adapter */ --extern void mgmt_fe_adapter_lock(struct mgmt_fe_client_adapter *adapter); -- --/* Remove lock from frontend adapter */ --extern void --mgmt_fe_adapter_unlock(struct mgmt_fe_client_adapter **adapter); -- --/* Create frontend adapter */ --extern struct msg_conn *mgmt_fe_create_adapter(int conn_fd, -- union sockunion *su); -- - /* - * Send commit-config reply to the frontend client. -+ * -+ * This also cleans up and frees the transaction. - */ - extern int mgmt_fe_send_commit_cfg_reply(uint64_t session_id, uint64_t txn_id, - enum mgmt_ds_id src_ds_id, enum mgmt_ds_id dst_ds_id, -@@ -94,16 +71,11 @@ extern int mgmt_fe_send_commit_cfg_reply(uint64_t session_id, uint64_t txn_id, - * tree: the results. - * partial_error: if there were errors while gather results. - * short_circuit_ok: True if OK to short-circuit the call. -- * -- * Return: -- * the return value from the underlying send function. -- * - */ --extern int --mgmt_fe_adapter_send_tree_data(uint64_t session_id, uint64_t txn_id, -- uint64_t req_id, LYD_FORMAT result_type, -- uint32_t wd_options, const struct lyd_node *tree, -- int partial_error, bool short_circuit_ok); -+extern void mgmt_fe_adapter_send_tree_data(uint64_t session_id, uint64_t txn_id, uint64_t req_id, -+ LYD_FORMAT result_type, uint32_t wd_options, -+ const struct lyd_node *tree, int partial_error, -+ bool short_circuit_ok); - - /** - * Send RPC reply back to client. -@@ -116,14 +88,9 @@ mgmt_fe_adapter_send_tree_data(uint64_t session_id, uint64_t txn_id, - * req_id: the req id for the rpc message - * result_type: the format of the result data. - * result: the results. -- * -- * Return: -- * the return value from the underlying send function. - */ --extern int mgmt_fe_adapter_send_rpc_reply(uint64_t session_id, uint64_t txn_id, -- uint64_t req_id, -- LYD_FORMAT result_type, -- const struct lyd_node *result); -+extern void mgmt_fe_adapter_send_rpc_reply(uint64_t session_id, uint64_t txn_id, uint64_t req_id, -+ LYD_FORMAT result_type, const struct lyd_node *result); - - /** - * Send edit reply back to client. If error is not 0, a native error is sent. -@@ -136,16 +103,20 @@ extern int mgmt_fe_adapter_send_rpc_reply(uint64_t session_id, uint64_t txn_id, - * req_id: the req id for the edit message - * unlock: implicit-lock flag was set in the request - * commit: implicit-commit flag was set in the request -- * created: true if the node was just created -- * xpath: the xpath of the data node that was created/updated -- * error: >0 LY_ERR, < 0 -errno -+ * edit: the edit request info created when processing the edit -+ * error: mgmt result code - * errstr: the error string, if error is non-zero - */ --extern int mgmt_fe_adapter_send_edit_reply(uint64_t session_id, uint64_t txn_id, -- uint64_t req_id, bool unlock, -- bool commit, bool created, -- const char *xpath, int16_t error, -- const char *errstr); -+extern int mgmt_fe_adapter_send_edit_reply(uint64_t session_id, uint64_t txn_id, uint64_t req_id, -+ bool unlock, bool commit, struct mgmt_edit_req **edit, -+ enum mgmt_result error, const char *errstr); -+ -+/** -+ * mgmt_fe_adapter_send_notify() - notify FE clients of a notification. -+ * @msg: the notify message from the backend client. -+ * @msglen: the length of the notify message. -+ */ -+extern void mgmt_fe_adapter_send_notify(struct mgmt_msg_notify_data *msg, size_t msglen); - - /** - * Send an error back to the FE client using native messaging. -@@ -163,10 +134,8 @@ extern int mgmt_fe_adapter_send_edit_reply(uint64_t session_id, uint64_t txn_id, - * the return value from the underlying send function. - * - */ --extern int mgmt_fe_adapter_txn_error(uint64_t txn_id, uint64_t req_id, -- bool short_circuit_ok, int16_t error, -- const char *errstr); -- -+extern int mgmt_fe_adapter_txn_error(uint64_t txn_id, uint64_t req_id, bool short_circuit_ok, -+ int16_t error, const char *errstr); - - /** - * mgmt_fe_get_all_selectors() - Get all selectors for all frontend adapters. -diff --git a/mgmtd/mgmt_history.c b/mgmtd/mgmt_history.c -index 934748b1f2..91fae105ae 100644 ---- a/mgmtd/mgmt_history.c -+++ b/mgmtd/mgmt_history.c -@@ -216,7 +216,7 @@ static int mgmt_history_rollback_to_cmt(struct vty *vty, - - ret = mgmt_ds_lock(dst_ds_ctx, vty->mgmt_session_id); - if (ret != 0) { -- mgmt_ds_unlock(src_ds_ctx); -+ mgmt_ds_unlock(src_ds_ctx, vty->mgmt_session_id); - vty_out(vty, - "Failed to lock the DS %u for rollback Reason: %s!\n", - MGMTD_DS_RUNNING, strerror(ret)); -@@ -260,16 +260,20 @@ static int mgmt_history_rollback_to_cmt(struct vty *vty, - return 0; - - failed_unlock: -- mgmt_ds_unlock(src_ds_ctx); -- mgmt_ds_unlock(dst_ds_ctx); -+ mgmt_ds_unlock(src_ds_ctx, vty->mgmt_session_id); -+ mgmt_ds_unlock(dst_ds_ctx, vty->mgmt_session_id); - return ret; - } - - void mgmt_history_rollback_complete(bool success) - { -- vty_mgmt_resume_response(rollback_vty, -- success ? CMD_SUCCESS -- : CMD_WARNING_CONFIG_FAILED); -+ struct mgmt_ds_ctx *src_ds_ctx = mgmt_ds_get_ctx_by_id(mm, MGMTD_DS_CANDIDATE); -+ struct mgmt_ds_ctx *dst_ds_ctx = mgmt_ds_get_ctx_by_id(mm, MGMTD_DS_RUNNING); -+ -+ mgmt_ds_unlock(src_ds_ctx, rollback_vty->mgmt_session_id); -+ mgmt_ds_unlock(dst_ds_ctx, rollback_vty->mgmt_session_id); -+ -+ vty_mgmt_resume_response(rollback_vty, success ? CMD_SUCCESS : CMD_WARNING_CONFIG_FAILED); - rollback_vty = NULL; - } - -diff --git a/mgmtd/mgmt_main.c b/mgmtd/mgmt_main.c -index 0858fede2a..720286c9e0 100644 ---- a/mgmtd/mgmt_main.c -+++ b/mgmtd/mgmt_main.c -@@ -114,6 +114,8 @@ static FRR_NORETURN void mgmt_exit(int status) - /* it only makes sense for this to be called on a clean exit */ - assert(status == 0); - -+ vty_mgmt_terminate(); -+ - frr_early_fini(); - - /* stop pthreads (if any) */ -diff --git a/mgmtd/mgmt_memory.c b/mgmtd/mgmt_memory.c -index 6d07212d99..0183e4b362 100644 ---- a/mgmtd/mgmt_memory.c -+++ b/mgmtd/mgmt_memory.c -@@ -26,7 +26,4 @@ DEFINE_MTYPE(MGMTD, MGMTD_FE_ADPATER, "frontend adapter"); - DEFINE_MTYPE(MGMTD, MGMTD_FE_SESSION, "frontend session"); - DEFINE_MTYPE(MGMTD, MGMTD_TXN, "txn"); - DEFINE_MTYPE(MGMTD, MGMTD_TXN_REQ, "txn request"); --DEFINE_MTYPE(MGMTD, MGMTD_TXN_COMMCFG_REQ, "txn commit-config requests"); --DEFINE_MTYPE(MGMTD, MGMTD_TXN_GETTREE_REQ, "txn get-tree requests"); --DEFINE_MTYPE(MGMTD, MGMTD_TXN_RPC_REQ, "txn rpc requests"); - DEFINE_MTYPE(MGMTD, MGMTD_CMT_INFO, "commit info"); -diff --git a/mgmtd/mgmt_memory.h b/mgmtd/mgmt_memory.h -index 7e904e03ab..36156f7eb7 100644 ---- a/mgmtd/mgmt_memory.h -+++ b/mgmtd/mgmt_memory.h -@@ -20,8 +20,5 @@ DECLARE_MTYPE(MGMTD_FE_ADPATER); - DECLARE_MTYPE(MGMTD_FE_SESSION); - DECLARE_MTYPE(MGMTD_TXN); - DECLARE_MTYPE(MGMTD_TXN_REQ); --DECLARE_MTYPE(MGMTD_TXN_COMMCFG_REQ); --DECLARE_MTYPE(MGMTD_TXN_GETTREE_REQ); --DECLARE_MTYPE(MGMTD_TXN_RPC_REQ); - DECLARE_MTYPE(MGMTD_CMT_INFO); - #endif /* _FRR_MGMTD_MEMORY_H */ -diff --git a/mgmtd/mgmt_txn.c b/mgmtd/mgmt_txn.c -index fb98c810af..386b14c837 100644 ---- a/mgmtd/mgmt_txn.c -+++ b/mgmtd/mgmt_txn.c -@@ -4,6 +4,7 @@ - * - * Copyright (C) 2021 Vmware, Inc. - * Pushpasis Sarkar <spushpasis@vmware.com> -+ * Copyright (c) 2025, LabN Consulting, L.L.C. - */ - - #include <zebra.h> -@@ -14,119 +15,17 @@ - #include "mgmt_msg.h" - #include "mgmt_msg_native.h" - #include "mgmtd/mgmt.h" --#include "mgmtd/mgmt_memory.h" --#include "mgmtd/mgmt_txn.h" --#include "mgmtd/mgmt_be_adapter.h" -+#include "mgmtd/mgmt_txn_priv.h" - --#define _dbg(fmt, ...) DEBUGD(&mgmt_debug_txn, "TXN: %s: " fmt, __func__, ##__VA_ARGS__) --#define _log_err(fmt, ...) zlog_err("%s: ERROR: " fmt, __func__, ##__VA_ARGS__) -- --#define MGMTD_TXN_LOCK(txn) mgmt_txn_lock(txn, __FILE__, __LINE__) --#define MGMTD_TXN_UNLOCK(txn, in_hash_free) mgmt_txn_unlock(txn, in_hash_free, __FILE__, __LINE__) -- --enum mgmt_txn_req_type { -- MGMTD_TXN_PROC_COMMITCFG = 1, -- MGMTD_TXN_PROC_GETTREE, -- MGMTD_TXN_PROC_RPC, --}; -- --enum mgmt_txn_frr_event { -- MGMTD_TXN_EVENT_COMMITCFG = 1, -- MGMTD_TXN_EVENT_COMMITCFG_TIMEOUT, --}; -- --PREDECL_LIST(mgmt_txn_reqs); -- --enum mgmt_commit_phase { -- MGMTD_COMMIT_PHASE_PREPARE_CFG = 0, -- MGMTD_COMMIT_PHASE_TXN_CREATE, -- MGMTD_COMMIT_PHASE_APPLY_CFG, -- MGMTD_COMMIT_PHASE_TXN_DELETE, -- MGMTD_COMMIT_PHASE_MAX --}; -- --static inline const char *mgmt_commit_phase2str(enum mgmt_commit_phase cmt_phase) --{ -- switch (cmt_phase) { -- case MGMTD_COMMIT_PHASE_PREPARE_CFG: -- return "PREP-CFG"; -- case MGMTD_COMMIT_PHASE_TXN_CREATE: -- return "CREATE-TXN"; -- case MGMTD_COMMIT_PHASE_APPLY_CFG: -- return "APPLY-CFG"; -- case MGMTD_COMMIT_PHASE_TXN_DELETE: -- return "DELETE-TXN"; -- case MGMTD_COMMIT_PHASE_MAX: -- return "Invalid/Unknown"; -- } -- -- return "Invalid/Unknown"; --} -- --struct mgmt_edit_req { -- char xpath_created[XPATH_MAXLEN]; -- bool created; -- bool unlock; --}; -- --struct mgmt_commit_cfg_req { -- enum mgmt_ds_id src_ds_id; -- struct mgmt_ds_ctx *src_ds_ctx; -- enum mgmt_ds_id dst_ds_id; -- struct mgmt_ds_ctx *dst_ds_ctx; -- uint32_t nb_txn_id; -- uint8_t validate_only : 1; -- uint8_t abort : 1; -- uint8_t implicit : 1; -- uint8_t rollback : 1; -- uint8_t init : 1; -- uint8_t unlock : 1; -- -- /* Track commit phases */ -- enum mgmt_commit_phase phase; -- -- enum mgmt_commit_phase be_phase[MGMTD_BE_CLIENT_ID_MAX]; -- -- /* -- * Additional information when the commit is triggered by native edit -- * request. -- */ -- struct mgmt_edit_req *edit; -- -- /* -- * Set of config changes to commit. This is used only -- * when changes are NOT to be determined by comparing -- * candidate and running DSs. This is typically used -- * for downloading all relevant configs for a new backend -- * client that has recently come up and connected with -- * MGMTD. -- */ -- struct nb_config_cbs *cfg_chgs; -- -- /* Used for holding changes mgmtd itself is interested in */ -- struct nb_transaction *mgmtd_nb_txn; -- -- /* -- * Details on all the Backend Clients associated with -- * this commit. -- */ -- uint64_t clients; -- -- /* -- * Config messages to send to the backends, and their action strings to -- * add to the message just prior to sending. -- */ -- struct mgmt_msg_cfg_req *cfg_msgs[MGMTD_BE_CLIENT_ID_MAX]; -- char *cfg_actions[MGMTD_BE_CLIENT_ID_MAX]; -- uint64_t num_cfg_data[MGMTD_BE_CLIENT_ID_MAX]; -- -- struct mgmt_commit_stats *cmt_stats; --}; -+/* ================= */ -+/* TXN Request Types */ -+/* ================= */ - - struct txn_req_get_tree { -+ struct txn_req req; - char *xpath; /* xpath of tree to get */ -- uint64_t sent_clients; /* Bitmask of clients sent req to */ -- uint64_t recv_clients; /* Bitmask of clients recv reply from */ -+ uint64_t clients; /* Bitmask of clients sent req to */ -+ uint64_t clients_wait; /* Bitmask of clients waiting for reply from */ - int32_t partial_error; /* an error while gather results */ - uint8_t result_type; /* LYD_FORMAT for results */ - uint8_t wd_options; /* LYD_PRINT_WD_* flags for results */ -@@ -134,807 +33,147 @@ struct txn_req_get_tree { - uint8_t simple_xpath; /* if xpath is simple */ - struct lyd_node *client_results; /* result tree from clients */ - }; -+#define as_get_tree(txn_req) \ -+ ({ \ -+ assert((txn_req)->req_type == TXN_REQ_TYPE_GETTREE); \ -+ (struct txn_req_get_tree *)(txn_req); \ -+ }) - - struct txn_req_rpc { -+ struct txn_req req; - char *xpath; /* xpath of rpc/action to invoke */ -- uint64_t sent_clients; /* Bitmask of clients sent req to */ -- uint64_t recv_clients; /* Bitmask of clients recv reply from */ -+ uint64_t clients; /* Bitmask of clients sent req to */ -+ uint64_t clients_wait; /* Bitmask of clients waiting for reply from */ - uint8_t result_type; /* LYD_FORMAT for results */ -- char *errstr; /* error string */ - struct lyd_node *client_results; /* result tree from clients */ - }; -- --struct mgmt_txn_req { -- struct mgmt_txn_ctx *txn; -- enum mgmt_txn_req_type req_type; -- uint64_t req_id; -- union { -- /* -- * XXX Make the sub-structure variants either allocated or -- * embedded -- not both; embedding only the commit_cfg variant -- * makes little sense since it is the largest of the variants -- * though! -- */ -- struct txn_req_get_tree *get_tree; -- struct txn_req_rpc *rpc; -- struct mgmt_commit_cfg_req commit_cfg; -- } req; -- -- struct mgmt_txn_reqs_item list_linkage; -+#define as_rpc(txn_req) \ -+ ({ \ -+ assert((txn_req)->req_type == TXN_REQ_TYPE_RPC); \ -+ (struct txn_req_rpc *)(txn_req); \ -+ }) -+ -+const char *txn_req_names[] = { -+ [TXN_REQ_TYPE_COMMIT] = "COMMIT", -+ [TXN_REQ_TYPE_GETTREE] = "GET-TREE", -+ [TXN_REQ_TYPE_RPC] = "RPC", - }; - --DECLARE_LIST(mgmt_txn_reqs, struct mgmt_txn_req, list_linkage); -- --#define FOREACH_TXN_REQ_IN_LIST(list, req) \ -- frr_each_safe (mgmt_txn_reqs, list, req) -- --struct mgmt_txn_ctx { -- uint64_t session_id; /* One transaction per client session */ -- uint64_t txn_id; -- enum mgmt_txn_type type; -- -- /* struct mgmt_master *mm; */ -- -- struct event *proc_comm_cfg; -- struct event *proc_get_tree; -- struct event *comm_cfg_timeout; -- struct event *get_tree_timeout; -- struct event *rpc_timeout; -- struct event *clnup; -- -- /* List of backend adapters involved in this transaction */ -- /* XXX reap this */ -- struct mgmt_txn_badapters_head be_adapters; -- -- int refcount; -- -- struct mgmt_txns_item list_linkage; -- -- /* TODO: why do we need unique lists for each type of transaction since -- * a transaction is of only 1 type? -- */ -- -- /* -- * List of pending get-tree requests. -- */ -- struct mgmt_txn_reqs_head get_tree_reqs; -- /* -- * List of pending rpc requests. -- */ -- struct mgmt_txn_reqs_head rpc_reqs; -- /* -- * There will always be one commit-config allowed for a given -- * transaction/session. No need to maintain lists for it. -- */ -- struct mgmt_txn_req *commit_cfg_req; --}; -+/* Global list of all transactions */ -+TAILQ_HEAD(mgmt_txn_head, mgmt_txn) txn_txns = TAILQ_HEAD_INITIALIZER(txn_txns); - --DECLARE_LIST(mgmt_txns, struct mgmt_txn_ctx, list_linkage); -+/* The single instance of config transaction allowed at any time */ -+struct mgmt_txn *txn_config_txn; - --#define FOREACH_TXN_IN_LIST(mm, txn) \ -- frr_each_safe (mgmt_txns, &(mm)->txn_list, (txn)) -+/* Map of Transactions and its ID */ -+struct hash *txn_id_tab; -+uint64_t txn_next_id; - --static int mgmt_txn_send_commit_cfg_reply(struct mgmt_txn_ctx *txn, -- enum mgmt_result result, -- const char *error_if_any); -+static void txn_incref(struct mgmt_txn *txn, const char *file, int line); - --static inline const char *mgmt_txn_commit_phase_str(struct mgmt_txn_ctx *txn) -+/* ====================== */ -+/* Txn Request Management */ -+/* ====================== */ -+ -+static inline struct txn_req *txn_txn_req(struct mgmt_txn *txn, uint64_t req_id) - { -- if (!txn->commit_cfg_req) -- return "None"; -+ struct txn_req *txn_req; - -- return mgmt_commit_phase2str(txn->commit_cfg_req->req.commit_cfg.phase); -+ TAILQ_FOREACH (txn_req, &txn->reqs, link) -+ if (txn_req->req_id == req_id) -+ return txn_req; -+ return NULL; - } - --static void mgmt_txn_lock(struct mgmt_txn_ctx *txn, const char *file, int line); --static void mgmt_txn_unlock(struct mgmt_txn_ctx **txn, bool in_hash_free, const char *file, -- int line); --static int mgmt_txn_send_be_txn_delete(struct mgmt_txn_ctx *txn, -- struct mgmt_be_client_adapter *adapter); -- --static struct event_loop *mgmt_txn_tm; --static struct mgmt_master *mgmt_txn_mm; -- --static void mgmt_txn_register_event(struct mgmt_txn_ctx *txn, enum mgmt_txn_frr_event event); -- --static void mgmt_txn_cleanup_txn(struct mgmt_txn_ctx **txn); -- --static struct mgmt_txn_req *mgmt_txn_req_alloc(struct mgmt_txn_ctx *txn, uint64_t req_id, -- enum mgmt_txn_req_type req_type) -+struct txn_req *txn_req_alloc(struct mgmt_txn *txn, uint64_t req_id, enum txn_req_type req_type, -+ size_t size) - { -- struct mgmt_txn_req *txn_req; -+ struct txn_req *txn_req = XCALLOC(MTYPE_MGMTD_TXN_REQ, size); - -- txn_req = XCALLOC(MTYPE_MGMTD_TXN_REQ, sizeof(struct mgmt_txn_req)); -- assert(txn_req); -- txn_req->txn = txn; -- txn_req->req_id = req_id; - txn_req->req_type = req_type; -+ txn_req->req_id = req_id; - -- switch (txn_req->req_type) { -- case MGMTD_TXN_PROC_COMMITCFG: -- txn->commit_cfg_req = txn_req; -- _dbg("Added a new COMMITCFG req-id: %" PRIu64 " txn-id: %" PRIu64 -- " session-id: %" PRIu64, -- txn_req->req_id, txn->txn_id, txn->session_id); -- -- txn_req->req.commit_cfg.phase = MGMTD_COMMIT_PHASE_PREPARE_CFG; -- break; -- case MGMTD_TXN_PROC_GETTREE: -- txn_req->req.get_tree = XCALLOC(MTYPE_MGMTD_TXN_GETTREE_REQ, -- sizeof(struct txn_req_get_tree)); -- mgmt_txn_reqs_add_tail(&txn->get_tree_reqs, txn_req); -- _dbg("Added a new GETTREE req-id: %" PRIu64 " txn-id: %" PRIu64 -- " session-id: %" PRIu64, -- txn_req->req_id, txn->txn_id, txn->session_id); -- break; -- case MGMTD_TXN_PROC_RPC: -- txn_req->req.rpc = XCALLOC(MTYPE_MGMTD_TXN_RPC_REQ, -- sizeof(struct txn_req_rpc)); -- assert(txn_req->req.rpc); -- mgmt_txn_reqs_add_tail(&txn->rpc_reqs, txn_req); -- _dbg("Added a new RPC req-id: %" PRIu64 " txn-id: %" PRIu64 " session-id: %" PRIu64, -- txn_req->req_id, txn->txn_id, txn->session_id); -- break; -- } -+ txn_req->txn = txn; -+ TAILQ_INSERT_TAIL(&txn->reqs, txn_req, link); -+ TXN_INCREF(txn); - -- MGMTD_TXN_LOCK(txn); -+ _dbg("Added %s txn-req req-id: %Lu txn-id: %Lu session-id: %Lu", txn_req_names[req_type], -+ req_id, txn->txn_id, txn->session_id); - - return txn_req; - } - --static void mgmt_txn_req_free(struct mgmt_txn_req **txn_req) -+static struct txn_req_get_tree *txn_req_get_tree_alloc(struct mgmt_txn *txn, uint64_t req_id) - { -- struct mgmt_txn_reqs_head *req_list = NULL; -- enum mgmt_be_client_id id; -- struct mgmt_be_client_adapter *adapter; -- struct mgmt_commit_cfg_req *ccreq; -- bool cleanup; -- -- switch ((*txn_req)->req_type) { -- case MGMTD_TXN_PROC_COMMITCFG: -- _dbg("Deleting COMMITCFG req-id: %" PRIu64 " txn-id: %" PRIu64, (*txn_req)->req_id, -- (*txn_req)->txn->txn_id); -- -- ccreq = &(*txn_req)->req.commit_cfg; -- cleanup = (ccreq->phase >= MGMTD_COMMIT_PHASE_TXN_CREATE && -- ccreq->phase < MGMTD_COMMIT_PHASE_TXN_DELETE); -- -- XFREE(MTYPE_MGMTD_TXN_REQ, ccreq->edit); -- -- if (ccreq->mgmtd_nb_txn) { -- nb_candidate_commit_abort(ccreq->mgmtd_nb_txn, NULL, 0); -- ccreq->mgmtd_nb_txn = NULL; -- } -- -- FOREACH_MGMTD_BE_CLIENT_ID (id) { -- /* -- * Send TXN_DELETE to cleanup state for this -- * transaction on backend -- */ -- -- /* -- * Delete any config messages and action data -- */ -- mgmt_msg_native_free_msg(ccreq->cfg_msgs[id]); -- darr_free(ccreq->cfg_actions[id]); -- -- /* -- * If we were in the middle of the state machine then -- * send a txn delete message -- */ -- adapter = mgmt_be_get_adapter_by_id(id); -- if (adapter && cleanup && IS_IDBIT_SET(ccreq->clients, id)) -- mgmt_txn_send_be_txn_delete((*txn_req)->txn, -- adapter); -- } -- break; -- case MGMTD_TXN_PROC_GETTREE: -- _dbg("Deleting GETTREE req-id: %" PRIu64 " of txn-id: %" PRIu64, (*txn_req)->req_id, -- (*txn_req)->txn->txn_id); -- req_list = &(*txn_req)->txn->get_tree_reqs; -- lyd_free_all((*txn_req)->req.get_tree->client_results); -- XFREE(MTYPE_MGMTD_XPATH, (*txn_req)->req.get_tree->xpath); -- XFREE(MTYPE_MGMTD_TXN_GETTREE_REQ, (*txn_req)->req.get_tree); -- break; -- case MGMTD_TXN_PROC_RPC: -- _dbg("Deleting RPC req-id: %" PRIu64 " txn-id: %" PRIu64, (*txn_req)->req_id, -- (*txn_req)->txn->txn_id); -- req_list = &(*txn_req)->txn->rpc_reqs; -- lyd_free_all((*txn_req)->req.rpc->client_results); -- XFREE(MTYPE_MGMTD_ERR, (*txn_req)->req.rpc->errstr); -- XFREE(MTYPE_MGMTD_XPATH, (*txn_req)->req.rpc->xpath); -- XFREE(MTYPE_MGMTD_TXN_RPC_REQ, (*txn_req)->req.rpc); -- break; -- } -- -- if (req_list) { -- mgmt_txn_reqs_del(req_list, *txn_req); -- _dbg("Removed req-id: %" PRIu64 " from request-list (left:%zu)", (*txn_req)->req_id, -- mgmt_txn_reqs_count(req_list)); -- } -- -- MGMTD_TXN_UNLOCK(&(*txn_req)->txn, false); -- XFREE(MTYPE_MGMTD_TXN_REQ, (*txn_req)); -- *txn_req = NULL; --} -- --static int mgmt_txn_send_commit_cfg_reply(struct mgmt_txn_ctx *txn, -- enum mgmt_result result, -- const char *error_if_any) --{ -- bool success, create_cmt_info_rec; -- -- if (!txn->commit_cfg_req) -- return -1; -- -- success = (result == MGMTD_SUCCESS || result == MGMTD_NO_CFG_CHANGES); -- -- /* TODO: these replies should not be send if it's a rollback -- * b/c right now that is special cased.. that special casing should be -- * removed; however... -- */ -- if (!txn->commit_cfg_req->req.commit_cfg.edit && -- !txn->commit_cfg_req->req.commit_cfg.implicit && txn->session_id && -- !txn->commit_cfg_req->req.commit_cfg.rollback && -- mgmt_fe_send_commit_cfg_reply(txn->session_id, txn->txn_id, -- txn->commit_cfg_req->req.commit_cfg.src_ds_id, -- txn->commit_cfg_req->req.commit_cfg.dst_ds_id, -- txn->commit_cfg_req->req_id, -- txn->commit_cfg_req->req.commit_cfg.validate_only, -- txn->commit_cfg_req->req.commit_cfg.unlock, result, -- error_if_any) != 0) { -- _log_err("Failed to send COMMIT-CONFIG-REPLY txn-id: %" PRIu64 -- " session-id: %" PRIu64, -- txn->txn_id, txn->session_id); -- } -- -- if (txn->commit_cfg_req->req.commit_cfg.edit && -- mgmt_fe_adapter_send_edit_reply(txn->session_id, txn->txn_id, -- txn->commit_cfg_req->req_id, -- txn->commit_cfg_req->req.commit_cfg -- .edit->unlock, -- true, -- txn->commit_cfg_req->req.commit_cfg -- .edit->created, -- txn->commit_cfg_req->req.commit_cfg -- .edit->xpath_created, -- success ? 0 : -1, -- error_if_any) != 0) { -- _log_err("Failed to send EDIT-REPLY txn-id: %" PRIu64 " session-id: %" PRIu64, -- txn->txn_id, txn->session_id); -- } -- -- if (success) { -- /* Stop the commit-timeout timer */ -- /* XXX why only on success? */ -- event_cancel(&txn->comm_cfg_timeout); -- -- create_cmt_info_rec = -- (result != MGMTD_NO_CFG_CHANGES && -- !txn->commit_cfg_req->req.commit_cfg.rollback); -- -- /* -- * Successful commit: Copy Src DS to Dst DS if and only if -- * this was not a validate-only or abort request. -- */ -- if ((txn->session_id && -- !txn->commit_cfg_req->req.commit_cfg.validate_only && -- !txn->commit_cfg_req->req.commit_cfg.abort) || -- txn->commit_cfg_req->req.commit_cfg.rollback) { -- mgmt_ds_copy_dss(txn->commit_cfg_req->req.commit_cfg.dst_ds_ctx, -- txn->commit_cfg_req->req.commit_cfg.src_ds_ctx, -- create_cmt_info_rec); -- } -- -- /* -- * Restore Src DS back to Dest DS only through a commit abort -- * request. -- */ -- if (txn->session_id && txn->commit_cfg_req->req.commit_cfg.abort) -- mgmt_ds_copy_dss(txn->commit_cfg_req->req.commit_cfg.src_ds_ctx, -- txn->commit_cfg_req->req.commit_cfg.dst_ds_ctx, false); -- } else { -- /* -- * The commit has failied. For implicit commit requests restore -- * back the contents of the candidate DS. For non-implicit -- * commit we want to allow the user to re-commit on the changes -- * (whether further modified or not). -- */ -- if (txn->commit_cfg_req->req.commit_cfg.implicit || -- txn->commit_cfg_req->req.commit_cfg.unlock) -- mgmt_ds_copy_dss(txn->commit_cfg_req->req.commit_cfg.src_ds_ctx, -- txn->commit_cfg_req->req.commit_cfg.dst_ds_ctx, false); -- } -- -- if (txn->commit_cfg_req->req.commit_cfg.rollback) { -- mgmt_ds_unlock(txn->commit_cfg_req->req.commit_cfg.src_ds_ctx); -- mgmt_ds_unlock(txn->commit_cfg_req->req.commit_cfg.dst_ds_ctx); -- /* -- * Resume processing the rollback command. -- * -- * TODO: there's no good reason to special case rollback, the -- * rollback boolean should be passed back to the FE client and it -- * can do the right thing. -- */ -- mgmt_history_rollback_complete(success); -- } -- -- if (txn->commit_cfg_req->req.commit_cfg.init) { -- /* -- * This is the backend init request. -- * We need to unlock the running datastore. -- */ -- mgmt_ds_unlock(txn->commit_cfg_req->req.commit_cfg.dst_ds_ctx); -- } -- -- txn->commit_cfg_req->req.commit_cfg.cmt_stats = NULL; -- mgmt_txn_req_free(&txn->commit_cfg_req); -- -- /* -- * The CONFIG Transaction should be destroyed from Frontend-adapter. -- * But in case the transaction is not triggered from a front-end session -- * we need to cleanup by itself. -- */ -- if (!txn->session_id) -- mgmt_txn_cleanup_txn(&txn); -- -- return 0; --} -- --static int --mgmt_try_move_commit_to_next_phase(struct mgmt_txn_ctx *txn, -- struct mgmt_commit_cfg_req *cmtcfg_req) --{ -- enum mgmt_be_client_id id; -- -- _dbg("txn-id: %" PRIu64 ", Phase '%s'", txn->txn_id, mgmt_txn_commit_phase_str(txn)); -- -- /* -- * Check if all clients has moved to next phase or not. -- */ -- FOREACH_MGMTD_BE_CLIENT_ID (id) { -- if (IS_IDBIT_SET(cmtcfg_req->clients, id) && -- cmtcfg_req->be_phase[id] == cmtcfg_req->phase) { -- /* -- * There's atleast once client who hasn't moved to -- * next phase. -- * -- * TODO: Need to re-think this design for the case -- * set of validators for a given YANG data item is -- * different from the set of notifiers for the same. -- */ -- return -1; -- } -- } -- -- /* -- * If we are here, it means all the clients has moved to next phase. -- * So we can move the whole commit to next phase. -- */ -- cmtcfg_req->phase++; -- -- _dbg("Move entire txn-id: %" PRIu64 " to phase '%s'", txn->txn_id, -- mgmt_txn_commit_phase_str(txn)); -- -- mgmt_txn_register_event(txn, MGMTD_TXN_EVENT_COMMITCFG); -- -- return 0; --} -- --/* -- * This is the real workhorse -- */ --static int mgmt_txn_create_config_msgs(struct mgmt_txn_req *txn_req, struct nb_config_cbs *changes) --{ -- struct nb_config_cb *cb, *nxt; -- struct nb_config_change *chg; -- struct nb_config_cbs mgmtd_changes = { 0 }; -- char *xpath = NULL, *value = NULL; -- enum mgmt_be_client_id id; -- struct mgmt_be_client_adapter *adapter; -- struct mgmt_commit_cfg_req *cmtcfg_req; -- int num_chgs = 0; -- uint64_t clients, chg_clients; -- char op; -- -- cmtcfg_req = &txn_req->req.commit_cfg; -- RB_INIT(nb_config_cbs, &mgmtd_changes); -- -- RB_FOREACH_SAFE (cb, nb_config_cbs, changes, nxt) { -- chg = (struct nb_config_change *)cb; -- -- /* -- * Could have directly pointed to xpath in nb_node. -- * But dont want to mess with it now. -- * xpath = chg->cb.nb_node->xpath; -- */ -- xpath = lyd_path(chg->cb.dnode, LYD_PATH_STD, NULL, 0); -- if (!xpath) { -- (void)mgmt_txn_send_commit_cfg_reply( -- txn_req->txn, MGMTD_INTERNAL_ERROR, -- "Internal error! Could not get Xpath from Ds node!"); -- nb_config_diff_del_changes(&mgmtd_changes); -- return -1; -- } -- -- value = (char *)lyd_get_value(chg->cb.dnode); -- if (!value) -- value = (char *)MGMTD_BE_CONTAINER_NODE_VAL; -- -- _dbg("XPATH: %s, Value: '%s'", xpath, value ? value : "NIL"); -- -- /* Collect changes for mgmtd itself */ -- if (mgmt_is_mgmtd_interested(xpath) && -- /* We send tree changes to BEs that we don't need callbacks for */ -- nb_cb_operation_is_valid(cb->operation, cb->dnode->schema)) { -- uint32_t seq = cb->seq; -- -- nb_config_diff_add_change(&mgmtd_changes, cb->operation, &seq, cb->dnode); -- num_chgs++; -- } -- -- clients = mgmt_be_interested_clients(xpath, MGMT_BE_XPATH_SUBSCR_TYPE_CFG); -- if (!clients) { -- _dbg("No backends interested in xpath: %s", xpath); -- continue; -- } -- -- chg_clients = 0; -- FOREACH_BE_CLIENT_BITS (id, clients) { -- adapter = mgmt_be_get_adapter_by_id(id); -- if (!adapter) -- continue; -- -- chg_clients |= (1ull << id); -- -- if (!cmtcfg_req->cfg_msgs[id]) { -- /* Allocate a new config message */ -- struct mgmt_msg_cfg_req *msg; -- -- msg = mgmt_msg_native_alloc_msg(struct mgmt_msg_cfg_req, 0, -- MTYPE_MSG_NATIVE_CFG_REQ); -- msg->code = MGMT_MSG_CODE_CFG_REQ; -- msg->refer_id = txn_req->txn->txn_id; -- msg->req_id = txn_req->req_id; -- cmtcfg_req->cfg_msgs[id] = msg; -- } -- -- /* -- * On the backend, we don't really care if it's CREATE -- * or MODIFY, because the existence was already checked -- * on the frontend. Therefore we use SET for both. -- */ -- op = chg->cb.operation == NB_CB_DESTROY ? 'd' : 'm'; -- darr_push(cmtcfg_req->cfg_actions[id], op); -- -- mgmt_msg_native_add_str(cmtcfg_req->cfg_msgs[id], xpath); -- if (op == 'm') -- mgmt_msg_native_add_str(cmtcfg_req->cfg_msgs[id], value); -- cmtcfg_req->num_cfg_data[id]++; -- -- _dbg(" -- %s, batch item: %Lu", adapter->name, cmtcfg_req->num_cfg_data[id]); -- -- num_chgs++; -- } -- -- if (!chg_clients) -- _dbg("Daemons interested in XPATH are not currently connected: %s", xpath); -- -- cmtcfg_req->clients |= chg_clients; -- -- free(xpath); -- } -- -- cmtcfg_req->cmt_stats->last_batch_cnt = num_chgs; -- if (!num_chgs) { -- assert(RB_EMPTY(nb_config_cbs, &mgmtd_changes)); -- (void)mgmt_txn_send_commit_cfg_reply(txn_req->txn, MGMTD_NO_CFG_CHANGES, -- "No connected daemons interested in changes"); -- return -1; -- } -- -- if (!RB_EMPTY(nb_config_cbs, &mgmtd_changes)) { -- /* Create a transaction for local mgmtd config changes */ -- char errmsg[BUFSIZ] = { 0 }; -- size_t errmsg_len = sizeof(errmsg); -- struct nb_context nb_ctx = { 0 }; -- -- _dbg("Processing mgmtd bound changes"); -- -- assert(!cmtcfg_req->mgmtd_nb_txn); -- nb_ctx.client = NB_CLIENT_MGMTD_SERVER; -- if (nb_changes_commit_prepare(nb_ctx, mgmtd_changes, "mgmtd-changes", NULL, -- &cmtcfg_req->mgmtd_nb_txn, errmsg, errmsg_len)) { -- _log_err("Failed to prepare local config for mgmtd: %s", errmsg); -- if (cmtcfg_req->mgmtd_nb_txn) { -- nb_candidate_commit_abort(cmtcfg_req->mgmtd_nb_txn, NULL, 0); -- cmtcfg_req->mgmtd_nb_txn = NULL; -- } -- (void)mgmt_txn_send_commit_cfg_reply(txn_req->txn, MGMTD_INTERNAL_ERROR, -- "Failed to prepare local config for mgmtd"); -- return -1; -- } -- assert(cmtcfg_req->mgmtd_nb_txn); -- } -- -- /* Move all BE clients to create phase -- XXX go straight to cfg_req */ -- FOREACH_MGMTD_BE_CLIENT_ID(id) { -- if (IS_IDBIT_SET(cmtcfg_req->clients, id)) -- cmtcfg_req->be_phase[id] = -- MGMTD_COMMIT_PHASE_TXN_CREATE; -- } -- -- return 0; --} -- --static int mgmt_txn_prepare_config(struct mgmt_txn_ctx *txn) --{ -- struct nb_context nb_ctx; -- struct nb_config *nb_config; -- struct nb_config_cbs changes; -- struct nb_config_cbs *cfg_chgs = NULL; -- int ret; -- bool del_cfg_chgs = false; -- -- ret = 0; -- memset(&nb_ctx, 0, sizeof(nb_ctx)); -- memset(&changes, 0, sizeof(changes)); -- if (txn->commit_cfg_req->req.commit_cfg.cfg_chgs) { -- cfg_chgs = txn->commit_cfg_req->req.commit_cfg.cfg_chgs; -- del_cfg_chgs = true; -- goto mgmt_txn_prep_config_validation_done; -- } -- -- if (txn->commit_cfg_req->req.commit_cfg.src_ds_id != MGMTD_DS_CANDIDATE) { -- (void)mgmt_txn_send_commit_cfg_reply( -- txn, MGMTD_INVALID_PARAM, -- "Source DS cannot be any other than CANDIDATE!"); -- ret = -1; -- goto mgmt_txn_prepare_config_done; -- } -- -- if (txn->commit_cfg_req->req.commit_cfg.dst_ds_id != MGMTD_DS_RUNNING) { -- (void)mgmt_txn_send_commit_cfg_reply( -- txn, MGMTD_INVALID_PARAM, -- "Destination DS cannot be any other than RUNNING!"); -- ret = -1; -- goto mgmt_txn_prepare_config_done; -- } -- -- if (!txn->commit_cfg_req->req.commit_cfg.src_ds_ctx) { -- (void)mgmt_txn_send_commit_cfg_reply(txn, MGMTD_INVALID_PARAM, -- "No such source datastore!"); -- ret = -1; -- goto mgmt_txn_prepare_config_done; -- } -- -- if (!txn->commit_cfg_req->req.commit_cfg.dst_ds_ctx) { -- (void)mgmt_txn_send_commit_cfg_reply(txn, MGMTD_INVALID_PARAM, -- "No such destination datastore!"); -- ret = -1; -- goto mgmt_txn_prepare_config_done; -- } -- -- if (txn->commit_cfg_req->req.commit_cfg.abort) { -- /* -- * This is a commit abort request. Return back success. -- * That should trigger a restore of Candidate datastore to -- * Running. -- */ -- (void)mgmt_txn_send_commit_cfg_reply(txn, MGMTD_SUCCESS, NULL); -- goto mgmt_txn_prepare_config_done; -- } -- -- nb_config = mgmt_ds_get_nb_config( -- txn->commit_cfg_req->req.commit_cfg.src_ds_ctx); -- if (!nb_config) { -- (void)mgmt_txn_send_commit_cfg_reply( -- txn, MGMTD_INTERNAL_ERROR, -- "Unable to retrieve Commit DS Config Tree!"); -- ret = -1; -- goto mgmt_txn_prepare_config_done; -- } -- -- /* -- * Validate YANG contents of the source DS and get the diff -- * between source and destination DS contents. -- */ -- char err_buf[BUFSIZ] = { 0 }; -- -- ret = nb_candidate_validate_yang(nb_config, true, err_buf, -- sizeof(err_buf) - 1); -- if (ret != NB_OK) { -- if (strncmp(err_buf, " ", strlen(err_buf)) == 0) -- strlcpy(err_buf, "Validation failed", sizeof(err_buf)); -- (void)mgmt_txn_send_commit_cfg_reply(txn, MGMTD_INVALID_PARAM, -- err_buf); -- ret = -1; -- goto mgmt_txn_prepare_config_done; -- } -- -- nb_config_diff(mgmt_ds_get_nb_config(txn->commit_cfg_req->req.commit_cfg -- .dst_ds_ctx), -- nb_config, &changes); -- cfg_chgs = &changes; -- del_cfg_chgs = true; -- -- if (RB_EMPTY(nb_config_cbs, cfg_chgs)) { -- /* -- * This means there's no changes to commit whatsoever -- * is the source of the changes in config. -- */ -- (void)mgmt_txn_send_commit_cfg_reply(txn, MGMTD_NO_CFG_CHANGES, -- "No changes found to be committed!"); -- ret = -1; -- goto mgmt_txn_prepare_config_done; -- } -- --mgmt_txn_prep_config_validation_done: -- -- if (mm->perf_stats_en) -- gettimeofday(&txn->commit_cfg_req->req.commit_cfg.cmt_stats -- ->prep_cfg_start, -- NULL); -- -- /* -- * Iterate over the diffs and create config messages to send to backends. -- */ -- ret = mgmt_txn_create_config_msgs(txn->commit_cfg_req, cfg_chgs); -- if (ret != 0) { -- ret = -1; -- goto mgmt_txn_prepare_config_done; -- } -- -- /* -- * If we have BE clients then move to the Transaction Create Phase -- * otherwise move to the Apply Config Phase to apply mgmtd changes. -- */ -- if (txn->commit_cfg_req->req.commit_cfg.clients) -- txn->commit_cfg_req->req.commit_cfg.phase = MGMTD_COMMIT_PHASE_TXN_CREATE; -- else -- txn->commit_cfg_req->req.commit_cfg.phase = MGMTD_COMMIT_PHASE_APPLY_CFG; -- mgmt_txn_register_event(txn, MGMTD_TXN_EVENT_COMMITCFG); -- -- /* -- * Start the COMMIT Timeout Timer to abort Txn if things get stuck at -- * backend. -- */ -- mgmt_txn_register_event(txn, MGMTD_TXN_EVENT_COMMITCFG_TIMEOUT); --mgmt_txn_prepare_config_done: -- -- if (cfg_chgs && del_cfg_chgs) -- nb_config_diff_del_changes(cfg_chgs); -- -- return ret; -+ struct txn_req *txn_req = txn_req_alloc(txn, req_id, TXN_REQ_TYPE_GETTREE, -+ sizeof(struct txn_req_get_tree)); -+ assert(txn->type == MGMTD_TXN_TYPE_SHOW); -+ return as_get_tree(txn_req); - } - --static int mgmt_txn_send_be_txn_create(struct mgmt_txn_ctx *txn) -+static struct txn_req_rpc *txn_req_rpc_alloc(struct mgmt_txn *txn, uint64_t req_id) - { -- enum mgmt_be_client_id id; -- struct mgmt_be_client_adapter *adapter; -- struct mgmt_commit_cfg_req *cmtcfg_req; -- -- assert(txn->type == MGMTD_TXN_TYPE_CONFIG && txn->commit_cfg_req); -- -- cmtcfg_req = &txn->commit_cfg_req->req.commit_cfg; -- FOREACH_MGMTD_BE_CLIENT_ID (id) { -- if (IS_IDBIT_SET(cmtcfg_req->clients, id)) { -- adapter = mgmt_be_get_adapter_by_id(id); -- if (mgmt_be_send_txn_req(adapter, txn->txn_id, true)) { -- (void)mgmt_txn_send_commit_cfg_reply( -- txn, MGMTD_INTERNAL_ERROR, -- "Could not send TXN_CREATE to backend adapter"); -- return -1; -- } -- } -- } -- -- /* -- * Dont move the commit to next phase yet. Wait for the TXN_REPLY to -- * come back. -- */ -- -- _dbg("txn-id: %" PRIu64 " session-id: %" PRIu64 " Phase '%s'", txn->txn_id, txn->session_id, -- mgmt_txn_commit_phase_str(txn)); -- -- return 0; -+ struct txn_req *txn_req = txn_req_alloc(txn, req_id, TXN_REQ_TYPE_RPC, -+ sizeof(struct txn_req_rpc)); -+ assert(txn->type == MGMTD_TXN_TYPE_RPC); -+ return as_rpc(txn_req); - } - --static int mgmt_txn_send_be_cfg_data(struct mgmt_txn_ctx *txn, -- struct mgmt_be_client_adapter *adapter) -+void txn_req_free(struct txn_req *txn_req) - { -- enum mgmt_be_client_id id = adapter->id; -- struct mgmt_commit_cfg_req *ccreq; -- int ret; -- -- assert(txn->type == MGMTD_TXN_TYPE_CONFIG && txn->commit_cfg_req); -- -- ccreq = &txn->commit_cfg_req->req.commit_cfg; -- assert(IS_IDBIT_SET(ccreq->clients, id)); -- -- /* NUL terminate the actions string */ -- darr_push(ccreq->cfg_actions[id], 0); -- -- _dbg("Finished CFG_REQ for '%s' txn-id: %Lu with actions: %s", adapter->name, txn->txn_id, -- ccreq->cfg_actions[id]); -+ struct mgmt_txn *txn = txn_req->txn; -+ struct txn_req_rpc *rpc; -+ struct txn_req_get_tree *get_tree; -+ uint64_t txn_id = txn->txn_id; - -- /* Add the final action string to the message */ -- mgmt_msg_native_add_str(ccreq->cfg_msgs[id], ccreq->cfg_actions[id]); -- darr_free(ccreq->cfg_actions[id]); -+ /* prevent recursion */ -+ _dbg("Deleting %s txn-req req-id: %Lu txn-id: %Lu", txn_req_names[txn_req->req_type], -+ txn_req->req_id, txn_id); - -- ret = mgmt_be_send_cfgdata_req(adapter, ccreq->cfg_msgs[id]); -- mgmt_msg_native_free_msg(ccreq->cfg_msgs[id]); -- if (ret) { -- (void)mgmt_txn_send_commit_cfg_reply(txn, MGMTD_INTERNAL_ERROR, -- "Internal Error! Could not send config data to backend!"); -- return -1; -+ switch (txn_req->req_type) { -+ case TXN_REQ_TYPE_COMMIT: -+ txn_cfg_cleanup(txn_req); -+ break; -+ case TXN_REQ_TYPE_GETTREE: -+ get_tree = as_get_tree(txn_req); -+ lyd_free_all(get_tree->client_results); -+ XFREE(MTYPE_MGMTD_XPATH, get_tree->xpath); -+ break; -+ case TXN_REQ_TYPE_RPC: -+ rpc = as_rpc(txn_req); -+ lyd_free_all(rpc->client_results); -+ XFREE(MTYPE_MGMTD_XPATH, rpc->xpath); -+ break; - } -- ccreq->cmt_stats->last_num_cfgdata_reqs++; -- -- /* -- * We don't advance the phase here, instead that is driven by the -- * cfg_reply. -- */ -- -- return 0; --} -- --static int mgmt_txn_send_be_txn_delete(struct mgmt_txn_ctx *txn, -- struct mgmt_be_client_adapter *adapter) --{ -- struct mgmt_commit_cfg_req *cmtcfg_req = -- &txn->commit_cfg_req->req.commit_cfg; -- -- assert(txn->type == MGMTD_TXN_TYPE_CONFIG); -- -- if (IS_IDBIT_UNSET(cmtcfg_req->clients, adapter->id)) -- return 0; -- -- return mgmt_be_send_txn_req(adapter, txn->txn_id, false); --} - --static void mgmt_txn_cfg_commit_timedout(struct event *thread) --{ -- struct mgmt_txn_ctx *txn; -- -- txn = (struct mgmt_txn_ctx *)EVENT_ARG(thread); -- assert(txn); -- -- assert(txn->type == MGMTD_TXN_TYPE_CONFIG); -- -- if (!txn->commit_cfg_req) -- return; -- -- _log_err("Backend timeout txn-id: %" PRIu64 " aborting commit", txn->txn_id); -+ TAILQ_REMOVE(&txn->reqs, txn_req, link); -+ _dbg("Removed req-id: %Lu from request-list", txn_req->req_id); - -- /* -- * Send a COMMIT_CONFIG_REPLY with failure. -- * NOTE: The transaction cleanup will be triggered from Front-end -- * adapter. -- */ -- mgmt_txn_send_commit_cfg_reply( -- txn, MGMTD_INTERNAL_ERROR, -- "Operation on the backend timed-out. Aborting commit!"); -+ darr_free(txn_req->err_info); -+ TXN_DECREF(txn_req->txn); -+ XFREE(MTYPE_MGMTD_TXN_REQ, txn_req); - } - -+/* =========================== */ -+/* GET TREE (data) BACKEND TXN */ -+/* =========================== */ - --static int txn_get_tree_data_done(struct mgmt_txn_ctx *txn, -- struct mgmt_txn_req *txn_req) -+static void txn_get_tree_data_done(struct txn_req_get_tree *get_tree) - { -- struct txn_req_get_tree *get_tree = txn_req->req.get_tree; -+ struct txn_req *txn_req = as_txn_req(get_tree); -+ struct mgmt_txn *txn = txn_req->txn; - uint64_t req_id = txn_req->req_id; - struct lyd_node *result; - int ret = NB_OK; - - /* cancel timer and send reply onward */ -- event_cancel(&txn->get_tree_timeout); -+ event_cancel(&txn_req->timeout); - - if (!get_tree->simple_xpath && get_tree->client_results) { - /* - * We have a complex query so Filter results by the xpath query. - */ -- if (yang_lyd_trim_xpath(&get_tree->client_results, -- txn_req->req.get_tree->xpath)) -+ if (yang_lyd_trim_xpath(&get_tree->client_results, get_tree->xpath)) - ret = NB_ERR; - } - -@@ -944,727 +183,110 @@ static int txn_get_tree_data_done(struct mgmt_txn_ctx *txn, - result = yang_dnode_get(result, get_tree->xpath); - - if (ret == NB_OK) -- ret = mgmt_fe_adapter_send_tree_data(txn->session_id, -- txn->txn_id, -- txn_req->req_id, -- get_tree->result_type, -- get_tree->wd_options, -- result, -- get_tree->partial_error, -- false); -- -- /* we're done with the request */ -- mgmt_txn_req_free(&txn_req); -- -- if (ret) { -- _log_err("Error sending the results of GETTREE for txn-id %" PRIu64 -- " req_id %" PRIu64 " to requested type %u", -+ mgmt_fe_adapter_send_tree_data(txn->session_id, txn->txn_id, txn_req->req_id, -+ get_tree->result_type, get_tree->wd_options, result, -+ get_tree->partial_error, false); -+ else { -+ _log_err("Error sending the results of GET-TREE for txn-id %Lu req_id %Lu to requested type %u", - txn->txn_id, req_id, get_tree->result_type); - - (void)mgmt_fe_adapter_txn_error(txn->txn_id, req_id, false, - errno_from_nb_error(ret), -- "Error converting results of GETTREE"); -- } -- -- return ret; --} -- --static int txn_rpc_done(struct mgmt_txn_ctx *txn, struct mgmt_txn_req *txn_req) --{ -- struct txn_req_rpc *rpc = txn_req->req.rpc; -- uint64_t req_id = txn_req->req_id; -- -- /* cancel timer and send reply onward */ -- event_cancel(&txn->rpc_timeout); -- -- if (rpc->errstr) -- mgmt_fe_adapter_txn_error(txn->txn_id, req_id, false, -EINVAL, -- rpc->errstr); -- else if (mgmt_fe_adapter_send_rpc_reply(txn->session_id, txn->txn_id, -- req_id, rpc->result_type, -- rpc->client_results)) { -- _log_err("Error sending the results of RPC for txn-id %" PRIu64 " req_id %" PRIu64 -- " to requested type %u", -- txn->txn_id, req_id, rpc->result_type); -- -- (void)mgmt_fe_adapter_txn_error(txn->txn_id, req_id, false, -- -EINVAL, -- "Error converting results of RPC"); -+ "Error converting results of GET-TREE"); - } - - /* we're done with the request */ -- mgmt_txn_req_free(&txn_req); -- -- return 0; -+ txn_req_free(txn_req); - } - - static void txn_get_tree_timeout(struct event *thread) - { -- struct mgmt_txn_ctx *txn; -- struct mgmt_txn_req *txn_req; -- -- txn_req = (struct mgmt_txn_req *)EVENT_ARG(thread); -- txn = txn_req->txn; -- -- assert(txn); -- assert(txn->type == MGMTD_TXN_TYPE_SHOW); -- -+ struct txn_req_get_tree *get_tree = EVENT_ARG(thread); -+ struct mgmt_txn *txn = get_tree->req.txn; - - _log_err("Backend timeout txn-id: %" PRIu64 " ending get-tree", txn->txn_id); - -- /* -- * Send a get-tree data reply. -- * -- * NOTE: The transaction cleanup will be triggered from Front-end -- * adapter. -- */ -- -- txn_req->req.get_tree->partial_error = -ETIMEDOUT; -- txn_get_tree_data_done(txn, txn_req); -+ get_tree->partial_error = -ETIMEDOUT; -+ get_tree->req.err_info = darr_strdup("Get data on the backend timed-out"); -+ txn_get_tree_data_done(get_tree); - } - --static void txn_rpc_timeout(struct event *thread) -+static void txn_get_tree_handle_error_reply(struct txn_req_get_tree *get_tree, -+ struct mgmt_be_client_adapter *adapter, int error, -+ const char *errstr) - { -- struct mgmt_txn_ctx *txn; -- struct mgmt_txn_req *txn_req; -- -- txn_req = (struct mgmt_txn_req *)EVENT_ARG(thread); -- txn = txn_req->txn; -- -- assert(txn); -- assert(txn->type == MGMTD_TXN_TYPE_RPC); -- -- _log_err("Backend timeout txn-id: %" PRIu64 " ending rpc", txn->txn_id); -+ UNSET_IDBIT(get_tree->clients_wait, adapter->id); -+ get_tree->partial_error = error; - -- /* -- * Send a get-tree data reply. -- * -- * NOTE: The transaction cleanup will be triggered from Front-end -- * adapter. -- */ -- -- txn_req->req.rpc->errstr = -- XSTRDUP(MTYPE_MGMTD_ERR, "Operation on the backend timed-out"); -- txn_rpc_done(txn, txn_req); -+ /* check if done yet */ -+ if (!get_tree->clients_wait) -+ txn_get_tree_data_done(get_tree); - } - - /* -- * Send CFG_APPLY_REQs to all the backend client. -- * -- * NOTE: This is always dispatched when all CFGDATA_CREATE_REQs -- * for all backend clients has been generated. Please see -- * mgmt_txn_register_event() and mgmt_txn_process_commit_cfg() -- * for details. -+ * Get-tree data from the backend client. - */ --static int mgmt_txn_send_be_cfg_apply(struct mgmt_txn_ctx *txn) --{ -- enum mgmt_be_client_id id; -- struct mgmt_be_client_adapter *adapter; -- struct mgmt_commit_cfg_req *cmtcfg_req; -- -- assert(txn->type == MGMTD_TXN_TYPE_CONFIG && txn->commit_cfg_req); -- -- cmtcfg_req = &txn->commit_cfg_req->req.commit_cfg; -- if (cmtcfg_req->validate_only) { -- /* -- * If this was a validate-only COMMIT request return success. -- */ -- (void)mgmt_txn_send_commit_cfg_reply(txn, MGMTD_SUCCESS, NULL); -- return 0; -- } -- -- /* -- * Handle mgmtd special case -- */ -- if (cmtcfg_req->mgmtd_nb_txn) { -- char errmsg[BUFSIZ] = { 0 }; -- -- _dbg("Applying mgmtd local bound changes"); -- -- nb_candidate_commit_apply(cmtcfg_req->mgmtd_nb_txn, false, NULL, errmsg, -- sizeof(errmsg)); -- cmtcfg_req->mgmtd_nb_txn = NULL; -- } -- -- FOREACH_MGMTD_BE_CLIENT_ID (id) { -- if (IS_IDBIT_SET(cmtcfg_req->clients, id)) { -- adapter = mgmt_be_get_adapter_by_id(id); -- if (!adapter) -- return -1; -- -- if (mgmt_be_send_cfgapply_req(adapter, txn->txn_id)) { -- (void)mgmt_txn_send_commit_cfg_reply( -- txn, MGMTD_INTERNAL_ERROR, -- "Could not send CFG_APPLY_REQ to backend adapter"); -- return -1; -- } -- cmtcfg_req->cmt_stats->last_num_apply_reqs++; -- -- UNSET_FLAG(adapter->flags, -- MGMTD_BE_ADAPTER_FLAGS_CFG_SYNCED); -- } -- } -- -- /* -- * Dont move the commit to next phase yet. Wait for all VALIDATE_REPLIES -- * to come back. -- */ -- -- return 0; --} -- --static void mgmt_txn_process_commit_cfg(struct event *thread) -+void mgmt_txn_handle_tree_data_reply(struct mgmt_be_client_adapter *adapter, -+ struct mgmt_msg_tree_data *data_msg, size_t msg_len) - { -- struct mgmt_txn_ctx *txn; -- struct mgmt_commit_cfg_req *cmtcfg_req; -- -- txn = (struct mgmt_txn_ctx *)EVENT_ARG(thread); -- assert(txn); -- -- _dbg("Processing COMMIT_CONFIG for txn-id: %" PRIu64 " session-id: %" PRIu64 " Phase '%s'", -- txn->txn_id, txn->session_id, mgmt_txn_commit_phase_str(txn)); -- -- assert(txn->commit_cfg_req); -- cmtcfg_req = &txn->commit_cfg_req->req.commit_cfg; -- switch (cmtcfg_req->phase) { -- case MGMTD_COMMIT_PHASE_PREPARE_CFG: -- mgmt_txn_prepare_config(txn); -- break; -- case MGMTD_COMMIT_PHASE_TXN_CREATE: -- if (mm->perf_stats_en) -- gettimeofday(&cmtcfg_req->cmt_stats->txn_create_start, -- NULL); -- /* -- * Send TXN_CREATE_REQ to all Backend now. -- */ -- mgmt_txn_send_be_txn_create(txn); -- break; -- case MGMTD_COMMIT_PHASE_APPLY_CFG: -- if (mm->perf_stats_en) -- gettimeofday(&cmtcfg_req->cmt_stats->apply_cfg_start, -- NULL); -- /* -- * We should have received successful CFG_VALIDATE_REPLY from -- * all concerned Backend Clients by now. Send out the -- * CFG_APPLY_REQs now. -- */ -- mgmt_txn_send_be_cfg_apply(txn); -- break; -- case MGMTD_COMMIT_PHASE_TXN_DELETE: -- if (mm->perf_stats_en) -- gettimeofday(&cmtcfg_req->cmt_stats->txn_del_start, -- NULL); -- /* -- * We would have sent TXN_DELETE_REQ to all backend by now. -- * Send a successful CONFIG_COMMIT_REPLY back to front-end. -- * NOTE: This should also trigger DS merge/unlock and Txn -- * cleanup. Please see mgmt_fe_send_commit_cfg_reply() for -- * more details. -- */ -- event_cancel(&txn->comm_cfg_timeout); -- mgmt_txn_send_commit_cfg_reply(txn, MGMTD_SUCCESS, NULL); -- break; -- case MGMTD_COMMIT_PHASE_MAX: -- break; -- } --} -- --static struct mgmt_txn_ctx * --mgmt_fe_find_txn_by_session_id(struct mgmt_master *cm, uint64_t session_id, -- enum mgmt_txn_type type) --{ -- struct mgmt_txn_ctx *txn; -- -- FOREACH_TXN_IN_LIST (cm, txn) { -- if (txn->session_id == session_id && txn->type == type) -- return txn; -- } -- -- return NULL; --} -- --static struct mgmt_txn_ctx *mgmt_txn_create_new(uint64_t session_id, -- enum mgmt_txn_type type) --{ -- struct mgmt_txn_ctx *txn = NULL; -+ uint64_t txn_id = data_msg->refer_id; -+ uint64_t req_id = data_msg->req_id; - -- /* Do not allow multiple config transactions */ -- if (type == MGMTD_TXN_TYPE_CONFIG && mgmt_config_txn_in_progress()) -- return NULL; -+ enum mgmt_be_client_id id = adapter->id; -+ struct mgmt_txn *txn = txn_lookup(txn_id); -+ struct txn_req *txn_req; -+ struct txn_req_get_tree *get_tree; -+ struct lyd_node *tree = NULL; -+ LY_ERR err; - -- txn = mgmt_fe_find_txn_by_session_id(mgmt_txn_mm, session_id, type); - if (!txn) { -- txn = XCALLOC(MTYPE_MGMTD_TXN, sizeof(struct mgmt_txn_ctx)); -- assert(txn); -- -- txn->session_id = session_id; -- txn->type = type; -- mgmt_txns_add_tail(&mgmt_txn_mm->txn_list, txn); -- /* TODO: why do we need N lists for one transaction */ -- mgmt_txn_reqs_init(&txn->get_tree_reqs); -- mgmt_txn_reqs_init(&txn->rpc_reqs); -- txn->commit_cfg_req = NULL; -- txn->refcount = 0; -- if (!mgmt_txn_mm->next_txn_id) -- mgmt_txn_mm->next_txn_id++; -- txn->txn_id = mgmt_txn_mm->next_txn_id++; -- hash_get(mgmt_txn_mm->txn_hash, txn, hash_alloc_intern); -- -- _dbg("Added new '%s' txn-id: %" PRIu64, mgmt_txn_type2str(type), txn->txn_id); -- -- if (type == MGMTD_TXN_TYPE_CONFIG) -- mgmt_txn_mm->cfg_txn = txn; -- -- MGMTD_TXN_LOCK(txn); -- } -- -- return txn; --} -- --static void mgmt_txn_delete(struct mgmt_txn_ctx **txn, bool in_hash_free) --{ -- MGMTD_TXN_UNLOCK(txn, in_hash_free); --} -- --static unsigned int mgmt_txn_hash_key(const void *data) --{ -- const struct mgmt_txn_ctx *txn = data; -- -- return jhash2((uint32_t *)&txn->txn_id, -- sizeof(txn->txn_id) / sizeof(uint32_t), 0); --} -- --static bool mgmt_txn_hash_cmp(const void *d1, const void *d2) --{ -- const struct mgmt_txn_ctx *txn1 = d1; -- const struct mgmt_txn_ctx *txn2 = d2; -- -- return (txn1->txn_id == txn2->txn_id); --} -- --static void mgmt_txn_hash_free(void *data) --{ -- struct mgmt_txn_ctx *txn = data; -- -- mgmt_txn_delete(&txn, true); --} -- --static void mgmt_txn_hash_init(void) --{ -- if (!mgmt_txn_mm || mgmt_txn_mm->txn_hash) -- return; -- -- mgmt_txn_mm->txn_hash = hash_create(mgmt_txn_hash_key, mgmt_txn_hash_cmp, -- "MGMT Transactions"); --} -- --static void mgmt_txn_hash_destroy(void) --{ -- if (!mgmt_txn_mm || !mgmt_txn_mm->txn_hash) -- return; -- -- hash_clean(mgmt_txn_mm->txn_hash, mgmt_txn_hash_free); -- hash_free(mgmt_txn_mm->txn_hash); -- mgmt_txn_mm->txn_hash = NULL; --} -- --static inline struct mgmt_txn_ctx *mgmt_txn_id2ctx(uint64_t txn_id) --{ -- struct mgmt_txn_ctx key = { 0 }; -- struct mgmt_txn_ctx *txn; -- -- if (!mgmt_txn_mm || !mgmt_txn_mm->txn_hash) -- return NULL; -- -- key.txn_id = txn_id; -- txn = hash_lookup(mgmt_txn_mm->txn_hash, &key); -- -- return txn; --} -- --uint64_t mgmt_txn_get_session_id(uint64_t txn_id) --{ -- struct mgmt_txn_ctx *txn = mgmt_txn_id2ctx(txn_id); -- -- return txn ? txn->session_id : MGMTD_SESSION_ID_NONE; --} -- --static void mgmt_txn_lock(struct mgmt_txn_ctx *txn, const char *file, int line) --{ -- txn->refcount++; -- _dbg("%s:%d --> Lock %s txn-id: %" PRIu64 " refcnt: %d", file, line, -- mgmt_txn_type2str(txn->type), txn->txn_id, txn->refcount); --} -- --static void mgmt_txn_unlock(struct mgmt_txn_ctx **txn, bool in_hash_free, const char *file, int line) --{ -- assert(*txn && (*txn)->refcount); -- -- (*txn)->refcount--; -- _dbg("%s:%d --> Unlock %s txn-id: %" PRIu64 " refcnt: %d", file, line, -- mgmt_txn_type2str((*txn)->type), (*txn)->txn_id, (*txn)->refcount); -- if (!(*txn)->refcount) { -- if ((*txn)->type == MGMTD_TXN_TYPE_CONFIG) -- if (mgmt_txn_mm->cfg_txn == *txn) -- mgmt_txn_mm->cfg_txn = NULL; -- event_cancel(&(*txn)->proc_comm_cfg); -- event_cancel(&(*txn)->comm_cfg_timeout); -- event_cancel(&(*txn)->get_tree_timeout); -- if (!in_hash_free) -- hash_release(mgmt_txn_mm->txn_hash, *txn); -- -- mgmt_txns_del(&mgmt_txn_mm->txn_list, *txn); -- -- _dbg("Deleted %s txn-id: %" PRIu64 " session-id: %" PRIu64, -- mgmt_txn_type2str((*txn)->type), (*txn)->txn_id, (*txn)->session_id); -- -- XFREE(MTYPE_MGMTD_TXN, *txn); -- } -- -- *txn = NULL; --} -- --static void mgmt_txn_cleanup_txn(struct mgmt_txn_ctx **txn) --{ -- /* TODO: Any other cleanup applicable */ -- -- mgmt_txn_delete(txn, false); --} -- --static void mgmt_txn_cleanup_all_txns(void) --{ -- struct mgmt_txn_ctx *txn; -- -- if (!mgmt_txn_mm || !mgmt_txn_mm->txn_hash) -+ _log_err("GETTREE reply from %s for a missing txn-id %" PRIu64, adapter->name, -+ txn_id); - return; -- -- FOREACH_TXN_IN_LIST (mgmt_txn_mm, txn) -- mgmt_txn_cleanup_txn(&txn); --} -- --static void mgmt_txn_register_event(struct mgmt_txn_ctx *txn, enum mgmt_txn_frr_event event) --{ -- struct timeval tv = { .tv_sec = 0, -- .tv_usec = MGMTD_TXN_PROC_DELAY_USEC }; -- -- assert(mgmt_txn_mm && mgmt_txn_tm); -- -- switch (event) { -- case MGMTD_TXN_EVENT_COMMITCFG: -- event_add_timer_tv(mgmt_txn_tm, mgmt_txn_process_commit_cfg, -- txn, &tv, &txn->proc_comm_cfg); -- break; -- case MGMTD_TXN_EVENT_COMMITCFG_TIMEOUT: -- event_add_timer(mgmt_txn_tm, mgmt_txn_cfg_commit_timedout, txn, -- MGMTD_TXN_CFG_COMMIT_MAX_DELAY_SEC, -- &txn->comm_cfg_timeout); -- break; - } --} -- --int mgmt_txn_init(struct mgmt_master *m, struct event_loop *loop) --{ -- if (mgmt_txn_mm || mgmt_txn_tm) -- assert(!"MGMTD TXN: Call txn_init() only once"); -- -- mgmt_txn_mm = m; -- mgmt_txn_tm = loop; -- mgmt_txns_init(&m->txn_list); -- mgmt_txn_hash_init(); -- assert(!m->cfg_txn); -- m->cfg_txn = NULL; -- -- return 0; --} -- --void mgmt_txn_destroy(void) --{ -- mgmt_txn_cleanup_all_txns(); -- mgmt_txn_hash_destroy(); --} -- --bool mgmt_config_txn_in_progress(void) --{ -- if (mgmt_txn_mm && mgmt_txn_mm->cfg_txn) -- return true; -- -- return false; --} -- --uint64_t mgmt_create_txn(uint64_t session_id, enum mgmt_txn_type type) --{ -- struct mgmt_txn_ctx *txn; -- -- txn = mgmt_txn_create_new(session_id, type); -- return txn ? txn->txn_id : MGMTD_TXN_ID_NONE; --} -- --void mgmt_destroy_txn(uint64_t *txn_id) --{ -- struct mgmt_txn_ctx *txn; - -- txn = mgmt_txn_id2ctx(*txn_id); -- if (!txn) -+ /* Find the request. */ -+ txn_req = txn_txn_req(txn, req_id); -+ if (!txn_req || txn_req->req_type != TXN_REQ_TYPE_GETTREE) { -+ _log_err("GETTREE reply from %s for txn-id %" PRIu64 " missing req_id %" PRIu64, -+ adapter->name, txn_id, req_id); - return; -- -- mgmt_txn_delete(&txn, false); -- *txn_id = MGMTD_TXN_ID_NONE; --} -- --int mgmt_txn_send_commit_config_req(uint64_t txn_id, uint64_t req_id, enum mgmt_ds_id src_ds_id, -- struct mgmt_ds_ctx *src_ds_ctx, enum mgmt_ds_id dst_ds_id, -- struct mgmt_ds_ctx *dst_ds_ctx, bool validate_only, bool abort, -- bool implicit, bool unlock, struct mgmt_edit_req *edit) --{ -- struct mgmt_txn_ctx *txn; -- struct mgmt_txn_req *txn_req; -- -- txn = mgmt_txn_id2ctx(txn_id); -- if (!txn) -- return -1; -- -- if (txn->commit_cfg_req) { -- _log_err("Commit already in-progress txn-id: %" PRIu64 " session-id: %" PRIu64 -- ". Cannot start another", -- txn->txn_id, txn->session_id); -- return -1; -- } -- -- txn_req = mgmt_txn_req_alloc(txn, req_id, MGMTD_TXN_PROC_COMMITCFG); -- txn_req->req.commit_cfg.src_ds_id = src_ds_id; -- txn_req->req.commit_cfg.src_ds_ctx = src_ds_ctx; -- txn_req->req.commit_cfg.dst_ds_id = dst_ds_id; -- txn_req->req.commit_cfg.dst_ds_ctx = dst_ds_ctx; -- txn_req->req.commit_cfg.validate_only = validate_only; -- txn_req->req.commit_cfg.abort = abort; -- txn_req->req.commit_cfg.implicit = implicit; -- txn_req->req.commit_cfg.unlock = unlock; -- txn_req->req.commit_cfg.edit = edit; -- txn_req->req.commit_cfg.cmt_stats = -- mgmt_fe_get_session_commit_stats(txn->session_id); -- -- /* -- * Trigger a COMMIT-CONFIG process. -- */ -- mgmt_txn_register_event(txn, MGMTD_TXN_EVENT_COMMITCFG); -- return 0; --} -- --int mgmt_txn_notify_be_adapter_conn(struct mgmt_be_client_adapter *adapter, -- bool connect) --{ -- struct mgmt_txn_ctx *txn; -- struct mgmt_txn_req *txn_req; -- struct mgmt_commit_cfg_req *cmtcfg_req; -- struct nb_config_cbs *adapter_cfgs = NULL; -- struct mgmt_ds_ctx *ds_ctx; -- -- if (connect) { -- ds_ctx = mgmt_ds_get_ctx_by_id(mm, MGMTD_DS_RUNNING); -- assert(ds_ctx); -- -- /* -- * Lock the running datastore to prevent any changes while we -- * are initializing the backend. -- */ -- if (mgmt_ds_lock(ds_ctx, 0) != 0) { -- _dbg("Failed to lock DS:%s for init of BE adapter '%s'", -- mgmt_ds_id2name(MGMTD_DS_RUNNING), adapter->name); -- return -1; -- } -- -- /* Get config for this single backend client */ -- mgmt_be_get_adapter_config(adapter, &adapter_cfgs); -- if (!adapter_cfgs || RB_EMPTY(nb_config_cbs, adapter_cfgs)) { -- SET_FLAG(adapter->flags, -- MGMTD_BE_ADAPTER_FLAGS_CFG_SYNCED); -- mgmt_ds_unlock(ds_ctx); -- return 0; -- } -- -- /* -- * Create a CONFIG transaction to push the config changes -- * provided to the backend client. -- */ -- txn = mgmt_txn_create_new(0, MGMTD_TXN_TYPE_CONFIG); -- if (!txn) { -- _log_err("Failed to create CONFIG Transaction for downloading CONFIGs for client '%s'", -- adapter->name); -- mgmt_ds_unlock(ds_ctx); -- nb_config_diff_del_changes(adapter_cfgs); -- return -1; -- } -- -- _dbg("Created initial txn-id: %" PRIu64 " for BE client '%s'", txn->txn_id, -- adapter->name); -- /* -- * Set the changeset for transaction to commit and trigger the -- * commit request. -- */ -- memset(&adapter->cfg_stats, 0, sizeof(adapter->cfg_stats)); -- txn_req = mgmt_txn_req_alloc(txn, 0, MGMTD_TXN_PROC_COMMITCFG); -- txn_req->req.commit_cfg.src_ds_id = MGMTD_DS_NONE; -- txn_req->req.commit_cfg.src_ds_ctx = 0; -- txn_req->req.commit_cfg.dst_ds_id = MGMTD_DS_RUNNING; -- txn_req->req.commit_cfg.dst_ds_ctx = ds_ctx; -- txn_req->req.commit_cfg.validate_only = false; -- txn_req->req.commit_cfg.abort = false; -- txn_req->req.commit_cfg.init = true; -- txn_req->req.commit_cfg.cmt_stats = &adapter->cfg_stats; -- txn_req->req.commit_cfg.cfg_chgs = adapter_cfgs; -- -- /* -- * Trigger a COMMIT-CONFIG process. -- */ -- mgmt_txn_register_event(txn, MGMTD_TXN_EVENT_COMMITCFG); -- -- } else { -- /* -- * Check if any transaction is currently on-going that -- * involves this backend client. If so, report the transaction -- * has failed. -- */ -- FOREACH_TXN_IN_LIST (mgmt_txn_mm, txn) { -- /* TODO: update with operational state when that is -- * completed */ -- if (txn->type == MGMTD_TXN_TYPE_CONFIG) { -- cmtcfg_req = txn->commit_cfg_req -- ? &txn->commit_cfg_req->req -- .commit_cfg -- : NULL; -- if (cmtcfg_req && IS_IDBIT_SET(cmtcfg_req->clients, -- adapter->id)) { -- mgmt_txn_send_commit_cfg_reply( -- txn, MGMTD_INTERNAL_ERROR, -- "Backend daemon disconnected while processing commit!"); -- } -- } -- } -- } -- -- return 0; --} -- --int mgmt_txn_notify_be_txn_reply(uint64_t txn_id, bool create, bool success, -- struct mgmt_be_client_adapter *adapter) --{ -- struct mgmt_txn_ctx *txn; -- struct mgmt_commit_cfg_req *cmtcfg_req = NULL; -- -- txn = mgmt_txn_id2ctx(txn_id); -- if (!txn || txn->type != MGMTD_TXN_TYPE_CONFIG) -- return -1; -- -- if (!create && !txn->commit_cfg_req) -- return 0; -- -- assert(txn->commit_cfg_req); -- cmtcfg_req = &txn->commit_cfg_req->req.commit_cfg; -- if (create) { -- if (success) { -- /* -- * Done with TXN_CREATE. Move the backend client to -- * next phase. -- */ -- assert(cmtcfg_req->phase == -- MGMTD_COMMIT_PHASE_TXN_CREATE); -- -- /* -- * Send CFGDATA_CREATE-REQs to the backend immediately. -- */ -- mgmt_txn_send_be_cfg_data(txn, adapter); -- } else { -- mgmt_txn_send_commit_cfg_reply( -- txn, MGMTD_INTERNAL_ERROR, -- "Internal error! Failed to initiate transaction at backend!"); -- } -- } -- -- return 0; --} -- --int mgmt_txn_notify_be_cfg_reply(uint64_t txn_id, bool success, const char *error_if_any, -- struct mgmt_be_client_adapter *adapter) --{ -- struct mgmt_txn_ctx *txn; -- struct mgmt_commit_cfg_req *cmtcfg_req; -- -- txn = mgmt_txn_id2ctx(txn_id); -- if (!txn) { -- _log_err("CFG_REPLY from '%s' failed no TXN for txn-id: %Lu", adapter->name, txn_id); -- return -1; -- } -- if (txn->type != MGMTD_TXN_TYPE_CONFIG) { -- _log_err("CFG_REPLY from '%s' failed wrong txn TYPE %u for txn-id: %Lu", -- adapter->name, txn->type, txn_id); -- return -1; -- } -- -- if (!txn->commit_cfg_req) { -- _log_err("CFG_REPLY from '%s' failed no COMMITCFG_REQ for txn-id: %Lu", -- adapter->name, txn_id); -- return -1; - } -- cmtcfg_req = &txn->commit_cfg_req->req.commit_cfg; -- -- if (!success) { -- _log_err("CFG_REQ sent to '%s' failed txn-id: %" PRIu64 " err: %s", adapter->name, -- txn->txn_id, error_if_any ? error_if_any : "None"); -- mgmt_txn_send_commit_cfg_reply(txn, MGMTD_VALIDATION_ERROR, -- error_if_any -- ? error_if_any -- : "config validation failed by backend daemon"); -- return 0; -- } -- -- _dbg("CFG_REQ sent to '%s' was successful txn-id: %" PRIu64 " err: %s", adapter->name, -- txn->txn_id, error_if_any ? error_if_any : "None"); -- -- cmtcfg_req->be_phase[adapter->id] = MGMTD_COMMIT_PHASE_APPLY_CFG; -+ get_tree = as_get_tree(txn_req); - -- mgmt_try_move_commit_to_next_phase(txn, cmtcfg_req); -- -- return 0; --} -- --int mgmt_txn_notify_be_cfg_apply_reply(uint64_t txn_id, bool success, -- char *error_if_any, -- struct mgmt_be_client_adapter *adapter) --{ -- struct mgmt_txn_ctx *txn; -- struct mgmt_commit_cfg_req *cmtcfg_req = NULL; -- -- txn = mgmt_txn_id2ctx(txn_id); -- if (!txn || txn->type != MGMTD_TXN_TYPE_CONFIG || !txn->commit_cfg_req) -- return -1; -- -- cmtcfg_req = &txn->commit_cfg_req->req.commit_cfg; -- -- if (!success) { -- _log_err("CFGDATA_APPLY_REQ sent to '%s' failed txn-id: %" PRIu64 " err: %s", -- adapter->name, txn->txn_id, error_if_any ? error_if_any : "None"); -- mgmt_txn_send_commit_cfg_reply( -- txn, MGMTD_INTERNAL_ERROR, -- error_if_any -- ? error_if_any -- : "Internal error! Failed to apply config data on backend!"); -- return 0; -+ /* store the result */ -+ err = lyd_parse_data_mem(ly_native_ctx, (const char *)data_msg->result, -+ data_msg->result_type, LYD_PARSE_STRICT | LYD_PARSE_ONLY, -+ 0 /*LYD_VALIDATE_OPERATIONAL*/, &tree); -+ if (err) { -+ _log_err("GETTREE reply from %s for txn-id %" PRIu64 " req_id %" PRIu64 -+ " error parsing result of type %u", -+ adapter->name, txn_id, req_id, data_msg->result_type); - } -+ if (!err) { -+ /* TODO: we could merge ly_errs here if it's not binary */ - -- cmtcfg_req->be_phase[adapter->id] = MGMTD_COMMIT_PHASE_TXN_DELETE; -- -- /* -- * All configuration for the specific backend has been applied. -- * Send TXN-DELETE to wrap up the transaction for this backend. -- */ -- SET_FLAG(adapter->flags, MGMTD_BE_ADAPTER_FLAGS_CFG_SYNCED); -- mgmt_txn_send_be_txn_delete(txn, adapter); -+ if (!get_tree->client_results) -+ get_tree->client_results = tree; -+ else -+ err = lyd_merge_siblings(&get_tree->client_results, tree, -+ LYD_MERGE_DESTRUCT); -+ if (err) { -+ _log_err("GETTREE reply from %s for txn-id %" PRIu64 " req_id %" PRIu64 -+ " error merging result", -+ adapter->name, txn_id, req_id); -+ } -+ } -+ if (!get_tree->partial_error) -+ get_tree->partial_error = (data_msg->partial_error ? data_msg->partial_error -+ : (int)err); - -- mgmt_try_move_commit_to_next_phase(txn, cmtcfg_req); -- if (mm->perf_stats_en) -- gettimeofday(&cmtcfg_req->cmt_stats->apply_cfg_end, NULL); -+ if (!data_msg->more) -+ UNSET_IDBIT(get_tree->clients_wait, id); - -- return 0; -+ /* check if done */ -+ if (!get_tree->clients_wait) -+ txn_get_tree_data_done(get_tree); - } - - /** -@@ -1676,21 +298,19 @@ int mgmt_txn_send_get_tree(uint64_t txn_id, uint64_t req_id, uint64_t clients, - uint32_t wd_options, bool simple_xpath, struct lyd_node **ylib, - const char *xpath) - { -+ struct mgmt_be_client_adapter *adapter; - struct mgmt_msg_get_tree *msg; -- struct mgmt_txn_ctx *txn; -- struct mgmt_txn_req *txn_req; - struct txn_req_get_tree *get_tree; -+ struct mgmt_txn *txn; - enum mgmt_be_client_id id; - ssize_t slen = strlen(xpath); - int ret; - -- txn = mgmt_txn_id2ctx(txn_id); -- if (!txn) -- return -1; -+ txn = txn_lookup(txn_id); -+ assert(txn && txn->type == MGMTD_TXN_TYPE_SHOW); - - /* If error in this function below here, be sure to free the req */ -- txn_req = mgmt_txn_req_alloc(txn, req_id, MGMTD_TXN_PROC_GETTREE); -- get_tree = txn_req->req.get_tree; -+ get_tree = txn_req_get_tree_alloc(txn, req_id); - get_tree->result_type = result_type; - get_tree->wd_options = wd_options; - get_tree->exact = CHECK_FLAG(flags, GET_DATA_FLAG_EXACT); -@@ -1702,10 +322,9 @@ int mgmt_txn_send_get_tree(uint64_t txn_id, uint64_t req_id, uint64_t clients, - * If the requested datastore is operational, get the config - * from running. - */ -- struct mgmt_ds_ctx *ds = -- mgmt_ds_get_ctx_by_id(mm, ds_id == MGMTD_DS_OPERATIONAL -- ? MGMTD_DS_RUNNING -- : ds_id); -+ struct mgmt_ds_ctx *ds = mgmt_ds_get_ctx_by_id(mm, ds_id == MGMTD_DS_OPERATIONAL -+ ? MGMTD_DS_RUNNING -+ : ds_id); - struct nb_config *config = mgmt_ds_get_nb_config(ds); - - if (config) { -@@ -1726,18 +345,16 @@ int mgmt_txn_send_get_tree(uint64_t txn_id, uint64_t req_id, uint64_t clients, - */ - if (set->count == 1) { - err = lyd_dup_single(set->dnodes[0], NULL, -- LYD_DUP_WITH_PARENTS | -- LYD_DUP_WITH_FLAGS | -+ LYD_DUP_WITH_PARENTS | LYD_DUP_WITH_FLAGS | - LYD_DUP_RECURSIVE, - &get_tree->client_results); - if (!err) - while (get_tree->client_results->parent) -- get_tree->client_results = lyd_parent( -- get_tree->client_results); -+ get_tree->client_results = -+ lyd_parent(get_tree->client_results); - } else if (set->count > 1) { - err = lyd_dup_siblings(config->dnode, NULL, -- LYD_DUP_RECURSIVE | -- LYD_DUP_WITH_FLAGS, -+ LYD_DUP_RECURSIVE | LYD_DUP_WITH_FLAGS, - &get_tree->client_results); - if (!err) - get_tree->simple_xpath = false; -@@ -1762,9 +379,10 @@ state: - } - - /* If we are only getting config, we are done */ -- if (!CHECK_FLAG(flags, GET_DATA_FLAG_STATE) || -- ds_id != MGMTD_DS_OPERATIONAL || !clients) -- return txn_get_tree_data_done(txn, txn_req); -+ if (!CHECK_FLAG(flags, GET_DATA_FLAG_STATE) || ds_id != MGMTD_DS_OPERATIONAL || !clients) { -+ txn_get_tree_data_done(get_tree); -+ return 0; -+ } - - msg = mgmt_msg_native_alloc_msg(struct mgmt_msg_get_tree, slen + 1, - MTYPE_MSG_NATIVE_GET_TREE); -@@ -1776,99 +394,146 @@ state: - strlcpy(msg->xpath, xpath, slen + 1); - - assert(clients); -- FOREACH_BE_CLIENT_BITS (id, clients) { -- ret = mgmt_be_send_native(id, msg); -+ FOREACH_BE_ADAPTER_BITS (id, adapter, clients) { -+ ret = mgmt_be_adapter_send(adapter, msg); - if (ret) { -- _log_err("Could not send get-tree message to backend client %s", -- mgmt_be_client_id2name(id)); -+ darr_in_sprintf(get_tree->req.err_info, "Failed to send GET-DATA to %s", -+ adapter->name); - continue; - } -- -- _dbg("Sent get-tree req to backend client %s", mgmt_be_client_id2name(id)); -- -- /* record that we sent the request to the client */ -- get_tree->sent_clients |= (1u << id); -+ SET_IDBIT(get_tree->clients, id); - } - - mgmt_msg_native_free_msg(msg); - -- /* Return if we didn't send any messages to backends */ -- if (!get_tree->sent_clients) -- return txn_get_tree_data_done(txn, txn_req); -- -- /* Start timeout timer - pulled out of register event code so we can -- * pass a different arg -- */ -- event_add_timer(mgmt_txn_tm, txn_get_tree_timeout, txn_req, -- MGMTD_TXN_GET_TREE_MAX_DELAY_SEC, -- &txn->get_tree_timeout); -+ get_tree->clients_wait = get_tree->clients; -+ if (!get_tree->clients_wait) -+ txn_get_tree_data_done(get_tree); -+ else -+ event_add_timer(mm->master, txn_get_tree_timeout, get_tree, -+ MGMTD_TXN_GET_TREE_MAX_DELAY_SEC, &get_tree->req.timeout); - return 0; - } - --int mgmt_txn_send_edit(uint64_t txn_id, uint64_t req_id, enum mgmt_ds_id ds_id, -- struct mgmt_ds_ctx *ds_ctx, enum mgmt_ds_id commit_ds_id, -- struct mgmt_ds_ctx *commit_ds_ctx, bool unlock, bool commit, -- LYD_FORMAT request_type, uint8_t flags, uint8_t operation, -- const char *xpath, const char *data) -+/* =============== */ -+/* RPC BACKEND TXN */ -+/* =============== */ -+ -+static void txn_rpc_done(struct txn_req_rpc *rpc) - { -- struct mgmt_txn_ctx *txn; -- struct mgmt_edit_req *edit; -- struct nb_config *nb_config; -- char errstr[BUFSIZ]; -- int ret; -+ struct txn_req *txn_req = as_txn_req(rpc); -+ struct mgmt_txn *txn = txn_req->txn; -+ uint64_t req_id = rpc->req.req_id; - -- txn = mgmt_txn_id2ctx(txn_id); -- if (!txn) -- return -1; -+ /* cancel timer and send reply onward */ -+ event_cancel(&rpc->req.timeout); -+ -+ if (txn_req->err_info) -+ mgmt_fe_adapter_txn_error(txn->txn_id, req_id, false, txn_req->error ?: -EINVAL, -+ txn_req->err_info); -+ else -+ mgmt_fe_adapter_send_rpc_reply(txn->session_id, txn->txn_id, req_id, -+ rpc->result_type, rpc->client_results); - -- edit = XCALLOC(MTYPE_MGMTD_TXN_REQ, sizeof(struct mgmt_edit_req)); -+ /* we're done with the request */ -+ txn_req_free(txn_req); -+} -+ -+static void txn_rpc_timeout(struct event *event) -+{ -+ struct txn_req_rpc *rpc = EVENT_ARG(event); -+ struct mgmt_txn *txn = rpc->req.txn; - -- nb_config = mgmt_ds_get_nb_config(ds_ctx); -- assert(nb_config); -+ _log_err("Backend timeout txn-id: %" PRIu64 " ending rpc", txn->txn_id); - -- /* XXX Should we do locking here? */ -+ rpc->req.error = -ETIMEDOUT; -+ rpc->req.err_info = darr_strdup("RPC on the backend timed-out"); -+ txn_rpc_done(rpc); -+} - -- ret = nb_candidate_edit_tree(nb_config, operation, request_type, xpath, -- data, &edit->created, edit->xpath_created, -- errstr, sizeof(errstr)); -- if (ret) -- goto reply; -+static void txn_rpc_handle_error_reply(struct txn_req_rpc *rpc, -+ struct mgmt_be_client_adapter *adapter, int error, -+ const char *errstr) -+{ -+ UNSET_IDBIT(rpc->clients_wait, adapter->id); -+ if (errstr) -+ darr_in_strdup(rpc->req.err_info, errstr); -+ /* check if done */ -+ if (!rpc->clients_wait) -+ txn_rpc_done(rpc); -+} - -- if (commit) { -- edit->unlock = unlock; -+void mgmt_txn_handle_rpc_reply(struct mgmt_be_client_adapter *adapter, -+ struct mgmt_msg_rpc_reply *reply_msg, size_t msg_len) -+{ -+ uint64_t txn_id = reply_msg->refer_id; -+ uint64_t req_id = reply_msg->req_id; -+ enum mgmt_be_client_id id = adapter->id; -+ struct mgmt_txn *txn = txn_lookup(txn_id); -+ struct txn_req *txn_req; -+ struct txn_req_rpc *rpc; -+ struct lyd_node *tree; -+ size_t data_len = msg_len - sizeof(*reply_msg); -+ LY_ERR err = LY_SUCCESS; - -- mgmt_txn_send_commit_config_req(txn_id, req_id, ds_id, ds_ctx, commit_ds_id, -- commit_ds_ctx, false, false, true, false, edit); -- return 0; -+ if (!txn) { -+ _log_err("RPC reply from %s for a missing txn-id %Lu", adapter->name, txn_id); -+ return; - } --reply: -- mgmt_fe_adapter_send_edit_reply(txn->session_id, txn->txn_id, req_id, -- unlock, commit, edit->created, -- edit->xpath_created, -- errno_from_nb_error(ret), errstr); - -- XFREE(MTYPE_MGMTD_TXN_REQ, edit); -+ /* Find the request. */ -+ txn_req = txn_txn_req(txn, req_id); -+ if (!txn_req || txn_req->req_type != TXN_REQ_TYPE_RPC) { -+ _log_err("RPC reply from %s for txn-id %Lu missing req_id %Lu", adapter->name, -+ txn_id, req_id); -+ return; -+ } -+ rpc = as_rpc(txn_req); - -- return 0; -+ tree = NULL; -+ if (data_len) -+ err = yang_parse_rpc(rpc->xpath, reply_msg->result_type, reply_msg->data, true, -+ &tree); -+ if (err) { -+ _log_err("RPC reply from %s for txn-id %" PRIu64 " req_id %" PRIu64 -+ " error parsing result of type %u: %s", -+ adapter->name, txn_id, req_id, reply_msg->result_type, ly_strerrcode(err)); -+ } -+ if (!err && tree) { -+ if (!rpc->client_results) -+ rpc->client_results = tree; -+ else -+ err = lyd_merge_siblings(&rpc->client_results, tree, LYD_MERGE_DESTRUCT); -+ if (err) { -+ _log_err("RPC reply from %s for txn-id %" PRIu64 " req_id %" PRIu64 -+ " error merging result: %s", -+ adapter->name, txn_id, req_id, ly_strerrcode(err)); -+ } -+ } -+ if (err) -+ darr_in_strdup(rpc->req.err_info, "Cannot parse result from the backend"); -+ -+ UNSET_IDBIT(rpc->clients_wait, id); -+ -+ /* check if done yet */ -+ if (!rpc->clients_wait) -+ txn_rpc_done(rpc); - } - --int mgmt_txn_send_rpc(uint64_t txn_id, uint64_t req_id, uint64_t clients, -- LYD_FORMAT result_type, const char *xpath, -- const char *data, size_t data_len) -+void mgmt_txn_send_rpc(uint64_t txn_id, uint64_t req_id, uint64_t clients, LYD_FORMAT result_type, -+ const char *xpath, const char *data, size_t data_len) - { -- struct mgmt_txn_ctx *txn; -- struct mgmt_txn_req *txn_req; -+ struct mgmt_be_client_adapter *adapter; -+ struct mgmt_txn *txn; - struct mgmt_msg_rpc *msg; - struct txn_req_rpc *rpc; -- uint64_t id; -+ enum mgmt_be_client_id id; - int ret; - -- txn = mgmt_txn_id2ctx(txn_id); -- if (!txn) -- return -1; -+ txn = txn_lookup(txn_id); -+ assert(txn && txn->type == MGMTD_TXN_TYPE_RPC); - -- txn_req = mgmt_txn_req_alloc(txn, req_id, MGMTD_TXN_PROC_RPC); -- rpc = txn_req->req.rpc; -+ rpc = txn_req_rpc_alloc(txn, req_id); - rpc->xpath = XSTRDUP(MTYPE_MGMTD_XPATH, xpath); - rpc->result_type = result_type; - -@@ -1884,38 +549,37 @@ int mgmt_txn_send_rpc(uint64_t txn_id, uint64_t req_id, uint64_t clients, - mgmt_msg_native_append(msg, data, data_len); - - assert(clients); -- FOREACH_BE_CLIENT_BITS (id, clients) { -- ret = mgmt_be_send_native(id, msg); -+ FOREACH_BE_ADAPTER_BITS (id, adapter, clients) { -+ ret = mgmt_be_adapter_send(adapter, msg); - if (ret) { -- _log_err("Could not send rpc message to backend client %s", -- mgmt_be_client_id2name(id)); -+ darr_in_sprintf(rpc->req.err_info, "Failed to send RPC to %s", -+ adapter->name); - continue; - } -- -- _dbg("Sent rpc req to backend client %s", mgmt_be_client_id2name(id)); -- -- /* record that we sent the request to the client */ -- rpc->sent_clients |= (1u << id); -+ SET_IDBIT(rpc->clients, id); - } - - mgmt_msg_native_free_msg(msg); - -- if (!rpc->sent_clients) -- return txn_rpc_done(txn, txn_req); -- -- event_add_timer(mgmt_txn_tm, txn_rpc_timeout, txn_req, -- MGMTD_TXN_RPC_MAX_DELAY_SEC, &txn->rpc_timeout); -- -- return 0; -+ rpc->clients_wait = rpc->clients; -+ if (!rpc->clients_wait) -+ txn_rpc_done(rpc); -+ else -+ event_add_timer(mm->master, txn_rpc_timeout, rpc, MGMTD_TXN_RPC_MAX_DELAY_SEC, -+ &rpc->req.timeout); - } - --int mgmt_txn_send_notify_selectors(uint64_t req_id, uint64_t session_id, uint64_t clients, -- const char **selectors) -+/* =========================== */ -+/* NOTIFY SELECTOR BACKEND TXN */ -+/* =========================== */ -+ -+void mgmt_txn_send_notify_selectors(uint64_t req_id, uint64_t session_id, uint64_t clients, -+ const char **selectors) - { -+ struct mgmt_be_client_adapter *adapter; - struct mgmt_msg_notify_select *msg; -+ enum mgmt_be_client_id id; - char **all_selectors = NULL; -- uint64_t id; -- int ret; - uint i; - - msg = mgmt_msg_native_alloc_msg(struct mgmt_msg_notify_select, 0, -@@ -1936,352 +600,255 @@ int mgmt_txn_send_notify_selectors(uint64_t req_id, uint64_t session_id, uint64_ - mgmt_msg_native_add_str(msg, selectors[i]); - - assert(clients); -- FOREACH_BE_CLIENT_BITS (id, clients) { -- /* make sure the backend is running/connected */ -- if (!mgmt_be_get_adapter_by_id(id)) -- continue; -- ret = mgmt_be_send_native(id, msg); -- if (ret) { -- _log_err("Could not send notify-select message to backend client %s", -- mgmt_be_client_id2name(id)); -+ FOREACH_BE_ADAPTER_BITS (id, adapter, clients) { -+ if (mgmt_be_adapter_send(adapter, msg)) { -+ /* -+ * Failed to send to this backend since we require -+ * reliable notifications from the backends we need to -+ * disconnect here. -+ */ -+ msg_conn_disconnect(adapter->conn, false); - continue; - } -- -- _dbg("Sent notify-select req to backend client %s", mgmt_be_client_id2name(id)); - } - mgmt_msg_native_free_msg(msg); - - if (all_selectors) - darr_free_free(all_selectors); -- -- return 0; - } - -+/* =============== */ -+/* TXN ERROR REPLY */ -+/* =============== */ -+ - /* - * Error reply from the backend client. -+ * -+ * NOTE: can disconnect (and delete) the backend. - */ --int mgmt_txn_notify_error(struct mgmt_be_client_adapter *adapter, -- uint64_t txn_id, uint64_t req_id, int error, -- const char *errstr) -+void mgmt_txn_handle_error_reply(struct mgmt_be_client_adapter *adapter, uint64_t txn_id, -+ uint64_t req_id, int error, const char *errstr) - { -- enum mgmt_be_client_id id = adapter->id; -- struct mgmt_txn_ctx *txn = mgmt_txn_id2ctx(txn_id); -- struct txn_req_get_tree *get_tree; -- struct txn_req_rpc *rpc; -- struct mgmt_txn_req *txn_req; -+ struct txn_req *txn_req; -+ struct mgmt_txn *txn; - -+ txn = txn_lookup(txn_id); - if (!txn) { -- _log_err("Error reply from %s cannot find txn-id %" PRIu64, adapter->name, txn_id); -- return -1; -+ _log_err("Error reply from %s cannot find txn-id %Lu", adapter->name, txn_id); -+ return; - } - -- if (txn->type == MGMTD_TXN_TYPE_CONFIG) { -- /* -- * Handle an error during a configuration transaction. -- */ -- txn_req = txn->commit_cfg_req; -- if (!txn_req) { -- _log_err("Error reply from %s for txn-id %Lu req_id %Lu but no commit config request", -- adapter->name, txn_id, req_id); -- return -1; -- } -- /* -- * Most of the C client code doesn't set req_id so handle this. -- * If we ever wanted to support multiple active CFG_REQ for under -- * txn (unlikely), we would need to fix this. -- */ -- if (req_id != 0 && txn_req->req_id != req_id) { -- _log_err("Error reply from %s for txn-id %Lu req_id %Lu does not match current config req_id %Lu", -- adapter->name, txn_id, req_id, txn_req->req_id); -- return -1; -- } -- switch (txn_req->req.commit_cfg.phase) { -- case MGMTD_COMMIT_PHASE_TXN_CREATE: -- /* This is a validation error.*/ -- return mgmt_txn_notify_be_cfg_reply(txn_id, false, errstr, adapter); -- case MGMTD_COMMIT_PHASE_PREPARE_CFG: -- case MGMTD_COMMIT_PHASE_APPLY_CFG: -- case MGMTD_COMMIT_PHASE_TXN_DELETE: -- /* Drop the connection these errors should never happen */ -- msg_conn_disconnect(adapter->conn, false); -+ TAILQ_FOREACH (txn_req, &txn->reqs, link) { -+ /* Clients do not set req_id for commit errors yet there's only -+ * one so this works for now. */ -+ if (req_id == 0 && txn_req->req_type == TXN_REQ_TYPE_COMMIT) - break; -- case MGMTD_COMMIT_PHASE_MAX: -- assert(!"should never have this phase"); -- } -- return 0; -- } -- -- /* -- * Find the non-config request. -- */ -- -- FOREACH_TXN_REQ_IN_LIST (&txn->get_tree_reqs, txn_req) -- if (txn_req->req_id == req_id) -+ else if (txn_req->req_id == req_id) - break; -- if (!txn_req) -- FOREACH_TXN_REQ_IN_LIST (&txn->rpc_reqs, txn_req) -- if (txn_req->req_id == req_id) -- break; -- -+ } - if (!txn_req) { -- _log_err("Error reply from %s for txn-id %" PRIu64 " cannot find req_id %" PRIu64, -+ _log_err("Error reply from %s for txn-id %Lu cannot find req_id %Lu", - adapter->name, txn_id, req_id); -- return -1; -+ return; - } - -- _log_err("Error reply from %s for txn-id %" PRIu64 " req_id %" PRIu64, adapter->name, -- txn_id, req_id); -+ _log_err("Error reply from %s for txn-id %Lu req_id %Lu", adapter->name, txn_id, req_id); - - switch (txn_req->req_type) { -- case MGMTD_TXN_PROC_GETTREE: -- get_tree = txn_req->req.get_tree; -- get_tree->recv_clients |= (1u << id); -- get_tree->partial_error = error; -- -- /* check if done yet */ -- if (get_tree->recv_clients != get_tree->sent_clients) -- return 0; -- return txn_get_tree_data_done(txn, txn_req); -- case MGMTD_TXN_PROC_RPC: -- rpc = txn_req->req.rpc; -- rpc->recv_clients |= (1u << id); -- if (errstr) { -- XFREE(MTYPE_MGMTD_ERR, rpc->errstr); -- rpc->errstr = XSTRDUP(MTYPE_MGMTD_ERR, errstr); -- } -- /* check if done yet */ -- if (rpc->recv_clients != rpc->sent_clients) -- return 0; -- return txn_rpc_done(txn, txn_req); -- -+ case TXN_REQ_TYPE_COMMIT: -+ txn_cfg_handle_error(txn_req, adapter, error, errstr); -+ return; -+ case TXN_REQ_TYPE_GETTREE: -+ txn_get_tree_handle_error_reply(as_get_tree(txn_req), adapter, error, errstr); -+ return; -+ case TXN_REQ_TYPE_RPC: -+ txn_rpc_handle_error_reply(as_rpc(txn_req), adapter, error, errstr); -+ return; - /* non-native message events */ -- case MGMTD_TXN_PROC_COMMITCFG: - default: - assert(!"non-native req type in native error path"); -- return -1; - } - } - --/* -- * Get-tree data from the backend client. -- */ --int mgmt_txn_notify_tree_data_reply(struct mgmt_be_client_adapter *adapter, -- struct mgmt_msg_tree_data *data_msg, -- size_t msg_len) -+/* ============== */ -+/* Txn Management */ -+/* ============== */ -+ -+static unsigned int txn_txn_id_hash(const struct mgmt_txn *txn) - { -- uint64_t txn_id = data_msg->refer_id; -- uint64_t req_id = data_msg->req_id; -+ uint64_t key = txn->txn_id; - -- enum mgmt_be_client_id id = adapter->id; -- struct mgmt_txn_ctx *txn = mgmt_txn_id2ctx(txn_id); -- struct mgmt_txn_req *txn_req; -- struct txn_req_get_tree *get_tree; -- struct lyd_node *tree = NULL; -- LY_ERR err; -+ return (uint32_t)key ^ (uint32_t)(key >> 32); -+} - -- if (!txn) { -- _log_err("GETTREE reply from %s for a missing txn-id %" PRIu64, adapter->name, -- txn_id); -- return -1; -- } -+static bool txn_txn_id_cmp(const struct mgmt_txn *a, const struct mgmt_txn *b) -+{ -+ return (a->txn_id == b->txn_id); -+} - -- /* Find the request. */ -- FOREACH_TXN_REQ_IN_LIST (&txn->get_tree_reqs, txn_req) -- if (txn_req->req_id == req_id) -- break; -- if (!txn_req) { -- _log_err("GETTREE reply from %s for txn-id %" PRIu64 " missing req_id %" PRIu64, -- adapter->name, txn_id, req_id); -- return -1; -- } -+struct mgmt_txn *txn_lookup(uint64_t txn_id) -+{ -+ struct mgmt_txn key = { 0 }; - -- get_tree = txn_req->req.get_tree; -+ if (!txn_id_tab) -+ return NULL; -+ key.txn_id = txn_id; -+ return hash_lookup(txn_id_tab, &key); -+} - -- /* store the result */ -- err = lyd_parse_data_mem(ly_native_ctx, (const char *)data_msg->result, -- data_msg->result_type, -- LYD_PARSE_STRICT | LYD_PARSE_ONLY, -- 0 /*LYD_VALIDATE_OPERATIONAL*/, &tree); -- if (err) { -- _log_err("GETTREE reply from %s for txn-id %" PRIu64 " req_id %" PRIu64 -- " error parsing result of type %u", -- adapter->name, txn_id, req_id, data_msg->result_type); -- } -- if (!err) { -- /* TODO: we could merge ly_errs here if it's not binary */ -+uint64_t mgmt_txn_get_session_id(uint64_t txn_id) -+{ -+ struct mgmt_txn *txn = txn_lookup(txn_id); - -- if (!get_tree->client_results) -- get_tree->client_results = tree; -- else -- err = lyd_merge_siblings(&get_tree->client_results, -- tree, LYD_MERGE_DESTRUCT); -- if (err) { -- _log_err("GETTREE reply from %s for txn-id %" PRIu64 " req_id %" PRIu64 -- " error merging result", -- adapter->name, txn_id, req_id); -- } -+ return txn ? txn->session_id : MGMTD_SESSION_ID_NONE; -+} -+ -+static void txn_incref(struct mgmt_txn *txn, const char *file, int line) -+{ -+ txn->refcount++; -+ _dbg("TXN-INCREF %s txn-id: %Lu refcnt: %d file: %s line: %d)", -+ mgmt_txn_type2str(txn->type), txn->txn_id, txn->refcount, file, line); -+} -+ -+void txn_decref(struct mgmt_txn *txn, const char *file, int line) -+{ -+ assert(txn && txn->refcount); -+ -+ txn->refcount--; -+ _dbg("TXN-DECREF %s txn-id: %Lu refcnt: %d file: %s line: %d", -+ mgmt_txn_type2str(txn->type), txn->txn_id, txn->refcount, file, line); -+ if (!txn->refcount) { -+ if (txn->type == MGMTD_TXN_TYPE_CONFIG) -+ if (txn_config_txn == txn) -+ txn_config_txn = NULL; -+ hash_release(txn_id_tab, txn); -+ TAILQ_REMOVE(&txn_txns, txn, link); -+ -+ _dbg("Deleted %s txn-id: %Lu session-id: %Lu", mgmt_txn_type2str(txn->type), -+ txn->txn_id, txn->session_id); -+ -+ XFREE(MTYPE_MGMTD_TXN, txn); - } -- if (!get_tree->partial_error) -- get_tree->partial_error = (data_msg->partial_error -- ? data_msg->partial_error -- : (int)err); -+} - -- if (!data_msg->more) -- get_tree->recv_clients |= (1u << id); -+struct mgmt_txn *txn_create(enum mgmt_txn_type type) -+{ -+ struct mgmt_txn *txn; - -- /* check if done yet */ -- if (get_tree->recv_clients != get_tree->sent_clients) -- return 0; -+ txn = XCALLOC(MTYPE_MGMTD_TXN, sizeof(struct mgmt_txn)); -+ txn->type = type; -+ TAILQ_INSERT_TAIL(&txn_txns, txn, link); -+ TAILQ_INIT(&txn->reqs); -+ txn->txn_id = txn_next_id++; -+ hash_get(txn_id_tab, txn, hash_alloc_intern); -+ -+ _dbg("Added new '%s' txn-id: %" PRIu64, mgmt_txn_type2str(type), txn->txn_id); - -- return txn_get_tree_data_done(txn, txn_req); -+ TXN_INCREF(txn); -+ return txn; - } - --int mgmt_txn_notify_rpc_reply(struct mgmt_be_client_adapter *adapter, -- struct mgmt_msg_rpc_reply *reply_msg, -- size_t msg_len) -+uint64_t mgmt_create_txn(uint64_t session_id, enum mgmt_txn_type type) - { -- uint64_t txn_id = reply_msg->refer_id; -- uint64_t req_id = reply_msg->req_id; -- enum mgmt_be_client_id id = adapter->id; -- struct mgmt_txn_ctx *txn = mgmt_txn_id2ctx(txn_id); -- struct mgmt_txn_req *txn_req; -- struct txn_req_rpc *rpc; -- struct lyd_node *tree; -- size_t data_len = msg_len - sizeof(*reply_msg); -- LY_ERR err = LY_SUCCESS; -+ struct mgmt_txn *txn; - -- if (!txn) { -- _log_err("RPC reply from %s for a missing txn-id %" PRIu64, adapter->name, txn_id); -- return -1; -- } -+ /* Do not allow multiple (external) config transactions */ -+ if (type == MGMTD_TXN_TYPE_CONFIG && txn_config_txn) -+ return MGMTD_TXN_ID_NONE; - -- /* Find the request. */ -- FOREACH_TXN_REQ_IN_LIST (&txn->rpc_reqs, txn_req) -- if (txn_req->req_id == req_id) -- break; -- if (!txn_req) { -- _log_err("RPC reply from %s for txn-id %" PRIu64 " missing req_id %" PRIu64, -- adapter->name, txn_id, req_id); -- return -1; -- } -+ /* Find existing txn for this session and type */ -+ TAILQ_FOREACH (txn, &txn_txns, link) -+ if (txn->session_id == session_id && txn->type == type) -+ return txn->txn_id; - -- rpc = txn_req->req.rpc; -+ txn = txn_create(type); -+ txn->session_id = session_id; -+ if (type == MGMTD_TXN_TYPE_CONFIG) -+ txn_config_txn = txn; -+ return txn->txn_id; -+} - -- tree = NULL; -- if (data_len) -- err = yang_parse_rpc(rpc->xpath, reply_msg->result_type, -- reply_msg->data, true, &tree); -- if (err) { -- _log_err("RPC reply from %s for txn-id %" PRIu64 " req_id %" PRIu64 -- " error parsing result of type %u: %s", -- adapter->name, txn_id, req_id, reply_msg->result_type, ly_strerrcode(err)); -- } -- if (!err && tree) { -- if (!rpc->client_results) -- rpc->client_results = tree; -- else -- err = lyd_merge_siblings(&rpc->client_results, tree, -- LYD_MERGE_DESTRUCT); -- if (err) { -- _log_err("RPC reply from %s for txn-id %" PRIu64 " req_id %" PRIu64 -- " error merging result: %s", -- adapter->name, txn_id, req_id, ly_strerrcode(err)); -- } -- } -- if (err) { -- XFREE(MTYPE_MGMTD_ERR, rpc->errstr); -- rpc->errstr = XSTRDUP(MTYPE_MGMTD_ERR, -- "Cannot parse result from the backend"); -- } -+void mgmt_destroy_txn(uint64_t *txn_id) -+{ -+ struct mgmt_txn *txn; - -- rpc->recv_clients |= (1u << id); -+ if (*txn_id == MGMTD_TXN_ID_NONE) -+ return; - -- /* check if done yet */ -- if (rpc->recv_clients != rpc->sent_clients) -- return 0; -+ txn = txn_lookup(*txn_id); -+ if (!txn) -+ return; -+ -+ TXN_DECREF(txn); -+ *txn_id = MGMTD_TXN_ID_NONE; -+} -+ -+bool mgmt_txn_config_in_progress(void) -+{ -+ return txn_config_txn != NULL; -+} -+ -+int mgmt_txn_handle_be_adapter_connect(struct mgmt_be_client_adapter *adapter, bool connect) -+{ -+ struct mgmt_txn *txn, *next; - -- return txn_rpc_done(txn, txn_req); -+ if (connect) -+ txn_cfg_be_client_connect(adapter); -+ else { -+ /* -+ * Check if any transaction is currently on-going that -+ * involves this backend client. If so check if we can now -+ * advance that configuration. -+ */ -+ TAILQ_FOREACH_SAFE (txn, &txn_txns, link, next) { -+ /* XXX update to handle get-tree and RPC too! */ -+ if (txn->type == MGMTD_TXN_TYPE_CONFIG) -+ txn_cfg_txn_be_client_disconnect(txn, adapter); -+ } -+ } -+ -+ return 0; - } - - void mgmt_txn_status_write(struct vty *vty) - { -- struct mgmt_txn_ctx *txn; -+ struct mgmt_txn *txn; -+ uint count = 0; - - vty_out(vty, "MGMTD Transactions\n"); - -- FOREACH_TXN_IN_LIST (mgmt_txn_mm, txn) { -+ TAILQ_FOREACH (txn, &txn_txns, link) { - vty_out(vty, " Txn: \t\t\t0x%p\n", txn); - vty_out(vty, " Txn-Id: \t\t\t%" PRIu64 "\n", txn->txn_id); -- vty_out(vty, " Session-Id: \t\t%" PRIu64 "\n", -- txn->session_id); -- vty_out(vty, " Type: \t\t\t%s\n", -- mgmt_txn_type2str(txn->type)); -+ vty_out(vty, " Session-Id: \t\t%" PRIu64 "\n", txn->session_id); -+ vty_out(vty, " Type: \t\t\t%s\n", mgmt_txn_type2str(txn->type)); - vty_out(vty, " Ref-Count: \t\t\t%d\n", txn->refcount); -+ count++; - } -- vty_out(vty, " Total: %d\n", -- (int)mgmt_txns_count(&mgmt_txn_mm->txn_list)); -+ vty_out(vty, " Total: %u\n", count); - } - --int mgmt_txn_rollback_trigger_cfg_apply(struct mgmt_ds_ctx *src_ds_ctx, -- struct mgmt_ds_ctx *dst_ds_ctx) -+void mgmt_txn_init(void) - { -- static struct nb_config_cbs changes; -- static struct mgmt_commit_stats dummy_stats; -- -- struct nb_config_cbs *cfg_chgs = NULL; -- struct mgmt_txn_ctx *txn; -- struct mgmt_txn_req *txn_req; -- -- memset(&changes, 0, sizeof(changes)); -- memset(&dummy_stats, 0, sizeof(dummy_stats)); -- /* -- * This could be the case when the config is directly -- * loaded onto the candidate DS from a file. Get the -- * diff from a full comparison of the candidate and -- * running DSs. -- */ -- nb_config_diff(mgmt_ds_get_nb_config(dst_ds_ctx), -- mgmt_ds_get_nb_config(src_ds_ctx), &changes); -- cfg_chgs = &changes; -- -- if (RB_EMPTY(nb_config_cbs, cfg_chgs)) { -- /* -- * This means there's no changes to commit whatsoever -- * is the source of the changes in config. -- */ -- return -1; -- } -+ assert(txn_next_id == 0); -+ txn_next_id = 1; -+ txn_id_tab = hash_create((unsigned int (*)(const void *))txn_txn_id_hash, -+ (bool (*)(const void *, const void *))txn_txn_id_cmp, -+ "Mgmt Txns by txn-id"); -+} - -- /* -- * Create a CONFIG transaction to push the config changes -- * provided to the backend client. -- */ -- txn = mgmt_txn_create_new(0, MGMTD_TXN_TYPE_CONFIG); -- if (!txn) { -- _log_err("Failed to create CONFIG Transaction for downloading CONFIGs"); -- return -1; -+void mgmt_txn_destroy(void) -+{ -+ struct mgmt_txn *txn, *next; -+ -+ TAILQ_FOREACH_SAFE (txn, &txn_txns, link, next) { -+ /* Free all txn_reqs associated with this txn */ -+ while (!TAILQ_EMPTY(&txn->reqs)) -+ txn_req_free(TAILQ_FIRST(&txn->reqs)); -+ assert(txn->refcount == 1); -+ TXN_DECREF(txn); - } - -- _dbg("Created rollback txn-id: %" PRIu64, txn->txn_id); -- -- /* -- * Set the changeset for transaction to commit and trigger the commit -- * request. -- */ -- txn_req = mgmt_txn_req_alloc(txn, 0, MGMTD_TXN_PROC_COMMITCFG); -- txn_req->req.commit_cfg.src_ds_id = MGMTD_DS_CANDIDATE; -- txn_req->req.commit_cfg.src_ds_ctx = src_ds_ctx; -- txn_req->req.commit_cfg.dst_ds_id = MGMTD_DS_RUNNING; -- txn_req->req.commit_cfg.dst_ds_ctx = dst_ds_ctx; -- txn_req->req.commit_cfg.validate_only = false; -- txn_req->req.commit_cfg.abort = false; -- txn_req->req.commit_cfg.rollback = true; -- txn_req->req.commit_cfg.cmt_stats = &dummy_stats; -- txn_req->req.commit_cfg.cfg_chgs = cfg_chgs; -- -- /* -- * Trigger a COMMIT-CONFIG process. -- */ -- mgmt_txn_register_event(txn, MGMTD_TXN_EVENT_COMMITCFG); -- return 0; -+ if (txn_id_tab) -+ hash_free(txn_id_tab); - } -diff --git a/mgmtd/mgmt_txn.h b/mgmtd/mgmt_txn.h -index f472d4f839..54ebbfd803 100644 ---- a/mgmtd/mgmt_txn.h -+++ b/mgmtd/mgmt_txn.h -@@ -4,6 +4,7 @@ - * - * Copyright (C) 2021 Vmware, Inc. - * Pushpasis Sarkar <spushpasis@vmware.com> -+ * Copyright (c) 2025, LabN Consulting, L.L.C. - */ - - #ifndef _FRR_MGMTD_TXN_H_ -@@ -27,10 +28,7 @@ - - #define MGMTD_TXN_ID_NONE 0 - --PREDECL_LIST(mgmt_txns); -- - struct mgmt_master; --struct mgmt_edit_req; - - enum mgmt_txn_type { - MGMTD_TXN_TYPE_NONE = 0, -@@ -82,105 +80,59 @@ static inline int16_t errno_from_nb_error(enum nb_error ret) - } - } - -+/* --------------------- */ -+/* Gernal Txn Functions. */ -+/* --------------------- */ - --/* Initialise transaction module. */ --extern int mgmt_txn_init(struct mgmt_master *cm, struct event_loop *tm); -- --/* Destroy the transaction module. */ -+extern void mgmt_txn_init(void); - extern void mgmt_txn_destroy(void); - --/* -- * Check if configuration transaction is in progress. -- * -- * Returns: -- * true if in-progress, false otherwise. -- */ --extern bool mgmt_config_txn_in_progress(void); -+extern enum mgmt_result nb_error_to_mgmt_result(enum nb_error error); - --/** -- * Get the session ID associated with the given ``txn-id``. -- * -- */ --extern uint64_t mgmt_txn_get_session_id(uint64_t txn_id); -+/* Dump transaction status to vty */ -+extern void mgmt_txn_status_write(struct vty *vty); - --/* -- * Create transaction. -- * -- * session_id -- * Session ID. -- * -- * type -- * Transaction type (CONFIG/SHOW/NONE) -- * -- * Returns: -- * transaction ID. -- */ --extern uint64_t mgmt_create_txn(uint64_t session_id, enum mgmt_txn_type type); -+/* ---------------------------------------- */ -+/* Txn API for Backend messages and events. */ -+/* ---------------------------------------- */ - --/* -- * Destroy transaction. -- * -- * txn_id -- * Unique transaction identifier. -- */ -+extern bool mgmt_txn_config_in_progress(void); -+extern uint64_t mgmt_txn_get_session_id(uint64_t txn_id); -+extern uint64_t mgmt_create_txn(uint64_t session_id, enum mgmt_txn_type type); - extern void mgmt_destroy_txn(uint64_t *txn_id); - --/* -- * Send commit-config request to be processed later in transaction. -- * -- * txn_id -- * Unique transaction identifier. -- * -- * req_id -- * Unique transaction request identifier. -- * -- * src_ds_id -- * Source datastore ID. -- * -- * src_ds_hndl -- * Source Datastore handle. -- * -- * validate_only -- * TRUE if commit request needs to be validated only, FALSE otherwise. -- * -- * abort -- * TRUE if need to restore Src DS back to Dest DS, FALSE otherwise. -- * -- * implicit -- * TRUE if the commit is implicit, FALSE otherwise. -- * -- * unlock -- * pass back in the commit config reply -- * -- * edit -- * Additional info when triggered from native edit request. -- * -- * Returns: -- * 0 on success, -1 on failures. -- */ --extern int -+/** -+ * mgmt_txn_send_commit_config_req() - Send commit-config to apply the config changes. -+ * @txn_id: Unique transaction identifier. -+ * @req_id: Unique transaction request identifier. -+ * @src_ds_id: Source datastore ID. -+ * @src_ds_hndl: Source Datastore handle. -+ * @validate_only: TRUE if commit request needs to be validated only, FALSE otherwise. -+ * @abort: TRUE if need to restore Src DS back to Dest DS, FALSE otherwise. -+ * @implicit: TRUE if the commit is implicit, FALSE otherwise. -+ * @unlock: pass back in the commit config reply -+ * @edit: Additional info when triggered from native edit request. -+ */ -+extern void - mgmt_txn_send_commit_config_req(uint64_t txn_id, uint64_t req_id, enum mgmt_ds_id src_ds_id, - struct mgmt_ds_ctx *dst_ds_ctx, enum mgmt_ds_id dst_ds_id, - struct mgmt_ds_ctx *src_ds_ctx, bool validate_only, bool abort, - bool implicit, bool unlock, struct mgmt_edit_req *edit); - - /** -- * Send get-tree to the backend `clients`. -- * -- * Args: -- * txn_id: Transaction identifier. -- * req_id: FE client request identifier. -- * clients: Bitmask of clients to send get-tree to. -- * ds_id: datastore ID. -- * result_type: LYD_FORMAT result format. -- * flags: option flags for the request. -- * wd_options: LYD_PRINT_WD_* flags for the result. -- * simple_xpath: true if xpath is simple (only key predicates). -- * ylib: libyang tree for yang-library module to be merged. -- * xpath: The xpath to get the tree from. -+ * mgmt_txn_send_get_tree() - Send get-tree to the backend `clients`. -+ * @txn_id: Transaction identifier. -+ * @req_id: FE client request identifier. -+ * @clients: Bitmask of clients to send get-tree to. -+ * @ds_id: datastore ID. -+ * @result_type: LYD_FORMAT result format. -+ * @flags: option flags for the request. -+ * @wd_options: LYD_PRINT_WD_* flags for the result. -+ * @simple_xpath: true if xpath is simple (only key predicates). -+ * @ylib: libyang tree for yang-library module to be merged. -+ * @xpath: The xpath to get the tree from. - * -- * Return: -- * 0 on success. -+ * Return 0 on success. - */ - extern int mgmt_txn_send_get_tree(uint64_t txn_id, uint64_t req_id, uint64_t clients, - enum mgmt_ds_id ds_id, LYD_FORMAT result_type, uint8_t flags, -@@ -188,47 +140,18 @@ extern int mgmt_txn_send_get_tree(uint64_t txn_id, uint64_t req_id, uint64_t cli - const char *xpath); - - /** -- * Send edit request. -- * -- * Args: -- * txn_id: Transaction identifier. -- * req_id: FE client request identifier. -- * ds_id: Datastore ID. -- * ds_ctx: Datastore context. -- * commit_ds_id: Commit datastore ID. -- * commit_ds_ctx: Commit datastore context. -- * unlock: Unlock datastores after the edit. -- * commit: Commit the candidate datastore after the edit. -- * request_type: LYD_FORMAT request type. -- * flags: option flags for the request. -- * operation: The operation to perform. -- * xpath: The xpath of data node to edit. -- * data: The data tree. -- */ --extern int mgmt_txn_send_edit(uint64_t txn_id, uint64_t req_id, enum mgmt_ds_id ds_id, -- struct mgmt_ds_ctx *ds_ctx, enum mgmt_ds_id commit_ds_id, -- struct mgmt_ds_ctx *commit_ds_ctx, bool unlock, bool commit, -- LYD_FORMAT request_type, uint8_t flags, uint8_t operation, -- const char *xpath, const char *data); -- --/** -- * Send RPC request. -- * -- * Args: -- * txn_id: Transaction identifier. -- * req_id: FE client request identifier. -- * clients: Bitmask of clients to send RPC to. -- * result_type: LYD_FORMAT result format. -- * xpath: The xpath of the RPC. -- * data: The input parameters data tree. -- * data_len: The length of the input parameters data. -- * -- * Return: -- * 0 on success. -+ * mgmt_txn_send_rpc() - Send RPC request. -+ * @txn_id: Transaction identifier. -+ * @req_id: FE client request identifier. -+ * @clients: Bitmask of clients to send RPC to. -+ * @result_type: LYD_FORMAT result format. -+ * @xpath: The xpath of the RPC. -+ * @data: The input parameters data tree. -+ * @data_len: The length of the input parameters data. - */ --extern int mgmt_txn_send_rpc(uint64_t txn_id, uint64_t req_id, uint64_t clients, -- LYD_FORMAT result_type, const char *xpath, -- const char *data, size_t data_len); -+extern void mgmt_txn_send_rpc(uint64_t txn_id, uint64_t req_id, uint64_t clients, -+ LYD_FORMAT result_type, const char *xpath, const char *data, -+ size_t data_len); - - /** - * mgmt_txn_send_notify_selectors() - Send NOTIFY SELECT request. -@@ -238,99 +161,32 @@ extern int mgmt_txn_send_rpc(uint64_t txn_id, uint64_t req_id, uint64_t clients, - * back to the given session. - * @clients: Bitmask of backend clients to send message to. - * @selectors: Array of selectors or NULL to resend all selectors to BE clients. -- * -- * Returns 0 on success. - */ --extern int mgmt_txn_send_notify_selectors(uint64_t req_id, uint64_t session_id, uint64_t clients, -- const char **selectors); -+extern void mgmt_txn_send_notify_selectors(uint64_t req_id, uint64_t session_id, uint64_t clients, -+ const char **selectors); - --/* -- * Notifiy backend adapter on connection. -- */ --extern int --mgmt_txn_notify_be_adapter_conn(struct mgmt_be_client_adapter *adapter, -- bool connect); -- --/* -- * Reply to backend adapter about transaction create/delete. -- */ --extern int --mgmt_txn_notify_be_txn_reply(uint64_t txn_id, bool create, bool success, -- struct mgmt_be_client_adapter *adapter); - - /* -- * Reply to backend adapater with config data create request. -- */ --extern int mgmt_txn_notify_be_cfg_reply(uint64_t txn_id, bool success, const char *error_if_any, -- struct mgmt_be_client_adapter *adapter); -- --/* -- * Reply to backend adapater with config data validate request. -- */ --extern int mgmt_txn_notify_be_cfg_validate_reply( -- uint64_t txn_id, bool success, uint64_t batch_ids[], -- size_t num_batch_ids, char *error_if_any, -- struct mgmt_be_client_adapter *adapter); -- --/* -- * Reply to backend adapater with config data apply request. -- */ --extern int --mgmt_txn_notify_be_cfg_apply_reply(uint64_t txn_id, bool success, -- char *error_if_any, -- struct mgmt_be_client_adapter *adapter); -- -- --/** -- * Process a reply from a backend client to our get-tree request -- * -- * Args: -- * adapter: The adapter that received the result. -- * txn_id: The transaction for this get-tree request. -- * req_id: The request ID for this transaction. -- * error: the integer error value (negative) -- * errstr: the string description of the error. -- */ --int mgmt_txn_notify_error(struct mgmt_be_client_adapter *adapter, -- uint64_t txn_id, uint64_t req_id, int error, -- const char *errstr); -- --/** -- * Process a reply from a backend client to our get-tree request -+ * Trigger rollback config apply. - * -- * Args: -- * adapter: The adapter that received the result. -- * data_msg: The message from the backend. -- * msg_len: Total length of the message. -+ * Creates a new transaction and commit request for rollback. - */ -+extern int mgmt_txn_rollback_trigger_cfg_apply(struct mgmt_ds_ctx *src_ds_ctx, -+ struct mgmt_ds_ctx *dst_ds_ctx); - --extern int mgmt_txn_notify_tree_data_reply(struct mgmt_be_client_adapter *adapter, -- struct mgmt_msg_tree_data *data_msg, -- size_t msg_len); -+/* ---------------------------------------- */ -+/* Txn API for Backend messages and events. */ -+/* ---------------------------------------- */ - --/** -- * Process a reply from a backend client to our RPC request -- * -- * Args: -- * adapter: The adapter that received the result. -- * reply_msg: The message from the backend. -- * msg_len: Total length of the message. -- */ --extern int mgmt_txn_notify_rpc_reply(struct mgmt_be_client_adapter *adapter, -- struct mgmt_msg_rpc_reply *reply_msg, -- size_t msg_len); -+extern void mgmt_txn_handle_cfg_reply(uint64_t txn_id, struct mgmt_be_client_adapter *adapter); -+extern void mgmt_txn_handle_cfg_apply_reply(uint64_t txn_id, -+ struct mgmt_be_client_adapter *adapter); -+extern void mgmt_txn_handle_error_reply(struct mgmt_be_client_adapter *adapter, uint64_t txn_id, -+ uint64_t req_id, int error, const char *errstr); -+extern int mgmt_txn_handle_be_adapter_connect(struct mgmt_be_client_adapter *adapter, bool connect); -+extern void mgmt_txn_handle_rpc_reply(struct mgmt_be_client_adapter *adapter, -+ struct mgmt_msg_rpc_reply *reply_msg, size_t msg_len); -+extern void mgmt_txn_handle_tree_data_reply(struct mgmt_be_client_adapter *adapter, -+ struct mgmt_msg_tree_data *data_msg, size_t msg_len); - --/* -- * Dump transaction status to vty. -- */ --extern void mgmt_txn_status_write(struct vty *vty); -- --/* -- * Trigger rollback config apply. -- * -- * Creates a new transaction and commit request for rollback. -- */ --extern int --mgmt_txn_rollback_trigger_cfg_apply(struct mgmt_ds_ctx *src_ds_ctx, -- struct mgmt_ds_ctx *dst_ds_ctx); - #endif /* _FRR_MGMTD_TXN_H_ */ -diff --git a/mgmtd/mgmt_txn_cfg.c b/mgmtd/mgmt_txn_cfg.c -new file mode 100644 -index 0000000000..742c03cc4c ---- /dev/null -+++ b/mgmtd/mgmt_txn_cfg.c -@@ -0,0 +1,1096 @@ -+// SPDX-License-Identifier: GPL-2.0-or-later -+/* -+ * November 18 2025, Christian Hopps <chopps@labn.net> -+ * -+ * Copyright (c) 2025, LabN Consulting, L.L.C. -+ * -+ */ -+ -+#include <zebra.h> -+#include "mgmtd/mgmt_txn_priv.h" -+ -+/* ================================ */ -+/* Config Request Types and Globals */ -+/* ================================ */ -+ -+enum mgmt_commit_phase { -+ MGMTD_COMMIT_PHASE_SEND_CFG = 0, -+ MGMTD_COMMIT_PHASE_APPLY_CFG, -+ MGMTD_COMMIT_PHASE_FINISH, -+}; -+ -+static const char *const mgmt_commit_phase_name[] = { -+ [MGMTD_COMMIT_PHASE_SEND_CFG] = "SEND-CFG", -+ [MGMTD_COMMIT_PHASE_APPLY_CFG] = "APPLY-CFG", -+ [MGMTD_COMMIT_PHASE_FINISH] = "FINISH", -+}; -+ -+struct txn_req_commit { -+ struct txn_req req; -+ enum mgmt_ds_id src_ds_id; -+ struct mgmt_ds_ctx *src_ds_ctx; -+ enum mgmt_ds_id dst_ds_id; -+ struct mgmt_ds_ctx *dst_ds_ctx; -+ -+ uint32_t nb_txn_id; -+ -+ uint8_t validate_only : 1; -+ uint8_t abort : 1; -+ uint8_t implicit : 1; -+ uint8_t rollback : 1; -+ uint8_t init : 1; -+ uint8_t unlock_info : 1; -+ uint8_t txn_lock : 1; -+ -+ /* Track commit phases */ -+ enum mgmt_commit_phase phase; -+ -+ /* -+ * Additional information when the commit is triggered by native edit -+ * request. -+ */ -+ struct mgmt_edit_req *edit; -+ -+ -+ /* Used for holding changes mgmtd itself is interested in */ -+ struct nb_transaction *mgmtd_nb_txn; -+ -+ /* -+ * Details on all the Backend Clients associated with -+ * this commit. -+ */ -+ uint64_t clients; /* interested clients */ -+ uint64_t clients_wait; /* set when cfg_req sent */ -+ -+ struct mgmt_commit_stats *cmt_stats; -+}; -+#define as_commit(txn_req) \ -+ ({ \ -+ assert((txn_req)->req_type == TXN_REQ_TYPE_COMMIT); \ -+ (struct txn_req_commit *)(txn_req); \ -+ }) -+ -+/* The number of init txn's in progress, we lock on 0->1 and unlock on 1->0 */ -+uint txn_init_readers; -+ -+static void txn_cfg_timeout(struct event *event); -+static void txn_cfg_adapter_acked(struct txn_req_commit *ccreq, -+ struct mgmt_be_client_adapter *adapter); -+static int txn_cfg_send_txn_delete(struct mgmt_be_client_adapter *adapter, uint64_t txn_id); -+ -+/* ------------------------------------- */ -+/* Utility Functions for Commit Requests */ -+/* ------------------------------------- */ -+ -+static inline struct txn_req_commit *txn_txn_req_commit(struct mgmt_txn *txn) -+{ -+ struct txn_req *first_req = TAILQ_FIRST(&txn->reqs); -+ -+ if (first_req && first_req->req_type == TXN_REQ_TYPE_COMMIT) -+ return as_commit(first_req); -+ return NULL; -+} -+ -+static int txn_set_config_error(struct txn_req *txn_req, enum mgmt_result error, -+ const char *error_info) -+{ -+ txn_req->error = error; -+ darr_in_strdup(txn_req->err_info, error_info); -+ return -1; -+} -+ -+/* ------------------------------------ */ -+/* Allocate and Cleanup Commit Requests */ -+/* ------------------------------------ */ -+ -+static struct txn_req_commit *txn_req_commit_alloc(struct mgmt_txn *txn, uint64_t req_id) -+{ -+ struct txn_req *txn_req = txn_req_alloc(txn, req_id, TXN_REQ_TYPE_COMMIT, -+ sizeof(struct txn_req_commit)); -+ assert(txn->type == MGMTD_TXN_TYPE_CONFIG); -+ return as_commit(txn_req); -+} -+ -+void txn_cfg_cleanup(struct txn_req *txn_req) -+{ -+ struct txn_req_commit *ccreq = as_commit(txn_req); -+ struct mgmt_be_client_adapter *adapter; -+ enum mgmt_commit_phase phase; -+ enum mgmt_be_client_id id; -+ uint64_t txn_id = txn_req->txn->txn_id; -+ uint64_t clients; -+ -+ _dbg("Deleting COMMITCFG req-id: %Lu txn-id: %Lu", txn_req->req_id, txn_id); -+ -+ XFREE(MTYPE_MGMTD_TXN_REQ, ccreq->edit); -+ -+ /* If we (still) had an internal nb transaction, abort it */ -+ if (ccreq->mgmtd_nb_txn) { -+ nb_candidate_commit_abort(ccreq->mgmtd_nb_txn, NULL, 0); -+ ccreq->mgmtd_nb_txn = NULL; -+ } -+ -+ /* -+ * What state are we in: -+ * -+ * FINISH: Everythign went OK. If this was NOT validate-only then all -+ * clients have replied to the CFG_APPLY and deleted their txn state. We -+ * have nothing to do. Otherwise, this is validate-only and all the -+ * clients still have config txn state with the changes. We need to -+ * clean that txn state up. Treat the phase as SEND-CFG so we send -+ * config abort (TXN-delete) to have the clients delete their txn state. -+ * -+ * APPLY: Any clients that have not CFG_APPLY_REPLY'd yet (clients_wait) -+ * need to be disconnected so they will resync config state on reconnect. -+ * -+ * SEND-CFG: Some clients may have cfg txn state. We send all clients -+ * config abort (TXN-delete) to cleanup any transaction state. Even if -+ * clients have rejected the config and deleted txn state already they -+ * are expected to handle receving TXN-delete gracefully (e.g., their -+ * reply may still be in flight when we got here). -+ */ -+ phase = ccreq->phase; -+ if (phase == MGMTD_COMMIT_PHASE_FINISH && ccreq->validate_only) -+ phase = MGMTD_COMMIT_PHASE_SEND_CFG; -+ -+ switch (phase) { -+ case MGMTD_COMMIT_PHASE_FINISH: -+ break; -+ case MGMTD_COMMIT_PHASE_APPLY_CFG: -+ /* cache and clear the bits as they are checked on client disconnect */ -+ clients = ccreq->clients_wait; -+ ccreq->clients_wait = 0; -+ ccreq->clients = 0; -+ FOREACH_BE_ADAPTER_BITS (id, adapter, clients) { -+ _dbg("Disconnect client: %s for txn-id: %Lu resync required", -+ adapter->name, txn_id); -+ msg_conn_disconnect(adapter->conn, false); -+ } -+ break; -+ case MGMTD_COMMIT_PHASE_SEND_CFG: -+ /* cache and clear the bits as they are checked on client disconnect */ -+ clients = ccreq->clients; -+ ccreq->clients_wait = 0; -+ ccreq->clients = 0; -+ FOREACH_BE_ADAPTER_BITS (id, adapter, clients) { -+ if (!txn_cfg_send_txn_delete(adapter, txn_id)) -+ continue; -+ _dbg("Disconnect client: %s for txn-id: %Lu failed to send CFG_ABORT", -+ adapter->name, txn_id); -+ msg_conn_disconnect(adapter->conn, false); -+ } -+ break; -+ } -+ -+ /* -+ * Now release the TXN locks if held. -+ */ -+ if (ccreq->txn_lock) { -+ mgmt_ds_txn_unlock(ccreq->src_ds_ctx, txn_id); -+ mgmt_ds_txn_unlock(ccreq->dst_ds_ctx, txn_id); -+ } -+} -+ -+/* =============================================== */ -+/* Config Phases For Syncing with Backend Clients */ -+/* =============================================== */ -+ -+/* --------- */ -+/* Any Phase */ -+/* --------- */ -+ -+/* -+ * Abort and Cleanup Config Changes. -+ * -+ * This will cleanup the Config Txn on the backend client. Backends that have -+ * replied to SEND_CFG with an error (validation/prepare) have already cleaned -+ * up. Likewise Backends that have replied to APPLY_CFG with an success -+ * have also already done this. In any case Backends must handle receiving this -+ * message even if they've already cleaned up. -+ */ -+static int txn_cfg_send_txn_delete(struct mgmt_be_client_adapter *adapter, uint64_t txn_id) -+{ -+ struct mgmt_msg_txn_req *msg; -+ int ret; -+ -+ msg = mgmt_msg_native_alloc_msg(struct mgmt_msg_txn_req, 0, MTYPE_MSG_NATIVE_TXN_REQ); -+ msg->code = MGMT_MSG_CODE_TXN_REQ; -+ msg->refer_id = txn_id; -+ msg->create = false; -+ -+ ret = mgmt_be_adapter_send(adapter, msg); -+ mgmt_msg_native_free_msg(msg); -+ return ret; -+} -+ -+ -+/* -------------- */ -+/* SEND_CFG phase */ -+/* -------------- */ -+ -+/* -+ * This is the real workhorse. Take the list of changes and check each change -+ * against our backend clients to see who is interested. For each interested -+ * client we create a config message -- we also track which changes mgmtd itself -+ * is interested in. If a client is interested we add the change to it's config -+ * message and track the type of chagne in another string array. -+ * -+ * When done with the changes we first handle mgmtd's own changes, validating, -+ * and preparing them into the mgmtd candidate (using normal lib/northbound -+ * routines). Then for each backend client we complete it's config message by -+ * append the action string and then we send it to the client. -+ * -+ * Return: 0 on success, the caller should arrange to receive REPLYS from the -+ * clients before proceeding further, if no clients were interested the caller -+ * should proceed to apply the mgmtd local changes (if any) to complete the txn. -+ * -+ * On failure, if we were called from a front-end client (the TXN has a -+ * session_id) we have it reply with the error and cleanup the txn. Otherwise -+ * this is an internal txn and we just cleanup the txn in that case. In either -+ * case we return -1 and the caller should not proceed further. -+ * -+ * Can disconnect backends, but this is not called from backends, so it's safe. -+ */ -+ -+static int txn_cfg_make_and_send_cfg_req(struct txn_req_commit *ccreq, -+ const struct nb_config_cbs *changes, -+ uint64_t init_client_mask) -+{ -+ struct nb_config_cb *cb, *nxt; -+ struct nb_config_change *chg; -+ struct nb_config_cbs mgmtd_changes = { 0 }; -+ char *xpath = NULL, *value = NULL; -+ enum mgmt_be_client_id id; -+ struct mgmt_be_client_adapter *adapter; -+ struct txn_req *txn_req = &ccreq->req; -+ struct mgmt_msg_cfg_req **cfg_msgs = NULL; -+ char **cfg_actions = NULL; -+ uint64_t *num_cfg_data = NULL; -+ bool mgmtd_interest; -+ uint batch_items = 0; -+ uint num_chgs = 0; -+ uint64_t clients, chg_clients; -+ char op; -+ int ret = -1; -+ -+ RB_FOREACH_SAFE (cb, nb_config_cbs, changes, nxt) { -+ chg = (struct nb_config_change *)cb; -+ -+ xpath = lyd_path(chg->cb.dnode, LYD_PATH_STD, NULL, 0); -+ assert(xpath); -+ -+ value = (char *)lyd_get_value(chg->cb.dnode); -+ if (!value) -+ value = (char *)MGMTD_BE_CONTAINER_NODE_VAL; -+ -+ _dbg("XPATH: %s, Value: '%s'", xpath, value ? value : "NIL"); -+ -+ /* Collect changes for mgmtd itself */ -+ mgmtd_interest = false; -+ if (!init_client_mask && mgmt_is_mgmtd_interested(xpath) && -+ /* We send tree changes to BEs that we don't need callbacks for */ -+ nb_cb_operation_is_valid(cb->operation, cb->dnode->schema)) { -+ uint32_t seq = cb->seq; -+ -+ nb_config_diff_add_change(&mgmtd_changes, cb->operation, &seq, cb->dnode); -+ mgmtd_interest = true; -+ } -+ if (init_client_mask) -+ clients = init_client_mask; -+ else -+ clients = mgmt_be_interested_clients(xpath, MGMT_BE_XPATH_SUBSCR_TYPE_CFG); -+ if (!clients) -+ _dbg("No backends interested in xpath: %s", xpath); -+ -+ if (mgmtd_interest || clients) -+ num_chgs++; -+ -+ chg_clients = 0; -+ FOREACH_BE_ADAPTER_BITS (id, adapter, clients) { -+ SET_IDBIT(chg_clients, id); -+ -+ darr_ensure_i(cfg_msgs, id); -+ darr_ensure_i(cfg_actions, id); -+ if (!cfg_msgs[id]) { -+ /* Allocate a new config message */ -+ struct mgmt_msg_cfg_req *msg; -+ -+ msg = mgmt_msg_native_alloc_msg(struct mgmt_msg_cfg_req, 0, -+ MTYPE_MSG_NATIVE_CFG_REQ); -+ msg->code = MGMT_MSG_CODE_CFG_REQ; -+ msg->refer_id = txn_req->txn->txn_id; -+ msg->req_id = txn_req->req_id; -+ cfg_msgs[id] = msg; -+ } -+ -+ /* -+ * On the backend, we don't really care if it's CREATE -+ * or MODIFY, because the existence was already checked -+ * on the frontend. Therefore we use SET for both. -+ */ -+ op = chg->cb.operation == NB_CB_DESTROY ? 'd' : 'm'; -+ darr_push(cfg_actions[id], op); -+ -+ mgmt_msg_native_add_str(cfg_msgs[id], xpath); -+ if (op == 'm') -+ mgmt_msg_native_add_str(cfg_msgs[id], value); -+ if (DEBUG_MODE_CHECK(&mgmt_debug_txn, DEBUG_MODE_ALL)) { -+ darr_ensure_i(num_cfg_data, id); -+ num_cfg_data[id]++; -+ _dbg(" -- %s item: %Lu", adapter->name, num_cfg_data[id]); -+ } -+ -+ batch_items++; -+ } -+ -+ if (clients && clients != chg_clients) -+ _dbg("Some deamons interested in XPATH are not currently connected"); -+ -+ ccreq->clients |= chg_clients; -+ -+ free(xpath); -+ } -+ _dbg("Total xpath changes processed: %u sent", num_chgs); -+ -+ ccreq->cmt_stats->last_batch_cnt = batch_items; -+ -+ if (!RB_EMPTY(nb_config_cbs, &mgmtd_changes)) { -+ /* Create a northbound transaction for local mgmtd config changes */ -+ char errmsg[BUFSIZ] = { 0 }; -+ size_t errmsg_len = sizeof(errmsg); -+ struct nb_context nb_ctx = { 0 }; -+ -+ _dbg("Processing mgmtd bound changes"); -+ -+ assert(!ccreq->mgmtd_nb_txn); -+ nb_ctx.client = NB_CLIENT_MGMTD_SERVER; -+ -+ /* Prepare the mgmtd local config changes */ -+ /* -+ * This isn't calling the VALIDATE callback, it's just -+ * running PREPARE. See #19948 -+ */ -+ if (nb_changes_commit_prepare(nb_ctx, mgmtd_changes, "mgmtd-changes", NULL, -+ &ccreq->mgmtd_nb_txn, errmsg, errmsg_len)) { -+ _log_err("Failed to prepare local config for mgmtd: %s", errmsg); -+ if (ccreq->mgmtd_nb_txn) { -+ nb_candidate_commit_abort(ccreq->mgmtd_nb_txn, NULL, 0); -+ ccreq->mgmtd_nb_txn = NULL; -+ } -+ (void)txn_set_config_error(txn_req, MGMTD_INTERNAL_ERROR, -+ "Failed to prepare local config for mgmtd"); -+ goto done; -+ } -+ assert(ccreq->mgmtd_nb_txn); -+ } -+ -+ /* Record txn create start time */ -+ if (mm->perf_stats_en) -+ gettimeofday(&ccreq->cmt_stats->txn_create_start, NULL); -+ -+ /* Send the messages to the backends */ -+ FOREACH_BE_ADAPTER_BITS (id, adapter, ccreq->clients) { -+ /* NUL terminate actions string and add to tail of message */ -+ darr_ensure_i(cfg_actions, id); /* required for clang SA NULL ptr check */ -+ darr_push(cfg_actions[id], 0); -+ mgmt_msg_native_add_str(cfg_msgs[id], cfg_actions[id]); -+ _dbg("Finished CFG_REQ for '%s' txn-id: %Lu with actions: %s", adapter->name, -+ txn_req->txn->txn_id, cfg_actions[id]); -+ -+ if (mgmt_be_adapter_send(adapter, cfg_msgs[id])) { -+ /* remove this client and reset the connection */ -+ UNSET_IDBIT(ccreq->clients, id); -+ msg_conn_disconnect(adapter->conn, false); -+ } -+ ccreq->cmt_stats->last_num_cfgdata_reqs++; -+ } -+ /* Record who we are waiting for */ -+ ccreq->clients_wait = ccreq->clients; -+ -+ if (ccreq->clients) { -+ /* set a timeout for hearing back from the backend clients */ -+ event_add_timer(mm->master, txn_cfg_timeout, txn_req, -+ MGMTD_TXN_CFG_COMMIT_MAX_DELAY_SEC, &txn_req->timeout); -+ } else { -+ /* We have no connected interested clients */ -+ if (ccreq->mgmtd_nb_txn) -+ _dbg("No connected and interested backend clients, proceed with mgmtd local changes"); -+ else -+ _dbg("No connected and interested backend clients, proceed to apply changes"); -+ txn_cfg_adapter_acked(ccreq, NULL); -+ } -+ -+ ret = 0; -+done: -+ darr_free(num_cfg_data); -+ darr_free_func(cfg_msgs, mgmt_msg_native_free_msg); -+ darr_free_free(cfg_actions); -+ return ret; -+} -+ -+/* --------------- */ -+/* APPLY_CFG phase */ -+/* --------------- */ -+ -+/* -+ * Send CFG_APPLY_REQs to all the backend client. -+ */ -+static void txn_cfg_send_cfg_apply(struct txn_req_commit *ccreq) -+{ -+ enum mgmt_be_client_id id; -+ struct mgmt_be_client_adapter *adapter; -+ struct mgmt_msg_cfg_apply_req *msg; -+ uint64_t txn_id = ccreq->req.txn->txn_id; -+ -+ assert(ccreq->req.txn->type == MGMTD_TXN_TYPE_CONFIG); -+ -+ assert(!ccreq->validate_only); -+ -+ if (mm->perf_stats_en) -+ gettimeofday(&ccreq->cmt_stats->apply_cfg_start, NULL); -+ /* -+ * Handle mgmtd internal special case -+ */ -+ if (ccreq->mgmtd_nb_txn) { -+ char errmsg[BUFSIZ] = { 0 }; -+ -+ _dbg("Applying mgmtd local bound changes"); -+ -+ (void)nb_candidate_commit_apply(ccreq->mgmtd_nb_txn, false, NULL, errmsg, -+ sizeof(errmsg)); -+ ccreq->mgmtd_nb_txn = NULL; -+ } -+ -+ msg = mgmt_msg_native_alloc_msg(struct mgmt_msg_cfg_apply_req, 0, -+ MTYPE_MSG_NATIVE_CFG_APPLY_REQ); -+ msg->code = MGMT_MSG_CODE_CFG_APPLY_REQ; -+ msg->refer_id = txn_id; -+ FOREACH_BE_ADAPTER_BITS (id, adapter, ccreq->clients) { -+ if (mgmt_be_adapter_send(adapter, msg)) { -+ msg_conn_disconnect(adapter->conn, false); -+ continue; -+ } -+ SET_IDBIT(ccreq->clients_wait, id); -+ ccreq->cmt_stats->last_num_apply_reqs++; -+ } -+ mgmt_msg_native_free_msg(msg); -+ -+ if (!ccreq->clients_wait) { -+ _dbg("No backends to wait for on CFG_APPLY for txn-id: %Lu", txn_id); -+ txn_cfg_adapter_acked(ccreq, NULL); -+ } -+} -+ -+ -+/* ------------ */ -+/* FINISH phase */ -+/* ------------ */ -+ -+/* -+ * Finish processing a commit-config request. -+ * -+ * NOTE: can disconnect (and delete) backends. -+ */ -+static void txn_finish_commit(struct txn_req_commit *ccreq, enum mgmt_result result, -+ const char *error_if_any) -+{ -+ bool success, apply_op, accept_changes, discard_changes; -+ struct txn_req *txn_req = as_txn_req(ccreq); -+ struct mgmt_txn *txn = txn_req->txn; -+ int ret = 0; -+ -+ success = (result == MGMTD_SUCCESS || result == MGMTD_NO_CFG_CHANGES); -+ -+ /* -+ * Stop the commit-timeout timer. -+ */ -+ event_cancel(&txn_req->timeout); -+ -+ /* -+ * Decide what our datastores should now look like -+ * -+ * Accept changes into running (candidate->running): -+ * -+ * If this is a commit (apply or rollback) and we've at least started -+ * telling backend clients to apply, we need to accept the changes -+ * into running. Any clients who have not acked the apply yet will be -+ * disconnected in the txn cleanup so they sync to running on -+ * reconnect. -+ * -+ * Discard candidate changes (running->candidate): -+ * -+ * If this is a successful abort -+ */ -+ apply_op = !ccreq->validate_only && !ccreq->abort && !ccreq->init; -+ accept_changes = ccreq->phase >= MGMTD_COMMIT_PHASE_APPLY_CFG && apply_op; -+ discard_changes = (result == MGMTD_SUCCESS && ccreq->abort); -+ if (accept_changes) { -+ bool create_cmt_info_rec = (result != MGMTD_NO_CFG_CHANGES && !ccreq->rollback); -+ -+ mgmt_ds_copy_dss(ccreq->dst_ds_ctx, ccreq->src_ds_ctx, create_cmt_info_rec); -+ } -+ if (discard_changes) -+ mgmt_ds_copy_dss(ccreq->src_ds_ctx, ccreq->dst_ds_ctx, false); -+ -+ /* -+ * For internal txns do lock cleanup, for front-end session send replies. -+ */ -+ if (ccreq->init) { -+ /* -+ * This is the backend init request. Unlock the running -+ * datastore if we are the last reader. -+ */ -+ if (!--txn_init_readers) -+ mgmt_ds_unlock(ccreq->dst_ds_ctx, 0); -+ TXN_DECREF(txn); -+ } else if (ccreq->rollback) { -+ /* -+ * Resume processing the rollback command. -+ * -+ * TODO: there's no good reason to special case rollback, the -+ * rollback boolean should be passed back to the FE client and it -+ * can do the right thing. -+ */ -+ mgmt_history_rollback_complete(success); -+ TXN_DECREF(txn); -+ } else if (!ccreq->edit) { -+ /* -+ * This means we are in the mgmtd CLI vty code -- session code -+ * will release it's reference on the TXN -+ */ -+ ret = mgmt_fe_send_commit_cfg_reply(txn->session_id, txn->txn_id, ccreq->src_ds_id, -+ ccreq->dst_ds_id, txn_req->req_id, -+ ccreq->validate_only, ccreq->unlock_info, -+ result, error_if_any); -+ } else { -+ /* Session code will release it's reference on the TXN */ -+ ret = mgmt_fe_adapter_send_edit_reply(txn->session_id, txn->txn_id, -+ txn_req->req_id, ccreq->unlock_info, -+ true /* commit */, &ccreq->edit, -+ success ? 0 : MGMTD_INTERNAL_ERROR, -+ error_if_any); -+ } -+ if (ret) -+ _log_err("Failed sending config reply for txn-id: %Lu session-id: %Lu", -+ txn->txn_id, txn->session_id); -+ -+ ccreq->cmt_stats = NULL; -+ txn_req_free(txn_req); -+} -+ -+/* ---------------------- */ -+/* Config Phase Machinery */ -+/* ---------------------- */ -+ -+static void txn_cfg_next_phase(struct txn_req_commit *ccreq) -+{ -+ struct txn_req *txn_req = &ccreq->req; -+ -+ switch (ccreq->phase) { -+ case MGMTD_COMMIT_PHASE_SEND_CFG: -+ if (ccreq->validate_only) -+ ccreq->phase = MGMTD_COMMIT_PHASE_FINISH; -+ else -+ ccreq->phase = MGMTD_COMMIT_PHASE_APPLY_CFG; -+ break; -+ case MGMTD_COMMIT_PHASE_APPLY_CFG: -+ if (mm->perf_stats_en) -+ gettimeofday(&ccreq->cmt_stats->apply_cfg_end, NULL); -+ ccreq->phase = MGMTD_COMMIT_PHASE_FINISH; -+ break; -+ case MGMTD_COMMIT_PHASE_FINISH: -+ default: -+ assert(!"Invalid commit phase transition from FINISH"); -+ break; -+ } -+ -+ _dbg("CONFIG-STATE-MACHINE txn-id: %Lu transition to state: %s", txn_req->txn->txn_id, -+ mgmt_commit_phase_name[ccreq->phase]); -+ -+ switch (ccreq->phase) { -+ case MGMTD_COMMIT_PHASE_APPLY_CFG: -+ txn_cfg_send_cfg_apply(ccreq); -+ break; -+ case MGMTD_COMMIT_PHASE_FINISH: -+ if (mm->perf_stats_en) -+ gettimeofday(&ccreq->cmt_stats->txn_del_start, NULL); -+ txn_finish_commit(ccreq, MGMTD_SUCCESS, NULL); -+ return; -+ case MGMTD_COMMIT_PHASE_SEND_CFG: -+ default: -+ assert(!"Invalid commit phase transition to SEND_CFG"); -+ } -+} -+ -+static void txn_cfg_adapter_acked(struct txn_req_commit *ccreq, -+ struct mgmt_be_client_adapter *adapter) -+{ -+ struct txn_req *txn_req = &ccreq->req; -+ -+ _dbg("CONFIG-STATE-MACHINE txn-id: %Lu in state: %s", txn_req->txn->txn_id, -+ mgmt_commit_phase_name[ccreq->phase]); -+ -+ if (adapter) { -+ enum mgmt_be_client_id id = adapter->id; -+ -+ if (IS_IDBIT_SET(ccreq->clients_wait, id)) -+ UNSET_IDBIT(ccreq->clients_wait, id); -+ else -+ _dbg("Wasn't waiting on client: %s", adapter->name); -+ } -+ -+ if (ccreq->clients_wait) { -+ _dbg("CONFIG-STATE-MACHINE txn-id: %Lu still waiting on clients: 0x%Lx", -+ txn_req->txn->txn_id, ccreq->clients_wait); -+ return; -+ } -+ -+ txn_cfg_next_phase(ccreq); -+} -+ -+static void txn_cfg_timeout(struct event *event) -+{ -+ struct txn_req *txn_req; -+ struct txn_req_commit *ccreq; -+ -+ txn_req = EVENT_ARG(event); -+ ccreq = as_commit(txn_req); -+ -+ /* -+ * If we are applying changes we need to return SUCCESS as there is no -+ * way to abort those. Slow backends that haven't replied yet will be -+ * disconnected. If we are still sending config for validate/prepare -+ * phase we return an error and abort the commit. -+ */ -+ if (ccreq->phase < MGMTD_COMMIT_PHASE_APPLY_CFG) { -+ _log_err("Backend timeout validating txn-id: %Lu waiting: 0x%Lx aborting commit", -+ txn_req->txn->txn_id, ccreq->clients_wait); -+ txn_finish_commit(ccreq, MGMTD_INTERNAL_ERROR, -+ "Some backend clients taking too long to validate the changes."); -+ } else { -+ _log_warn("Backend timeout applying txn-id: %Lu waiting: 0x%Lx, applying commit", -+ txn_req->txn->txn_id, ccreq->clients_wait); -+ txn_finish_commit(ccreq, MGMTD_SUCCESS, -+ "Some backend clients taking too long to apply the changes."); -+ } -+} -+ -+/* -+ * Given a list of config changes, make backend client messages and send them. Aslo -+ * apply any mgmtd specific config as well. -+ */ -+static int txn_cfg_send_config_changes(struct txn_req_commit *ccreq, -+ struct nb_config_cbs *cfg_chgs, uint64_t init_client_mask) -+{ -+ int ret; -+ -+ if (mm->perf_stats_en) -+ gettimeofday(&ccreq->cmt_stats->prep_cfg_start, NULL); -+ -+ ret = txn_cfg_make_and_send_cfg_req(ccreq, cfg_chgs, init_client_mask); -+ nb_config_diff_del_changes(cfg_chgs); -+ return ret; -+} -+ -+/* ================================== */ -+/* APIs for Backend Clients (daemons) */ -+/* ================================== */ -+ -+/* ------------------------------------ */ -+/* Handle Messages From Backend Clients */ -+/* ------------------------------------ */ -+ -+static struct txn_req_commit * -+txn_cfg_ensure_msg(uint64_t txn_id, struct mgmt_be_client_adapter *adapter, const char *tag) -+{ -+ struct mgmt_txn *txn = txn_lookup(txn_id); -+ struct txn_req_commit *ccreq; -+ -+ if (!txn) { -+ _log_err("%s reply from '%s' for txn-id: %Lu no TXN, resetting connection", tag, -+ adapter->name, txn_id); -+ return NULL; -+ } -+ if (txn->type != MGMTD_TXN_TYPE_CONFIG) { -+ _log_err("%s reply from '%s' for txn-id: %Lu failed wrong txn TYPE %u, resetting connection", -+ tag, adapter->name, txn_id, txn->type); -+ return NULL; -+ } -+ ccreq = txn_txn_req_commit(txn); -+ if (!ccreq) { -+ _log_err("%s reply from '%s' for txn-id: %Lu failed no COMMITCFG_REQ, resetting connection", -+ tag, adapter->name, txn_id); -+ return NULL; -+ } -+ /* make sure we are part of this config commit */ -+ if (!IS_IDBIT_SET(ccreq->clients, adapter->id)) { -+ _log_err("%s reply from '%s' for txn-id %Lu not participating, resetting connection", -+ tag, adapter->name, txn_id); -+ return NULL; -+ } -+ return ccreq; -+} -+ -+/* -+ * Handle CFG_REQ reply from backend client adapter. This is the only point of -+ * failure that is expected in the config commit process; it is where the -+ * backend client can report validation or prepare errors. -+ * -+ * NOTE: can disconnect (and delete) the backend. -+ */ -+static void txn_cfg_handle_cfg_reply(struct txn_req_commit *ccreq, bool success, -+ const char *error_if_any, -+ struct mgmt_be_client_adapter *adapter) -+{ -+ struct txn_req *txn_req = as_txn_req(ccreq); -+ -+ if (!IS_IDBIT_SET(ccreq->clients_wait, adapter->id)) { -+ _log_warn("CFG_REPLY from '%s' but not waiting for it txn-id: %Lu, resetting conn", -+ adapter->name, txn_req->txn->txn_id); -+ msg_conn_disconnect(adapter->conn, false); -+ return; -+ } -+ -+ if (success) -+ _dbg("CFG_REPLY from '%s'", adapter->name); -+ else { -+ _log_err("CFG_REQ to '%s' failed err: %s", adapter->name, error_if_any ?: "None"); -+ txn_finish_commit(ccreq, MGMTD_VALIDATION_ERROR, -+ error_if_any ?: "config validation failed by backend daemon"); -+ return; -+ } -+ -+ txn_cfg_adapter_acked(ccreq, adapter); -+} -+ -+/* -+ * NOTE: can disconnect (and delete) the backend. -+ */ -+void mgmt_txn_handle_cfg_reply(uint64_t txn_id, struct mgmt_be_client_adapter *adapter) -+{ -+ struct txn_req_commit *ccreq = txn_cfg_ensure_msg(txn_id, adapter, "CFG_REQ"); -+ -+ if (!ccreq) -+ msg_conn_disconnect(adapter->conn, false); -+ else -+ txn_cfg_handle_cfg_reply(ccreq, true, NULL, adapter); -+} -+ -+/* -+ * NOTE: can disconnect (and delete) the backend. -+ */ -+void mgmt_txn_handle_cfg_apply_reply(uint64_t txn_id, struct mgmt_be_client_adapter *adapter) -+{ -+ struct txn_req_commit *ccreq = txn_cfg_ensure_msg(txn_id, adapter, "CFG_APPLY"); -+ -+ if (!ccreq) -+ msg_conn_disconnect(adapter->conn, false); -+ else -+ txn_cfg_adapter_acked(ccreq, adapter); -+} -+ -+/* -+ * NOTE: can disconnect (and delete) the backend. -+ */ -+void txn_cfg_handle_error(struct txn_req *txn_req, struct mgmt_be_client_adapter *adapter, -+ int error, const char *errstr) -+{ -+ struct txn_req_commit *ccreq; -+ -+ /* -+ * Handle an error during a configuration transaction. -+ */ -+ ccreq = txn_cfg_ensure_msg(txn_req->txn->txn_id, adapter, "ERROR"); -+ if (!ccreq || as_txn_req(ccreq) != txn_req) { -+ msg_conn_disconnect(adapter->conn, false); -+ return; -+ } -+ /* -+ * We only handle errors in reply to our CFG_REQ messages (due -+ * to validation/preparation). Otherwise, this is an error -+ * during some other phase of the commit process and we -+ * disconnect the client and start over. -+ */ -+ if (ccreq->phase == MGMTD_COMMIT_PHASE_SEND_CFG) -+ txn_cfg_handle_cfg_reply(ccreq, false, errstr, adapter); -+ else -+ /* Drop the connection these errors should never happen */ -+ msg_conn_disconnect(adapter->conn, false); -+} -+ -+/* ----------------------------------- */ -+/* Backend Client Connect / Disconnect */ -+/* ----------------------------------- */ -+ -+int txn_cfg_be_client_connect(struct mgmt_be_client_adapter *adapter) -+{ -+ struct nb_config_cbs adapter_cfgs; -+ struct mgmt_txn *txn; -+ struct txn_req_commit *ccreq; -+ struct mgmt_ds_ctx *ds_ctx; -+ -+ ds_ctx = mgmt_ds_get_ctx_by_id(mm, MGMTD_DS_RUNNING); -+ assert(ds_ctx); -+ -+ /* We don't send configs to ourselves we already have them */ -+ if (adapter->id == MGMTD_BE_CLIENT_ID_MGMTD) { -+ _dbg("Not sending initial config to myself"); -+ return 0; -+ } -+ -+ /* -+ * If this is the first set of changes we are sending to a -+ * backend, obtain a lock on running. We will release the lock -+ * when the last init cfg request completes. This allows for -+ * initializing the backends in parallel. -+ */ -+ -+ if (!txn_init_readers++ && mgmt_ds_lock(ds_ctx, 0) != 0) { -+ _dbg("Failed to lock DS:%s for init of BE adapter '%s'", -+ mgmt_ds_id2name(MGMTD_DS_RUNNING), adapter->name); -+ --txn_init_readers; -+ return -1; -+ } -+ -+ /* Get config for this single backend client */ -+ adapter_cfgs = mgmt_be_adapter_get_config(adapter); -+ if (RB_EMPTY(nb_config_cbs, &adapter_cfgs)) { -+ if (!--txn_init_readers) -+ mgmt_ds_unlock(ds_ctx, 0); -+ return 0; -+ } -+ -+ /* -+ * Create a CONFIG transaction to push the config changes -+ * provided to the backend client. -+ */ -+ txn = txn_create(MGMTD_TXN_TYPE_CONFIG); -+ if (!txn) { -+ _log_err("Failed to create CONFIG Transaction for downloading CONFIGs for client '%s'", -+ adapter->name); -+ if (!--txn_init_readers) -+ mgmt_ds_unlock(ds_ctx, 0); -+ nb_config_diff_del_changes(&adapter_cfgs); -+ return -1; -+ } -+ -+ _dbg("Created initial txn-id: %" PRIu64 " for BE client '%s'", txn->txn_id, -+ adapter->name); -+ -+ /* -+ * Set the changeset for transaction to commit and trigger the -+ * commit request. -+ */ -+ memset(&adapter->cfg_stats, 0, sizeof(adapter->cfg_stats)); -+ ccreq = txn_req_commit_alloc(txn, 0); -+ ccreq->src_ds_id = MGMTD_DS_NONE; -+ ccreq->src_ds_ctx = 0; -+ ccreq->dst_ds_id = MGMTD_DS_RUNNING; -+ ccreq->dst_ds_ctx = ds_ctx; -+ ccreq->validate_only = false; -+ ccreq->abort = false; -+ ccreq->init = true; -+ ccreq->cmt_stats = &adapter->cfg_stats; -+ -+ /* -+ * Apply the initial changes. -+ */ -+ return txn_cfg_send_config_changes(ccreq, &adapter_cfgs, IDBIT_MASK(adapter->id)); -+} -+ -+/* static */ void txn_cfg_txn_be_client_disconnect(struct mgmt_txn *txn, -+ struct mgmt_be_client_adapter *adapter) -+{ -+ struct txn_req_commit *ccreq = txn_txn_req_commit(txn); -+ -+ if (!ccreq) -+ return; -+ -+ UNSET_IDBIT(ccreq->clients, adapter->id); -+ if (IS_IDBIT_SET(ccreq->clients_wait, adapter->id)) -+ txn_cfg_adapter_acked(ccreq, adapter); -+} -+ -+ -+/* ================================================ */ -+/* APIs for Frontend Clients (vtysh, restconf, ...) */ -+/* ================================================ */ -+ -+/* -+ * Prepare and send config changes by comparing the source and destination -+ * datastores. -+ */ -+static int txn_get_config_changes(struct txn_req_commit *ccreq, struct nb_config_cbs *cfg_chgs) -+{ -+ struct nb_config *nb_config; -+ struct txn_req *txn_req = &ccreq->req; -+ int ret = 0; -+ -+ if (ccreq->src_ds_id != MGMTD_DS_CANDIDATE) { -+ return txn_set_config_error(txn_req, MGMTD_INVALID_PARAM, -+ "Source DS cannot be any other than CANDIDATE!"); -+ } -+ -+ if (ccreq->dst_ds_id != MGMTD_DS_RUNNING) { -+ return txn_set_config_error(txn_req, MGMTD_INVALID_PARAM, -+ "Destination DS cannot be any other than RUNNING!"); -+ } -+ -+ if (!ccreq->src_ds_ctx) { -+ return txn_set_config_error(txn_req, MGMTD_INVALID_PARAM, -+ "No such source datastore!"); -+ } -+ -+ if (!ccreq->dst_ds_ctx) { -+ return txn_set_config_error(txn_req, MGMTD_INVALID_PARAM, -+ "No such destination datastore!"); -+ } -+ -+ if (ccreq->abort) { -+ /* -+ * This is a commit abort request. Return back success. -+ * The reply routing special cases abort, this isn't pretty, -+ * fix in later cleanup. -+ */ -+ return txn_set_config_error(txn_req, MGMTD_SUCCESS, "commit abort"); -+ } -+ -+ nb_config = mgmt_ds_get_nb_config(ccreq->src_ds_ctx); -+ if (!nb_config) { -+ return txn_set_config_error(txn_req, MGMTD_INTERNAL_ERROR, -+ "Unable to retrieve Commit DS Config Tree!"); -+ } -+ -+ /* -+ * Validate YANG contents of the source DS and get the diff -+ * between source and destination DS contents. -+ */ -+ char err_buf[BUFSIZ] = { 0 }; -+ -+ ret = nb_candidate_validate_yang(nb_config, true, err_buf, sizeof(err_buf) - 1); -+ if (ret != NB_OK) { -+ if (strncmp(err_buf, " ", strlen(err_buf)) == 0) -+ strlcpy(err_buf, "Validation failed", sizeof(err_buf)); -+ return txn_set_config_error(txn_req, MGMTD_INVALID_PARAM, err_buf); -+ } -+ -+ nb_config_diff(mgmt_ds_get_nb_config(ccreq->dst_ds_ctx), nb_config, cfg_chgs); -+ if (RB_EMPTY(nb_config_cbs, cfg_chgs)) { -+ return txn_set_config_error(txn_req, MGMTD_NO_CFG_CHANGES, -+ "No changes found to be committed!"); -+ } -+ return 0; -+} -+ -+/** -+ * mgmt_txn_send_commit_config_req() - Send a commit config request -+ * @txn_id - A config TXN which must exist. -+ * -+ * Return: -1 on setup failure -- should immediately handle error, Otherwise 0 -+ * and will reply to session with any downstream error. -+ */ -+void mgmt_txn_send_commit_config_req(uint64_t txn_id, uint64_t req_id, enum mgmt_ds_id src_ds_id, -+ struct mgmt_ds_ctx *src_ds_ctx, enum mgmt_ds_id dst_ds_id, -+ struct mgmt_ds_ctx *dst_ds_ctx, bool validate_only, bool abort, -+ bool implicit, bool unlock, struct mgmt_edit_req *edit) -+{ -+ struct mgmt_txn *txn; -+ struct txn_req_commit *ccreq; -+ struct nb_config_cbs changes = { 0 }; -+ int ret; -+ -+ txn = txn_lookup(txn_id); -+ assert(txn && txn->type == MGMTD_TXN_TYPE_CONFIG); -+ -+ /* Only one outstanding commit request per txn */ -+ assert(txn_txn_req_commit(txn) == NULL); -+ -+ ccreq = txn_req_commit_alloc(txn, req_id); -+ -+ /* Lock the datastores for this transaction */ -+ assert(!mgmt_ds_txn_lock(src_ds_ctx, txn->txn_id)); -+ assert(!mgmt_ds_txn_lock(dst_ds_ctx, txn->txn_id)); -+ ccreq->txn_lock = true; -+ -+ ccreq->src_ds_id = src_ds_id; -+ ccreq->src_ds_ctx = src_ds_ctx; -+ ccreq->dst_ds_id = dst_ds_id; -+ ccreq->dst_ds_ctx = dst_ds_ctx; -+ ccreq->validate_only = validate_only; -+ ccreq->abort = abort; -+ ccreq->implicit = implicit; /* this is only true iff edit */ -+ ccreq->unlock_info = unlock; /* this is true for implicit commit in front-end */ -+ ccreq->edit = edit; -+ ccreq->cmt_stats = mgmt_fe_get_session_commit_stats(txn->session_id); -+ -+ ret = txn_get_config_changes(ccreq, &changes); -+ if (ret == 0) -+ ret = txn_cfg_send_config_changes(ccreq, &changes, 0); -+ if (ret) -+ txn_finish_commit(ccreq, ccreq->req.error, ccreq->req.err_info); -+} -+ -+/* -+ * TODO: this needs to be fixed to be like a normal commit config request and -+ * use a VTY session not the special 0 session ID. We should only use 0 for the -+ * adapter init case. -+ */ -+int mgmt_txn_rollback_trigger_cfg_apply(struct mgmt_ds_ctx *src_ds_ctx, -+ struct mgmt_ds_ctx *dst_ds_ctx) -+{ -+ static struct mgmt_commit_stats dummy_stats = { 0 }; -+ -+ struct nb_config_cbs changes = { 0 }; -+ struct mgmt_txn *txn; -+ struct txn_req *txn_req; -+ struct txn_req_commit *ccreq; -+ int ret; -+ -+ /* -+ * This could be the case when the config is directly -+ * loaded onto the candidate DS from a file. Get the -+ * diff from a full comparison of the candidate and -+ * running DSs. -+ */ -+ nb_config_diff(mgmt_ds_get_nb_config(dst_ds_ctx), -+ mgmt_ds_get_nb_config(src_ds_ctx), &changes); -+ -+ if (RB_EMPTY(nb_config_cbs, &changes)) -+ return -1; -+ -+ /* -+ * Create a CONFIG transaction to push the config changes -+ * provided to the backend client. -+ */ -+ txn = txn_create(MGMTD_TXN_TYPE_CONFIG); -+ if (!txn) { -+ _log_err("Failed to create CONFIG Transaction for downloading CONFIGs"); -+ nb_config_diff_del_changes(&changes); -+ return -1; -+ } -+ -+ _dbg("Created rollback txn-id: %" PRIu64, txn->txn_id); -+ -+ /* -+ * Set the changeset for transaction to commit and trigger the commit -+ * request. -+ */ -+ ccreq = txn_req_commit_alloc(txn, 0); -+ txn_req = as_txn_req(ccreq); -+ ccreq->src_ds_id = MGMTD_DS_CANDIDATE; -+ ccreq->src_ds_ctx = src_ds_ctx; -+ ccreq->dst_ds_id = MGMTD_DS_RUNNING; -+ ccreq->dst_ds_ctx = dst_ds_ctx; -+ ccreq->validate_only = false; -+ ccreq->abort = false; -+ ccreq->rollback = true; -+ ccreq->cmt_stats = &dummy_stats; -+ -+ /* -+ * Send the changes. -+ */ -+ ret = txn_cfg_send_config_changes(ccreq, &changes, 0); -+ if (ret) -+ txn_finish_commit(ccreq, txn_req->error, txn_req->err_info); -+ return ret; -+} -diff --git a/mgmtd/mgmt_txn_priv.h b/mgmtd/mgmt_txn_priv.h -new file mode 100644 -index 0000000000..25707773b9 ---- /dev/null -+++ b/mgmtd/mgmt_txn_priv.h -@@ -0,0 +1,80 @@ -+// SPDX-License-Identifier: GPL-2.0-or-later -+/* -+ * November 17 2025, Christian Hopps <chopps@labn.net> -+ * -+ * Copyright (c) 2025, LabN Consulting, L.L.C. -+ * -+ */ -+#include <zebra.h> -+#include "mgmtd/mgmt.h" -+#include "mgmtd/mgmt_txn.h" -+#include "queue.h" -+ -+#define _dbg(fmt, ...) DEBUGD(&mgmt_debug_txn, "TXN: %s: " fmt, __func__, ##__VA_ARGS__) -+#define _log_warn(fmt, ...) zlog_warn("%s: WARNING: " fmt, __func__, ##__VA_ARGS__) -+#define _log_err(fmt, ...) zlog_err("%s: ERROR: " fmt, __func__, ##__VA_ARGS__) -+ -+#define TXN_INCREF(txn) txn_incref(txn, __FILE__, __LINE__) -+#define TXN_DECREF(txn) txn_decref(txn, __FILE__, __LINE__) -+ -+/* ----------------- */ -+/* Txn Private Types */ -+/* ----------------- */ -+ -+enum txn_req_type { -+ TXN_REQ_TYPE_COMMIT = 1, -+ TXN_REQ_TYPE_GETTREE, -+ TXN_REQ_TYPE_RPC, -+}; -+ -+struct txn_req { -+ TAILQ_ENTRY(txn_req) link; -+ enum txn_req_type req_type; -+ uint64_t req_id; -+ struct mgmt_txn *txn; -+ int16_t error; /* cfg: MGMTD return code, else errno */ -+ char *err_info; /* darr str */ -+ struct event *timeout; /* for timing out the request */ -+}; -+#define as_txn_req(xreq) (&(xreq)->req) /* Macro to coerce specific req back into txn_req */ -+ -+struct mgmt_txn { -+ TAILQ_ENTRY(mgmt_txn) link; -+ enum mgmt_txn_type type; -+ uint64_t txn_id; -+ int refcount; -+ uint64_t session_id; /* One transaction per client session */ -+ /* List of pending requests */ -+ TAILQ_HEAD(txn_req_head, txn_req) reqs; -+}; -+ -+/* ---------------------------- */ -+/* TXN Private Config Functions */ -+/* ---------------------------- */ -+ -+extern void txn_cfg_cleanup(struct txn_req *txn_req); -+extern void txn_cfg_handle_error(struct txn_req *txn_req, struct mgmt_be_client_adapter *adapter, -+ int error, const char *errstr); -+extern int txn_cfg_be_client_connect(struct mgmt_be_client_adapter *adapter); -+extern void txn_cfg_txn_be_client_disconnect(struct mgmt_txn *txn, -+ struct mgmt_be_client_adapter *adapter); -+/* ---------------------------- */ -+/* TXN Private Shared Functions */ -+/* ---------------------------- */ -+ -+extern struct txn_req *txn_req_alloc(struct mgmt_txn *txn, uint64_t req_id, -+ enum txn_req_type req_type, size_t size); -+extern void txn_req_free(struct txn_req *txn_req); -+extern struct mgmt_txn *txn_create(enum mgmt_txn_type type); -+extern void mgmt_txn_cleanup_txn(struct mgmt_txn **txn); -+extern struct mgmt_txn *txn_lookup(uint64_t txn_id); -+extern void txn_decref(struct mgmt_txn *txn, const char *file, int line); -+ -+extern struct event_loop *mgmt_txn_tm; -+ -+/* ------------------- */ -+/* TXN Private Globals */ -+/* ------------------- */ -+ -+extern struct event_loop *mgmt_txn_tm; -+extern struct mgmt_master *mgmt_txn_mm; -diff --git a/mgmtd/mgmt_vty.c b/mgmtd/mgmt_vty.c -index a8f7827f28..a9d2ce13ea 100644 ---- a/mgmtd/mgmt_vty.c -+++ b/mgmtd/mgmt_vty.c -@@ -29,8 +29,6 @@ - #include "staticd/static_vty.h" - #include "zebra/zebra_cli.h" - --extern struct frr_daemon_info *mgmt_daemon_info; -- - DEFPY(show_mgmt_be_adapter, - show_mgmt_be_adapter_cmd, - "show mgmt backend-adapter all", -@@ -147,6 +145,11 @@ DEFPY(mgmt_commit, - bool validate_only = type[0] == 'c'; - bool abort = type[1] == 'b'; - -+ if (!vty->mgmt_locked_candidate_ds) -+ vty_out(vty, "Warning: candidate datastore is not locked.\n"); -+ if (!validate_only && !vty->mgmt_locked_running_ds) -+ vty_out(vty, "Warning: running datastore is not locked.\n"); -+ - if (vty_mgmt_send_commit_config(vty, validate_only, abort, false) != 0) - return CMD_WARNING_CONFIG_FAILED; - return CMD_SUCCESS; -@@ -159,12 +162,14 @@ DEFPY(mgmt_create_config_data, mgmt_create_config_data_cmd, - "XPath expression specifying the YANG data path\n" - "Value of the data to create\n") - { -- strlcpy(vty->cfg_changes[0].xpath, path, -- sizeof(vty->cfg_changes[0].xpath)); -+ strlcpy(vty->cfg_changes[0].xpath, path, sizeof(vty->cfg_changes[0].xpath)); - vty->cfg_changes[0].value = value; - vty->cfg_changes[0].operation = NB_OP_CREATE_EXCL; - vty->num_cfg_changes = 1; - -+ if (!vty->mgmt_locked_candidate_ds) -+ vty_out(vty, "Warning: candidate datastore is not locked.\n"); -+ - return vty_mgmt_send_config_data(vty, NULL, false); - } - -@@ -175,12 +180,14 @@ DEFPY(mgmt_set_config_data, mgmt_set_config_data_cmd, - "XPath expression specifying the YANG data path\n" - "Value of the data to set\n") - { -- strlcpy(vty->cfg_changes[0].xpath, path, -- sizeof(vty->cfg_changes[0].xpath)); -+ strlcpy(vty->cfg_changes[0].xpath, path, sizeof(vty->cfg_changes[0].xpath)); - vty->cfg_changes[0].value = value; - vty->cfg_changes[0].operation = NB_OP_MODIFY; - vty->num_cfg_changes = 1; - -+ if (!vty->mgmt_locked_candidate_ds) -+ vty_out(vty, "Warning: candidate datastore is not locked.\n"); -+ - return vty_mgmt_send_config_data(vty, NULL, false); - } - -@@ -190,13 +197,14 @@ DEFPY(mgmt_delete_config_data, mgmt_delete_config_data_cmd, - "Delete configuration data\n" - "XPath expression specifying the YANG data path\n") - { -- -- strlcpy(vty->cfg_changes[0].xpath, path, -- sizeof(vty->cfg_changes[0].xpath)); -+ strlcpy(vty->cfg_changes[0].xpath, path, sizeof(vty->cfg_changes[0].xpath)); - vty->cfg_changes[0].value = NULL; - vty->cfg_changes[0].operation = NB_OP_DELETE; - vty->num_cfg_changes = 1; - -+ if (!vty->mgmt_locked_candidate_ds) -+ vty_out(vty, "Warning: candidate datastore is not locked.\n"); -+ - return vty_mgmt_send_config_data(vty, NULL, false); - } - -@@ -206,13 +214,14 @@ DEFPY(mgmt_remove_config_data, mgmt_remove_config_data_cmd, - "Remove configuration data\n" - "XPath expression specifying the YANG data path\n") - { -- -- strlcpy(vty->cfg_changes[0].xpath, path, -- sizeof(vty->cfg_changes[0].xpath)); -+ strlcpy(vty->cfg_changes[0].xpath, path, sizeof(vty->cfg_changes[0].xpath)); - vty->cfg_changes[0].value = NULL; - vty->cfg_changes[0].operation = NB_OP_DESTROY; - vty->num_cfg_changes = 1; - -+ if (!vty->mgmt_locked_candidate_ds) -+ vty_out(vty, "Warning: candidate datastore is not locked.\n"); -+ - return vty_mgmt_send_config_data(vty, NULL, false); - } - -@@ -230,6 +239,9 @@ DEFPY(mgmt_replace_config_data, mgmt_replace_config_data_cmd, - vty->cfg_changes[0].operation = NB_OP_REPLACE; - vty->num_cfg_changes = 1; - -+ if (!vty->mgmt_locked_candidate_ds) -+ vty_out(vty, "Warning: candidate datastore is not locked.\n"); -+ - return vty_mgmt_send_config_data(vty, NULL, false); - } - -@@ -251,7 +263,7 @@ DEFPY(mgmt_edit, mgmt_edit_cmd, - { - LYD_FORMAT format = (fmt && fmt[0] == 'x') ? LYD_XML : LYD_JSON; - uint8_t operation; -- uint8_t flags = 0; -+ uint8_t ds_id; - - switch (op[2]) { - case 'e': -@@ -280,14 +292,8 @@ DEFPY(mgmt_edit, mgmt_edit_cmd, - return CMD_WARNING_CONFIG_FAILED; - } - -- if (lock) -- flags |= EDIT_FLAG_IMPLICIT_LOCK; -- -- if (commit) -- flags |= EDIT_FLAG_IMPLICIT_COMMIT; -- -- return vty_mgmt_send_edit_req(vty, MGMT_MSG_DATASTORE_CANDIDATE, format, flags, operation, -- xpath, data); -+ ds_id = commit ? MGMT_MSG_DATASTORE_RUNNING : MGMT_MSG_DATASTORE_CANDIDATE; -+ return vty_mgmt_send_edit_req(vty, ds_id, format, 0, operation, xpath, data); - } - - DEFPY(mgmt_rpc, mgmt_rpc_cmd, -@@ -426,7 +432,7 @@ DEFPY(show_mgmt_map_xpath, - "Get YANG Backend Subscription\n" - "XPath expression specifying the YANG data path\n") - { -- mgmt_be_show_xpath_registries(vty, path); -+ mgmt_be_adapter_show_xpath_registries(vty, path); - return CMD_SUCCESS; - } - -@@ -568,18 +574,6 @@ DEFPY(debug_mgmt, debug_mgmt_cmd, - return CMD_SUCCESS; - } - --static void mgmt_config_read_in(struct event *event) --{ -- if (vty_mgmt_fe_enabled()) -- mgmt_vty_read_configs(); -- else { -- zlog_warn("%s: no connection to front-end server, retry in 1s", -- __func__); -- event_add_timer(mm->master, mgmt_config_read_in, NULL, 1, -- &mgmt_daemon_info->read_in); -- } --} -- - static int mgmtd_config_write(struct vty *vty) - { - struct lyd_node *root; -@@ -624,10 +618,6 @@ void mgmt_vty_init(void) - #ifdef HAVE_STATICD - static_vty_init(); - #endif -- -- event_add_event(mm->master, mgmt_config_read_in, NULL, 0, -- &mgmt_daemon_info->read_in); -- - install_node(&mgmtd_node); - - install_element(VIEW_NODE, &show_mgmt_be_adapter_cmd); -diff --git a/mgmtd/mgmt_vty_frontend.c b/mgmtd/mgmt_vty_frontend.c -new file mode 100644 -index 0000000000..a4f7cc0f7d ---- /dev/null -+++ b/mgmtd/mgmt_vty_frontend.c -@@ -0,0 +1,898 @@ -+// SPDX-License-Identifier: GPL-2.0-or-later -+/* -+ * November 15 2025, Christian Hopps <chopps@labn.net> -+ * -+ * Copyright (c) 2025, LabN Consulting, L.L.C. -+ * -+ */ -+ -+#include <zebra.h> -+#include <libyang/libyang.h> -+#include <libyang/version.h> -+#include "lib/debug.h" -+#include "lib/command.h" -+#include "lib/lib_vty.h" -+#include "lib/northbound_cli.h" -+#include "lib/vty.h" -+#include "mgmtd/mgmt.h" -+ -+#define _dbg debug_fe_client -+#define _log_err log_err_fe_client -+#define _log_warn(fmt, ...) zlog_warn("FE-CLIENT: %s: WARNING: " fmt, __func__, ##__VA_ARGS__) -+ -+/* static */ struct mgmt_fe_client *mgmt_fe_client; -+static bool mgmt_fe_connected; -+ -+static uint64_t mgmt_client_id_next; -+ -+char const *const mgmt_daemons[] = { -+ "zebra", -+#ifdef HAVE_RIPD -+ "ripd", -+#endif -+#ifdef HAVE_RIPNGD -+ "ripngd", -+#endif -+#ifdef HAVE_STATICD -+ "staticd", -+#endif -+}; -+uint mgmt_daemons_count = array_size(mgmt_daemons); -+ -+ -+/* ================= */ -+/* Utility Functions */ -+/* ================= */ -+ -+static bool vty_mgmt_fe_enabled(void) -+{ -+ return mgmt_fe_client && mgmt_fe_connected; -+} -+ -+static void fe_client_set_vty_callbacks(bool connected); -+static int vty_mgmt_send_lockds_req(struct vty *vty, enum mgmt_ds_id ds_id, bool lock, bool scok); -+ -+static int vty_mgmt_lock_candidate_inline(struct vty *vty) -+{ -+ assert(!vty->mgmt_locked_candidate_ds); -+ (void)vty_mgmt_send_lockds_req(vty, MGMTD_DS_CANDIDATE, true, true); -+ return vty->mgmt_locked_candidate_ds ? 0 : -1; -+} -+ -+static int vty_mgmt_unlock_candidate_inline(struct vty *vty) -+{ -+ assert(vty->mgmt_locked_candidate_ds); -+ (void)vty_mgmt_send_lockds_req(vty, MGMTD_DS_CANDIDATE, false, true); -+ return vty->mgmt_locked_candidate_ds ? -1 : 0; -+} -+ -+static int vty_mgmt_lock_running_inline(struct vty *vty) -+{ -+ assert(!vty->mgmt_locked_running_ds); -+ (void)vty_mgmt_send_lockds_req(vty, MGMTD_DS_RUNNING, true, true); -+ return vty->mgmt_locked_running_ds ? 0 : -1; -+} -+ -+static int vty_mgmt_unlock_running_inline(struct vty *vty) -+{ -+ assert(vty->mgmt_locked_running_ds); -+ (void)vty_mgmt_send_lockds_req(vty, MGMTD_DS_RUNNING, false, true); -+ return vty->mgmt_locked_running_ds ? -1 : 0; -+} -+ -+void vty_mgmt_resume_response(struct vty *vty, int ret) -+{ -+ if (!vty->mgmt_req_pending_cmd) { -+ zlog_err("vty resume response called without mgmt_req_pending_cmd"); -+ return; -+ } -+ -+ debug_fe_client("resuming CLI cmd after %s on vty session-id: %" PRIu64 " with '%s'", -+ vty->mgmt_req_pending_cmd, vty->mgmt_session_id, -+ ret == CMD_SUCCESS ? "success" : "failed"); -+ -+ vty->mgmt_req_pending_cmd = NULL; -+ -+ vty_resume_response(vty, ret); -+} -+ -+/* ======================================================= */ -+/* Startup Read Config Files for all mgmt-enabled daemons. */ -+/* ======================================================= */ -+ -+static bool mgmt_vty_read_configs(void) -+{ -+ char path[PATH_MAX]; -+ struct vty *vty; -+ FILE *confp; -+ uint line_num = 0; -+ uint count = 0; -+ uint index; -+ -+ vty = vty_new(); -+ vty->wfd = STDERR_FILENO; -+ vty->type = VTY_FILE; /* We don't send these changes to backends */ -+ vty->node = CONFIG_NODE; -+ vty->config = true; -+ vty->pending_allowed = true; -+ -+ vty->candidate_config = vty_shared_candidate_config; -+ -+ vty_mgmt_lock_candidate_inline(vty); -+ vty_mgmt_lock_running_inline(vty); -+ -+ for (index = 0; index < array_size(mgmt_daemons); index++) { -+ snprintf(path, sizeof(path), "%s/%s.conf", frr_sysconfdir, mgmt_daemons[index]); -+ -+ confp = vty_open_config(path, config_default); -+ if (!confp) -+ continue; -+ -+ zlog_info("mgmtd: reading config file: %s", path); -+ -+ /* Execute configuration file */ -+ line_num = 0; -+ (void)config_from_file(vty, confp, &line_num); -+ count++; -+ -+ fclose(confp); -+ } -+ -+ snprintf(path, sizeof(path), "%s/mgmtd.conf", frr_sysconfdir); -+ confp = vty_open_config(path, config_default); -+ if (confp) { -+ zlog_info("mgmtd: reading config file: %s", path); -+ -+ line_num = 0; -+ (void)config_from_file(vty, confp, &line_num); -+ count++; -+ -+ fclose(confp); -+ } -+ -+ /* Conditionally unlock as the config file may have "exit"d early which -+ * would then have unlocked things. -+ */ -+ if (vty->mgmt_locked_running_ds) -+ vty_mgmt_unlock_running_inline(vty); -+ if (vty->mgmt_locked_candidate_ds) -+ vty_mgmt_unlock_candidate_inline(vty); -+ -+ vty->pending_allowed = false; -+ -+ if (!count) -+ vty_close(vty); -+ else -+ vty_read_file_finish(vty, NULL); -+ -+ zlog_info("mgmtd: finished reading config files"); -+ -+ return true; -+} -+ -+ -+/* -+ * This is analogous to frr_config_read_in() in libfrr.c, but customized for -+ * mgmtd. It reads in all the mgmt-enabled daemon config files, this method is -+ * now deprecrated in favor of integrated config via vtysh. -+ */ -+static void mgmt_config_read_in(struct event *event) -+{ -+ if (vty_mgmt_fe_enabled()) -+ mgmt_vty_read_configs(); -+ else { -+ zlog_warn("%s: no connection to front-end server, retry in 1s", __func__); -+ event_add_timer(mm->master, mgmt_config_read_in, NULL, 1, -+ &mgmt_daemon_info->read_in); -+ } -+} -+ -+static ssize_t vty_mgmt_libyang_print(void *user_data, const void *buf, size_t count) -+{ -+ struct vty *vty = user_data; -+ -+ vty_out(vty, "%.*s", (int)count, (const char *)buf); -+ return count; -+} -+ -+static void vty_out_yang_error(struct vty *vty, LYD_FORMAT format, const struct ly_err_item *ei) -+{ -+#if (LY_VERSION_MAJOR < 3) -+#define data_path path -+#else -+#define data_path data_path -+#endif -+ bool have_apptag = ei->apptag && ei->apptag[0] != 0; -+ bool have_path = ei->data_path && ei->data_path[0] != 0; -+ bool have_msg = ei->msg && ei->msg[0] != 0; -+ const char *severity = NULL; -+ const char *evalid = NULL; -+ const char *ecode = NULL; -+#if (LY_VERSION_MAJOR < 3) -+ LY_ERR err = ei->no; -+#else -+ LY_ERR err = ei->err; -+#endif -+ -+ if (ei->level == LY_LLERR) -+ severity = "error"; -+ else if (ei->level == LY_LLWRN) -+ severity = "warning"; -+ -+ ecode = yang_ly_strerrcode(err); -+ if (err == LY_EVALID && ei->vecode != LYVE_SUCCESS) -+ evalid = yang_ly_strvecode(ei->vecode); -+ -+ switch (format) { -+ case LYD_XML: -+ vty_out(vty, "<rpc-error xmlns=\"urn:ietf:params:xml:ns:netconf:base:1.0\">"); -+ vty_out(vty, "<error-type>application</error-type>"); -+ if (severity) -+ vty_out(vty, "<error-severity>%s</error-severity>", severity); -+ if (ecode) -+ vty_out(vty, "<error-code>%s</error-code>", ecode); -+ if (evalid) -+ vty_out(vty, "<error-validation>%s</error-validation>\n", evalid); -+ if (have_path) -+ vty_out(vty, "<error-path>%s</error-path>\n", ei->data_path); -+ if (have_apptag) -+ vty_out(vty, "<error-app-tag>%s</error-app-tag>\n", ei->apptag); -+ if (have_msg) -+ vty_out(vty, "<error-message>%s</error-message>\n", ei->msg); -+ -+ vty_out(vty, "</rpc-error>"); -+ break; -+ case LYD_JSON: -+ vty_out(vty, "{ \"error-type\": \"application\""); -+ if (severity) -+ vty_out(vty, ", \"error-severity\": \"%s\"", severity); -+ if (ecode) -+ vty_out(vty, ", \"error-code\": \"%s\"", ecode); -+ if (evalid) -+ vty_out(vty, ", \"error-validation\": \"%s\"", evalid); -+ if (have_path) -+ vty_out(vty, ", \"error-path\": \"%s\"", ei->data_path); -+ if (have_apptag) -+ vty_out(vty, ", \"error-app-tag\": \"%s\"", ei->apptag); -+ if (have_msg) -+ vty_out(vty, ", \"error-message\": \"%s\"", ei->msg); -+ -+ vty_out(vty, "}"); -+ break; -+ case LYD_UNKNOWN: -+ case LYD_LYB: -+ default: -+ vty_out(vty, "%% error"); -+ if (severity) -+ vty_out(vty, " severity: %s", severity); -+ if (evalid) -+ vty_out(vty, " invalid: %s", evalid); -+ if (have_path) -+ vty_out(vty, " path: %s", ei->data_path); -+ if (have_apptag) -+ vty_out(vty, " app-tag: %s", ei->apptag); -+ if (have_msg) -+ vty_out(vty, " msg: %s", ei->msg); -+ break; -+ } -+#undef data_path -+} -+ -+static uint vty_out_yang_errors(struct vty *vty, LYD_FORMAT format) -+{ -+ const struct ly_err_item *ei = ly_err_first(ly_native_ctx); -+ uint count; -+ -+ if (!ei) -+ return 0; -+ -+ if (format == LYD_JSON) -+ vty_out(vty, "\"ietf-restconf:errors\": [ "); -+ -+ for (count = 0; ei; count++, ei = ei->next) { -+ if (count) -+ vty_out(vty, ", "); -+ vty_out_yang_error(vty, format, ei); -+ } -+ -+ if (format == LYD_JSON) -+ vty_out(vty, " ]"); -+ -+ ly_err_clean(ly_native_ctx, NULL); -+ -+ return count; -+} -+ -+ -+static int vty_mgmt_handle_error_reply(struct mgmt_fe_client *client, uintptr_t user_data, -+ uint64_t client_id, uint64_t session_id, uintptr_t session_ctx, -+ uint64_t req_id, int error, const char *errstr) -+{ -+ struct vty *vty = (struct vty *)session_ctx; -+ const char *cname = mgmt_fe_client_name(client); -+ -+ if (!vty->mgmt_req_pending_cmd) { -+ debug_fe_client("Error with no pending command: %d returned for client %s 0x%Lx session-id %Lu req-id %Lu error-str %s", -+ error, cname, client_id, session_id, req_id, errstr); -+ vty_out(vty, "%% Error %d from MGMTD for %s with no pending command: %s\n", error, -+ cname, errstr); -+ return CMD_WARNING; -+ } -+ -+ debug_fe_client("Error %d returned for client %s 0x%" PRIx64 " session-id %" PRIu64 -+ " req-id %" PRIu64 "error-str %s", -+ error, cname, client_id, session_id, req_id, errstr); -+ -+ vty_out(vty, "%% %s (for %s, client %s)\n", errstr, vty->mgmt_req_pending_cmd, cname); -+ -+ vty_mgmt_resume_response(vty, error ? CMD_WARNING : CMD_SUCCESS); -+ -+ return 0; -+} -+ -+/* =================================== */ -+/* Mgmtd Frontend Client Functionality */ -+/* =================================== */ -+ -+/* ------- */ -+/* Locking */ -+/* ------- */ -+ -+static int vty_mgmt_send_lockds_req(struct vty *vty, enum mgmt_ds_id ds_id, bool lock, bool scok) -+{ -+ assert(mgmt_fe_client); -+ assert(vty->mgmt_session_id); -+ -+ vty->mgmt_req_id++; -+ if (mgmt_fe_send_lockds_req(mgmt_fe_client, vty->mgmt_session_id, vty->mgmt_req_id, ds_id, -+ lock, scok)) { -+ zlog_err("Failed sending %sLOCK-DS-REQ req-id %" PRIu64, lock ? "" : "UN", -+ vty->mgmt_req_id); -+ vty_out(vty, "Failed to send %sLOCK-DS-REQ to MGMTD!\n", lock ? "" : "UN"); -+ return -1; -+ } -+ -+ if (!scok) -+ vty->mgmt_req_pending_cmd = "MESSAGE_LOCKDS_REQ"; -+ -+ return 0; -+} -+ -+static void vty_mgmt_handle_lock_ds_reply(struct mgmt_fe_client *client, uintptr_t usr_data, -+ uint64_t client_id, uintptr_t session_id, -+ uintptr_t session_ctx, uint64_t req_id, bool lock_ds, -+ bool success, enum mgmt_ds_id ds_id, char *errmsg_if_any) -+{ -+ struct vty *vty; -+ bool is_short_circuit = mgmt_fe_client_current_msg_short_circuit(client); -+ -+ vty = (struct vty *)session_ctx; -+ -+ assert(ds_id == MGMTD_DS_CANDIDATE || ds_id == MGMTD_DS_RUNNING); -+ if (!success) -+ zlog_err("%socking for DS %u failed, Err: '%s' vty %p", lock_ds ? "L" : "Unl", -+ ds_id, errmsg_if_any, vty); -+ else { -+ debug_fe_client("%socked DS %u successfully", lock_ds ? "L" : "Unl", ds_id); -+ if (ds_id == MGMTD_DS_CANDIDATE) -+ vty->mgmt_locked_candidate_ds = lock_ds; -+ else -+ vty->mgmt_locked_running_ds = lock_ds; -+ } -+ -+ if (!is_short_circuit && vty->mgmt_req_pending_cmd) { -+ assert(!strcmp(vty->mgmt_req_pending_cmd, "MESSAGE_LOCKDS_REQ")); -+ vty_mgmt_resume_response(vty, success ? CMD_SUCCESS : CMD_WARNING); -+ } -+} -+ -+/* ------------------------------------------------ */ -+/* "Send" Config Data -- actually just edits inline */ -+/* ------------------------------------------------ */ -+ -+int vty_mgmt_send_config_data(struct vty *vty, const char *xpath_base, bool implicit_commit) -+{ -+ char err_buf[BUFSIZ]; -+ bool error = false; -+ -+ if (implicit_commit) { -+ assert(vty->mgmt_client_id && vty->mgmt_session_id); -+ if (vty_mgmt_lock_candidate_inline(vty)) { -+ vty_out(vty, "%% could not lock candidate DS\n"); -+ return CMD_WARNING_CONFIG_FAILED; -+ } else if (vty_mgmt_lock_running_inline(vty)) { -+ vty_out(vty, "%% could not lock running DS\n"); -+ vty_mgmt_unlock_candidate_inline(vty); -+ return CMD_WARNING_CONFIG_FAILED; -+ } -+ } -+ -+ if (!vty->mgmt_locked_candidate_ds) -+ vty_out(vty, "%% WARNING: changing candidate datastore without lock.\n"); -+ -+ nb_candidate_edit_config_changes(vty->candidate_config, vty->cfg_changes, -+ vty->num_cfg_changes, xpath_base, false, err_buf, -+ sizeof(err_buf), &error); -+ if (error) { -+ /* -+ * Failure to edit the candidate configuration should never -+ * happen in practice, unless there's a bug in the code. When -+ * that happens, log the error but otherwise ignore it. -+ */ -+ vty_out(vty, "%% Couldn't apply changes: %s", err_buf); -+error: -+ if (implicit_commit) { -+ vty_mgmt_unlock_running_inline(vty); -+ vty_mgmt_unlock_candidate_inline(vty); -+ } -+ return CMD_WARNING_CONFIG_FAILED; -+ } -+ -+ if (!implicit_commit) -+ return CMD_SUCCESS; -+ -+ assert(vty->mgmt_client_id && vty->mgmt_session_id); -+ if (vty_mgmt_send_commit_config(vty, false, false, true) < 0) -+ goto error; -+ -+ return CMD_SUCCESS; -+} -+ -+/* ------------- */ -+/* Commit Config */ -+/* ------------- */ -+ -+int vty_mgmt_send_commit_config(struct vty *vty, bool validate_only, bool abort, bool unlock) -+{ -+ if (mgmt_fe_client && vty->mgmt_session_id) { -+ vty->mgmt_req_id++; -+ if (mgmt_fe_send_commit_req(mgmt_fe_client, vty->mgmt_session_id, vty->mgmt_req_id, -+ MGMTD_DS_CANDIDATE, MGMTD_DS_RUNNING, validate_only, -+ abort, unlock)) { -+ zlog_err("Failed sending COMMIT-REQ req-id %" PRIu64, vty->mgmt_req_id); -+ vty_out(vty, "Failed to send COMMIT-REQ to MGMTD!\n"); -+ return -1; -+ } -+ -+ vty->mgmt_req_pending_cmd = "MESSAGE_COMMCFG_REQ"; -+ vty->mgmt_num_pending_setcfg = 0; -+ } -+ -+ return 0; -+} -+ -+static void vty_mgmt_handle_commit_config_reply(struct mgmt_fe_client *client, uintptr_t usr_data, -+ uint64_t client_id, uintptr_t session_id, -+ uintptr_t session_ctx, uint64_t req_id, -+ bool success, enum mgmt_ds_id src_ds_id, -+ enum mgmt_ds_id dst_ds_id, bool validate_only, -+ bool unlock, char *errmsg_if_any) -+{ -+ struct vty *vty; -+ -+ vty = (struct vty *)session_ctx; -+ -+ if (!success) { -+ zlog_err("COMMIT_CONFIG request for client 0x%" PRIx64 " failed, Error: '%s'", -+ client_id, errmsg_if_any ? errmsg_if_any : "Unknown"); -+ vty_out(vty, "%% Configuration failed.\n\n"); -+ if (errmsg_if_any) -+ vty_out(vty, "%s\n", errmsg_if_any); -+ } else { -+ debug_fe_client("COMMIT_CONFIG request for client 0x%" PRIx64 " req-id %" PRIu64 -+ " was successfull%s%s", -+ client_id, req_id, errmsg_if_any ? ": " : "", errmsg_if_any ?: ""); -+ if (!unlock && errmsg_if_any) -+ vty_out(vty, "MGMTD: %s\n", errmsg_if_any); -+ } -+ -+ if (unlock) { -+ /* we locked these when we sent the commit, unlock now */ -+ vty_mgmt_unlock_candidate_inline(vty); -+ vty_mgmt_unlock_running_inline(vty); -+ } -+ -+ vty_mgmt_resume_response(vty, success ? CMD_SUCCESS : CMD_WARNING_CONFIG_FAILED); -+} -+ -+/* -------- */ -+/* Get Data */ -+/* -------- */ -+ -+int vty_mgmt_send_get_data_req(struct vty *vty, uint8_t datastore, LYD_FORMAT result_type, -+ uint8_t flags, uint8_t defaults, const char *xpath) -+{ -+ LYD_FORMAT intern_format = result_type; -+ -+ vty->mgmt_req_id++; -+ -+ if (mgmt_fe_send_get_data_req(mgmt_fe_client, vty->mgmt_session_id, vty->mgmt_req_id, -+ datastore, intern_format, flags, defaults, xpath)) { -+ zlog_err("Failed to send GET-DATA to MGMTD session-id: %" PRIu64 " req-id %" PRIu64 -+ ".", -+ vty->mgmt_session_id, vty->mgmt_req_id); -+ vty_out(vty, "Failed to send GET-DATA to MGMTD!\n"); -+ return -1; -+ } -+ -+ vty->mgmt_req_pending_cmd = "MESSAGE_GET_DATA_REQ"; -+ vty->mgmt_req_pending_data = result_type; -+ -+ return 0; -+} -+ -+static int vty_mgmt_handle_get_tree_reply(struct mgmt_fe_client *client, uintptr_t user_data, -+ uint64_t client_id, uint64_t session_id, -+ uintptr_t session_ctx, uint64_t req_id, -+ enum mgmt_ds_id ds_id, LYD_FORMAT result_type, -+ void *result, size_t len, int partial_error) -+{ -+ struct vty *vty; -+ struct lyd_node *dnode; -+ int ret = CMD_SUCCESS; -+ LY_ERR err; -+ -+ vty = (struct vty *)session_ctx; -+ -+ debug_fe_client("GET_TREE request %ssucceeded, client 0x%" PRIx64 " req-id %" PRIu64, -+ partial_error ? "partially " : "", client_id, req_id); -+ -+ assert(result_type == LYD_LYB || result_type == vty->mgmt_req_pending_data); -+ -+ if (vty->mgmt_req_pending_data == LYD_XML && partial_error) -+ vty_out(vty, "<!-- some errors occurred gathering results -->\n"); -+ -+ if (result_type == LYD_LYB) { -+ /* -+ * parse binary into tree and print in the specified format -+ */ -+ result_type = vty->mgmt_req_pending_data; -+ -+ err = lyd_parse_data_mem(ly_native_ctx, result, LYD_LYB, 0, 0, &dnode); -+ if (!err) -+ err = lyd_print_clb(vty_mgmt_libyang_print, vty, dnode, result_type, -+ LYD_PRINT_WITHSIBLINGS); -+ lyd_free_all(dnode); -+ -+ if (vty_out_yang_errors(vty, result_type) || err) -+ ret = CMD_WARNING; -+ } else { -+ /* -+ * Print the in-format result -+ */ -+ assert(result_type == LYD_XML || result_type == LYD_JSON); -+ vty_out(vty, "%.*s\n", (int)len - 1, (const char *)result); -+ } -+ -+ vty_mgmt_resume_response(vty, ret); -+ -+ return 0; -+} -+ -+ -+/* ----------- */ -+/* Edit Config */ -+/* ----------- */ -+ -+int vty_mgmt_send_edit_req(struct vty *vty, uint8_t datastore, LYD_FORMAT request_type, -+ uint8_t flags, uint8_t operation, const char *xpath, const char *data) -+{ -+ vty->mgmt_req_id++; -+ -+ if (mgmt_fe_send_edit_req(mgmt_fe_client, vty->mgmt_session_id, vty->mgmt_req_id, -+ datastore, request_type, flags, operation, xpath, data)) { -+ zlog_err("Failed to send EDIT to MGMTD session-id: %" PRIu64 " req-id %" PRIu64 ".", -+ vty->mgmt_session_id, vty->mgmt_req_id); -+ vty_out(vty, "Failed to send EDIT to MGMTD!\n"); -+ return -1; -+ } -+ -+ vty->mgmt_req_pending_cmd = "MESSAGE_EDIT_REQ"; -+ -+ return 0; -+} -+ -+static int vty_mgmt_handle_edit_reply(struct mgmt_fe_client *client, uintptr_t user_data, -+ uint64_t client_id, uint64_t session_id, -+ uintptr_t session_ctx, uint64_t req_id, const char *xpath) -+{ -+ struct vty *vty = (struct vty *)session_ctx; -+ -+ debug_fe_client("EDIT request for client 0x%" PRIx64 " req-id %" PRIu64 -+ " was successful, xpath: %s", -+ client_id, req_id, xpath); -+ -+ vty_mgmt_resume_response(vty, CMD_SUCCESS); -+ -+ return 0; -+} -+ -+ -+/* =========== */ -+/* Execute RPC */ -+/* =========== */ -+ -+int vty_mgmt_send_rpc_req(struct vty *vty, LYD_FORMAT request_type, const char *xpath, -+ const char *data) -+{ -+ vty->mgmt_req_id++; -+ -+ if (mgmt_fe_send_rpc_req(mgmt_fe_client, vty->mgmt_session_id, vty->mgmt_req_id, -+ request_type, xpath, data)) { -+ zlog_err("Failed to send RPC to MGMTD session-id: %" PRIu64 " req-id %" PRIu64 ".", -+ vty->mgmt_session_id, vty->mgmt_req_id); -+ vty_out(vty, "Failed to send RPC to MGMTD!\n"); -+ return -1; -+ } -+ -+ vty->mgmt_req_pending_cmd = "MESSAGE_RPC_REQ"; -+ -+ return 0; -+} -+ -+static int vty_mgmt_handle_rpc_reply(struct mgmt_fe_client *client, uintptr_t user_data, -+ uint64_t client_id, uint64_t session_id, -+ uintptr_t session_ctx, uint64_t req_id, const char *result) -+{ -+ struct vty *vty = (struct vty *)session_ctx; -+ -+ debug_fe_client("RPC request for client 0x%" PRIx64 " req-id %" PRIu64 " was successful", -+ client_id, req_id); -+ -+ if (result) -+ vty_out(vty, "%s\n", result); -+ -+ vty_mgmt_resume_response(vty, CMD_SUCCESS); -+ -+ return 0; -+} -+ -+ -+/* ==================================== */ -+/* VTY Augmenting/Hooking Functionality */ -+/* ==================================== */ -+ -+static void vty_new_mgmt(struct vty *new) -+{ -+ if (!mgmt_fe_client) -+ return; -+ if (!mgmt_client_id_next) -+ mgmt_client_id_next++; -+ new->mgmt_client_id = mgmt_client_id_next++; -+ new->mgmt_session_id = 0; -+ mgmt_fe_create_client_session(mgmt_fe_client, new->mgmt_client_id, (uintptr_t)new); -+ -+ /* we short-circuit create the session so it must be set now */ -+ assertf(new->mgmt_session_id != 0, "Failed to create client session for VTY"); -+} -+ -+static void vty_close_mgmt(struct vty *vty) -+{ -+ if (!mgmt_fe_client || !vty->mgmt_client_id) -+ return; -+ -+ debug_fe_client("closing vty session"); -+ mgmt_fe_destroy_client_session(mgmt_fe_client, vty->mgmt_client_id); -+ vty->mgmt_session_id = 0; -+} -+ -+static int vty_config_enter_mgmt(struct vty *vty, bool private_config, bool exclusive, -+ bool file_lock) -+{ -+ /* if no file lock requested, nothing to do */ -+ if (!file_lock) -+ return CMD_SUCCESS; -+ -+ /* Working on a private config is outside normal mgmtd actions */ -+ if (private_config) -+ return CMD_SUCCESS; -+ -+ /* Exclude is actually what we are doing with file-lock so it's no-op */ -+ -+ /* -+ * We only need to do a lock when reading a config file as we will be -+ * sending a batch of setcfg changes followed by a single commit -+ * message. For user interactive mode we are doing implicit commits -+ * those will obtain the lock (or not) when they try and commit. -+ */ -+ if (vty_mgmt_lock_candidate_inline(vty)) { -+ vty_out(vty, -+ "%% Can't enter config; candidate datastore locked by another session\n"); -+ return CMD_WARNING_CONFIG_FAILED; -+ } -+ if (vty_mgmt_lock_running_inline(vty)) { -+ vty_out(vty, -+ "%% Can't enter config; running datastore locked by another session\n"); -+ vty_mgmt_unlock_candidate_inline(vty); -+ return CMD_WARNING_CONFIG_FAILED; -+ } -+ assert(vty->mgmt_locked_candidate_ds); -+ assert(vty->mgmt_locked_running_ds); -+ -+ /* -+ * As datastores are locked explicitly, we don't need implicit commits -+ * and should allow pending changes. -+ */ -+ vty->pending_allowed = true; -+ -+ return CMD_SUCCESS; -+} -+ -+static void vty_config_node_exit_mgmt(struct vty *vty) -+{ -+ if (vty->mgmt_locked_running_ds) -+ vty_mgmt_unlock_running_inline(vty); -+ -+ if (vty->mgmt_locked_candidate_ds) -+ vty_mgmt_unlock_candidate_inline(vty); -+} -+ -+static void vty_end_config_mgmt(struct vty *vty) -+{ -+ /* -+ * If we have made changes with vty_mgmt_send_config_data(), but without -+ * implicit commit then we need to do a commit now to apply all those -+ * pending changes. -+ */ -+ if (vty->mgmt_num_pending_setcfg) -+ vty_mgmt_send_commit_config(vty, false, false, false); -+} -+ -+static int nb_cli_apply_changes_mgmt(struct vty *vty, const char *xpath_base_abs) -+{ -+ bool implicit_commit; -+ -+ VTY_CHECK_XPATH; -+ -+ assert(vty->type != VTY_FILE && vty_mgmt_fe_enabled()); -+ /* -+ * The legacy user wanted to clear pending (i.e., perform a -+ * commit immediately) due to some non-yang compatible -+ * functionality. This new mgmtd code however, continues to send -+ * changes putting off the commit until XFRR_end is received -+ * (i.e., end-of-config-file). This should be fine b/c all -+ * conversions to mgmtd require full proper implementations. -+ */ -+ if (!vty->num_cfg_changes) -+ return CMD_SUCCESS; -+ -+ implicit_commit = frr_get_cli_mode() == FRR_CLI_CLASSIC && !vty->pending_allowed; -+ if (vty_mgmt_send_config_data(vty, xpath_base_abs, implicit_commit) < 0) { -+ vty_out(vty, "%% Failed to apply configuration data.\n"); -+ return CMD_WARNING_CONFIG_FAILED; -+ } -+ if (!implicit_commit) -+ ++vty->mgmt_num_pending_setcfg; -+ return CMD_SUCCESS; -+} -+ -+static int nb_cli_rpc_mgmt(struct vty *vty, const char *xpath, const struct lyd_node *input) -+{ -+ char *data = NULL; -+ LY_ERR err; -+ int ret; -+ -+ err = lyd_print_mem(&data, input, LYD_JSON, LYD_PRINT_SHRINK); -+ assert(err == LY_SUCCESS); -+ -+ ret = vty_mgmt_send_rpc_req(vty, LYD_JSON, xpath, data); -+ -+ free(data); -+ if (ret < 0) -+ return CMD_WARNING; -+ return CMD_SUCCESS; -+} -+ -+static void fe_client_set_vty_callbacks(bool connected) -+{ -+ if (connected) { -+ /* only call when connected */ -+ vty_config_enter_mgmt_cb = vty_config_enter_mgmt; -+ vty_config_node_exit_mgmt_cb = vty_config_node_exit_mgmt; -+ cmd_init_config_callbacks(NULL, vty_end_config_mgmt); -+ nb_cli_apply_changes_mgmt_cb = nb_cli_apply_changes_mgmt; -+ nb_cli_rpc_mgmt_cb = nb_cli_rpc_mgmt; -+ } else { -+ vty_config_enter_mgmt_cb = NULL; -+ vty_config_node_exit_mgmt_cb = NULL; -+ cmd_init_config_callbacks(NULL, NULL); -+ nb_cli_apply_changes_mgmt_cb = NULL; -+ nb_cli_rpc_mgmt_cb = NULL; -+ } -+} -+ -+/* ====================== */ -+/* Initialize and Cleanup */ -+/* ====================== */ -+ -+static void vty_mgmt_client_connect_notified(struct mgmt_fe_client *client, uintptr_t usr_data, -+ bool connected) -+{ -+ debug_fe_client("Got %sconnected %s MGMTD Frontend Server", !connected ? "dis: " : "", -+ !connected ? "from" : "to"); -+ -+ /* -+ * We should not have any sessions for connecting or disconnecting case. -+ * The fe client library will delete all session on disconnect before -+ * calling us. -+ */ -+ assert(mgmt_fe_client_session_count(client) == 0); -+ -+ mgmt_fe_connected = connected; -+ fe_client_set_vty_callbacks(connected); -+ -+ /* Start or stop listening for vty connections */ -+ if (connected) -+ frr_vty_serv_start(true); -+ else -+ frr_vty_serv_stop(); -+} -+ -+/* -+ * A session has successfully been created for a vty. -+ */ -+static void vty_mgmt_client_session_notified(struct mgmt_fe_client *client, uintptr_t usr_data, -+ uint64_t client_id, bool create, bool success, -+ uintptr_t session_id, uintptr_t session_ctx) -+{ -+ struct vty *vty; -+ -+ vty = (struct vty *)session_ctx; -+ -+ if (!success) { -+ zlog_err("%s session for client %" PRIu64 " failed!", -+ create ? "Creating" : "Destroying", client_id); -+ return; -+ } -+ -+ debug_fe_client("%s session for client %" PRIu64 " successfully", -+ create ? "Created" : "Destroyed", client_id); -+ -+ if (create) { -+ assert(session_id != 0); -+ vty->mgmt_session_id = session_id; -+ } else { -+ vty->mgmt_session_id = 0; -+ /* We may come here by way of vty_close() and short-circuits */ -+ if (vty->status != VTY_CLOSE) -+ vty_close(vty); -+ } -+} -+ -+ -+static struct mgmt_fe_client_cbs mgmt_cbs = { -+ .client_connect_notify = vty_mgmt_client_connect_notified, -+ .client_session_notify = vty_mgmt_client_session_notified, -+ .lock_ds_notify = vty_mgmt_handle_lock_ds_reply, -+ .commit_config_notify = vty_mgmt_handle_commit_config_reply, -+ .get_tree_notify = vty_mgmt_handle_get_tree_reply, -+ .edit_notify = vty_mgmt_handle_edit_reply, -+ .rpc_notify = vty_mgmt_handle_rpc_reply, -+ .error_notify = vty_mgmt_handle_error_reply, -+ -+}; -+ -+void vty_mgmt_init(void) -+{ -+ char name[40]; -+ -+ assert(mm->master); -+ assert(!mgmt_fe_client); -+ snprintf(name, sizeof(name), "vty-%s-%ld", frr_get_progname(), (long)getpid()); -+ mgmt_fe_client = mgmt_fe_client_create(name, &mgmt_cbs, 0, mm->master); -+ vty_new_mgmt_cb = vty_new_mgmt; -+ vty_close_mgmt_cb = vty_close_mgmt; -+ assert(mgmt_fe_client); -+ -+ event_add_event(mm->master, mgmt_config_read_in, NULL, 0, &mgmt_daemon_info->read_in); -+} -+ -+void vty_mgmt_terminate(void) -+{ -+ if (mgmt_fe_client) { -+ mgmt_fe_client_destroy(mgmt_fe_client); -+ mgmt_fe_client = NULL; -+ vty_new_mgmt_cb = NULL; -+ vty_close_mgmt_cb = NULL; -+ } -+} -diff --git a/mgmtd/subdir.am b/mgmtd/subdir.am -index 14544c4f05..bbe272d84e 100644 ---- a/mgmtd/subdir.am -+++ b/mgmtd/subdir.am -@@ -34,7 +34,9 @@ mgmtd_libmgmtd_a_SOURCES = \ - mgmtd/mgmt_history.c \ - mgmtd/mgmt_memory.c \ - mgmtd/mgmt_txn.c \ -+ mgmtd/mgmt_txn_cfg.c \ - mgmtd/mgmt_vty.c \ -+ mgmtd/mgmt_vty_frontend.c \ - # end - - noinst_HEADERS += \ -@@ -45,6 +47,7 @@ noinst_HEADERS += \ - mgmtd/mgmt_history.h \ - mgmtd/mgmt_memory.h \ - mgmtd/mgmt_txn.h \ -+ mgmtd/mgmt_txn_priv.h \ - zebra/zebra_cli.h \ - # end - -diff --git a/ospf6d/ospf6_main.c b/ospf6d/ospf6_main.c -index 4aa5771a79..5d7c7ee245 100644 ---- a/ospf6d/ospf6_main.c -+++ b/ospf6d/ospf6_main.c -@@ -210,7 +210,7 @@ static void ospf6_config_finish(struct event *t) - OSPF6_PRE_CONFIG_MAX_WAIT_SECONDS); - } - --static void ospf6_config_start(void) -+static void ospf6_config_start(struct vty *vty) - { - if (IS_OSPF6_DEBUG_EVENT) - zlog_debug("ospf6d config start received"); -@@ -219,7 +219,7 @@ static void ospf6_config_start(void) - OSPF6_PRE_CONFIG_MAX_WAIT_SECONDS, &t_ospf6_cfg); - } - --static void ospf6_config_end(void) -+static void ospf6_config_end(struct vty *vty) - { - if (IS_OSPF6_DEBUG_EVENT) - zlog_debug("ospf6d config end received"); -diff --git a/ospfd/ospf_main.c b/ospfd/ospf_main.c -index 17c6f3f196..7107af9637 100644 ---- a/ospfd/ospf_main.c -+++ b/ospfd/ospf_main.c -@@ -180,7 +180,7 @@ static void ospf_config_finish(struct event *t) - OSPF_PRE_CONFIG_MAX_WAIT_SECONDS); - } - --static void ospf_config_start(void) -+static void ospf_config_start(struct vty *vty) - { - event_cancel(&t_ospf_cfg); - if (IS_DEBUG_OSPF_EVENT) -@@ -189,7 +189,7 @@ static void ospf_config_start(void) - OSPF_PRE_CONFIG_MAX_WAIT_SECONDS, &t_ospf_cfg); - } - --static void ospf_config_end(void) -+static void ospf_config_end(struct vty *vty) - { - if (IS_DEBUG_OSPF_EVENT) - zlog_debug("ospfd config end callback received."); -diff --git a/sharpd/sharp_main.c b/sharpd/sharp_main.c -index 2374070102..76aa923e09 100644 ---- a/sharpd/sharp_main.c -+++ b/sharpd/sharp_main.c -@@ -151,12 +151,12 @@ FRR_DAEMON_INFO(sharpd, SHARP, - ); - /* clang-format on */ - --static void sharp_start_configuration(void) -+static void sharp_start_configuration(struct vty *vty) - { - zlog_debug("Configuration has started to be read"); - } - --static void sharp_end_configuration(void) -+static void sharp_end_configuration(struct vty *vty) - { - zlog_debug("Configuration has finished being read"); - } -diff --git a/tools/checkpatch.pl b/tools/checkpatch.pl -index 63c1f60b4f..7060334c4c 100755 ---- a/tools/checkpatch.pl -+++ b/tools/checkpatch.pl -@@ -563,7 +563,7 @@ our $Iterators = qr{ - SUBGRP_FOREACH_PEER|SUBGRP_FOREACH_PEER_SAFE| - SUBGRP_FOREACH_ADJ|SUBGRP_FOREACH_ADJ_SAFE| - AF_FOREACH|FOREACH_AFI_SAFI|FOREACH_SAFI| -- FOREACH_BE_CLIENT_BITS|FOREACH_MGMTD_BE_CLIENT_ID| -+ FOREACH_BE_CLIENT_BITS|FOREACH_MGMTD_BE_CLIENT_ID|FOREACH_BE_ADAPTER_BITS| - FOREACH_SESSION_IN_LIST| - LSDB_LOOP - }x; -diff --git a/watchfrr/watchfrr.c b/watchfrr/watchfrr.c -index 62c6a1f42c..6bfbd47950 100644 ---- a/watchfrr/watchfrr.c -+++ b/watchfrr/watchfrr.c -@@ -1275,12 +1275,12 @@ static FRR_NORETURN void netns_setup(const char *nsname) - } - #endif - --static void watchfrr_start_config(void) -+static void watchfrr_start_config(struct vty *vty) - { - gs.reading_configuration = true; - } - --static void watchfrr_end_config(void) -+static void watchfrr_end_config(struct vty *vty) - { - gs.reading_configuration = false; - } --- -2.50.1 - diff --git a/scripts/package-build/frr/patches/frr/0019-ospf6d-Fix-router-LSA-when-no-new-BDR-elected.patch b/scripts/package-build/frr/patches/frr/0019-ospf6d-Fix-router-LSA-when-no-new-BDR-elected.patch index 9fd57a97..80539471 100644 --- a/scripts/package-build/frr/patches/frr/0019-ospf6d-Fix-router-LSA-when-no-new-BDR-elected.patch +++ b/scripts/package-build/frr/patches/frr/0019-ospf6d-Fix-router-LSA-when-no-new-BDR-elected.patch @@ -1,4 +1,4 @@ -From e439e12198474db79ceedfcb34685d6fdc372355 Mon Sep 17 00:00:00 2001 +From 0f1808a6a89c71bf7dea1d351c9cd81007638a1a Mon Sep 17 00:00:00 2001 From: Kyrylo Yatsenko <hedrok@gmail.com> Date: Thu, 26 Feb 2026 14:38:17 +0200 Subject: [PATCH] ospf6d: Fix router LSA when no new BDR elected @@ -24,22 +24,22 @@ specific case. Signed-off-by: Kyrylo Yatsenko <hedrok@gmail.com> --- - ospf6d/ospf6_interface.c | 12 +++++++++--- - 1 file changed, 9 insertions(+), 3 deletions(-) + ospf6d/ospf6_interface.c | 13 ++++++++++--- + 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/ospf6d/ospf6_interface.c b/ospf6d/ospf6_interface.c -index 101d47dd35..dc56ac56cd 100644 +index 7f16a96d4d..74aa013bfb 100644 --- a/ospf6d/ospf6_interface.c +++ b/ospf6d/ospf6_interface.c -@@ -919,6 +919,7 @@ void backup_seen(struct event *thread) - void neighbor_change(struct event *thread) +@@ -951,6 +951,7 @@ void backup_seen(struct event *event) + void neighbor_change(struct event *event) { struct ospf6_interface *oi; + int ret; - oi = (struct ospf6_interface *)EVENT_ARG(thread); + oi = (struct ospf6_interface *)EVENT_ARG(event); assert(oi && oi->interface); -@@ -927,9 +928,14 @@ void neighbor_change(struct event *thread) +@@ -959,9 +960,15 @@ void neighbor_change(struct event *event) zlog_debug("Interface Event %s: [NeighborChange]", oi->interface->name); @@ -51,12 +51,13 @@ index 101d47dd35..dc56ac56cd 100644 + ret = ospf6_interface_state_change(dr_election(oi), oi); + + /* In this case router LSA is not originated elsewhere */ -+ if (oi->type == OSPF_IFTYPE_BROADCAST && ret == -1 && oi->bdrouter == htonl(0)) ++ if (oi->area && oi->type == OSPF_IFTYPE_BROADCAST && ret == -1 && ++ oi->bdrouter == htonl(0)) + OSPF6_ROUTER_LSA_SCHEDULE(oi->area); + } } - void interface_down(struct event *thread) + void interface_down(struct event *event) -- 2.51.2 diff --git a/scripts/package-build/frr/patches/frr/0020-BGP-LS-support.patch b/scripts/package-build/frr/patches/frr/0020-BGP-LS-support.patch index 44d474f0..bb399662 100644 --- a/scripts/package-build/frr/patches/frr/0020-BGP-LS-support.patch +++ b/scripts/package-build/frr/patches/frr/0020-BGP-LS-support.patch @@ -1,1350 +1,69 @@ -From 5fb319d193444bcb3b92b55190922993a933d4b2 Mon Sep 17 00:00:00 2001 +From 8ed49fa657d2be4f5eb76ea6773209f40f1a5559 Mon Sep 17 00:00:00 2001 From: Carmine Scarpitta <cscarpit@cisco.com> Date: Fri, 23 Jan 2026 10:27:38 +0000 Subject: [PATCH] *: BGP-LS support -MIME-Version: 1.0 -Content-Type: text/plain; charset=UTF-8 -Content-Transfer-Encoding: 8bit Squash commits from PRs: * https://github.com/FRRouting/frr/pull/20470 * https://github.com/FRRouting/frr/pull/21077 * https://github.com/FRRouting/frr/pull/21078 -* https://github.com/FRRouting/frr/pull/21092 * https://github.com/FRRouting/frr/pull/21102 * https://github.com/FRRouting/frr/pull/21108 * https://github.com/FRRouting/frr/pull/21109 +* https://github.com/FRRouting/frr/pull/21076 * https://github.com/FRRouting/frr/pull/21285 * https://github.com/FRRouting/frr/pull/21286 -* https://github.com/FRRouting/frr/pull/21076 +* https://github.com/FRRouting/frr/pull/21092 +* https://github.com/FRRouting/frr/pull/21453 +* https://github.com/FRRouting/frr/pull/21455 +* https://github.com/FRRouting/frr/pull/21456 +* https://github.com/FRRouting/frr/pull/21600 +* https://github.com/FRRouting/frr/pull/21604 +* https://github.com/FRRouting/frr/pull/21607 +* https://github.com/FRRouting/frr/pull/21609 +* https://github.com/FRRouting/frr/pull/21827 +* git cherry-pick -x 81906f419d41e5791096a103d316a3d3a34b9db9 +* https://github.com/FRRouting/frr/pull/21657 +* https://github.com/FRRouting/frr/pull/20726 +* https://github.com/FRRouting/frr/pull/21755 * https://github.com/FRRouting/frr/pull/21376 - -Plus e62419b8940 that is also needed - -*: Add BGP-LS AFI/SAFI constants - -Add BGP Link-State (BGP-LS) Address Family infrastructure to prepare -for full protocol implementation in subsequent commits (RFC 9552). - -Introduce AFI_BGP_LS (16388) and SAFI_BGP_LS (71) and update all -AFI/SAFI-aware code paths to recognize the new address family. - -Protocol-specific logic is marked with TODO placeholders to be -implemented in subsequent commits. - -Signed-off-by: Carmine Scarpitta <cscarpit@cisco.com> -(cherry picked from commit 314b3eb5e6f3a6688d726661dfb8fb8985c36f5a) - -bgpd: Add debugging support for BGP-LS - -Add debugging support for BGP Link-State operations to aid development -and troubleshooting. - -- Add `debug bgp link-state` command -- Add `no debug bgp link-state` command -- Extend `no debug bgp` to also disable link-state debugging -- Show link-state debugging status in `show debugging bgp` - -Signed-off-by: Carmine Scarpitta <cscarpit@cisco.com> -(cherry picked from commit a98f8055d23ea97fb40343aa97c5cee9483ef535) - -bgpd: Add BGP-LS instance structure and initialization - -Introduce core BGP-LS module infrastructure. - -- Add `struct bgp_ls` to hold BGP-LS state -- Add `bgp_ls_init()` to initialize BGP-LS module during BGP instance - creation -- Add `bgp_ls_terminate()` to cleanup BGP-LS module during BGP instance - destruction - -Subsequent commits will extend this structure with NLRI and TED support. - -Signed-off-by: Carmine Scarpitta <cscarpit@cisco.com> -(cherry picked from commit 301b23373c3ad836f3cf9697c755faff00ba208e) - -bgpd: Add BGP-LS NLRI data structures - -Define structures for BGP Link-State topology elements per RFC 9552. - -- Add Node, Link, and Prefix NLRI structures -- Support Node Descriptor TLVs (AS Number, BGP-LS ID, OSPF Area ID, - IGP Router ID) -- Support Link Descriptor TLVs (Link ID, IPv4/IPv6 interface/neighbor - addresses, MT-ID) -- Support Prefix Descriptor TLVs (OSPF Route Type, IP Reachability, - MT-ID) -- Define BGP-LS Attribute structure for path attribute type 29 - -The BGP-LS Attribute (codepoint 29) carries topology information -for Link-State NLRIs including node attributes (router capabilities, -SR capabilities), link attributes (TE metrics, bandwidth, delay), -and prefix attributes (prefix metrics, SIDs). - -Signed-off-by: Carmine Scarpitta <cscarpit@cisco.com> -(cherry picked from commit e49a5cd8187b3ec1d24ab3b3e38a4546b161d8d7) - -bgpd: Add BGP-LS NLRI allocation and free functions - -Add memory management functions for BGP-LS NLRI structures. - -- Add bgp_ls_nlri_alloc() to allocate NLRI structures -- Add bgp_ls_nlri_free() to free NLRI and cleanup dynamic fields -- Add bgp_ls_attr_alloc() to allocate BGP-LS attribute structures -- Add bgp_ls_attr_free() to free BGP-LS attributes and cleanup - dynamic fields - -These functions handle proper initialization and cleanup of all -NLRI types (Node, Link, Prefix) and their associated BGP-LS -attributes, ensuring no memory leaks from dynamically allocated -descriptor fields. - -Signed-off-by: Carmine Scarpitta <cscarpit@cisco.com> -(cherry picked from commit be4c9269a49894ea80a359f61e49f42518b314aa) - -bgpd: Add BGP-LS NLRI comparison functions - -Add functions to determine if two BGP-LS NLRIs are equal. - -These functions will be used in subsequent commits for NLRI hash table -lookups and duplicate handling. - -Signed-off-by: Carmine Scarpitta <cscarpit@cisco.com> -(cherry picked from commit 3e6d2ddcd36108667f1e9c0230d5cae76fa04a79) - -bgpd: Add BGP-LS NLRI validation functions - -Add validation functions to verify BGP-LS NLRI is valid per RFC 9552. - -Used in subsequent commits to validate NLRIs received from peers. - -Signed-off-by: Carmine Scarpitta <cscarpit@cisco.com> -(cherry picked from commit 86e38b2cdec5b66df6a4698baf5ef8745a822ca6) - -bgpd: Add BGP-LS NLRI size calculation helper - -Add function to calculate wire format size of BGP-LS NLRI structures. - -Used in subsequent commits for NLRI encoding. - -Signed-off-by: Carmine Scarpitta <cscarpit@cisco.com> -(cherry picked from commit 280716cfbeb028b16602d596b091f55b82822339) - -bgpd: Add BGP-LS NLRI string conversion functions - -Add functions to convert BGP-LS enums to human-readable strings. - -Used for debugging output and display purposes. - -Signed-off-by: Carmine Scarpitta <cscarpit@cisco.com> -(cherry picked from commit 186ba5f1db7c20276ab1af4acb385f5cbadadb42) - -bgpd: Add BGP-LS NLRI deep copy function - -Add function to create deep copy of BGP-LS NLRI structures. - -Used in subsequent commits for NLRI storage and management. - -Signed-off-by: Carmine Scarpitta <cscarpit@cisco.com> -(cherry picked from commit e96a272c5020c47c1e1fe88986f55b8197f9c8d2) - -bgpd: Add hash table to store BGP-LS NLRIs - -Add hash table infrastructure to store BGP-LS NLRIs originated by the -local node and received from peers. - -- Add nlri_hash to bgp_ls structure for NLRI storage -- Add hash functions to compute hash keys and compare NLRIs -- Add bgp_ls_nlri_intern() to insert NLRI and increment reference count -- Add bgp_ls_nlri_unintern() to remove NLRI and decrement reference count -- Add bgp_ls_entry_lookup() to lookup NLRI in the hash table -- Add bgp_ls_nlri_get() to return existing NLRI or create new entry -- Integrate ID allocator to assign unique IDs when NLRI is interned - -NLRIs are reference counted and automatically freed when refcount -reaches zero. - -Signed-off-by: Carmine Scarpitta <cscarpit@cisco.com> -(cherry picked from commit 81657bd198fd77a6e2af4eea941a2e05397fc2d1) - -bgpd: Add hash table to store BGP-LS Attributes - -Implement unified hash table for BGP-LS attribute interning and lookup. - -- Implement bgp_ls_attr_get(): Insert BGP-LS attribute into unified - hash table, returning existing entry if already present or creating - and inserting new entry -- Implement bgp_ls_attr_lookup(): Search hash table for BGP-LS - attribute matching the provided attributes -- Implement bgp_ls_attr_intern(): Perform attribute interning by - looking up attribute in hash table, incrementing reference counter - if found, storing pointer for later unintern -- Implement bgp_ls_attr_unintern(): Decrement reference counter for - interned attribute and remove from hash table when counter reaches zero - -The unified hash table ensures that duplicate BGP-LS attributes across -multiple paths and peers are deduplicated, reducing memory consumption -and improving lookup performance for route decision making. - -Signed-off-by: Carmine Scarpitta <cscarpit@cisco.com> -(cherry picked from commit 8076ae20fac48f1be7815ef76cdfed763dc8fdc2) - -bgpd: Add error code for BGP-LS packet errors - -Add EC_BGP_LS_PACKET error code for BGP-LS NLRI and attribute packet -parsing/encoding errors. - -This error code is used by BGP-LS decode functions to log warnings when -malformed packets are received from peers, such as invalid TLV lengths, -missing mandatory fields, or unknown NLRI types. - -Signed-off-by: Carmine Scarpitta <cscarpit@cisco.com> -(cherry picked from commit 5a49df37f5a333b934687183e11a9e4ed076b892) - -bgpd: Add BGP-LS NLRI encode functions - -Add functions to serialize BGP-LS NLRIs to wire format per RFC 9552. - -The encode functions convert in-memory NLRI structures to the TLV-based -wire format used in MP_REACH_NLRI and MP_UNREACH_NLRI path attributes. - -- bgp_ls_encode_node_descriptor() - Encode Node Descriptors -- bgp_ls_encode_link_descriptor() - Encode Link Descriptors -- bgp_ls_encode_prefix_descriptor() - Encode Prefix Descriptors -- bgp_ls_encode_node_nlri() - Encode Node NLRI -- bgp_ls_encode_link_nlri() - Encode Link NLRI -- bgp_ls_encode_prefix_nlri() - Encode IPv4/IPv6 Prefix NLRIs -- bgp_ls_encode_nlri() - Top-level encoder for any BGP-LS NLRI -- bgp_ls_encode_attr() - Encode BGP-LS Attributes (Type 29) - -Signed-off-by: Carmine Scarpitta <cscarpit@cisco.com> -(cherry picked from commit 1ff2315405f719238d3bcb67f5625757c7bdf4c6) - -bgpd: Add BGP-LS NLRI decode functions - -Add functions to parse BGP-LS NLRIs from wire format per RFC 9552. - -The decode functions parse NLRIs received in MP_REACH_NLRI and -MP_UNREACH_NLRI path attributes into in-memory structures. - -- bgp_ls_decode_node_descriptor() - Parse Node Descriptor -- bgp_ls_decode_link_descriptor() - Parse Link Descriptor -- bgp_ls_decode_prefix_descriptor() - Parse Prefix Descriptor -- bgp_ls_decode_node_nlri() - Parse Node NLRI -- bgp_ls_decode_link_nlri() - Parse Link NLRI -- bgp_ls_decode_prefix_nlri() - Parse IPv4/IPv6 Prefix NLRI -- bgp_ls_decode_nlri() - Top-level decoder for BGP-LS NLRI -- bgp_ls_parse_attr() - Main attribute parser dispatcher - -Signed-off-by: Carmine Scarpitta <cscarpit@cisco.com> -(cherry picked from commit 9a95368f300ef4ecfff5e514e1e288bea88e1ca8) - -bgpd: Add TED instance to BGP-LS data structure - -Add Traffic Engineering Database (TED) instance to BGP-LS structure to -store link-state topology. - -In FRR's Link State architecture, IGP protocols (IS-IS, OSPF) act as -producers that build link-state databases from protocol exchanges and -send updates via ZAPI Opaque messages. BGP-LS acts as a consumer that -synchronizes with IGPs and listens for updates, maintaining a TED with -the topology information. - -The TED models network topology as a graph G(V, E) with: -- Vertices (V): Network nodes from BGP-LS Node NLRIs -- Edges (E): Unidirectional links from BGP-LS Link NLRIs -- Subnets (P): Prefixes from BGP-LS Prefix NLRIs - -Signed-off-by: Carmine Scarpitta <cscarpit@cisco.com> -(cherry picked from commit 337e6cd45bd100ab7be58aea80077e8365e01bd3) - -bgpd: Add helper functions to register linkstate db notifications - -Add helper functions to register and unregister BGP-LS for Link State -database notifications from IGP producers. - -In FRR's Link State architecture, consumers must register with zebra to -receive ZAPI Opaque Link State Update messages from producers (IS-IS, OSPF). -These functions wrap the Link State API registration mechanism: - -- bgp_ls_register() - Register to receive LS updates from IGPs -- bgp_ls_unregister() - Unregister from LS updates - -Registration enables BGP-LS to receive Node, Link, and Prefix updates -when IGPs detect topology changes. This is required for BGP-LS to maintain -an up-to-date TED and advertise topology to peers per RFC 9552. - -Signed-off-by: Carmine Scarpitta <cscarpit@cisco.com> -(cherry picked from commit 92404d7ae624f147c823251a7fc8797730257561) - -bgpd: Add BGP-LS Attribute support to attr structure - -Extend the core BGP attribute structure to support BGP-LS Attributes -(RFC 9552 Section 5.3) by adding ls_attr field and updating all related -attribute processing functions. - -Changes: -- Add ls_attr field to struct attr to store BGP-LS Attribute TLVs -- Update attrhash_key_make() to include ls_attr in attribute hash key -- Update attrhash_cmp() to compare ls_attr when present -- Extend bgp_attr_intern() to intern BGP-LS attributes -- Extend bgp_attr_unintern() to properly deintern/free BGP-LS attributes - -This enables the core BGP attribute handling infrastructure to properly -manage BGP-LS attributes through the standard attribute interning mechanism, -ensuring reference counting and deduplication of BGP-LS attribute data -across the RIB. - -Signed-off-by: Carmine Scarpitta <cscarpit@cisco.com> -(cherry picked from commit c8acf7a2c75404e030b16f1326001152d60bfc46) - -bgpd: Add BGP-LS update and withdraw functions - -Implement BGP-LS route installation and withdrawal to enable processing -of BGP-LS NLRIs received in BGP UPDATE messages. - -In BGP-LS, UPDATE messages contain BGP-LS NLRIs (not traditional prefixes). -When a BGP UPDATE with BGP-LS NLRI is received, a bgp_dest is allocated -to represent that NLRI location in the RIB and stored in the BGP RIB tree. -The ls_nlri field in bgp_dest stores the BGP-LS NLRI at that destination. - -Changes: -- Add bgp_ls_update() - Install or update BGP-LS NLRI in the BGP RIB - Called when a BGP UPDATE message contains a BGP-LS NLRI to add/modify - a BGP-LS route in the RIB -- Add bgp_ls_withdraw() - Withdraw BGP-LS NLRI from the BGP RIB - Called when a BGP UPDATE message contains a withdrawn BGP-LS NLRI - to remove the BGP-LS route from the RIB -- Add ls_nlri field to bgp_dest structure - Stores the BGP-LS NLRI at the destination in the RIB (used as the - destination key for BGP-LS routes) -- Extend bgp_dest_unlock_node() cleanup logic - Properly free ls_nlri when destination is being freed from the RIB - -Behavior: -- When a BGP UPDATE is received with BGP-LS NLRI, bgp_ls_update() - creates/updates a bgp_dest with that NLRI and installs it in the RIB -- When a BGP UPDATE is received with withdrawn BGP-LS NLRI, bgp_ls_withdraw() - removes the bgp_dest containing that NLRI from the RIB -- Proper resource cleanup ensures ls_nlri is freed when destinations - are removed from the RIB (bgp_dest_unlock_node) - -Signed-off-by: Carmine Scarpitta <cscarpit@cisco.com> -(cherry picked from commit 31cffad03f28c33e9ac7294efe95005df967412c) - -bgpd: Add functions to parse updates received from linkstate db - -Add callback functions to parse and process Link State updates received -from IGP producers via ZAPI Opaque messages. - -When IGPs (IS-IS, OSPF) detect topology changes, they send Link State -Update messages through zebra to registered consumers. These functions -handle the received messages: - -- bgp_ls_parse_update() - Parse ZAPI Opaque Link State messages -- bgp_ls_process_node() - Process Node updates (ADD/UPDATE/DELETE) -- bgp_ls_process_link() - Process Link updates (ADD/UPDATE/DELETE) -- bgp_ls_process_prefix() - Process Prefix updates (ADD/UPDATE/DELETE) - -The functions use the Link State API to extract Node, Link, or Prefix -information and update the TED accordingly. Updates trigger BGP-LS NLRI -generation for advertisement to peers. - -This enables BGP-LS to maintain a synchronized TED reflecting current -IGP topology and propagate changes to peers per RFC 9552. - -Signed-off-by: Carmine Scarpitta <cscarpit@cisco.com> -(cherry picked from commit baeefede22fbceb7135ac23933bcc2f89670bf06) - -bgpd: Handle zebra opaque messages - -Add callback handler to process ZAPI Opaque messages from zebra containing -Link State updates from IGP producers. - -Register bgp_zebra_opaque_msg_handler() as the callback for -ZEBRA_OPAQUE_MESSAGE type in the zebra client handler table. The handler: - -- Decodes ZAPI Opaque message header using zclient_opaque_decode() -- Routes Link State messages (LINK_STATE_SYNC, LINK_STATE_UPDATE) to - bgp_ls_process_linkstate_message() for TED processing -- Logs warnings for unknown opaque message types - -This completes the communication path between IGP producers (IS-IS, OSPF) -and BGP-LS consumer, enabling BGP-LS to receive topology updates and -database synchronization messages via zebra's ZAPI Opaque mechanism. - -Signed-off-by: Carmine Scarpitta <cscarpit@cisco.com> -(cherry picked from commit 88ced6ec9eed41a5c371a07133ddaf7b5bd3cce4) - -lib: Add CLI node for BGP Link-State address family configuration - -Add BGP_LS_NODE to enum node_type in command.h to enable BGP Link-State -address family configuration mode. - -This allows creating a dedicated configuration context for BGP-LS: - - router bgp 65000 - address-family link-state - <BGP-LS specific commands> - exit-address-family - -Follows the same pattern as other BGP address families (BGP_EVPN_NODE, -BGP_SRV6_NODE). - -Signed-off-by: Carmine Scarpitta <cscarpit@cisco.com> -(cherry picked from commit a3e90577db3b3cdadf1aae1dc8238f09c5cba048) - -bgpd: Add BGP-LS address family CLI commands - -Install BGP_LS_NODE in the CLI node hierarchy and add commands to -activate and configure BGP Link-State address family. - -New commands: -- address-family link-state [link-state] - Enters BGP-LS configuration mode under router bgp - -- neighbor PEER activate - Activates BGP-LS address family for the specified neighbor - -Configuration example: - router bgp 65000 - neighbor 192.0.2.1 remote-as 65001 - address-family link-state - neighbor 192.0.2.1 activate - exit-address-family - -Signed-off-by: Carmine Scarpitta <cscarpit@cisco.com> -(cherry picked from commit d4f914528b784611f6526c213733806e774d3d20) - -bgpd: Add BGP-LS configuration write support - -Extend bgp_config_write() to serialize BGP Link-State address family -configuration. - -This enables BGP-LS configuration to be: -- Displayed via show running-config -- Saved to startup-config (write memory) -- Written to configuration files - -Signed-off-by: Carmine Scarpitta <cscarpit@cisco.com> -(cherry picked from commit a20dfd7c81622071ca5bafe96339051fa1abac5e) - -bgpd: Add show commands for BGP-LS NLRIs - -Add commands to display BGP Link-State NLRIs from the BGP RIB. - -New commands: -- show bgp link-state link-state [json] - Display all BGP-LS routes with node/link/prefix NLRIs - -- show bgp link-state link-state NLRI [json] - Display specific BGP-LS route by NLRI identifier - NLRI format: [Type][ID][Protocol][Instance][AS][Area][Descriptors...] - -Signed-off-by: Carmine Scarpitta <cscarpit@cisco.com> -(cherry picked from commit 5ccafe4b1c163ad83057cb30c249004be340120c) - -vtysh: Integrate BGP-LS node into vtysh - -Integrate BGP_LS_NODE into vtysh to enable link-state address family -configuration. - -This allows users to configure BGP-LS through vtysh using: - router bgp 65000 - address-family link-state - neighbor <peer> activate - exit-address-family - -Signed-off-by: Carmine Scarpitta <cscarpit@cisco.com> -(cherry picked from commit 5251bf3aa4af17331bfc22e898fca9e626696aaa) - -bgpd: Register for linkstate db updates when peer is activated - -Register for Link State database updates when the first BGP-LS peer is -activated, and unregister when the last peer is deactivated. - -Modify peer_activate() and peer_deactivate() to manage Link State -database registration based on active BGP-LS peers: - -- When first peer is activated for link-state address family, call - bgp_ls_register() to subscribe to IGP topology updates from zebra -- When last peer is deactivated, call bgp_ls_unregister() to stop - receiving updates - -This optimizes resource usage by only receiving IGP topology updates -(from IS-IS, OSPF) when there are active BGP-LS peers that need to -receive the information. - -Signed-off-by: Carmine Scarpitta <cscarpit@cisco.com> -(cherry picked from commit c85fff38ea39ab708ae42e00f024f8de74451015) - -bgpd: Add BGP-LS capability display support - -Add display support for BGP-LS multi-protocol capability in BGP OPEN -message output. - -Update bgp_capability_vty_out() to properly display BGP-LS capabilities: - -- Display "AFI BGP-LS" for AFI_BGP_LS (16388) -- Display "SAFI BGP-LS" for SAFI_BGP_LS (71) -- Add JSON output support for BGP-LS capabilities - -This allows users to verify BGP-LS capability negotiation between peers -using "show bgp neighbor" commands, displaying both text and JSON output -for link-state address family capability advertisement and reception. - -Signed-off-by: Carmine Scarpitta <cscarpit@cisco.com> -(cherry picked from commit abccc105243cac9e7bcd94409b9135d04bdf3c19) - -bgpd: Add BGP-LS AFI/SAFI to negotiation functions - -Add BGP-LS capability checks to peer activity and address family -negotiation functions. - -Update peer status and capability negotiation functions to include -BGP-LS AFI/SAFI: - -- peer_active() - Check if peer has BGP-LS configured -- peer_active_nego() - Check if BGP-LS was negotiated with peer -- peer_group_af_configured() - Check if peer group has BGP-LS enabled -- bgp_open_option_parse() - Validate BGP-LS in capability negotiation - -This ensures BGP sessions with link-state address family are properly -recognized as active and that capability negotiation correctly validates -BGP-LS multi-protocol extensions. - -Signed-off-by: Carmine Scarpitta <cscarpit@cisco.com> -(cherry picked from commit 50e92bbb7f4660d427c2a5243f5b54acd50cb4d2) - -bgpd: Encode BGP-LS NLRI and attribute when building UPDATE packet - -Add BGP-LS encoding support to UPDATE message generation for -transmitting Link-State topology information to peers. - -Implement bgp_packet_ls_attribute() to encode BGP-LS Attribute (Type 29) -by dispatching to type-specific encoders (node/link/prefix attributes). - -Extend packet building functions to accept ls_nlri parameter: -- bgp_packet_attribute() - encode BGP-LS attribute when present -- bgp_packet_mpattr_prefix() - call bgp_ls_encode_nlri() for SAFI_BGP_LS -- bgp_packet_mpunreach_prefix() - handle BGP-LS withdrawals - -Update subgroup_update_packet() and subgroup_withdraw_packet() to extract -ls_nlri from path_info->extra and pass to encoding functions. - -This enables BGP-LS routes to be advertised and withdrawn using the -RFC 9552 wire format. - -Signed-off-by: Carmine Scarpitta <cscarpit@cisco.com> -(cherry picked from commit bfb67d5364a18542606299e753c5696f096caf8e) - -bgpd: Receive BGP-LS attribute - -Add bgp_attr_ls() function to parse and intern BGP-LS attributes (Type 29) -from received BGP update messages. The function validates peer configuration, -parses Type 29 TLVs, and interns the resulting attribute for use in route -processing. - -Signed-off-by: Carmine Scarpitta <cscarpit@cisco.com> -(cherry picked from commit aa7c9b97f20069f33e7efad501f8ee0ceb1929a9) - -bgpd: Unregister from lsdb on termination - -Call bgp_ls_unregister() in bgp_ls_terminate() to cleanly unregister -from the zebra link-state database when BGP instance terminates. - -This ensures proper cleanup and stops receiving IGP topology updates -when the BGP-LS functionality is no longer needed. - -Signed-off-by: Carmine Scarpitta <cscarpit@cisco.com> -(cherry picked from commit d70169ee48fc55ea8fe81d5c04ea1d81994b56fc) - -bgpd: Parse BGP-LS NLRI received from peers - -Add inbound BGP-LS message processing to receive Link-State topology -information from BGP peers. - -When BGP receives a BGP UPDATE message from a peer, it calls -bgp_nlri_parse() to parse the NLRI. The bgp_nlri_parse() function -checks the SAFI (Subsequent Address Family Identifier) contained in -the UPDATE message and dispatches to the appropriate SAFI-specific -handler. Currently supported SAFIs include: -- SAFI_UNICAST, SAFI_MULTICAST, SAFI_LABELED_UNICAST → bgp_nlri_parse_ip() -- SAFI_MPLS_VPN → bgp_nlri_parse_vpn() -- SAFI_ENCAP → bgp_nlri_parse_encap() -- SAFI_EVPN → bgp_nlri_parse_evpn() -- SAFI_FLOWSPEC → bgp_nlri_parse_flowspec() - -This commit extends bgp_nlri_parse() to handle SAFI_BGP_LS and -dispatch to a new handler bgp_nlri_parse_ls(). - -The bgp_nlri_parse_ls() function performs the following operations: -- Decodes BGP-LS NLRIs from MP_REACH_NLRI and MP_UNREACH_NLRI - attributes using bgp_ls_decode_nlri() -- Interns decoded NLRI into hash table via bgp_ls_nlri_get() -- Creates synthetic prefix (AF_UNSPEC) with NLRI ID for RIB storage -- Calls bgp_update() for route advertisements or bgp_withdraw() for - route removals to install/remove BGP-LS routes in the RIB - -This completes the inbound UPDATE message processing path, enabling -BGP to receive and store Link-State topology from peers per RFC 9552. - -Signed-off-by: Carmine Scarpitta <cscarpit@cisco.com> -(cherry picked from commit 1da3f4420cbe9f95c4a0468db848b0a9d19b0903) - -bgpd: Set nexthop for BGP-LS based on peer address family - -BGP-LS routes advertised to peers require a nexthop with the same -address family as the peer session (IPv4 peer needs IPv4 nexthop, -IPv6 peer needs IPv6 nexthop). - -The MP_REACH_NLRI and MP_UNREACH_NLRI encoding code in bgp_attr does -not have direct knowledge of the peer's address family, making it -unable to select the appropriate nexthop address family for BGP-LS -advertisements. - -Add peer flags PEER_FLAG_BGP_LS_IPV4 and PEER_FLAG_BGP_LS_IPV6 that -are set during peer activation for BGP-LS address family. The bgp_attr -encoding functions check these flags to determine whether to encode -IPv4 or IPv6 nexthop addresses in MP_REACH_NLRI attributes. - -This ensures BGP-LS routes are advertised with nexthops matching the -peer session's address family, allowing proper route exchange with -both IPv4 and IPv6 BGP-LS peers. - -Signed-off-by: Carmine Scarpitta <cscarpit@cisco.com> -(cherry picked from commit 399c64b834ac488795d303057b8ab5cce5125642) - -tests: Add BGP-LS topotest suite - -Add pytest topology test for BGP Link-State covering producer and -consumer modes with ISIS IGP backend. - -Tests BGP-LS capability negotiation, NLRI advertisement/reception -(Node, Link, Prefix), and dynamic topology change propagation -including route additions/removals and link state changes. - -Verifies BGP-LS updates flow correctly from IGP through BGP-LS -producer to BGP-LS consumer. - -Signed-off-by: Carmine Scarpitta <cscarpit@cisco.com> -(cherry picked from commit 78deb305498ff4cf86fa5063a985f157487eb458) - -doc: Add documentation for BGP-LS - -Signed-off-by: Carmine Scarpitta <cscarpit@cisco.com> -(cherry picked from commit 3a97a2faf9c9886873dc1a1dd23aa413667944ea) - -Fix compilation issues - -bgpd: Fix integer truncation of SRLG count when parsing SRLG TLV - -`count` is declared as `uint8_t` but receives `length / 4` where -`length` is a `uint16_t`. For `length >= 1024`, the division result -exceeds 255, which cannot be stored in `count` since `uint8_t` holds -at most 255, and the value silently truncates. The result is then -stored in `attr->srlg_count`, also `uint8_t`, which would re-truncate -it. - -Widen `count` in `parse_srlg()` and `srlg_count` in `struct -bgp_ls_attr` to `uint16_t`. - -Signed-off-by: Carmine Scarpitta <cscarpit@cisco.com> -(cherry picked from commit ef8a4c68cb7bc7a8dc3dc63d81ffefc53823ba77) - -bgpd: Fix integer truncation of count when parsing Route Tag TLV - -`count` is declared as `uint8_t` but receives `length / 4` where -`length` is a `uint16_t`. For `length >= 1024`, the division result -exceeds 255, which cannot be stored in `count` since `uint8_t` holds -at most 255, and the value silently truncates. The result is then -stored in `attr->route_tag_count`, also `uint8_t`, which would -re-truncate it. - -Widen `count` in `parse_route_tag()` and `route_tag_count` in `struct -bgp_ls_attr` to `uint16_t`. - -Signed-off-by: Carmine Scarpitta <cscarpit@cisco.com> -(cherry picked from commit 83b9a4fb8b0b9168465bf375f78a52eeea15d0b0) - -bgpd: Treat duplicate TLVs as malformed NLRI - -BGP receives BGP Update messages that can contain a BGP-LS NLRI, then -processes the TLVs carried in that NLRI. The NLRI may include duplicate -TLVs. Today, when a duplicate TLV is received, BGP-LS overwrites the -previous value with the new one. - -RFC 9552 requires duplicate TLVs to be treated as a malformed NLRI. -Also, replacing an earlier TLV value with a new one without freeing -previously allocated dynamic memory can cause a memory leak. - -Fix by checking whether the same TLV type appears more than once and -returning an error on duplicates. This propagates the parse failure to -the caller, which then treats the NLRI as malformed. - -Signed-off-by: Carmine Scarpitta <cscarpit@cisco.com> -(cherry picked from commit 3098d5f03e51760c59da71c5d27ee0b3ab9b7e19) - -bgpd: Check stream space before encoding BGP-LS TLV data - -Some encode paths wrote to the stream without first checking that enough -space was available. - -Writing when the stream is full can trigger an assert or crash. - -Fix by adding `STREAM_WRITEABLE()` checks before writing, and return -1 -when space is insufficient. - -Signed-off-by: Carmine Scarpitta <cscarpit@cisco.com> -(cherry picked from commit f02173760f17d350e1a26dc671fd89d1773b1d08) - -bgpd: Bound Extended Admin Group TLV decode size - -parse_ext_admin_group() only verified that the TLV length was a multiple -of 4, with no upper bound on the number of 32-bit words to decode. -A very large length could trigger excessive growth/work in -admin_group_bulk_set(). - -Define BGP_LS_MAX_EXT_ADMIN_GROUP_WORDS in bgp_ls_nlri.h and reject -Extended Admin Group TLVs that exceed this limit during decode. - -Signed-off-by: Carmine Scarpitta <cscarpit@cisco.com> -(cherry picked from commit ff2ab26af4e04b066e1f1be41f58fba6bd74f957) - -bgpd: Fix Extended Admin Group decode byte order - -`parse_ext_admin_group()` applied `ntohl()` to the value returned by -`stream_getl()`. `stream_getl()` already returns the 32-bit value in host -byte order, so this caused a second conversion. - -On little-endian systems, the extra conversion byte-swapped the value -again and corrupted decoded Extended Admin Group words. - -Fix by removing the redundant conversion and passing `stream_getl()` -output directly to `admin_group_bulk_set()`. - -Signed-off-by: Carmine Scarpitta <cscarpit@cisco.com> -(cherry picked from commit d57a9ed3087158f896f825ee57bb0883e89eb9ba) - -bgpd: Fix leak in BGP-LS NLRI parsing loop - -In `bgp_nlri_parse_ls()`, `bgp_ls_nlri` was declared on the stack and then -filled by `bgp_ls_decode_nlri()` from the input stream. - -`bgp_ls_decode_nlri()` can allocate dynamic fields inside the NLRI object. -Because the parsing loop reused the same stack object on each iteration, -those dynamic allocations were leaked across iterations. - -Fix by moving the NLRI object to heap allocation and using the BGP-LS NLRI -helpers for lifecycle management: allocate with `bgp_ls_nlri_alloc()` and -free with `bgp_ls_nlri_free()` on both success and error paths. - -Signed-off-by: Carmine Scarpitta <cscarpit@cisco.com> -(cherry picked from commit fa0eebc6c87091616336b2e8afc442d33425a32f) - -bgpd: Free descriptor MT-ID on BGP-LS parse errors - -bgp_ls_decode_link_descriptor() and bgp_ls_decode_prefix_descriptor() -can allocate desc->mt_id when parsing MT-ID TLVs. - -If a later TLV in the same descriptor fails validation, the functions -returned -1 without releasing that allocation, leaking memory on error -unwind. - -Fix by adding centralized error cleanup paths in both descriptor parsers -that free desc->mt_id before returning -1. - -Signed-off-by: Carmine Scarpitta <cscarpit@cisco.com> -(cherry picked from commit a773e278a2bc61e85d290f9ff5f4bcf8528c40cb) - -bgpd: Use extended length for BGP-LS path attribute - -bgp_packet_ls_attribute() encoded BGP-LS attributes with a 1-byte -length field, which can encode up to 255 bytes of BGP-LS attribute data. - -BGP-LS attributes often exceed 255 bytes when carrying multiple -node/link/prefix TLVs, so a 1-byte length is not sufficient. - -Fix by setting BGP_ATTR_FLAG_EXTLEN for BGP-LS attributes and -encoding the attribute length as 2 bytes. Add a bounds check to -ensure the encoded attribute length fits in the extended-length field. - -Signed-off-by: Carmine Scarpitta <cscarpit@cisco.com> -(cherry picked from commit a8b23e3df8e2966e424bbc3ceb1939ed063f1778) - -bgpd: Guard BGP-LS NLRI parsing when `ls_info` is unavailable - -`bgp_nlri_parse_ls()` dereferenced `peer->bgp->ls_info` without checking -that BGP-LS state was initialized. - -Add defensive checks for peer, peer->bgp, peer->bgp->ls_info, and -packet before parsing, and return BGP_NLRI_PARSE_ERROR on invalid state. - -Signed-off-by: Carmine Scarpitta <cscarpit@cisco.com> -(cherry picked from commit 2e533d3d1310a2516e8366633cb526ec396a49ae) - -bgpd: Fix CID 1670210 remove dead code in `bgp_ls_process_edge` - -`bgp_ls_process_edge()` builds a Link NLRI from an edge, which has both a -source node and a destination node. - -Before this change, edge processing was split into two switch blocks: -the first switch populated source (local) router-id fields, and the second -switch populated destination (remote) router-id fields. - -Coverity reported DEADCODE in the second switch, where several protocol -cases were flagged as unreachable. - -Simplify the logic by merging source and destination handling into a single -switch statement. This removes unreachable branches, keeps behavior unchanged, -and fixes CID 1670210. - -** CID 1670210: (DEADCODE) -/bgpd/bgp_ls_ted.c: 1030 in bgp_ls_process_edge() -/bgpd/bgp_ls_ted.c: 1027 in bgp_ls_process_edge() -/bgpd/bgp_ls_ted.c: 1029 in bgp_ls_process_edge() -/bgpd/bgp_ls_ted.c: 1028 in bgp_ls_process_edge() -/bgpd/bgp_ls_ted.c: 1031 in bgp_ls_process_edge() - -_____________________________________________________________________________________________ -*** CID 1670210: (DEADCODE) -/bgpd/bgp_ls_ted.c: 1030 in bgp_ls_process_edge() -1024 // router_id_len = 7; -1025 remote_router_id_len = 6; -1026 break; -1027 case BGP_LS_PROTO_DIRECT: -1028 case BGP_LS_PROTO_STATIC: -1029 case BGP_LS_PROTO_OSPFV3: ->>> CID 1670210: (DEADCODE) ->>> Execution cannot reach this statement: "case BGP_LS_PROTO_BGP:". -1030 case BGP_LS_PROTO_BGP: -1031 case BGP_LS_PROTO_RESERVED: -1032 zlog_err("BGP-LS: Unsupported protocol %u", protocol_id); -1033 return -1; -1034 } -1035 -/bgpd/bgp_ls_ted.c: 1027 in bgp_ls_process_edge() -1021 memcpy(remote_router_id, edge->destination->node->adv.id.iso.sys_id, -1022 ISO_SYS_ID_LEN); -1023 // router_id[6] = 0; /* Pseudonode ID = 0 for router */ -1024 // router_id_len = 7; -1025 remote_router_id_len = 6; -1026 break; ->>> CID 1670210: (DEADCODE) ->>> Execution cannot reach this statement: "case BGP_LS_PROTO_DIRECT:". -1027 case BGP_LS_PROTO_DIRECT: -1028 case BGP_LS_PROTO_STATIC: -1029 case BGP_LS_PROTO_OSPFV3: -1030 case BGP_LS_PROTO_BGP: -1031 case BGP_LS_PROTO_RESERVED: -1032 zlog_err("BGP-LS: Unsupported protocol %u", protocol_id); -/bgpd/bgp_ls_ted.c: 1029 in bgp_ls_process_edge() -1023 // router_id[6] = 0; /* Pseudonode ID = 0 for router */ -1024 // router_id_len = 7; -1025 remote_router_id_len = 6; -1026 break; -1027 case BGP_LS_PROTO_DIRECT: -1028 case BGP_LS_PROTO_STATIC: ->>> CID 1670210: (DEADCODE) ->>> Execution cannot reach this statement: "case BGP_LS_PROTO_OSPFV3:". -1029 case BGP_LS_PROTO_OSPFV3: -1030 case BGP_LS_PROTO_BGP: -1031 case BGP_LS_PROTO_RESERVED: -1032 zlog_err("BGP-LS: Unsupported protocol %u", protocol_id); -1033 return -1; -1034 } -/bgpd/bgp_ls_ted.c: 1028 in bgp_ls_process_edge() -1022 ISO_SYS_ID_LEN); -1023 // router_id[6] = 0; /* Pseudonode ID = 0 for router */ -1024 // router_id_len = 7; -1025 remote_router_id_len = 6; -1026 break; -1027 case BGP_LS_PROTO_DIRECT: ->>> CID 1670210: (DEADCODE) ->>> Execution cannot reach this statement: "case BGP_LS_PROTO_STATIC:". -1028 case BGP_LS_PROTO_STATIC: -1029 case BGP_LS_PROTO_OSPFV3: -1030 case BGP_LS_PROTO_BGP: -1031 case BGP_LS_PROTO_RESERVED: -1032 zlog_err("BGP-LS: Unsupported protocol %u", protocol_id); -1033 return -1; -/bgpd/bgp_ls_ted.c: 1031 in bgp_ls_process_edge() -1025 remote_router_id_len = 6; -1026 break; -1027 case BGP_LS_PROTO_DIRECT: -1028 case BGP_LS_PROTO_STATIC: -1029 case BGP_LS_PROTO_OSPFV3: -1030 case BGP_LS_PROTO_BGP: ->>> CID 1670210: (DEADCODE) ->>> Execution cannot reach this statement: "case BGP_LS_PROTO_RESERVED:". -1031 case BGP_LS_PROTO_RESERVED: -1032 zlog_err("BGP-LS: Unsupported protocol %u", protocol_id); -1033 return -1; -1034 } -1035 -1036 switch (event) { - -Signed-off-by: Carmine Scarpitta <cscarpit@cisco.com> -(cherry picked from commit 48690880f6386edf56e919f2ff9b52d3adc1f865) - -bgpd: Fix IGP Router-ID buffer overflow in BGP-LS node descriptor - -RFC 9552 defines the IGP Router-ID as variable length from 4 to 16 bytes. -Right now, igp_router_id is defined as an 8-byte array in the -bgp_ls_node_descriptor data structure. - -This can cause an overflow when decoding a valid IGP Router-ID TLV with -length greater than 8, because the parser accepts up to 16 bytes and then -copies up to 16 bytes into the fixed 8-byte buffer. - -Fix by sizing igp_router_id to BGP_LS_IGP_ROUTER_ID_MAX_SIZE (16 bytes), -so any RFC-valid IGP Router-ID length fits safely and decoding cannot -write past the end of the descriptor buffer. - -Fix CID 1670214, 1670212, 1670211, 1670208, 1670207, 1670206, 1670205, 1670204. - -Signed-off-by: Carmine Scarpitta <cscarpit@cisco.com> -(cherry picked from commit 36cd87dc13c6193d3d2ddaeb6c91fe884e4a2807) - -bgpd: Fix null dereference when `bgp->ls_info` is NULL - -`bgp_ls_register()` calls `bgp_ls_is_registered()` as an early-return -guard, but that function returns false when `bgp->ls_info` is NULL. -The code then proceeds to dereference `bgp->ls_info->registered_ls_db` -unconditionally, causing a crash. - -Add explicit null guards at the top of `bgp_ls_register()`, -`bgp_ls_unregister()`, and `bgp_ls_cleanup()` to return early when -`bgp->ls_info` is not initialized. - -Signed-off-by: Carmine Scarpitta <cscarpit@cisco.com> -(cherry picked from commit e310588e9918fcb3707f33e3ef7067b4969e9e0d) - -bgpd: Fix memory leak in `bgp_ls_originate_node()` - -In `bgp_ls_originate_node()`, `ls_attr` is allocated with -`bgp_ls_attr_alloc()` and populated before being passed to -`bgp_ls_update()`. If `bgp_ls_update()` fails, the function -returns -1 without freeing `ls_attr`, causing a memory leak. - -Free `ls_attr` before returning on the `bgp_ls_update()` failure -path. - -Signed-off-by: Carmine Scarpitta <cscarpit@cisco.com> -(cherry picked from commit ef1da17f6dc78cabf5a26502b6fff93525a26651) - -bgpd: Align `bgp_ls_populate_prefix_attr()` with node/link pattern - -`bgp_ls_populate_prefix_attr()` used a double-pointer `**attr` and -allocated `ls_attr` internally, unlike `bgp_ls_populate_node_attr()` -and `bgp_ls_populate_link_attr()` which receive a pre-allocated -`*attr` from the caller. - -The internal `!encoded` path also had a bug: `attr = NULL` set the -local variable instead of `*attr`, leaving the caller with a dangling -pointer to freed memory. - -Refactor `bgp_ls_populate_prefix_attr()` to take a single `*attr` -pointer, removing the internal `bgp_ls_attr_alloc()` call and the -`encoded`/free logic. Update `bgp_ls_originate_prefix()` to -pre-allocate `ls_attr` with `bgp_ls_attr_alloc()` before calling -the function, consistent with how node and link attributes are -handled. - -Signed-off-by: Carmine Scarpitta <cscarpit@cisco.com> -(cherry picked from commit 1a454f66cd811831f0c3c65b52108c442881595a) - -bgpd: Remove duplicate log calls in `bgp_ls_withdraw_node()` - -`bgp_ls_withdraw_node()` called `zlog_err()` immediately followed by -`flog_err()` with identical messages on two error paths. `flog_err()` -already logs to the standard output, making the `zlog_err()` calls -redundant. Remove the duplicates. - -Signed-off-by: Carmine Scarpitta <cscarpit@cisco.com> -(cherry picked from commit d0c1911106b8304d289a3b122b940ea89606e986) - -bgpd: Demote unknown opaque message warning to debug in opaque handler - -BGP registers only for `LINK_STATE_SYNC` and `LINK_STATE_UPDATE` -opaque messages, but other daemons may send opaque messages on the -same channel for their own purposes. The `default` case in -`bgp_zebra_opaque_msg_handler()` emitted a `zlog_warn()` for every -such message, which would be noisy and gives the operator nothing to -act on. - -Demote to a debug log guarded by `BGP_DEBUG(zebra, ZEBRA)`. - -Signed-off-by: Carmine Scarpitta <cscarpit@cisco.com> -(cherry picked from commit f232a7fbd18375318022051d68e978c7b6710684) - -bgpd: Fix `edge->destination` null deref in `bgp_ls_withdraw_link()` - -`bgp_ls_withdraw_link()` dereferenced `edge->destination->node` to -read the remote AS number, but the guard only checked -`edge->attributes` — it did not verify that `edge->destination` or -`edge->destination->node` were non-NULL, leading to a potential null -dereference. - -Add the missing null checks on `edge->destination` and -`edge->destination->node`, consistent with the guard used for the -same field in `bgp_ls_originate_link()`. - -Signed-off-by: Carmine Scarpitta <cscarpit@cisco.com> -(cherry picked from commit d649b691938ab08885e8642f018930595ff09816) - -bgpd: Use `bgp_node_lookup()` in `bgp_ls_withdraw()` - -`bgp_ls_withdraw()` was calling `bgp_afi_node_get()` to locate the RIB -destination before marking the route as withdrawn. `bgp_afi_node_get()` -creates a new RIB node when none exists, so a withdraw for an NLRI that -was never installed silently created a phantom RIB entry with no path -info attached, wasting memory and polluting the table. - -Replace the call with `bgp_node_lookup()`, which is a pure read-only -lookup that returns NULL when the prefix is not present. Add an early -return with a debug-guarded log message for the not-found case so the -condition is visible under `debug bgp linkstate` without being noisy in -production. - -Signed-off-by: Carmine Scarpitta <cscarpit@cisco.com> -(cherry picked from commit bfc0419434afb230ea1c23e307071d076d94c5c7) - -bgpd: Unintern old NLRI reference in `bgp_ls_update()` - -`bgp_afi_node_get()` returns an existing RIB node when the prefix is -already present. In that case `dest->ls_nlri` already holds an -interned pointer. Unconditionally overwriting it with the new value -dropped the old reference without calling `bgp_ls_nlri_unintern()`, -leaking the previous allocation. - -Unintern the existing pointer before installing the new one so the -refcount is kept accurate in both the new-node and update paths. - -Signed-off-by: Carmine Scarpitta <cscarpit@cisco.com> -(cherry picked from commit a24ac728e9173589a11bd975ac3f00aa535555e0) - -bgpd: Clear `registered_ls_db` before calling `ls_unregister()` - -`bgp_ls_unregister()` only cleared `registered_ls_db` after a -successful `ls_unregister()` call. When `ls_unregister()` failed, the -flag was left as true, making `bgp_ls_is_registered()` report "still -registered". Any subsequent call to `bgp_ls_register()` would then -return early thinking registration was already in place, leaving BGP -permanently unable to receive link-state updates from zebra. - -Fix this by clearing `registered_ls_db` before the `ls_unregister()` -call. If the zebra unregister fails, the flag is already false so -`bgp_ls_register()` can attempt re-registration and recover. The -`false` return value is preserved to signal the zebra-side failure to -the caller. - -Signed-off-by: Carmine Scarpitta <cscarpit@cisco.com> -(cherry picked from commit 5f118f0dca9cc9ae7ceba20ac143b4e143af83b7) - -bgpd: Fix use-after-free in BGP-LS node origination - -`bgp_ls_originate_node()` could free `ls_attr` after `bgp_ls_populate_node_attr()` -failure, then continue and pass the freed pointer to `bgp_ls_update()`. - -Fix by returning immediately after `bgp_ls_attr_free(ls_attr)` on -populate failure. - -Signed-off-by: Carmine Scarpitta <cscarpit@cisco.com> -(cherry picked from commit 06036a10e40ccc3e50d23507e1679c4ecce553de) - -bgpd: Include local-node ASN in link withdraw NLRI - -A Link NLRI contains two nodes: local node and remote node. -Per RFC 9552, each node is identified by ASN, OSPF Area ID, and -IGP Router ID. - -For the remote node, `bgp_ls_withdraw_link` sets ASN, OSPF Area ID, and -IGP Router ID when generating the NLRI. - -For the local node, `bgp_ls_withdraw_link` sets only OSPF Area ID and -IGP Router ID when generating the NLRI. - -Add ASN for the local node as well, to keep symmetry with the remote -node and remain compliant with RFC 9552. - -Signed-off-by: Carmine Scarpitta <cscarpit@cisco.com> -(cherry picked from commit 46c3d1c85275cd48fbe82b79e0e8a3fd8a285c08) - -bgpd: Require valid TED objects in BGP-LS originate/withdraw APIs - -BGP-LS node/link/prefix originate and withdraw handlers are expected to -receive valid TED objects. - -Add explicit checks at the beginning of each function and return early on -invalid inputs, before any further processing. - -This makes the API contract clear, avoids NULL dereferences, and keeps the -originate/withdraw paths consistent. - -Signed-off-by: Carmine Scarpitta <cscarpit@cisco.com> -(cherry picked from commit 6e2323aabe65888274f0f2a7ad7de3c6805a2c69) - -bgpd: Fix missing present_tlvs bit for Link ID in link NLRI - -When originating/withdrawing a Link NLRI, link_remote_id is filled in -the bgp_ls_nlri structure but BGP_LS_LINK_DESC_LINK_ID_BIT is not set -in link_desc.present_tlvs. - -Fix by setting BGP_LS_LINK_DESC_LINK_ID_BIT in both bgp_ls_originate_link() -and bgp_ls_withdraw_link(). - -Signed-off-by: Carmine Scarpitta <cscarpit@cisco.com> -(cherry picked from commit 8fbdb4fa767a79c7cb3b0fb984df21f1ef8a8dfb) - -bgpd: Fix destination linkage for delayed reverse-edge updates - -An edge A->B can arrive before reverse edge B->A is present in TED. -When that happens, A->B may not have a destination yet and link -origination for that direction is deferred. - -Today, when the reverse edge arrives, the direct edge is not updated -in TED and can remain without a destination. - -As a result, when bgp_ls_process_edge tries to generate BGP-LS -NLRI, generation fails because the edge still has no destination. - -Fix this by looking up the reverse edge and completing missing -destination linkage in both directions before processing. - -This follows the same logic used in ISIS TED edge handling. - -Signed-off-by: Carmine Scarpitta <cscarpit@cisco.com> -(cherry picked from commit 350933411c82c51e857e5712f36949bf6aecec2c) - -bgpd: show BGP-LS attributes in json output - -show bgp link-state link-state NLRI json didn't show any BGP-LS -attributes. - -* Add `bgp_ls_attr_to_json` that converts `bgp_ls_attr` to json_object -* Call it from `route_vty_out_detail` and attach to output -* Small fix in `bgp_ls_attr_display`: show "Packet Loss" in % - -Signed-off-by: Kyrylo Yatsenko <hedrok@gmail.com> -(cherry picked from commit a2576fd6b0191095bf8c1821138c4d8ceb77c6f0) - -bgpd: BGP-LS: add Prefix SID (TLV 1158) - -ADD TLV 1158 Prefix SID to BGP-LS: RFC 9085 Section 2.3.1 - -Signed-off-by: Kyrylo Yatsenko <hedrok@gmail.com> -(cherry picked from commit e67b6c7a25aa229b0bda7fdeedebf9e26b07df21) - -tests: BGP-LS Prefix SID attribute - -Signed-off-by: Kyrylo Yatsenko <hedrok@gmail.com> -(cherry picked from commit 500018fb32d1a14e0ca7121bb0653e149b168eab) - -bgpd: Fix incorrect comparisons in BGP-LS *_cmp() functions - -Comparison functions in bgp_ls_nlri.c return (a - b) on unsigned -integer fields. The unsigned subtraction result is then implicitly -converted to int (the return type). When the difference exceeds -INT_MAX the sign flips, inverting the result: - - a = 0xFFFFFFFF, b = 0 - (uint32_t)a - b = 0xFFFFFFFF -> cast to int gives -1 - -> caller sees a < b, which is wrong - -These functions are expected to return a negative value when a < b, -zero when a == b, and a positive value when a > b. Currently the -top-level functions bgp_ls_nlri_cmp() and bgp_ls_attr_cmp() are -registered as hash comparison functions (DECLARE_HASH), which only -tests them for equality (== 0). All other cmp functions are called -exclusively from within those two, so in practice the sign is never -observed. However, a future caller relying on the sign to determine -ordering would silently get incorrect results. - -Replace all subtraction-based comparisons with numcmp(a, b), which -correctly implements the three-way comparator contract. - -Signed-off-by: Carmine Scarpitta <cscarpit@cisco.com> -(cherry picked from commit a462e15977c761860d03a239644bc3b4825ec41f) - -bgpd: Request initial TED sync on link-state registration - -After registering with the LS database, no initial sync is requested, -so the TED remains empty until the IGP sends unsolicited updates. -Any topology changes that occurred before registration are -permanently missed and never originated as BGP-LS NLRIs. - -Additionally, LS_MSG_EVENT_SYNC messages are not handled in the TED -processors, so any sync response from zebra is silently dropped. - -Request a sync via ls_request_sync() immediately after registration, -following the same pattern used by other TED consumers such as pathd. -Handle LS_MSG_EVENT_SYNC alongside LS_MSG_EVENT_ADD in -bgp_ls_process_vertex(), bgp_ls_process_edge(), and -bgp_ls_process_subnet(). - -Signed-off-by: Carmine Scarpitta <cscarpit@cisco.com> -(cherry picked from commit 13cc5be80825d3087873dbfe4e5b721983e30372) - -bgpd: Withdraw BGP-LS routes and reset TED on last peer deactivation - -When the last BGP-LS peer is deactivated, locally originated BGP-LS -routes are not withdrawn from the RIB, leaving stale routes on peers. -The TED is also not cleared, so the next registration re-originates -on top of stale state. - -Add bgp_ls_withdraw_ted() which removes all self-originated paths via -bgp_clear_route() and clears all TED entries. Call it in -peer_deactivate() when the last BGP-LS peer is being deactivated, -before unregistering from the LS database. - -Signed-off-by: Carmine Scarpitta <cscarpit@cisco.com> -(cherry picked from commit 906445760f177df1850bef6977b7ecbe47df8391) - -tests: Verify BGP-LS routes withdrawn on peer deactivate - -Add test_bgp_ls_peer_deactivate() to verify that deactivating the -last BGP-LS peer on r2 withdraws all locally originated routes on -r2 and clears all received routes on rr. - -Add test_bgp_ls_peer_reactivate() to verify that reactivating the -peer triggers a fresh TED sync, re-originates all BGP-LS NLRIs on -r2, and re-advertises them to rr. - -Signed-off-by: Carmine Scarpitta <cscarpit@cisco.com> -(cherry picked from commit 431b6814721723fe02c04c022d6c65d6e6656deb) - -bgpd: BGP-LS Fix bgp_ls_attr_prefix_sid_len - -Signed-off-by: Kyrylo Yatsenko <hedrok@gmail.com> -(cherry picked from commit e34b0cf424bf21b2911fe43bae17ec2f64988492) - -bgpd: BGP-LS: add SR Capabilities (TLV 1034) - -Signed-off-by: Kyrylo Yatsenko <hedrok@gmail.com> -(cherry picked from commit 349679c2d17f9d1c90b960fc25f98718e18ee113) - -tests: BGP-LS SR Capabilities (TLV 1034) - -Signed-off-by: Kyrylo Yatsenko <hedrok@gmail.com> -(cherry picked from commit 8b8c198300a4cc5a89bc087235778c3651f803e7) - -bgpd: BGP-LS: add Node MSD (TLV 266) - -Signed-off-by: Kyrylo Yatsenko <hedrok@gmail.com> -(cherry picked from commit b33f7f4cd36ef0ec8044a514bf09a527ab99a322) - -tests: BGP-LS add Node MSD (TLV 266) - -Signed-off-by: Kyrylo Yatsenko <hedrok@gmail.com> -(cherry picked from commit cfe13bb9fb9e19f051fbcfb2076c0ef4d6574a67) - -isis, lib: add isis area id to ls_node - -It is needed for BGP-LS IS-IS Area Identifier (TLV 1027) - -Signed-off-by: Kyrylo Yatsenko <hedrok@gmail.com> -(cherry picked from commit 19adc1a2f1fe180fcab37be672e31ed890b97d00) - -bgpd: BGP-LS: update IS-IS Area Identifier (TLV 1027) - -* Console output (bgp_ls_attr_display) -* JSON output (bgp_ls_attr_to_json) -* Fill in from TED (bgp_ls_populate_node_attr) - -Signed-off-by: Kyrylo Yatsenko <hedrok@gmail.com> -(cherry picked from commit 1bb63fabb913d7d9c92ec74322fefa907ec6a5bf) - -tests: BGP-LS IS-IS Area Identifier (TLV 1027) - -Signed-off-by: Kyrylo Yatsenko <hedrok@gmail.com> -(cherry picked from commit a23737163f808ab25fc0f9ac06266bb43f971c90) - -bgpd: BGP-LS: simplify bgp_ls_attr_to_json - -Simplify bgp_ls_attr_to_json using json_object_string_addf -instead of snprintfrr + json_object_string_add - -Signed-off-by: Kyrylo Yatsenko <hedrok@gmail.com> -(cherry picked from commit b4c5646d4cbb0caaae4185414a6ab30c518175ac) - -bgpd: BGP-LS: add Adjacency SID (TLV 1099) - -To properly encode/decode `Adjacency SID` protocol id must be known -as length of SID depends on flags V and L that have different bit -positions for IS-IS and OSPF. - -This commit adds `protocol_id` argument to `bgp_ls_encode_attr` -and `bgp_packet_ls_attribute`. - -During run of `bgp_ls_parse_attr` NLRI is not decoded yet, so instead of -adding complex logic of pre-parsing NLRI and getting protocol id out of -it function `bgp_ls_attr_process_with_protocol_id` is added. It is -called after parsing of attributes and after protocol id is parsed in -NLRI. - -Signed-off-by: Kyrylo Yatsenko <hedrok@gmail.com> -(cherry picked from commit 3df60175a487148f79dc2a2985001893412d85f2) - -tests: BGP-LS Adjacency SID (TLV 1099) - -Signed-off-by: Kyrylo Yatsenko <hedrok@gmail.com> -(cherry picked from commit 7461a4a3aee80bbeb7524de164481102469af7da) - -isisd: convert area_addrs from linked list to typesafe DLIST - -Convert the isis_area->area_addrs field from the legacy struct list * -to the FRR typesafe DLIST container. This change is part of the ongoing -effort to migrate IS-IS data structures to typesafe containers for -improved type safety and code clarity. - -Signed-off-by: Vincent Jardin <vjardin@free.fr> -(cherry picked from commit e62419b8940ce25233386630d7ecd1311404a575) +* https://github.com/FRRouting/frr/pull/21825 --- - bgpd/bgp_attr.c | 139 +- - bgpd/bgp_attr.h | 18 +- + bgpd/bgp_attr.c | 146 +- + bgpd/bgp_attr.h | 29 +- bgpd/bgp_bmp.c | 8 +- bgpd/bgp_debug.c | 52 + bgpd/bgp_debug.h | 3 + bgpd/bgp_errors.c | 6 + bgpd/bgp_errors.h | 1 + - bgpd/bgp_ls.c | 878 +++ - bgpd/bgp_ls.h | 86 + - bgpd/bgp_ls_nlri.c | 5406 +++++++++++++++++ - bgpd/bgp_ls_nlri.h | 895 +++ - bgpd/bgp_ls_ted.c | 1403 +++++ + bgpd/bgp_fsm.c | 12 + + bgpd/bgp_ls.c | 1515 +++++ + bgpd/bgp_ls.h | 124 + + bgpd/bgp_ls_nlri.c | 5537 +++++++++++++++++ + bgpd/bgp_ls_nlri.h | 910 +++ + bgpd/bgp_ls_ted.c | 1381 ++++ bgpd/bgp_ls_ted.h | 226 + bgpd/bgp_open.c | 19 +- bgpd/bgp_packet.c | 3 + - bgpd/bgp_route.c | 178 +- + bgpd/bgp_route.c | 234 +- bgpd/bgp_route.h | 10 + - bgpd/bgp_table.c | 8 + + bgpd/bgp_table.c | 7 + bgpd/bgp_table.h | 2 + bgpd/bgp_updgrp.c | 4 + bgpd/bgp_updgrp_packet.c | 116 +- - bgpd/bgp_vty.c | 70 +- + bgpd/bgp_vty.c | 319 +- bgpd/bgp_zebra.c | 34 +- - bgpd/bgpd.c | 76 +- - bgpd/bgpd.h | 29 +- + bgpd/bgpd.c | 85 +- + bgpd/bgpd.h | 37 +- bgpd/rfapi/rfapi_import.c | 5 + bgpd/rfapi/rfapi_monitor.c | 2 + bgpd/subdir.am | 6 + - doc/user/bgp-linkstate.rst | 207 + + doc/user/bgp-linkstate.rst | 274 + doc/user/bgp.rst | 2 + - isisd/isis_lsp.c | 2 +- - isisd/isis_nb_config.c | 28 +- - isisd/isis_pdu.c | 16 +- - isisd/isis_snmp.c | 6 +- - isisd/isis_te.c | 11 + - isisd/isis_tlvs.c | 22 +- - isisd/isis_tlvs.h | 17 +- - isisd/isis_zebra.c | 2 +- - isisd/isisd.c | 56 +- - isisd/isisd.h | 2 +- + isisd/isis_te.c | 10 + lib/command.h | 1 + lib/iana_afi.h | 10 + - lib/iso.h | 10 + lib/link_state.c | 34 + lib/link_state.h | 4 + lib/prefix.c | 6 + @@ -1352,7 +71,6 @@ Signed-off-by: Vincent Jardin <vjardin@free.fr> pbrd/pbr_zebra.c | 8 + staticd/static_vty.c | 1 + tests/bgpd/test_peer_attr.c | 2 + - tests/isisd/test_isis_spf.c | 2 +- tests/topotests/bgp_link_state/__init__.py | 0 tests/topotests/bgp_link_state/r1/frr.conf | 57 + .../topotests/bgp_link_state/r1/isis_adj.json | 18 + @@ -1367,18 +85,25 @@ Signed-off-by: Vincent Jardin <vjardin@free.fr> .../topotests/bgp_link_state/r4/isis_adj.json | 18 + .../bgp_link_state/rr/bgp_capability.json | 11 + .../rr/bgp_ls_attrs_link43.json | 14 + - .../bgp_link_state/rr/bgp_ls_attrs_node4.json | 15 + + .../bgp_link_state/rr/bgp_ls_attrs_node4.json | 20 + .../bgp_link_state/rr/bgp_ls_nlri.json | 1552 +++++ .../bgp_link_state/rr/bgp_ls_prefix4.json | 13 + .../bgp_link_state/rr/bgp_neighbor.json | 7 + tests/topotests/bgp_link_state/rr/frr.conf | 23 + .../bgp_link_state/test_bgp_link_state.py | 1007 +++ + .../bgp_link_state_bgp_fabric/r1/frr.conf | 50 + + .../bgp_link_state_bgp_fabric/r2/frr.conf | 47 + + .../bgp_link_state_bgp_fabric/r3/frr.conf | 55 + + .../bgp_link_state_bgp_fabric/r4/frr.conf | 54 + + .../rr/expected_bgp_ls.json | 1785 ++++++ + .../bgp_link_state_bgp_fabric/rr/frr.conf | 53 + + .../test_bgp_link_state_bgp_fabric.py | 1180 ++++ vtysh/vtysh.c | 27 +- zebra/connected.c | 2 + zebra/router-id.c | 2 + zebra/zapi_msg.c | 1 + zebra/zebra_nhg.c | 2 + - 76 files changed, 14508 insertions(+), 142 deletions(-) + 73 files changed, 18872 insertions(+), 60 deletions(-) create mode 100644 bgpd/bgp_ls.c create mode 100644 bgpd/bgp_ls.h create mode 100644 bgpd/bgp_ls_nlri.c @@ -1406,12 +131,19 @@ Signed-off-by: Vincent Jardin <vjardin@free.fr> create mode 100644 tests/topotests/bgp_link_state/rr/bgp_neighbor.json create mode 100644 tests/topotests/bgp_link_state/rr/frr.conf create mode 100644 tests/topotests/bgp_link_state/test_bgp_link_state.py + create mode 100644 tests/topotests/bgp_link_state_bgp_fabric/r1/frr.conf + create mode 100644 tests/topotests/bgp_link_state_bgp_fabric/r2/frr.conf + create mode 100644 tests/topotests/bgp_link_state_bgp_fabric/r3/frr.conf + create mode 100644 tests/topotests/bgp_link_state_bgp_fabric/r4/frr.conf + create mode 100644 tests/topotests/bgp_link_state_bgp_fabric/rr/expected_bgp_ls.json + create mode 100644 tests/topotests/bgp_link_state_bgp_fabric/rr/frr.conf + create mode 100644 tests/topotests/bgp_link_state_bgp_fabric/test_bgp_link_state_bgp_fabric.py diff --git a/bgpd/bgp_attr.c b/bgpd/bgp_attr.c -index fcfff91cd7..924e24fd19 100644 +index 09d4948ab8..cfbde632bd 100644 --- a/bgpd/bgp_attr.c +++ b/bgpd/bgp_attr.c -@@ -44,6 +44,7 @@ +@@ -45,6 +45,7 @@ #include "bgp_evpn.h" #include "bgp_flowspec_private.h" #include "bgp_mac.h" @@ -1419,7 +151,7 @@ index fcfff91cd7..924e24fd19 100644 /* Attribute strings for logging. */ static const struct message attr_str[] = { -@@ -992,6 +993,8 @@ unsigned int attrhash_key_make(const void *p) +@@ -1048,6 +1049,8 @@ unsigned int attrhash_key_make(const void *p) key = jhash(&attr->rmac, sizeof(attr->rmac), key); if (bgp_attr_get_nhc(attr)) MIX(bgp_nhc_hash_key_make(bgp_attr_get_nhc(attr))); @@ -1428,7 +160,7 @@ index fcfff91cd7..924e24fd19 100644 return key; } -@@ -1041,7 +1044,8 @@ bool attrhash_cmp(const void *p1, const void *p2) +@@ -1097,7 +1100,8 @@ bool attrhash_cmp(const void *p1, const void *p2) attr1->srte_color == attr2->srte_color && attr1->nh_type == attr2->nh_type && attr1->bh_type == attr2->bh_type && attr1->otc == attr2->otc && !memcmp(&attr1->rmac, &attr2->rmac, sizeof(struct ethaddr)) && @@ -1438,7 +170,7 @@ index fcfff91cd7..924e24fd19 100644 return true; } -@@ -1291,6 +1295,13 @@ struct attr *bgp_attr_intern(struct attr *attr) +@@ -1349,6 +1353,13 @@ struct attr *bgp_attr_intern(struct attr *attr) nhc->refcnt++; } @@ -1452,7 +184,7 @@ index fcfff91cd7..924e24fd19 100644 /* At this point, attr only contains intern'd pointers. that means * if we find it in attrhash, it has all the same pointers and we * correctly updated the refcounts on these. -@@ -1504,6 +1515,8 @@ void bgp_attr_unintern_sub(struct attr *attr) +@@ -1583,6 +1594,8 @@ void bgp_attr_unintern_sub(struct attr *attr) bre = bgp_attr_get_evpn_overlay(attr); evpn_overlay_unintern(&bre); bgp_attr_set_evpn_overlay(attr, NULL); @@ -1460,15 +192,16 @@ index fcfff91cd7..924e24fd19 100644 + bgp_ls_attr_unintern(&attr->ls_attr); } - /* Free bgp attribute and aspath. */ -@@ -3830,6 +3843,37 @@ otc_ignore: + /* Clear cached intern_attr if it points to the attr that is being uninterned */ +@@ -4068,6 +4081,43 @@ otc_ignore: return bgp_attr_ignore(peer, args->type); } +/* BGP-LS attribute (rfc9552) */ +static enum bgp_attr_parse_ret bgp_attr_ls(struct bgp_attr_parser_args *args) +{ -+ struct peer *const peer = args->peer; ++ struct peer_connection *const connection = args->connection; ++ struct peer *const peer = connection->peer; + struct attr *const attr = args->attr; + int ret; + struct bgp_ls_attr *ls_attr; @@ -1478,10 +211,15 @@ index fcfff91cd7..924e24fd19 100644 + + ls_attr = bgp_ls_attr_alloc(); + -+ ret = bgp_ls_parse_attr(peer->curr, args->length, ls_attr); ++ ret = bgp_ls_parse_attr(connection->curr, args->length, ls_attr); + if (ret != 0) { + bgp_ls_attr_free(ls_attr); -+ return BGP_ATTR_PARSE_ERROR; ++ /* ++ * RFC 9552 §5.1 + RFC 7606 §5.4: a malformed BGP-LS TLV ++ * requires NLRI discard (treat-as-withdraw) while the BGP ++ * session itself continues. ++ */ ++ return BGP_ATTR_PARSE_WITHDRAW; + } + + attr->ls_attr = bgp_ls_attr_intern(ls_attr); @@ -1491,7 +229,7 @@ index fcfff91cd7..924e24fd19 100644 + return BGP_ATTR_PARSE_PROCEED; + +ls_attr_ignore: -+ stream_forward_getp(peer->curr, args->length); ++ stream_forward_getp(connection->curr, args->length); + + return bgp_attr_ignore(peer, args->type); +} @@ -1499,7 +237,7 @@ index fcfff91cd7..924e24fd19 100644 /* BGP unknown attribute treatment. */ static enum bgp_attr_parse_ret bgp_attr_unknown(struct bgp_attr_parser_args *args) -@@ -4260,6 +4304,9 @@ enum bgp_attr_parse_ret bgp_attr_parse(struct peer *peer, struct attr *attr, +@@ -4449,6 +4499,9 @@ enum bgp_attr_parse_ret bgp_attr_parse(struct peer *peer, struct attr *attr, case BGP_ATTR_NHC: ret = bgp_attr_nhc(&attr_args); break; @@ -1509,7 +247,7 @@ index fcfff91cd7..924e24fd19 100644 default: ret = bgp_attr_unknown(&attr_args); break; -@@ -4511,6 +4558,10 @@ size_t bgp_packet_mpattr_start(struct stream *s, struct peer *peer, afi_t afi, +@@ -4696,6 +4749,10 @@ size_t bgp_packet_mpattr_start(struct stream *s, struct peer *peer, afi_t afi, nh_afi = peer_cap_enhe(peer, afi, safi) ? AFI_IP6 : AFI_IP; else if (safi == SAFI_FLOWSPEC) nh_afi = afi; @@ -1520,7 +258,7 @@ index fcfff91cd7..924e24fd19 100644 else nh_afi = BGP_NEXTHOP_AFI_FROM_NHLEN(attr->mp_nexthop_len); -@@ -4544,6 +4595,10 @@ size_t bgp_packet_mpattr_start(struct stream *s, struct peer *peer, afi_t afi, +@@ -4729,6 +4786,10 @@ size_t bgp_packet_mpattr_start(struct stream *s, struct peer *peer, afi_t afi, stream_put_ipv4(s, attr->nexthop.s_addr); } break; @@ -1531,7 +269,7 @@ index fcfff91cd7..924e24fd19 100644 case SAFI_UNSPEC: case SAFI_MAX: assert(!"SAFI's UNSPEC or MAX being specified are a DEV ESCAPE"); -@@ -4598,6 +4653,12 @@ size_t bgp_packet_mpattr_start(struct stream *s, struct peer *peer, afi_t afi, +@@ -4783,6 +4844,12 @@ size_t bgp_packet_mpattr_start(struct stream *s, struct peer *peer, afi_t afi, case SAFI_FLOWSPEC: stream_putc(s, 0); /* no nexthop for flowspec */ break; @@ -1544,7 +282,7 @@ index fcfff91cd7..924e24fd19 100644 case SAFI_UNSPEC: case SAFI_MAX: assert(!"SAFI's UNSPEC or MAX being specified are a DEV ESCAPE"); -@@ -4611,9 +4672,10 @@ size_t bgp_packet_mpattr_start(struct stream *s, struct peer *peer, afi_t afi, +@@ -4796,9 +4863,10 @@ size_t bgp_packet_mpattr_start(struct stream *s, struct peer *peer, afi_t afi, "Bad nexthop when sending to %s, AFI %u SAFI %u nhlen %d", peer->host, afi, safi, attr->mp_nexthop_len); break; @@ -1556,7 +294,7 @@ index fcfff91cd7..924e24fd19 100644 break; } -@@ -4717,11 +4779,42 @@ static void bgp_packet_nhc(struct stream *s, struct peer *peer, afi_t afi, safi_ +@@ -4902,11 +4970,42 @@ static void bgp_packet_nhc(struct stream *s, struct peer *peer, afi_t afi, safi_ stream_putc_at(s, sizep, (stream_get_endp(s) - sizep) - 1); } @@ -1602,9 +340,9 @@ index fcfff91cd7..924e24fd19 100644 + bool addpath_capable, uint32_t addpath_tx_id, struct attr *attr, + struct bgp_ls_nlri *ls_nlri) { - mpls_label_t restore_label; - -@@ -4748,6 +4841,9 @@ void bgp_packet_mpattr_prefix(struct stream *s, afi_t afi, safi_t safi, + switch (safi) { + case SAFI_UNSPEC: +@@ -4931,6 +5030,9 @@ void bgp_packet_mpattr_prefix(struct stream *s, afi_t afi, safi_t safi, else assert(!"Add encoding bits here for other AFI's"); break; @@ -1612,9 +350,9 @@ index fcfff91cd7..924e24fd19 100644 + bgp_ls_encode_nlri(s, ls_nlri); + break; case SAFI_LABELED_UNICAST: - if (num_labels > 1) { - zlog_warn("%s: stream_put_labeled_prefix currently supports only one label, ignoring rest (num_labels=%d)", -@@ -4811,6 +4907,10 @@ size_t bgp_packet_mpattr_prefix_size(afi_t afi, safi_t safi, + /* Prefix write with label. */ + bgp_attr_stream_put_labeled_prefix(s, p, label, num_labels, addpath_capable, +@@ -4988,6 +5090,10 @@ size_t bgp_packet_mpattr_prefix_size(afi_t afi, safi_t safi, case SAFI_FLOWSPEC: size = ((struct prefix_fs *)p)->prefix.prefixlen; break; @@ -1625,16 +363,17 @@ index fcfff91cd7..924e24fd19 100644 } return size; -@@ -4961,7 +5061,7 @@ bgp_size_t bgp_packet_attribute(struct bgp *bgp, struct peer *peer, struct strea +@@ -5138,7 +5244,8 @@ bgp_size_t bgp_packet_attribute(struct bgp *bgp, struct peer *peer, struct strea struct prefix *p, afi_t afi, safi_t safi, struct peer *from, struct prefix_rd *prd, mpls_label_t *label, uint8_t num_labels, - bool addpath_capable, uint32_t addpath_tx_id, -- struct bgp_path_info *bpi) -+ struct bgp_path_info *bpi, struct bgp_ls_nlri *ls_nlri) + struct bgp_attr_srv6_l3service *srv6_unicast, bool addpath_capable, +- uint32_t addpath_tx_id, struct bgp_path_info *bpi) ++ uint32_t addpath_tx_id, struct bgp_path_info *bpi, ++ struct bgp_ls_nlri *ls_nlri) { size_t cp; size_t aspath_sizep; -@@ -4984,9 +5084,8 @@ bgp_size_t bgp_packet_attribute(struct bgp *bgp, struct peer *peer, struct strea +@@ -5161,9 +5268,8 @@ bgp_size_t bgp_packet_attribute(struct bgp *bgp, struct peer *peer, struct strea mpattrlen_pos = bgp_packet_mpattr_start(s, peer, afi, safi, vecarr, attr); @@ -1646,7 +385,7 @@ index fcfff91cd7..924e24fd19 100644 bgp_packet_mpattr_end(s, mpattrlen_pos); } -@@ -5444,6 +5543,10 @@ bgp_size_t bgp_packet_attribute(struct bgp *bgp, struct peer *peer, struct strea +@@ -5625,6 +5731,10 @@ bgp_size_t bgp_packet_attribute(struct bgp *bgp, struct peer *peer, struct strea stream_put_bgp_aigp_tlv_metric(s, attr->aigp_metric); } @@ -1657,7 +396,7 @@ index fcfff91cd7..924e24fd19 100644 /* draft-ietf-idr-entropy-label */ if (peergroup_flag_check(peer, PEER_FLAG_SEND_NHC_ATTRIBUTE)) bgp_packet_nhc(s, peer, afi, safi, attr, bpi); -@@ -5480,12 +5583,10 @@ size_t bgp_packet_mpunreach_start(struct stream *s, afi_t afi, safi_t safi) +@@ -5661,12 +5771,10 @@ size_t bgp_packet_mpunreach_start(struct stream *s, afi_t afi, safi_t safi) return attrlen_pnt; } @@ -1674,7 +413,7 @@ index fcfff91cd7..924e24fd19 100644 { uint8_t wlabel[4] = {0x80, 0x00, 0x00}; -@@ -5500,8 +5601,8 @@ void bgp_packet_mpunreach_prefix(struct stream *s, const struct prefix *p, +@@ -5681,8 +5789,8 @@ void bgp_packet_mpunreach_prefix(struct stream *s, const struct prefix *p, num_labels = 1; } @@ -1686,10 +425,10 @@ index fcfff91cd7..924e24fd19 100644 void bgp_packet_mpunreach_end(struct stream *s, size_t attrlen_pnt) diff --git a/bgpd/bgp_attr.h b/bgpd/bgp_attr.h -index 3340822d58..88fb156bc1 100644 +index b5a6ba317c..3fd848b889 100644 --- a/bgpd/bgp_attr.h +++ b/bgpd/bgp_attr.h -@@ -321,6 +321,9 @@ struct attr { +@@ -315,6 +315,9 @@ struct attr { /* Next-hop characteristics */ struct bgp_nhc *nhc; @@ -1699,22 +438,33 @@ index 3340822d58..88fb156bc1 100644 }; /* rmap_change_flags definition */ -@@ -399,7 +402,8 @@ extern bgp_size_t bgp_packet_attribute(struct bgp *bgp, struct peer *peer, struc - struct prefix *p, afi_t afi, safi_t safi, struct peer *from, - struct prefix_rd *prd, mpls_label_t *label, - uint8_t num_labels, bool addpath_capable, -- uint32_t addpath_tx_id, struct bgp_path_info *bpi); -+ uint32_t addpath_tx_id, struct bgp_path_info *bpi, -+ struct bgp_ls_nlri *ls_nlri); +@@ -397,12 +400,14 @@ extern struct attr *bgp_attr_aggregate_intern( + struct community *community, struct ecommunity *ecommunity, + struct lcommunity *lcommunity, struct bgp_aggregate *aggregate, + uint8_t atomic_aggregate, const struct prefix *p); +-extern bgp_size_t +-bgp_packet_attribute(struct bgp *bgp, struct peer *peer, struct stream *s, struct attr *attr, +- struct bpacket_attr_vec_arr *vecarr, struct prefix *p, afi_t afi, safi_t safi, +- struct peer *from, struct prefix_rd *prd, mpls_label_t *label, +- uint8_t num_labels, struct bgp_attr_srv6_l3service *srv6_unicast, +- bool addpath_capable, uint32_t addpath_tx_id, struct bgp_path_info *bpi); ++extern bgp_size_t bgp_packet_attribute(struct bgp *bgp, struct peer *peer, struct stream *s, ++ struct attr *attr, struct bpacket_attr_vec_arr *vecarr, ++ struct prefix *p, afi_t afi, safi_t safi, struct peer *from, ++ struct prefix_rd *prd, mpls_label_t *label, ++ uint8_t num_labels, ++ struct bgp_attr_srv6_l3service *srv6_unicast, ++ bool addpath_capable, uint32_t addpath_tx_id, ++ struct bgp_path_info *bpi, struct bgp_ls_nlri *ls_nlri); extern void bgp_dump_routes_attr(struct stream *s, struct bgp_path_info *bpi, const struct prefix *p); extern bool attrhash_cmp(const void *arg1, const void *arg2); -@@ -458,17 +462,19 @@ extern void bgp_packet_mpattr_prefix(struct stream *s, afi_t afi, safi_t safi, - const struct prefix_rd *prd, - mpls_label_t *label, uint8_t num_labels, - bool addpath_capable, -- uint32_t addpath_tx_id, struct attr *); -+ uint32_t addpath_tx_id, struct attr *, +@@ -459,17 +464,19 @@ extern size_t bgp_packet_mpattr_start(struct stream *s, struct peer *peer, + extern void bgp_packet_mpattr_prefix(struct stream *s, afi_t afi, safi_t safi, + const struct prefix *p, const struct prefix_rd *prd, + mpls_label_t *label, uint8_t num_labels, bool addpath_capable, +- uint32_t addpath_tx_id, struct attr *attr); ++ uint32_t addpath_tx_id, struct attr *attr, + struct bgp_ls_nlri *ls_nlri); extern size_t bgp_packet_mpattr_prefix_size(afi_t afi, safi_t safi, const struct prefix *p); @@ -1735,15 +485,15 @@ index 3340822d58..88fb156bc1 100644 extern enum bgp_attr_parse_ret bgp_attr_nexthop_valid(struct peer *peer, diff --git a/bgpd/bgp_bmp.c b/bgpd/bgp_bmp.c -index c095a3ea77..554766433e 100644 +index af23eca96c..a81aeebd96 100644 --- a/bgpd/bgp_bmp.c +++ b/bgpd/bgp_bmp.c @@ -1118,7 +1118,7 @@ static struct stream *bmp_update(const struct prefix *p, struct prefix_rd *prd, /* 5: Encode all the attributes, except MP_REACH_NLRI attr. */ total_attr_len = bgp_packet_attribute(NULL, peer, s, attr, &vecarr, NULL, afi, safi, peer, -- NULL, NULL, 0, 0, 0, NULL); -+ NULL, NULL, 0, 0, 0, NULL, NULL); +- NULL, NULL, 0, 0, 0, 0, NULL); ++ NULL, NULL, 0, 0, 0, 0, NULL, NULL); /* space check? */ @@ -1768,7 +518,7 @@ index c095a3ea77..554766433e 100644 bgp_packet_mpunreach_end(s, mplen_pos); diff --git a/bgpd/bgp_debug.c b/bgpd/bgp_debug.c -index 73b5280525..bb2fe37304 100644 +index e2be888ca0..e2ebc826ed 100644 --- a/bgpd/bgp_debug.c +++ b/bgpd/bgp_debug.c @@ -62,6 +62,7 @@ unsigned long conf_bgp_debug_evpn_mh; @@ -1787,7 +537,7 @@ index 73b5280525..bb2fe37304 100644 struct list *bgp_debug_neighbor_events_peers = NULL; struct list *bgp_debug_keepalive_peers = NULL; -@@ -2255,6 +2257,41 @@ DEFPY (debug_bgp_cond_adv, +@@ -2284,6 +2286,41 @@ DEFPY (debug_bgp_cond_adv, return CMD_SUCCESS; } @@ -1829,7 +579,7 @@ index 73b5280525..bb2fe37304 100644 DEFUN (no_debug_bgp, no_debug_bgp_cmd, "no debug bgp", -@@ -2299,6 +2336,7 @@ DEFUN (no_debug_bgp, +@@ -2329,6 +2366,7 @@ DEFUN (no_debug_bgp, TERM_DEBUG_OFF(evpn_mh, EVPN_MH_RT); TERM_DEBUG_OFF(bfd, BFD_LIB); TERM_DEBUG_OFF(cond_adv, COND_ADV); @@ -1837,7 +587,7 @@ index 73b5280525..bb2fe37304 100644 vty_out(vty, "All possible debugging has been turned off\n"); -@@ -2396,6 +2434,9 @@ DEFUN_NOSH (show_debugging_bgp, +@@ -2429,6 +2467,9 @@ DEFUN_NOSH (show_debugging_bgp, vty_out(vty, " BGP conditional advertisement debugging is on\n"); @@ -1847,7 +597,7 @@ index 73b5280525..bb2fe37304 100644 cmd_show_lib_debugs(vty); hook_call(bgp_hook_config_write_debug, vty, false); -@@ -2534,6 +2575,11 @@ static int bgp_config_write_debug(struct vty *vty) +@@ -2572,6 +2613,11 @@ static int bgp_config_write_debug(struct vty *vty) write++; } @@ -1859,7 +609,7 @@ index 73b5280525..bb2fe37304 100644 if (CONF_BGP_DEBUG(aggregate, AGGREGATE)) { if (!bgp_debug_aggregate_prefixes || list_isempty(bgp_debug_aggregate_prefixes)) { vty_out(vty, "debug bgp aggregate\n"); -@@ -2691,6 +2737,12 @@ void bgp_debug_init(void) +@@ -2731,6 +2777,12 @@ void bgp_debug_init(void) /* debug bgp conditional advertisement */ install_element(ENABLE_NODE, &debug_bgp_cond_adv_cmd); install_element(CONFIG_NODE, &debug_bgp_cond_adv_cmd); @@ -1873,7 +623,7 @@ index 73b5280525..bb2fe37304 100644 /* Return true if this prefix is on the per_prefix_list of prefixes to debug diff --git a/bgpd/bgp_debug.h b/bgpd/bgp_debug.h -index e7d43bfacd..f16c6bcbc4 100644 +index 58e5a04aa3..1e48ce431f 100644 --- a/bgpd/bgp_debug.h +++ b/bgpd/bgp_debug.h @@ -72,6 +72,7 @@ extern unsigned long conf_bgp_debug_evpn_mh; @@ -1892,7 +642,7 @@ index e7d43bfacd..f16c6bcbc4 100644 extern struct list *bgp_debug_neighbor_events_peers; extern struct list *bgp_debug_keepalive_peers; -@@ -139,6 +141,7 @@ struct bgp_debug_filter { +@@ -140,6 +142,7 @@ struct bgp_debug_filter { #define BGP_DEBUG_BFD_LIB 0x01 #define BGP_DEBUG_COND_ADV 0x01 #define BGP_DEBUG_AGGREGATE 0x01 @@ -1901,12 +651,12 @@ index e7d43bfacd..f16c6bcbc4 100644 #define CONF_DEBUG_ON(a, b) (conf_bgp_debug_ ## a |= (BGP_DEBUG_ ## b)) #define CONF_DEBUG_OFF(a, b) (conf_bgp_debug_ ## a &= ~(BGP_DEBUG_ ## b)) diff --git a/bgpd/bgp_errors.c b/bgpd/bgp_errors.c -index 977868adc7..f084022482 100644 +index fcfd26645f..d92cbcc674 100644 --- a/bgpd/bgp_errors.c +++ b/bgpd/bgp_errors.c -@@ -491,6 +491,12 @@ static struct log_ref ferr_bgp_err[] = { - .description = "BGP could not find the label for the specified path_info", - .suggestion = "Most likely a bug. This should never happen. Please try to build a reproducer and report.", +@@ -569,6 +569,12 @@ static struct log_ref ferr_bgp_err[] = { + .description = "BGP failed to configure TTL security parameters (min/max TTL) on peer connection", + .suggestion = "Ensure both peers support TTL security feature and verify kernel supports TTL security socket options. Check peer TTL security configuration for compatibility.", }, + { + .code = EC_BGP_LS_PACKET, @@ -1918,23 +668,60 @@ index 977868adc7..f084022482 100644 .code = END_FERR, } diff --git a/bgpd/bgp_errors.h b/bgpd/bgp_errors.h -index c15692112c..d65dbc8a52 100644 +index e71be1882e..0d12fc0c5f 100644 --- a/bgpd/bgp_errors.h +++ b/bgpd/bgp_errors.h -@@ -93,6 +93,7 @@ enum bgp_log_refs { - EC_BGP_SENDQ_STUCK_WARN, - EC_BGP_SENDQ_STUCK_PROPER, - EC_BGP_PATH_WITHOUT_LABEL, +@@ -106,6 +106,7 @@ enum bgp_log_refs { + EC_BGP_CONFIG_TIMEOUT, + EC_BGP_LABEL_POOL_INSERT_FAIL, + EC_BGP_TTL_SECURITY_FAIL, + EC_BGP_LS_PACKET, }; extern void bgp_error_init(void); +diff --git a/bgpd/bgp_fsm.c b/bgpd/bgp_fsm.c +index e0baad77f4..a4f5c38749 100644 +--- a/bgpd/bgp_fsm.c ++++ b/bgpd/bgp_fsm.c +@@ -43,6 +43,7 @@ + #include "bgpd/bgp_zebra.h" + #include "bgpd/bgp_vty.h" + #include "bgpd/bgp_trace.h" ++#include "bgpd/bgp_ls.h" + + DEFINE_HOOK(peer_backward_transition, (struct peer * peer), (peer)); + DEFINE_HOOK(peer_status_changed, (struct peer * peer), (peer)); +@@ -2092,6 +2093,12 @@ enum bgp_fsm_state_progress bgp_stop(struct peer_connection *connection) + bgp_peer_get_connection_direction_string(connection)); + update_group_remove_peer_afs(peer); + ++ /* Withdraw Link NLRI for BGP session (local -> peer) */ ++ if (bgp && bgp->ls_info && bgp->ls_info->enable_distribution) ++ if (bgp_ls_withdraw_bgp_link(bgp, peer) != 0) ++ zlog_warn("BGP-LS: Failed to withdraw link NLRI for peer %s", ++ peer->host); ++ + /* Reset peer synctime */ + peer->synctime = 0; + } +@@ -2863,6 +2870,11 @@ bgp_establish(struct peer_connection *connection) + PEER_STATUS_ORF_WAIT_REFRESH); + } + ++ /* Generate Link NLRI for BGP session (local -> peer) */ ++ if (bgp && bgp->ls_info && bgp->ls_info->enable_distribution) ++ if (bgp_ls_originate_bgp_link(bgp, peer) != 0) ++ zlog_warn("BGP-LS: Failed to originate link NLRI for peer %s", peer->host); ++ + bgp_announce_peer(peer); + + /* Start the route advertisement timer to send updates to the peer - if diff --git a/bgpd/bgp_ls.c b/bgpd/bgp_ls.c new file mode 100644 -index 0000000000..199e390bee +index 0000000000..97c82b3654 --- /dev/null +++ b/bgpd/bgp_ls.c -@@ -0,0 +1,878 @@ +@@ -0,0 +1,1515 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * BGP Link-State (RFC 9552) - Core Implementation @@ -1971,17 +758,17 @@ index 0000000000..199e390bee +{ + json_object *json_node = json_object_new_object(); + -+ if (BGP_LS_TLV_CHECK(node->present_tlvs, BGP_LS_NODE_DESC_AS_BIT)) ++ if (CHECK_FLAG(node->present_tlvs, BGP_LS_NODE_DESC_AS_BIT)) + json_object_int_add(json_node, "asn", node->asn); + -+ if (BGP_LS_TLV_CHECK(node->present_tlvs, BGP_LS_NODE_DESC_BGP_LS_ID_BIT)) ++ if (CHECK_FLAG(node->present_tlvs, BGP_LS_NODE_DESC_BGP_LS_ID_BIT)) + json_object_int_add(json_node, "bgplsId", node->bgp_ls_id); + -+ if (BGP_LS_TLV_CHECK(node->present_tlvs, BGP_LS_NODE_DESC_OSPF_AREA_BIT)) ++ if (CHECK_FLAG(node->present_tlvs, BGP_LS_NODE_DESC_OSPF_AREA_BIT)) + json_object_string_addf(json_node, "ospfAreaId", "%pI4", + (in_addr_t *)&node->ospf_area_id); + -+ if (BGP_LS_TLV_CHECK(node->present_tlvs, BGP_LS_NODE_DESC_IGP_ROUTER_BIT)) { ++ if (CHECK_FLAG(node->present_tlvs, BGP_LS_NODE_DESC_IGP_ROUTER_BIT)) { + char igp_router_id[256]; + char *p = igp_router_id; + @@ -1996,6 +783,9 @@ index 0000000000..199e390bee + json_object_string_add(json_node, "igpRouterId", igp_router_id); + } + ++ if (CHECK_FLAG(node->present_tlvs, BGP_LS_NODE_DESC_BGP_ROUTER_ID_BIT)) ++ json_object_string_addf(json_node, "bgpRouterId", "%pI4", &node->bgp_router_id); ++ + return json_node; +} + @@ -2004,28 +794,28 @@ index 0000000000..199e390bee +{ + json_object *json_link = json_object_new_object(); + -+ if (BGP_LS_TLV_CHECK(link_desc->present_tlvs, BGP_LS_LINK_DESC_LINK_ID_BIT)) { ++ if (CHECK_FLAG(link_desc->present_tlvs, BGP_LS_LINK_DESC_LINK_ID_BIT)) { + json_object_int_add(json_link, "linkLocalId", link_desc->link_local_id); + json_object_int_add(json_link, "linkRemoteId", link_desc->link_remote_id); + } + -+ if (BGP_LS_TLV_CHECK(link_desc->present_tlvs, BGP_LS_LINK_DESC_IPV4_INTF_BIT)) ++ if (CHECK_FLAG(link_desc->present_tlvs, BGP_LS_LINK_DESC_IPV4_INTF_BIT)) + json_object_string_addf(json_link, "ipv4InterfaceAddress", "%pI4", + &link_desc->ipv4_intf_addr); + -+ if (BGP_LS_TLV_CHECK(link_desc->present_tlvs, BGP_LS_LINK_DESC_IPV4_NEIGH_BIT)) ++ if (CHECK_FLAG(link_desc->present_tlvs, BGP_LS_LINK_DESC_IPV4_NEIGH_BIT)) + json_object_string_addf(json_link, "ipv4NeighborAddress", "%pI4", + &link_desc->ipv4_neigh_addr); + -+ if (BGP_LS_TLV_CHECK(link_desc->present_tlvs, BGP_LS_LINK_DESC_IPV6_INTF_BIT)) ++ if (CHECK_FLAG(link_desc->present_tlvs, BGP_LS_LINK_DESC_IPV6_INTF_BIT)) + json_object_string_addf(json_link, "ipv6InterfaceAddress", "%pI6", + &link_desc->ipv6_intf_addr); + -+ if (BGP_LS_TLV_CHECK(link_desc->present_tlvs, BGP_LS_LINK_DESC_IPV6_NEIGH_BIT)) ++ if (CHECK_FLAG(link_desc->present_tlvs, BGP_LS_LINK_DESC_IPV6_NEIGH_BIT)) + json_object_string_addf(json_link, "ipv6NeighborAddress", "%pI6", + &link_desc->ipv6_neigh_addr); + -+ if (BGP_LS_TLV_CHECK(link_desc->present_tlvs, BGP_LS_LINK_DESC_MT_ID_BIT) && ++ if (CHECK_FLAG(link_desc->present_tlvs, BGP_LS_LINK_DESC_MT_ID_BIT) && + link_desc->mt_id_count > 0) + json_object_int_add(json_link, "mtId", link_desc->mt_id[0]); + @@ -2043,11 +833,16 @@ index 0000000000..199e390bee + &prefix_desc->prefix); + + /* OSPF Route Type */ -+ if (BGP_LS_TLV_CHECK(prefix_desc->present_tlvs, BGP_LS_PREFIX_DESC_OSPF_ROUTE_BIT)) ++ if (CHECK_FLAG(prefix_desc->present_tlvs, BGP_LS_PREFIX_DESC_OSPF_ROUTE_BIT)) + json_object_string_addf(json_prefix, "ospfRouteType", "%s", + bgp_ls_ospf_route_type_str_json( + prefix_desc->ospf_route_type)); + ++ /* BGP Route Type */ ++ if (CHECK_FLAG(prefix_desc->present_tlvs, BGP_LS_PREFIX_DESC_BGP_ROUTE_TYPE_BIT)) ++ json_object_string_addf(json_prefix, "bgpRouteType", "%s", ++ bgp_ls_bgp_route_type_str_json(prefix_desc->bgp_route_type)); ++ + return json_prefix; +} + @@ -2129,14 +924,14 @@ index 0000000000..199e390bee + *remain -= len; + + /* AS Number */ -+ if (BGP_LS_TLV_CHECK(node->present_tlvs, BGP_LS_NODE_DESC_AS_BIT)) { ++ if (CHECK_FLAG(node->present_tlvs, BGP_LS_NODE_DESC_AS_BIT)) { + len = snprintfrr(*p, *remain, "[c%u]", node->asn); + *p += len; + *remain -= len; + } + + /* BGP-LS Identifier (deprecated but still used) */ -+ if (BGP_LS_TLV_CHECK(node->present_tlvs, BGP_LS_NODE_DESC_BGP_LS_ID_BIT)) { ++ if (CHECK_FLAG(node->present_tlvs, BGP_LS_NODE_DESC_BGP_LS_ID_BIT)) { + len = snprintfrr(*p, *remain, "[b%u.%u.%u.%u]", (node->bgp_ls_id >> 24) & 0xFF, + (node->bgp_ls_id >> 16) & 0xFF, (node->bgp_ls_id >> 8) & 0xFF, + node->bgp_ls_id & 0xFF); @@ -2145,7 +940,7 @@ index 0000000000..199e390bee + } + + /* IGP Router ID */ -+ if (BGP_LS_TLV_CHECK(node->present_tlvs, BGP_LS_NODE_DESC_IGP_ROUTER_BIT)) { ++ if (CHECK_FLAG(node->present_tlvs, BGP_LS_NODE_DESC_IGP_ROUTER_BIT)) { + len = snprintfrr(*p, *remain, "[s"); + *p += len; + *remain -= len; @@ -2164,6 +959,13 @@ index 0000000000..199e390bee + *remain -= len; + } + ++ /* BGP Router ID (TLV 516) */ ++ if (CHECK_FLAG(node->present_tlvs, BGP_LS_NODE_DESC_BGP_ROUTER_ID_BIT)) { ++ len = snprintfrr(*p, *remain, "[q%pI4]", &node->bgp_router_id); ++ *p += len; ++ *remain -= len; ++ } ++ + len = snprintfrr(*p, *remain, "]"); + *p += len; + *remain -= len; @@ -2179,7 +981,7 @@ index 0000000000..199e390bee + *remain -= len; + + /* Link Local/Remote Identifiers (TLV 258) */ -+ if (BGP_LS_TLV_CHECK(link_desc->present_tlvs, BGP_LS_LINK_DESC_LINK_ID_BIT)) { ++ if (CHECK_FLAG(link_desc->present_tlvs, BGP_LS_LINK_DESC_LINK_ID_BIT)) { + len = snprintfrr(*p, *remain, "[l%u/%u]", link_desc->link_local_id, + link_desc->link_remote_id); + *p += len; @@ -2187,28 +989,28 @@ index 0000000000..199e390bee + } + + /* IPv4 Interface Address (TLV 259) */ -+ if (BGP_LS_TLV_CHECK(link_desc->present_tlvs, BGP_LS_LINK_DESC_IPV4_INTF_BIT)) { ++ if (CHECK_FLAG(link_desc->present_tlvs, BGP_LS_LINK_DESC_IPV4_INTF_BIT)) { + len = snprintfrr(*p, *remain, "[i%pI4]", &link_desc->ipv4_intf_addr); + *p += len; + *remain -= len; + } + + /* IPv4 Neighbor Address (TLV 260) */ -+ if (BGP_LS_TLV_CHECK(link_desc->present_tlvs, BGP_LS_LINK_DESC_IPV4_NEIGH_BIT)) { ++ if (CHECK_FLAG(link_desc->present_tlvs, BGP_LS_LINK_DESC_IPV4_NEIGH_BIT)) { + len = snprintfrr(*p, *remain, "[n%pI4]", &link_desc->ipv4_neigh_addr); + *p += len; + *remain -= len; + } + + /* IPv6 Interface Address (TLV 261) */ -+ if (BGP_LS_TLV_CHECK(link_desc->present_tlvs, BGP_LS_LINK_DESC_IPV6_INTF_BIT)) { ++ if (CHECK_FLAG(link_desc->present_tlvs, BGP_LS_LINK_DESC_IPV6_INTF_BIT)) { + len = snprintfrr(*p, *remain, "[i%pI6]", &link_desc->ipv6_intf_addr); + *p += len; + *remain -= len; + } + + /* IPv6 Neighbor Address (TLV 262) */ -+ if (BGP_LS_TLV_CHECK(link_desc->present_tlvs, BGP_LS_LINK_DESC_IPV6_NEIGH_BIT)) { ++ if (CHECK_FLAG(link_desc->present_tlvs, BGP_LS_LINK_DESC_IPV6_NEIGH_BIT)) { + len = snprintfrr(*p, *remain, "[n%pI6]", &link_desc->ipv6_neigh_addr); + *p += len; + *remain -= len; @@ -2329,6 +1131,15 @@ index 0000000000..199e390bee + len = snprintfrr(p, remain, "]]"); + p += len; + remain -= len; ++ ++ /* Format BGP Route Type if present */ ++ if (CHECK_FLAG(nlri->nlri_data.prefix.prefix_desc.present_tlvs, ++ BGP_LS_PREFIX_DESC_BGP_ROUTE_TYPE_BIT)) { ++ len = snprintfrr(p, remain, "[br0x%02x]", ++ nlri->nlri_data.prefix.prefix_desc.bgp_route_type); ++ p += len; ++ remain -= len; ++ } + } +} + @@ -2813,12 +1624,625 @@ index 0000000000..199e390bee + + zlog_info("BGP-LS: Module terminated for instance %s", bgp->name_pretty); +} ++ ++/* ++ * =========================================================================== ++ * BGP Topology Export (BGP-only fabrics) ++ * =========================================================================== ++ */ ++ ++/* ++ * Originate Node NLRI for local BGP router ++ * ++ * Reference: draft-ietf-idr-bgp-ls-bgp-only-fabric-04 Section 4.1 ++ * ++ * Generates a Node NLRI for the local BGP router with: ++ * - Protocol-ID = BGP_LS_PROTO_BGP (7) ++ * - Autonomous System Number (TLV 512) - local BGP ASN ++ * - BGP Router-ID (TLV 516) - local BGP Identifier ++ */ ++int bgp_ls_originate_bgp_node(struct bgp *bgp) ++{ ++ struct bgp_ls_nlri *nlri; ++ struct bgp_ls_attr *ls_attr; ++ int ret; ++ ++ if (!bgp || !bgp->ls_info || !bgp->ls_info->enable_distribution) ++ return 0; ++ ++ nlri = bgp_ls_nlri_alloc(); ++ ++ /* Set NLRI type and protocol */ ++ nlri->nlri_type = BGP_LS_NLRI_TYPE_NODE; ++ nlri->nlri_data.node.protocol_id = BGP_LS_PROTO_BGP; ++ nlri->nlri_data.node.identifier = bgp->ls_info->instance_id; ++ ++ /* Local Node Descriptor */ ++ /* TLV 512: Autonomous System Number */ ++ nlri->nlri_data.node.local_node.asn = bgp->as; ++ SET_FLAG(nlri->nlri_data.node.local_node.present_tlvs, BGP_LS_NODE_DESC_AS_BIT); ++ ++ /* TLV 516: BGP Router-ID */ ++ nlri->nlri_data.node.local_node.bgp_router_id = bgp->router_id; ++ SET_FLAG(nlri->nlri_data.node.local_node.present_tlvs, BGP_LS_NODE_DESC_BGP_ROUTER_ID_BIT); ++ ++ /* Node Attributes */ ++ ls_attr = bgp_ls_attr_alloc(); ++ ++ /* TLV 1026: Node Name */ ++ ls_attr->node_name = XSTRDUP(MTYPE_BGP_LS_ATTR, bgp->peer_self->host); ++ SET_FLAG(ls_attr->present_tlvs, BGP_LS_ATTR_NODE_NAME_BIT); ++ ++ ret = bgp_ls_update(bgp, nlri, ls_attr); ++ if (ret != 0) { ++ zlog_err("BGP-LS: Failed to originate BGP Node NLRI"); ++ bgp_ls_attr_free(ls_attr); ++ bgp_ls_nlri_free(nlri); ++ return -1; ++ } ++ ++ if (BGP_DEBUG(linkstate, LINKSTATE)) ++ zlog_debug("BGP-LS: Originated BGP Node NLRI for AS %u, Router-ID %pI4", bgp->as, ++ &bgp->router_id); ++ ++ bgp_ls_attr_free(ls_attr); ++ bgp_ls_nlri_free(nlri); ++ return 0; ++} ++ ++static struct interface *bgp_ls_get_ifp_from_connection(struct peer *peer, ++ struct peer_connection *connection) ++{ ++ struct interface *ifp = NULL; ++ ++ if (!peer || !peer->bgp) ++ return NULL; ++ ++ if (connection && connection->su_local) { ++ if (connection->su_local->sa.sa_family == AF_INET) { ++ ifp = if_lookup_by_ipv4_exact(&connection->su_local->sin.sin_addr, ++ peer->bgp->vrf_id); ++ } else if (connection->su_local->sa.sa_family == AF_INET6) { ++ ifp = if_lookup_by_ipv6_exact(&connection->su_local->sin6.sin6_addr, ++ connection->su_local->sin6.sin6_scope_id, ++ peer->bgp->vrf_id); ++ } ++ } ++ ++ if (!ifp && peer->conf_if) ++ ifp = if_lookup_by_name(peer->conf_if, peer->bgp->vrf_id); ++ ++ if (!ifp && peer->ifname) ++ ifp = if_lookup_by_name(peer->ifname, peer->bgp->vrf_id); ++ ++ if (!ifp && peer->update_if) ++ ifp = if_lookup_by_name(peer->update_if, peer->bgp->vrf_id); ++ ++ return ifp; ++} ++ ++/* ++ * Originate BGP Link NLRI for a BGP session ++ * ++ * This generates a Link NLRI representing a BGP session from the local ++ * router to a peer. Per draft-ietf-idr-bgp-ls-bgp-only-fabric-04 Section 4.2, ++ * the NLRI includes: ++ * - Local Node Descriptor: Local BGP router (ASN + BGP Router-ID) ++ * - Remote Node Descriptor: Peer BGP router (ASN + BGP Router-ID) ++ * - Link Descriptor: IPv4/IPv6 interface and neighbor addresses ++ * - Protocol-ID: BGP (7) ++ * ++ * @param bgp - BGP instance ++ * @param peer - BGP peer (remote endpoint of the session) ++ * @return 0 on success, -1 on error ++ */ ++int bgp_ls_originate_bgp_link(struct bgp *bgp, struct peer *peer) ++{ ++ struct bgp_ls_nlri *nlri; ++ struct peer_connection *connection; ++ struct interface *ifp; ++ uint32_t local_link_id; ++ uint32_t remote_link_id; ++ int ret; ++ ++ if (!bgp || !bgp->ls_info || !bgp->ls_info->enable_distribution) ++ return 0; ++ ++ if (!peer) { ++ zlog_err("BGP-LS: Cannot originate BGP link without BGP instance or peer"); ++ return -1; ++ } ++ ++ if (!bgp->peer_self) { ++ zlog_err("BGP-LS: No local peer for BGP link origination"); ++ return -1; ++ } ++ ++ connection = peer->connection; ++ ifp = bgp_ls_get_ifp_from_connection(peer, connection); ++ ++ if (!CHECK_FLAG(peer->flags, PEER_FLAG_LS_LOCAL_LINK_ID) && !ifp) { ++ zlog_err("BGP-LS: Cannot originate BGP link NLRI for peer %s: missing local link-id and interface", ++ peer->host); ++ return -1; ++ } ++ ++ nlri = bgp_ls_nlri_alloc(); ++ ++ /* Set NLRI type and protocol */ ++ nlri->nlri_type = BGP_LS_NLRI_TYPE_LINK; ++ nlri->nlri_data.link.protocol_id = BGP_LS_PROTO_BGP; ++ nlri->nlri_data.link.identifier = bgp->ls_info->instance_id; ++ ++ /* Local Node Descriptor */ ++ nlri->nlri_data.link.local_node.asn = bgp->as; ++ SET_FLAG(nlri->nlri_data.link.local_node.present_tlvs, BGP_LS_NODE_DESC_AS_BIT); ++ ++ nlri->nlri_data.link.local_node.bgp_router_id = bgp->router_id; ++ SET_FLAG(nlri->nlri_data.link.local_node.present_tlvs, BGP_LS_NODE_DESC_BGP_ROUTER_ID_BIT); ++ ++ /* Remote Node Descriptor */ ++ nlri->nlri_data.link.remote_node.asn = peer->as; ++ SET_FLAG(nlri->nlri_data.link.remote_node.present_tlvs, BGP_LS_NODE_DESC_AS_BIT); ++ ++ nlri->nlri_data.link.remote_node.bgp_router_id = peer->remote_id; ++ SET_FLAG(nlri->nlri_data.link.remote_node.present_tlvs, BGP_LS_NODE_DESC_BGP_ROUTER_ID_BIT); ++ ++ /* Link Descriptor: Link Local/Remote Identifiers (TLV 258) */ ++ local_link_id = CHECK_FLAG(peer->flags, PEER_FLAG_LS_LOCAL_LINK_ID) ++ ? peer->ls_local_link_id ++ : (uint32_t)ifp->ifindex; ++ remote_link_id = CHECK_FLAG(peer->flags, PEER_FLAG_LS_REMOTE_LINK_ID) ++ ? peer->ls_remote_link_id ++ : 0; ++ ++ nlri->nlri_data.link.link_desc.link_local_id = local_link_id; ++ nlri->nlri_data.link.link_desc.link_remote_id = remote_link_id; ++ SET_FLAG(nlri->nlri_data.link.link_desc.present_tlvs, BGP_LS_LINK_DESC_LINK_ID_BIT); ++ ++ /* Link Descriptor: IPv4 Neighbor Address (TLV 260) */ ++ if (connection && connection->su.sa.sa_family == AF_INET) { ++ nlri->nlri_data.link.link_desc.ipv4_neigh_addr = connection->su.sin.sin_addr; ++ SET_FLAG(nlri->nlri_data.link.link_desc.present_tlvs, ++ BGP_LS_LINK_DESC_IPV4_NEIGH_BIT); ++ } ++ ++ /* Link Descriptor: IPv6 Neighbor Address (TLV 262) */ ++ if (connection && connection->su.sa.sa_family == AF_INET6) { ++ nlri->nlri_data.link.link_desc.ipv6_neigh_addr = connection->su.sin6.sin6_addr; ++ SET_FLAG(nlri->nlri_data.link.link_desc.present_tlvs, ++ BGP_LS_LINK_DESC_IPV6_NEIGH_BIT); ++ } ++ ++ /* Link Descriptor: IPv4 Interface Address (TLV 259) */ ++ if (connection && connection->su_local && connection->su_local->sa.sa_family == AF_INET) { ++ nlri->nlri_data.link.link_desc.ipv4_intf_addr = connection->su_local->sin.sin_addr; ++ SET_FLAG(nlri->nlri_data.link.link_desc.present_tlvs, ++ BGP_LS_LINK_DESC_IPV4_INTF_BIT); ++ } ++ ++ /* Link Descriptor: IPv6 Interface Address (TLV 261) */ ++ if (connection && connection->su_local && connection->su_local->sa.sa_family == AF_INET6) { ++ nlri->nlri_data.link.link_desc.ipv6_intf_addr = ++ connection->su_local->sin6.sin6_addr; ++ SET_FLAG(nlri->nlri_data.link.link_desc.present_tlvs, ++ BGP_LS_LINK_DESC_IPV6_INTF_BIT); ++ } ++ ++ ret = bgp_ls_update(bgp, nlri, NULL); ++ if (ret != 0) { ++ zlog_err("BGP-LS: Failed to originate BGP link NLRI"); ++ bgp_ls_nlri_free(nlri); ++ return -1; ++ } ++ ++ if (BGP_DEBUG(linkstate, LINKSTATE)) ++ zlog_debug("BGP-LS: Originated BGP Link NLRI: Local AS %u Router-ID %pI4 -> Peer AS %u Router-ID %pI4", ++ bgp->as, &bgp->router_id, peer->as, &peer->remote_id); ++ ++ bgp_ls_nlri_free(nlri); ++ return 0; ++} ++ ++/* ++ * Originate BGP Prefix NLRI ++ * ++ * Reference: draft-ietf-idr-bgp-ls-bgp-only-fabric-04 Section 4.3 ++ * ++ * Generates a BGP-LS Prefix NLRI for BGP routes from the local RIB. ++ * ++ * @param bgp - BGP instance ++ * @param afi - Address family (AFI_IP or AFI_IP6) ++ * @param safi - Subsequent address family (SAFI_UNICAST, etc.) ++ * @param dest - BGP destination (route) ++ * @param path - BGP path info ++ * @return 0 on success, -1 on failure ++ */ ++int bgp_ls_originate_bgp_prefix(struct bgp *bgp, afi_t afi, safi_t safi, struct bgp_dest *dest, ++ struct bgp_path_info *path) ++{ ++ struct bgp_ls_nlri *nlri; ++ const struct prefix *p; ++ int ret; ++ ++ if (!bgp || !bgp->ls_info || !bgp->ls_info->enable_distribution) ++ return 0; ++ ++ if (!dest || !path) { ++ zlog_err("BGP-LS: Invalid parameters for BGP prefix origination"); ++ return -1; ++ } ++ ++ if (safi != SAFI_UNICAST) ++ return 0; ++ ++ /* Get the prefix */ ++ p = bgp_dest_get_prefix(dest); ++ if (!p) { ++ zlog_err("BGP-LS: Cannot get prefix from destination"); ++ return -1; ++ } ++ ++ nlri = bgp_ls_nlri_alloc(); ++ ++ /* Set NLRI type and protocol */ ++ if (afi == AFI_IP) ++ nlri->nlri_type = BGP_LS_NLRI_TYPE_IPV4_PREFIX; ++ else if (afi == AFI_IP6) ++ nlri->nlri_type = BGP_LS_NLRI_TYPE_IPV6_PREFIX; ++ else { ++ zlog_err("BGP-LS: Unsupported AFI %d for BGP prefix", afi); ++ bgp_ls_nlri_free(nlri); ++ return -1; ++ } ++ ++ nlri->nlri_data.prefix.protocol_id = BGP_LS_PROTO_BGP; ++ nlri->nlri_data.prefix.identifier = bgp->ls_info->instance_id; ++ ++ /* Local Node Descriptor */ ++ /* TLV 512: Autonomous System Number */ ++ nlri->nlri_data.prefix.local_node.asn = bgp->as; ++ SET_FLAG(nlri->nlri_data.prefix.local_node.present_tlvs, BGP_LS_NODE_DESC_AS_BIT); ++ ++ /* TLV 516: BGP Router-ID */ ++ nlri->nlri_data.prefix.local_node.bgp_router_id = bgp->router_id; ++ SET_FLAG(nlri->nlri_data.prefix.local_node.present_tlvs, ++ BGP_LS_NODE_DESC_BGP_ROUTER_ID_BIT); ++ ++ /* Prefix Descriptor */ ++ /* TLV 267: BGP Route Type */ ++ if (path->type == ZEBRA_ROUTE_LOCAL) ++ nlri->nlri_data.prefix.prefix_desc.bgp_route_type = BGP_LS_BGP_RT_LOCAL; ++ else if (path->type == ZEBRA_ROUTE_CONNECT) ++ nlri->nlri_data.prefix.prefix_desc.bgp_route_type = BGP_LS_BGP_RT_ATTACHED; ++ else if (path->type == ZEBRA_ROUTE_BGP && path->peer && path->peer->sort == BGP_PEER_EBGP) ++ nlri->nlri_data.prefix.prefix_desc.bgp_route_type = BGP_LS_BGP_RT_EXTERNAL_BGP; ++ else if (path->type == ZEBRA_ROUTE_BGP && path->peer && path->peer->sort == BGP_PEER_IBGP) ++ nlri->nlri_data.prefix.prefix_desc.bgp_route_type = BGP_LS_BGP_RT_INTERNAL_BGP; ++ else ++ nlri->nlri_data.prefix.prefix_desc.bgp_route_type = BGP_LS_BGP_RT_REDISTRIBUTED; ++ SET_FLAG(nlri->nlri_data.prefix.prefix_desc.present_tlvs, ++ BGP_LS_PREFIX_DESC_BGP_ROUTE_TYPE_BIT); ++ ++ /* TLV 265: IP Reachability Information */ ++ nlri->nlri_data.prefix.prefix_desc.prefix = *p; ++ SET_FLAG(nlri->nlri_data.prefix.prefix_desc.present_tlvs, BGP_LS_PREFIX_DESC_IP_REACH_BIT); ++ ++ ret = bgp_ls_update(bgp, nlri, NULL); ++ if (ret != 0) { ++ zlog_err("BGP-LS: Failed to originate BGP prefix NLRI for %pFX", p); ++ bgp_ls_nlri_free(nlri); ++ return -1; ++ } ++ ++ if (BGP_DEBUG(linkstate, LINKSTATE)) ++ zlog_debug("BGP-LS: Originated BGP Prefix NLRI for %pFX", p); ++ ++ bgp_ls_nlri_free(nlri); ++ return 0; ++} ++ ++/* ++ * Export BGP topology as BGP-LS NLRIs. ++ * ++ * Generates Node, Link, and Prefix NLRIs for the local BGP topology. ++ * ++ * @param bgp - BGP instance ++ * @return 0 on success, -1 on invalid input or when distribution is disabled ++ */ ++int bgp_ls_export_bgp_topology(struct bgp *bgp) ++{ ++ struct listnode *node; ++ struct peer *peer; ++ int nlri_count = 0; ++ ++ if (!bgp || !bgp->ls_info || !bgp->ls_info->enable_distribution) { ++ zlog_err("BGP-LS: BGP topology distribution not enabled"); ++ return -1; ++ } ++ ++ if (BGP_DEBUG(linkstate, LINKSTATE)) ++ zlog_debug("BGP-LS: Exporting BGP topology for instance %s", bgp->name_pretty); ++ ++ /* Export the local node once. */ ++ if (bgp_ls_originate_bgp_node(bgp) != 0) ++ zlog_warn("BGP-LS: Failed to originate local BGP node NLRI"); ++ else ++ nlri_count++; ++ ++ /* Export one link NLRI for each established non-group peer. */ ++ for (ALL_LIST_ELEMENTS_RO(bgp->peer, node, peer)) { ++ if (CHECK_FLAG(peer->sflags, PEER_STATUS_GROUP)) ++ continue; ++ ++ if (!peer->connection || peer->connection->status != Established) ++ continue; ++ ++ if (bgp_ls_originate_bgp_link(bgp, peer) != 0) ++ zlog_warn("BGP-LS: Failed to originate link NLRI for peer %s", peer->host); ++ else ++ nlri_count++; ++ } ++ ++ /* Export prefix NLRIs from the IPv4/IPv6 unicast RIBs. */ ++ afi_t afis[] = { AFI_IP, AFI_IP6 }; ++ ++ for (int i = 0; i < 2; i++) { ++ afi_t afi = afis[i]; ++ struct bgp_dest *dest; ++ struct bgp_path_info *path; ++ ++ if (!bgp->rib[afi][SAFI_UNICAST]) ++ continue; ++ ++ for (dest = bgp_table_top(bgp->rib[afi][SAFI_UNICAST]); dest; ++ dest = bgp_route_next(dest)) { ++ for (path = bgp_dest_get_bgp_path_info(dest); path; path = path->next) { ++ if (bgp_ls_originate_bgp_prefix(bgp, afi, SAFI_UNICAST, dest, ++ path) != 0) { ++ zlog_warn("BGP-LS: Failed to originate prefix NLRI for %pFX", ++ bgp_dest_get_prefix(dest)); ++ } else ++ nlri_count++; ++ } ++ } ++ } ++ ++ if (BGP_DEBUG(linkstate, LINKSTATE)) ++ zlog_debug("BGP-LS: Exported %d BGP-LS NLRIs", nlri_count); ++ ++ return 0; ++} ++ ++/* Withdraw all locally originated BGP-LS routes. */ ++void bgp_ls_withdraw_all(struct bgp *bgp) ++{ ++ struct bgp_dest *dest; ++ struct bgp_table *table; ++ struct bgp_ls_nlri *nlri; ++ ++ if (!bgp) ++ return; ++ ++ if (CHECK_FLAG(bgp->flags, BGP_FLAG_DELETE_IN_PROGRESS) || bgp->peer_self == NULL) ++ return; ++ ++ if (!bgp->ls_info) ++ return; ++ ++ table = bgp->rib[AFI_BGP_LS][SAFI_BGP_LS]; ++ if (!table) ++ return; ++ ++ for (dest = bgp_table_top(table); dest; dest = bgp_route_next(dest)) { ++ nlri = dest->ls_nlri; ++ if (!nlri) ++ continue; ++ ++ bgp_ls_withdraw(bgp, nlri); ++ } ++} ++ ++static struct bgp_ls_nlri *bgp_ls_lookup_bgp_link_nlri(struct bgp *bgp, struct peer *peer) ++{ ++ struct bgp_dest *dest; ++ struct bgp_table *table; ++ struct bgp_ls_nlri *nlri; ++ struct interface *ifp; ++ struct peer_connection *connection; ++ bool expect_link_id; ++ uint32_t expected_local_link_id = 0; ++ uint32_t expected_remote_link_id = 0; ++ ++ connection = peer ? peer->connection : NULL; ++ ifp = bgp_ls_get_ifp_from_connection(peer, connection); ++ ++ expect_link_id = CHECK_FLAG(peer->flags, PEER_FLAG_LS_LOCAL_LINK_ID) || (ifp != NULL); ++ if (expect_link_id) { ++ expected_local_link_id = CHECK_FLAG(peer->flags, PEER_FLAG_LS_LOCAL_LINK_ID) ++ ? peer->ls_local_link_id ++ : (uint32_t)ifp->ifindex; ++ expected_remote_link_id = CHECK_FLAG(peer->flags, PEER_FLAG_LS_REMOTE_LINK_ID) ++ ? peer->ls_remote_link_id ++ : 0; ++ } ++ ++ table = bgp->rib[AFI_BGP_LS][SAFI_BGP_LS]; ++ if (!table) ++ return NULL; ++ ++ for (dest = bgp_table_top(table); dest; dest = bgp_route_next(dest)) { ++ nlri = dest->ls_nlri; ++ if (!nlri) ++ continue; ++ ++ if (nlri->nlri_type != BGP_LS_NLRI_TYPE_LINK) ++ continue; ++ if (nlri->nlri_data.link.protocol_id != BGP_LS_PROTO_BGP) ++ continue; ++ ++ if (nlri->nlri_data.link.local_node.asn != bgp->as) ++ continue; ++ ++ if (nlri->nlri_data.link.remote_node.asn != peer->as) ++ continue; ++ ++ if (!IPV4_ADDR_SAME(&nlri->nlri_data.link.local_node.bgp_router_id, ++ &bgp->router_id)) ++ continue; ++ ++ if (!IPV4_ADDR_SAME(&nlri->nlri_data.link.remote_node.bgp_router_id, ++ &peer->remote_id)) ++ continue; ++ ++ if (expect_link_id) { ++ if (!CHECK_FLAG(nlri->nlri_data.link.link_desc.present_tlvs, ++ BGP_LS_LINK_DESC_LINK_ID_BIT)) ++ continue; ++ ++ if (nlri->nlri_data.link.link_desc.link_local_id != expected_local_link_id) ++ continue; ++ ++ if (nlri->nlri_data.link.link_desc.link_remote_id != ++ expected_remote_link_id) ++ continue; ++ } ++ ++ return nlri; ++ } ++ ++ return NULL; ++} ++ ++static struct bgp_ls_nlri *bgp_ls_lookup_bgp_prefix_nlri(struct bgp *bgp, const struct prefix *p, ++ enum bgp_ls_bgp_route_type route_type) ++{ ++ struct bgp_dest *bn; ++ struct bgp_table *table; ++ struct bgp_ls_nlri *nlri; ++ enum bgp_ls_nlri_type expected_nlri_type; ++ uint64_t expected_identifier; ++ ++ expected_nlri_type = (p->family == AF_INET) ? BGP_LS_NLRI_TYPE_IPV4_PREFIX ++ : BGP_LS_NLRI_TYPE_IPV6_PREFIX; ++ expected_identifier = bgp->ls_info->instance_id; ++ ++ table = bgp->rib[AFI_BGP_LS][SAFI_BGP_LS]; ++ if (!table) ++ return NULL; ++ ++ for (bn = bgp_table_top(table); bn; bn = bgp_route_next(bn)) { ++ nlri = bn->ls_nlri; ++ if (!nlri) ++ continue; ++ ++ if (nlri->nlri_type != expected_nlri_type) ++ continue; ++ ++ if (nlri->nlri_data.prefix.protocol_id != BGP_LS_PROTO_BGP) ++ continue; ++ ++ if (nlri->nlri_data.prefix.identifier != expected_identifier) ++ continue; ++ ++ if (nlri->nlri_data.prefix.local_node.asn != bgp->as) ++ continue; ++ ++ if (!IPV4_ADDR_SAME(&nlri->nlri_data.prefix.local_node.bgp_router_id, ++ &bgp->router_id)) ++ continue; ++ ++ if (nlri->nlri_data.prefix.prefix_desc.bgp_route_type != route_type) ++ continue; ++ ++ if (!prefix_same(&nlri->nlri_data.prefix.prefix_desc.prefix, p)) ++ continue; ++ ++ return nlri; ++ } ++ ++ return NULL; ++} ++ ++int bgp_ls_withdraw_bgp_link(struct bgp *bgp, struct peer *peer) ++{ ++ struct bgp_ls_nlri *nlri; ++ ++ if (!bgp || !peer) { ++ flog_err(EC_BGP_LS_PACKET, "BGP-LS: Invalid parameters to %s", __func__); ++ return -1; ++ } ++ ++ if (CHECK_FLAG(bgp->flags, BGP_FLAG_DELETE_IN_PROGRESS) || bgp->peer_self == NULL) ++ return 0; ++ ++ if (!bgp->ls_info) { ++ if (BGP_DEBUG(linkstate, LINKSTATE)) ++ zlog_debug("%s: No BGP-LS info exists for withdraw", __func__); ++ ++ return 0; ++ } ++ ++ nlri = bgp_ls_lookup_bgp_link_nlri(bgp, peer); ++ if (!nlri) ++ return 0; ++ ++ return bgp_ls_withdraw(bgp, nlri); ++} ++ ++int bgp_ls_withdraw_bgp_prefix(struct bgp *bgp, afi_t afi, safi_t safi, struct bgp_dest *dest, ++ struct bgp_path_info *path) ++{ ++ const struct prefix *p; ++ enum bgp_ls_bgp_route_type route_type; ++ struct bgp_ls_nlri *nlri; ++ ++ if (!bgp || !dest || !path) { ++ zlog_err("BGP-LS: Invalid parameters for BGP prefix withdraw"); ++ return -1; ++ } ++ ++ if (CHECK_FLAG(bgp->flags, BGP_FLAG_DELETE_IN_PROGRESS) || bgp->peer_self == NULL) ++ return 0; ++ ++ if (!bgp->ls_info) ++ return 0; ++ ++ if (safi != SAFI_UNICAST) ++ return 0; ++ ++ p = bgp_dest_get_prefix(dest); ++ if (!p) { ++ zlog_err("BGP-LS: Cannot get prefix from destination"); ++ return -1; ++ } ++ ++ if ((afi == AFI_IP && p->family != AF_INET) || (afi == AFI_IP6 && p->family != AF_INET6)) ++ return 0; ++ ++ if (path->type == ZEBRA_ROUTE_LOCAL) ++ route_type = BGP_LS_BGP_RT_LOCAL; ++ else if (path->type == ZEBRA_ROUTE_CONNECT) ++ route_type = BGP_LS_BGP_RT_ATTACHED; ++ else if (path->type == ZEBRA_ROUTE_BGP && path->peer && path->peer->sort == BGP_PEER_EBGP) ++ route_type = BGP_LS_BGP_RT_EXTERNAL_BGP; ++ else if (path->type == ZEBRA_ROUTE_BGP && path->peer && path->peer->sort == BGP_PEER_IBGP) ++ route_type = BGP_LS_BGP_RT_INTERNAL_BGP; ++ else ++ route_type = BGP_LS_BGP_RT_REDISTRIBUTED; ++ ++ nlri = bgp_ls_lookup_bgp_prefix_nlri(bgp, p, route_type); ++ if (!nlri) ++ return 0; ++ ++ return bgp_ls_withdraw(bgp, nlri); ++} diff --git a/bgpd/bgp_ls.h b/bgpd/bgp_ls.h new file mode 100644 -index 0000000000..06078783bb +index 0000000000..e833be08a2 --- /dev/null +++ b/bgpd/bgp_ls.h -@@ -0,0 +1,86 @@ +@@ -0,0 +1,124 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * BGP Link-State (RFC 9552) - Core Constants and Structures @@ -2849,6 +2273,11 @@ index 0000000000..06078783bb + + /* Link-state database registration status */ + bool registered_ls_db; ++ ++ bool enable_distribution; ++ ++ /* BGP-fabric link-state instance ID */ ++ uint64_t instance_id; +}; + +/* Function prototypes */ @@ -2904,13 +2333,46 @@ index 0000000000..06078783bb +extern void bgp_ls_init(struct bgp *bgp); +extern void bgp_ls_cleanup(struct bgp *bgp); + ++/* ++ * =========================================================================== ++ * BGP Topology Export (BGP-only fabrics) ++ * =========================================================================== ++ */ ++ ++/* ++ * Export BGP topology as BGP-LS NLRIs ++ * ++ * Iterates through all BGP peers and generates Node and Link NLRIs ++ * representing the BGP topology. Used for BGP-only fabrics per ++ * draft-ietf-idr-bgp-ls-bgp-only-fabric. ++ * ++ * @param bgp - BGP instance ++ * @return 0 on success, -1 on error ++ */ ++extern int bgp_ls_export_bgp_topology(struct bgp *bgp); ++void bgp_ls_withdraw_all(struct bgp *bgp); ++int bgp_ls_withdraw_bgp_link(struct bgp *bgp, struct peer *peer); ++int bgp_ls_withdraw_bgp_prefix(struct bgp *bgp, afi_t afi, safi_t safi, struct bgp_dest *dest, ++ struct bgp_path_info *path); ++ ++/* ++ * =========================================================================== ++ * Link State Message Processing ++ * =========================================================================== ++ */ ++ ++extern int bgp_ls_originate_bgp_node(struct bgp *bgp); ++extern int bgp_ls_originate_bgp_link(struct bgp *bgp, struct peer *peer); ++extern int bgp_ls_originate_bgp_prefix(struct bgp *bgp, afi_t afi, safi_t safi, ++ struct bgp_dest *dest, struct bgp_path_info *path); ++ +#endif /* _FRR_BGP_LS_H */ diff --git a/bgpd/bgp_ls_nlri.c b/bgpd/bgp_ls_nlri.c new file mode 100644 -index 0000000000..db93d2fc29 +index 0000000000..c34f8ca296 --- /dev/null +++ b/bgpd/bgp_ls_nlri.c -@@ -0,0 +1,5406 @@ +@@ -0,0 +1,5537 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * BGP Link-State NLRI (RFC 9552) @@ -2948,25 +2410,25 @@ index 0000000000..db93d2fc29 + return numcmp(d1->present_tlvs, d2->present_tlvs); + + /* Compare AS Number if present */ -+ if (BGP_LS_TLV_CHECK(d1->present_tlvs, BGP_LS_NODE_DESC_AS_BIT)) { ++ if (CHECK_FLAG(d1->present_tlvs, BGP_LS_NODE_DESC_AS_BIT)) { + if (d1->asn != d2->asn) + return numcmp(d1->asn, d2->asn); + } + + /* Compare BGP-LS ID if present */ -+ if (BGP_LS_TLV_CHECK(d1->present_tlvs, BGP_LS_NODE_DESC_BGP_LS_ID_BIT)) { ++ if (CHECK_FLAG(d1->present_tlvs, BGP_LS_NODE_DESC_BGP_LS_ID_BIT)) { + if (d1->bgp_ls_id != d2->bgp_ls_id) + return numcmp(d1->bgp_ls_id, d2->bgp_ls_id); + } + + /* Compare OSPF Area ID if present */ -+ if (BGP_LS_TLV_CHECK(d1->present_tlvs, BGP_LS_NODE_DESC_OSPF_AREA_BIT)) { ++ if (CHECK_FLAG(d1->present_tlvs, BGP_LS_NODE_DESC_OSPF_AREA_BIT)) { + if (d1->ospf_area_id != d2->ospf_area_id) + return numcmp(d1->ospf_area_id, d2->ospf_area_id); + } + + /* Compare IGP Router ID if present */ -+ if (BGP_LS_TLV_CHECK(d1->present_tlvs, BGP_LS_NODE_DESC_IGP_ROUTER_BIT)) { ++ if (CHECK_FLAG(d1->present_tlvs, BGP_LS_NODE_DESC_IGP_ROUTER_BIT)) { + if (d1->igp_router_id_len != d2->igp_router_id_len) + return numcmp(d1->igp_router_id_len, d2->igp_router_id_len); + ret = memcmp(d1->igp_router_id, d2->igp_router_id, d1->igp_router_id_len); @@ -2974,6 +2436,13 @@ index 0000000000..db93d2fc29 + return ret; + } + ++ /* Compare BGP Router ID if present */ ++ if (CHECK_FLAG(d1->present_tlvs, BGP_LS_NODE_DESC_BGP_ROUTER_ID_BIT)) { ++ ret = IPV4_ADDR_CMP(&d1->bgp_router_id, &d2->bgp_router_id); ++ if (ret != 0) ++ return ret; ++ } ++ + return 0; +} + @@ -2997,7 +2466,7 @@ index 0000000000..db93d2fc29 + return numcmp(d1->present_tlvs, d2->present_tlvs); + + /* Compare Link IDs if present */ -+ if (BGP_LS_TLV_CHECK(d1->present_tlvs, BGP_LS_LINK_DESC_LINK_ID_BIT)) { ++ if (CHECK_FLAG(d1->present_tlvs, BGP_LS_LINK_DESC_LINK_ID_BIT)) { + if (d1->link_local_id != d2->link_local_id) + return numcmp(d1->link_local_id, d2->link_local_id); + if (d1->link_remote_id != d2->link_remote_id) @@ -3005,35 +2474,35 @@ index 0000000000..db93d2fc29 + } + + /* Compare IPv4 Interface Address if present */ -+ if (BGP_LS_TLV_CHECK(d1->present_tlvs, BGP_LS_LINK_DESC_IPV4_INTF_BIT)) { ++ if (CHECK_FLAG(d1->present_tlvs, BGP_LS_LINK_DESC_IPV4_INTF_BIT)) { + ret = IPV4_ADDR_CMP(&d1->ipv4_intf_addr, &d2->ipv4_intf_addr); + if (ret != 0) + return ret; + } + + /* Compare IPv4 Neighbor Address if present */ -+ if (BGP_LS_TLV_CHECK(d1->present_tlvs, BGP_LS_LINK_DESC_IPV4_NEIGH_BIT)) { ++ if (CHECK_FLAG(d1->present_tlvs, BGP_LS_LINK_DESC_IPV4_NEIGH_BIT)) { + ret = IPV4_ADDR_CMP(&d1->ipv4_neigh_addr, &d2->ipv4_neigh_addr); + if (ret != 0) + return ret; + } + + /* Compare IPv6 Interface Address if present */ -+ if (BGP_LS_TLV_CHECK(d1->present_tlvs, BGP_LS_LINK_DESC_IPV6_INTF_BIT)) { ++ if (CHECK_FLAG(d1->present_tlvs, BGP_LS_LINK_DESC_IPV6_INTF_BIT)) { + ret = IPV6_ADDR_CMP(&d1->ipv6_intf_addr, &d2->ipv6_intf_addr); + if (ret != 0) + return ret; + } + + /* Compare IPv6 Neighbor Address if present */ -+ if (BGP_LS_TLV_CHECK(d1->present_tlvs, BGP_LS_LINK_DESC_IPV6_NEIGH_BIT)) { ++ if (CHECK_FLAG(d1->present_tlvs, BGP_LS_LINK_DESC_IPV6_NEIGH_BIT)) { + ret = IPV6_ADDR_CMP(&d1->ipv6_neigh_addr, &d2->ipv6_neigh_addr); + if (ret != 0) + return ret; + } + + /* Compare Multi-Topology IDs if present */ -+ if (BGP_LS_TLV_CHECK(d1->present_tlvs, BGP_LS_LINK_DESC_MT_ID_BIT)) { ++ if (CHECK_FLAG(d1->present_tlvs, BGP_LS_LINK_DESC_MT_ID_BIT)) { + if (d1->mt_id_count != d2->mt_id_count) + return numcmp(d1->mt_id_count, d2->mt_id_count); + for (int i = 0; i < d1->mt_id_count; i++) { @@ -3070,13 +2539,13 @@ index 0000000000..db93d2fc29 + return ret; + + /* Compare OSPF Route Type if present */ -+ if (BGP_LS_TLV_CHECK(d1->present_tlvs, BGP_LS_PREFIX_DESC_OSPF_ROUTE_BIT)) { ++ if (CHECK_FLAG(d1->present_tlvs, BGP_LS_PREFIX_DESC_OSPF_ROUTE_BIT)) { + if (d1->ospf_route_type != d2->ospf_route_type) + return numcmp(d1->ospf_route_type, d2->ospf_route_type); + } + + /* Compare Multi-Topology IDs if present */ -+ if (BGP_LS_TLV_CHECK(d1->present_tlvs, BGP_LS_PREFIX_DESC_MT_ID_BIT)) { ++ if (CHECK_FLAG(d1->present_tlvs, BGP_LS_PREFIX_DESC_MT_ID_BIT)) { + if (d1->mt_id_count != d2->mt_id_count) + return numcmp(d1->mt_id_count, d2->mt_id_count); + for (int i = 0; i < d1->mt_id_count; i++) { @@ -3085,6 +2554,12 @@ index 0000000000..db93d2fc29 + } + } + ++ /* Compare BGP Route Type if present */ ++ if (CHECK_FLAG(d1->present_tlvs, BGP_LS_PREFIX_DESC_BGP_ROUTE_TYPE_BIT)) { ++ if (d1->bgp_route_type != d2->bgp_route_type) ++ return numcmp(d1->bgp_route_type, d2->bgp_route_type); ++ } ++ + return 0; +} + @@ -3191,18 +2666,18 @@ index 0000000000..db93d2fc29 + if (attr1->present_tlvs != attr2->present_tlvs) + return numcmp(attr1->present_tlvs, attr2->present_tlvs); + -+ if (BGP_LS_TLV_CHECK(attr1->present_tlvs, BGP_LS_ATTR_NODE_FLAGS_BIT)) { ++ if (CHECK_FLAG(attr1->present_tlvs, BGP_LS_ATTR_NODE_FLAGS_BIT)) { + if (attr1->node_flags != attr2->node_flags) + return numcmp(attr1->node_flags, attr2->node_flags); + } + -+ if (BGP_LS_TLV_CHECK(attr1->present_tlvs, BGP_LS_ATTR_NODE_NAME_BIT)) { ++ if (CHECK_FLAG(attr1->present_tlvs, BGP_LS_ATTR_NODE_NAME_BIT)) { + ret = strcmp(attr1->node_name, attr2->node_name); + if (ret != 0) + return ret; + } + -+ if (BGP_LS_TLV_CHECK(attr1->present_tlvs, BGP_LS_ATTR_ISIS_AREA_BIT)) { ++ if (CHECK_FLAG(attr1->present_tlvs, BGP_LS_ATTR_ISIS_AREA_BIT)) { + if (attr1->isis_area_id_len != attr2->isis_area_id_len) + return numcmp(attr1->isis_area_id_len, attr2->isis_area_id_len); + ret = memcmp(attr1->isis_area_id, attr2->isis_area_id, attr1->isis_area_id_len); @@ -3210,7 +2685,7 @@ index 0000000000..db93d2fc29 + return ret; + } + -+ if (BGP_LS_TLV_CHECK(attr1->present_tlvs, BGP_LS_ATTR_SR_CAPABILITIES_BIT)) { ++ if (CHECK_FLAG(attr1->present_tlvs, BGP_LS_ATTR_SR_CAPABILITIES_BIT)) { + if (attr1->srgb.flag != attr2->srgb.flag) + return numcmp(attr1->srgb.flag, attr2->srgb.flag); + if (attr1->srgb.lower_bound != attr2->srgb.lower_bound) @@ -3219,55 +2694,55 @@ index 0000000000..db93d2fc29 + return numcmp(attr1->srgb.range_size, attr2->srgb.range_size); + } + -+ if (BGP_LS_TLV_CHECK(attr1->present_tlvs, BGP_LS_ATTR_NODE_MSD_BIT)) { ++ if (CHECK_FLAG(attr1->present_tlvs, BGP_LS_ATTR_NODE_MSD_BIT)) { + if (attr1->msd != attr2->msd) + return numcmp(attr1->msd, attr2->msd); + } + -+ if (BGP_LS_TLV_CHECK(attr1->present_tlvs, BGP_LS_ATTR_IPV4_ROUTER_ID_LOCAL_BIT)) { ++ if (CHECK_FLAG(attr1->present_tlvs, BGP_LS_ATTR_IPV4_ROUTER_ID_LOCAL_BIT)) { + ret = IPV4_ADDR_CMP(&attr1->ipv4_router_id_local, &attr2->ipv4_router_id_local); + if (ret != 0) + return ret; + } + -+ if (BGP_LS_TLV_CHECK(attr1->present_tlvs, BGP_LS_ATTR_IPV6_ROUTER_ID_LOCAL_BIT)) { ++ if (CHECK_FLAG(attr1->present_tlvs, BGP_LS_ATTR_IPV6_ROUTER_ID_LOCAL_BIT)) { + ret = IPV6_ADDR_CMP(&attr1->ipv6_router_id_local, &attr2->ipv6_router_id_local); + if (ret != 0) + return ret; + } + -+ if (BGP_LS_TLV_CHECK(attr1->present_tlvs, BGP_LS_ATTR_IPV4_ROUTER_ID_REMOTE_BIT)) { ++ if (CHECK_FLAG(attr1->present_tlvs, BGP_LS_ATTR_IPV4_ROUTER_ID_REMOTE_BIT)) { + ret = IPV4_ADDR_CMP(&attr1->ipv4_router_id_remote, &attr2->ipv4_router_id_remote); + if (ret != 0) + return ret; + } + -+ if (BGP_LS_TLV_CHECK(attr1->present_tlvs, BGP_LS_ATTR_IPV6_ROUTER_ID_REMOTE_BIT)) { ++ if (CHECK_FLAG(attr1->present_tlvs, BGP_LS_ATTR_IPV6_ROUTER_ID_REMOTE_BIT)) { + ret = IPV6_ADDR_CMP(&attr1->ipv6_router_id_remote, &attr2->ipv6_router_id_remote); + if (ret != 0) + return ret; + } + -+ if (BGP_LS_TLV_CHECK(attr1->present_tlvs, BGP_LS_ATTR_ADMIN_GROUP_BIT)) { ++ if (CHECK_FLAG(attr1->present_tlvs, BGP_LS_ATTR_ADMIN_GROUP_BIT)) { + if (attr1->admin_group != attr2->admin_group) + return numcmp(attr1->admin_group, attr2->admin_group); + } + -+ if (BGP_LS_TLV_CHECK(attr1->present_tlvs, BGP_LS_ATTR_MAX_LINK_BW_BIT)) { ++ if (CHECK_FLAG(attr1->present_tlvs, BGP_LS_ATTR_MAX_LINK_BW_BIT)) { + if (attr1->max_link_bw < attr2->max_link_bw) + return -1; + if (attr1->max_link_bw > attr2->max_link_bw) + return 1; + } + -+ if (BGP_LS_TLV_CHECK(attr1->present_tlvs, BGP_LS_ATTR_MAX_RESV_BW_BIT)) { ++ if (CHECK_FLAG(attr1->present_tlvs, BGP_LS_ATTR_MAX_RESV_BW_BIT)) { + if (attr1->max_resv_bw < attr2->max_resv_bw) + return -1; + if (attr1->max_resv_bw > attr2->max_resv_bw) + return 1; + } + -+ if (BGP_LS_TLV_CHECK(attr1->present_tlvs, BGP_LS_ATTR_UNRESV_BW_BIT)) { ++ if (CHECK_FLAG(attr1->present_tlvs, BGP_LS_ATTR_UNRESV_BW_BIT)) { + for (int i = 0; i < BGP_LS_MAX_UNRESV_BW; i++) { + if (attr1->unreserved_bw[i] < attr2->unreserved_bw[i]) + return -1; @@ -3276,29 +2751,29 @@ index 0000000000..db93d2fc29 + } + } + -+ if (BGP_LS_TLV_CHECK(attr1->present_tlvs, BGP_LS_ATTR_TE_METRIC_BIT)) { ++ if (CHECK_FLAG(attr1->present_tlvs, BGP_LS_ATTR_TE_METRIC_BIT)) { + if (attr1->te_metric != attr2->te_metric) + return numcmp(attr1->te_metric, attr2->te_metric); + } + -+ if (BGP_LS_TLV_CHECK(attr1->present_tlvs, BGP_LS_ATTR_LINK_PROTECTION_BIT)) { ++ if (CHECK_FLAG(attr1->present_tlvs, BGP_LS_ATTR_LINK_PROTECTION_BIT)) { + if (attr1->link_protection != attr2->link_protection) + return numcmp(attr1->link_protection, attr2->link_protection); + } + -+ if (BGP_LS_TLV_CHECK(attr1->present_tlvs, BGP_LS_ATTR_MPLS_PROTOCOL_BIT)) { ++ if (CHECK_FLAG(attr1->present_tlvs, BGP_LS_ATTR_MPLS_PROTOCOL_BIT)) { + if (attr1->mpls_protocol_mask != attr2->mpls_protocol_mask) + return numcmp(attr1->mpls_protocol_mask, attr2->mpls_protocol_mask); + } + -+ if (BGP_LS_TLV_CHECK(attr1->present_tlvs, BGP_LS_ATTR_IGP_METRIC_BIT)) { ++ if (CHECK_FLAG(attr1->present_tlvs, BGP_LS_ATTR_IGP_METRIC_BIT)) { + if (attr1->igp_metric_len != attr2->igp_metric_len) + return numcmp(attr1->igp_metric_len, attr2->igp_metric_len); + if (attr1->igp_metric != attr2->igp_metric) + return numcmp(attr1->igp_metric, attr2->igp_metric); + } + -+ if (BGP_LS_TLV_CHECK(attr1->present_tlvs, BGP_LS_ATTR_SRLG_BIT)) { ++ if (CHECK_FLAG(attr1->present_tlvs, BGP_LS_ATTR_SRLG_BIT)) { + if (attr1->srlg_count != attr2->srlg_count) + return numcmp(attr1->srlg_count, attr2->srlg_count); + for (int i = 0; i < attr1->srlg_count; i++) { @@ -3307,13 +2782,13 @@ index 0000000000..db93d2fc29 + } + } + -+ if (BGP_LS_TLV_CHECK(attr1->present_tlvs, BGP_LS_ATTR_LINK_NAME_BIT)) { ++ if (CHECK_FLAG(attr1->present_tlvs, BGP_LS_ATTR_LINK_NAME_BIT)) { + ret = strcmp(attr1->link_name, attr2->link_name); + if (ret != 0) + return ret; + } + -+ if (BGP_LS_TLV_CHECK(attr1->present_tlvs, BGP_LS_ATTR_ADJ_SID_BIT)) { ++ if (CHECK_FLAG(attr1->present_tlvs, BGP_LS_ATTR_ADJ_SID_BIT)) { + if (attr1->adj_sid_count != attr2->adj_sid_count) + return numcmp(attr1->adj_sid_count, attr2->adj_sid_count); + for (int i = 0; i < attr1->adj_sid_count; i++) { @@ -3326,12 +2801,12 @@ index 0000000000..db93d2fc29 + } + } + -+ if (BGP_LS_TLV_CHECK(attr1->present_tlvs, BGP_LS_ATTR_IGP_FLAGS_BIT)) { ++ if (CHECK_FLAG(attr1->present_tlvs, BGP_LS_ATTR_IGP_FLAGS_BIT)) { + if (attr1->igp_flags != attr2->igp_flags) + return numcmp(attr1->igp_flags, attr2->igp_flags); + } + -+ if (BGP_LS_TLV_CHECK(attr1->present_tlvs, BGP_LS_ATTR_ROUTE_TAG_BIT)) { ++ if (CHECK_FLAG(attr1->present_tlvs, BGP_LS_ATTR_ROUTE_TAG_BIT)) { + if (attr1->route_tag_count != attr2->route_tag_count) + return numcmp(attr1->route_tag_count, attr2->route_tag_count); + for (int i = 0; i < attr1->route_tag_count; i++) { @@ -3340,7 +2815,7 @@ index 0000000000..db93d2fc29 + } + } + -+ if (BGP_LS_TLV_CHECK(attr1->present_tlvs, BGP_LS_ATTR_EXTENDED_TAG_BIT)) { ++ if (CHECK_FLAG(attr1->present_tlvs, BGP_LS_ATTR_EXTENDED_TAG_BIT)) { + if (attr1->extended_tag_count != attr2->extended_tag_count) + return numcmp(attr1->extended_tag_count, attr2->extended_tag_count); + for (int i = 0; i < attr1->extended_tag_count; i++) { @@ -3349,12 +2824,12 @@ index 0000000000..db93d2fc29 + } + } + -+ if (BGP_LS_TLV_CHECK(attr1->present_tlvs, BGP_LS_ATTR_PREFIX_METRIC_BIT)) { ++ if (CHECK_FLAG(attr1->present_tlvs, BGP_LS_ATTR_PREFIX_METRIC_BIT)) { + if (attr1->prefix_metric != attr2->prefix_metric) + return numcmp(attr1->prefix_metric, attr2->prefix_metric); + } + -+ if (BGP_LS_TLV_CHECK(attr1->present_tlvs, BGP_LS_ATTR_OSPF_FWD_ADDR_BIT)) { ++ if (CHECK_FLAG(attr1->present_tlvs, BGP_LS_ATTR_OSPF_FWD_ADDR_BIT)) { + ret = IPV4_ADDR_CMP(&attr1->ospf_fwd_addr, &attr2->ospf_fwd_addr); + if (ret != 0) + return ret; @@ -3363,7 +2838,7 @@ index 0000000000..db93d2fc29 + return ret; + } + -+ if (BGP_LS_TLV_CHECK(attr1->present_tlvs, BGP_LS_ATTR_PREFIX_SID_BIT)) { ++ if (CHECK_FLAG(attr1->present_tlvs, BGP_LS_ATTR_PREFIX_SID_BIT)) { + if (attr1->prefix_sid.sid != attr2->prefix_sid.sid) + return numcmp(attr1->prefix_sid.sid, attr2->prefix_sid.sid); + if (attr1->prefix_sid.sid_flag != attr2->prefix_sid.sid_flag) @@ -3637,10 +3112,12 @@ index 0000000000..db93d2fc29 + return false; + + /* IGP Router ID is mandatory (RFC 9552 Section 5.2.1.4) */ -+ if (!BGP_LS_TLV_CHECK(n->local_node.present_tlvs, BGP_LS_NODE_DESC_IGP_ROUTER_BIT)) ++ if (!CHECK_FLAG(n->local_node.present_tlvs, BGP_LS_NODE_DESC_IGP_ROUTER_BIT) && ++ !CHECK_FLAG(n->local_node.present_tlvs, BGP_LS_NODE_DESC_BGP_ROUTER_ID_BIT)) + return false; + -+ if (!bgp_ls_igp_router_id_len_valid(n->protocol_id, ++ if (CHECK_FLAG(n->local_node.present_tlvs, BGP_LS_NODE_DESC_IGP_ROUTER_BIT) && ++ !bgp_ls_igp_router_id_len_valid(n->protocol_id, + n->local_node.igp_router_id_len)) + return false; + @@ -3654,17 +3131,21 @@ index 0000000000..db93d2fc29 + return false; + + /* Local node IGP Router ID is mandatory */ -+ if (!BGP_LS_TLV_CHECK(l->local_node.present_tlvs, BGP_LS_NODE_DESC_IGP_ROUTER_BIT)) ++ if (!CHECK_FLAG(l->local_node.present_tlvs, BGP_LS_NODE_DESC_IGP_ROUTER_BIT) && ++ !CHECK_FLAG(l->local_node.present_tlvs, BGP_LS_NODE_DESC_BGP_ROUTER_ID_BIT)) + return false; + + /* Remote node IGP Router ID is mandatory */ -+ if (!BGP_LS_TLV_CHECK(l->remote_node.present_tlvs, BGP_LS_NODE_DESC_IGP_ROUTER_BIT)) ++ if (!CHECK_FLAG(l->remote_node.present_tlvs, BGP_LS_NODE_DESC_IGP_ROUTER_BIT) && ++ !CHECK_FLAG(l->remote_node.present_tlvs, BGP_LS_NODE_DESC_BGP_ROUTER_ID_BIT)) + return false; + -+ if (!bgp_ls_igp_router_id_len_valid(l->protocol_id, -+ l->local_node.igp_router_id_len) || -+ !bgp_ls_igp_router_id_len_valid(l->protocol_id, -+ l->remote_node.igp_router_id_len)) ++ if ((CHECK_FLAG(l->local_node.present_tlvs, BGP_LS_NODE_DESC_IGP_ROUTER_BIT) && ++ !bgp_ls_igp_router_id_len_valid(l->protocol_id, ++ l->local_node.igp_router_id_len)) || ++ (CHECK_FLAG(l->remote_node.present_tlvs, BGP_LS_NODE_DESC_IGP_ROUTER_BIT) && ++ !bgp_ls_igp_router_id_len_valid(l->protocol_id, ++ l->remote_node.igp_router_id_len))) + return false; + + return true; @@ -3677,10 +3158,12 @@ index 0000000000..db93d2fc29 + return false; + + /* Local node IGP Router ID is mandatory */ -+ if (!BGP_LS_TLV_CHECK(p->local_node.present_tlvs, BGP_LS_NODE_DESC_IGP_ROUTER_BIT)) ++ if (!CHECK_FLAG(p->local_node.present_tlvs, BGP_LS_NODE_DESC_IGP_ROUTER_BIT) && ++ !CHECK_FLAG(p->local_node.present_tlvs, BGP_LS_NODE_DESC_BGP_ROUTER_ID_BIT)) + return false; + -+ if (!bgp_ls_igp_router_id_len_valid(p->protocol_id, ++ if (CHECK_FLAG(p->local_node.present_tlvs, BGP_LS_NODE_DESC_IGP_ROUTER_BIT) && ++ !bgp_ls_igp_router_id_len_valid(p->protocol_id, + p->local_node.igp_router_id_len)) + return false; + @@ -3698,10 +3181,12 @@ index 0000000000..db93d2fc29 + return false; + + /* Local node IGP Router ID is mandatory */ -+ if (!BGP_LS_TLV_CHECK(p->local_node.present_tlvs, BGP_LS_NODE_DESC_IGP_ROUTER_BIT)) ++ if (!CHECK_FLAG(p->local_node.present_tlvs, BGP_LS_NODE_DESC_IGP_ROUTER_BIT) && ++ !CHECK_FLAG(p->local_node.present_tlvs, BGP_LS_NODE_DESC_BGP_ROUTER_ID_BIT)) + return false; + -+ if (!bgp_ls_igp_router_id_len_valid(p->protocol_id, ++ if (CHECK_FLAG(p->local_node.present_tlvs, BGP_LS_NODE_DESC_IGP_ROUTER_BIT) && ++ !bgp_ls_igp_router_id_len_valid(p->protocol_id, + p->local_node.igp_router_id_len)) + return false; + @@ -3732,17 +3217,20 @@ index 0000000000..db93d2fc29 + size_t size = BGP_LS_TLV_HDR_SIZE; /* Descriptor TLV header */ + + /* AS Number TLV */ -+ if (BGP_LS_TLV_CHECK(desc->present_tlvs, BGP_LS_NODE_DESC_AS_BIT)) ++ if (CHECK_FLAG(desc->present_tlvs, BGP_LS_NODE_DESC_AS_BIT)) + size += BGP_LS_TLV_HDR_SIZE + BGP_LS_AS_NUMBER_SIZE; + /* BGP-LS ID TLV */ -+ if (BGP_LS_TLV_CHECK(desc->present_tlvs, BGP_LS_NODE_DESC_BGP_LS_ID_BIT)) ++ if (CHECK_FLAG(desc->present_tlvs, BGP_LS_NODE_DESC_BGP_LS_ID_BIT)) + size += BGP_LS_TLV_HDR_SIZE + BGP_LS_BGP_LS_ID_SIZE; + /* OSPF Area ID TLV */ -+ if (BGP_LS_TLV_CHECK(desc->present_tlvs, BGP_LS_NODE_DESC_OSPF_AREA_BIT)) ++ if (CHECK_FLAG(desc->present_tlvs, BGP_LS_NODE_DESC_OSPF_AREA_BIT)) + size += BGP_LS_TLV_HDR_SIZE + BGP_LS_OSPF_AREA_ID_SIZE; + /* IGP Router ID TLV */ -+ if (BGP_LS_TLV_CHECK(desc->present_tlvs, BGP_LS_NODE_DESC_IGP_ROUTER_BIT)) ++ if (CHECK_FLAG(desc->present_tlvs, BGP_LS_NODE_DESC_IGP_ROUTER_BIT)) + size += BGP_LS_TLV_HDR_SIZE + desc->igp_router_id_len; ++ /* BGP Router ID TLV */ ++ if (CHECK_FLAG(desc->present_tlvs, BGP_LS_NODE_DESC_BGP_ROUTER_ID_BIT)) ++ size += BGP_LS_TLV_HDR_SIZE + BGP_LS_BGP_ROUTER_ID_SIZE; + + return size; +} @@ -3778,17 +3266,17 @@ index 0000000000..db93d2fc29 + size += bgp_ls_node_descriptor_size(&l->remote_node); + + /* Link Descriptor */ -+ if (BGP_LS_TLV_CHECK(l->link_desc.present_tlvs, BGP_LS_LINK_DESC_LINK_ID_BIT)) ++ if (CHECK_FLAG(l->link_desc.present_tlvs, BGP_LS_LINK_DESC_LINK_ID_BIT)) + size += BGP_LS_TLV_HDR_SIZE + BGP_LS_LINK_ID_SIZE; -+ if (BGP_LS_TLV_CHECK(l->link_desc.present_tlvs, BGP_LS_LINK_DESC_IPV4_INTF_BIT)) ++ if (CHECK_FLAG(l->link_desc.present_tlvs, BGP_LS_LINK_DESC_IPV4_INTF_BIT)) + size += BGP_LS_TLV_HDR_SIZE + BGP_LS_IPV4_ADDR_SIZE; -+ if (BGP_LS_TLV_CHECK(l->link_desc.present_tlvs, BGP_LS_LINK_DESC_IPV4_NEIGH_BIT)) ++ if (CHECK_FLAG(l->link_desc.present_tlvs, BGP_LS_LINK_DESC_IPV4_NEIGH_BIT)) + size += BGP_LS_TLV_HDR_SIZE + BGP_LS_IPV4_ADDR_SIZE; -+ if (BGP_LS_TLV_CHECK(l->link_desc.present_tlvs, BGP_LS_LINK_DESC_IPV6_INTF_BIT)) ++ if (CHECK_FLAG(l->link_desc.present_tlvs, BGP_LS_LINK_DESC_IPV6_INTF_BIT)) + size += BGP_LS_TLV_HDR_SIZE + BGP_LS_IPV6_ADDR_SIZE; -+ if (BGP_LS_TLV_CHECK(l->link_desc.present_tlvs, BGP_LS_LINK_DESC_IPV6_NEIGH_BIT)) ++ if (CHECK_FLAG(l->link_desc.present_tlvs, BGP_LS_LINK_DESC_IPV6_NEIGH_BIT)) + size += BGP_LS_TLV_HDR_SIZE + BGP_LS_IPV6_ADDR_SIZE; -+ if (BGP_LS_TLV_CHECK(l->link_desc.present_tlvs, BGP_LS_LINK_DESC_MT_ID_BIT)) ++ if (CHECK_FLAG(l->link_desc.present_tlvs, BGP_LS_LINK_DESC_MT_ID_BIT)) + size += BGP_LS_TLV_HDR_SIZE + + (l->link_desc.mt_id_count * BGP_LS_MT_ID_SIZE); + @@ -3803,11 +3291,10 @@ index 0000000000..db93d2fc29 + size += bgp_ls_node_descriptor_size(&p->local_node); + + /* Prefix Descriptor */ -+ if (BGP_LS_TLV_CHECK(p->prefix_desc.present_tlvs, BGP_LS_PREFIX_DESC_MT_ID_BIT)) ++ if (CHECK_FLAG(p->prefix_desc.present_tlvs, BGP_LS_PREFIX_DESC_MT_ID_BIT)) + size += BGP_LS_TLV_HDR_SIZE + + (p->prefix_desc.mt_id_count * BGP_LS_MT_ID_SIZE); -+ if (BGP_LS_TLV_CHECK(p->prefix_desc.present_tlvs, -+ BGP_LS_PREFIX_DESC_OSPF_ROUTE_BIT)) ++ if (CHECK_FLAG(p->prefix_desc.present_tlvs, BGP_LS_PREFIX_DESC_OSPF_ROUTE_BIT)) + size += BGP_LS_TLV_HDR_SIZE + BGP_LS_OSPF_ROUTE_TYPE_SIZE; + + /* IP Reachability Info TLV */ @@ -3818,6 +3305,10 @@ index 0000000000..db93d2fc29 + size += BGP_LS_TLV_HDR_SIZE + BGP_LS_PREFIX_LEN_SIZE + + BGP_LS_IPV6_ADDR_SIZE; + ++ /* BGP Route Type TLV */ ++ if (CHECK_FLAG(p->prefix_desc.present_tlvs, BGP_LS_PREFIX_DESC_BGP_ROUTE_TYPE_BIT)) ++ size += BGP_LS_TLV_HDR_SIZE + BGP_LS_BGP_ROUTE_TYPE_SIZE; ++ + break; + } + @@ -3908,6 +3399,8 @@ index 0000000000..db93d2fc29 + return "OSPF Area-ID"; + case BGP_LS_TLV_IGP_ROUTER_ID: + return "IGP Router-ID"; ++ case BGP_LS_TLV_BGP_ROUTER_ID: ++ return "BGP Router-ID"; + } + + return "Unknown"; @@ -3942,6 +3435,8 @@ index 0000000000..db93d2fc29 + return "OSPF Route Type"; + case BGP_LS_TLV_IP_REACH_INFO: + return "IP Reachability Information"; ++ case BGP_LS_TLV_BGP_ROUTE_TYPE: ++ return "BGP Route Type"; + } + + return "Unknown"; @@ -3967,6 +3462,24 @@ index 0000000000..db93d2fc29 + return "Unknown"; +} + ++const char *bgp_ls_bgp_route_type_str(enum bgp_ls_bgp_route_type route_type) ++{ ++ switch (route_type) { ++ case BGP_LS_BGP_RT_LOCAL: ++ return "Local"; ++ case BGP_LS_BGP_RT_ATTACHED: ++ return "Attached"; ++ case BGP_LS_BGP_RT_EXTERNAL_BGP: ++ return "External BGP"; ++ case BGP_LS_BGP_RT_INTERNAL_BGP: ++ return "Internal BGP"; ++ case BGP_LS_BGP_RT_REDISTRIBUTED: ++ return "Redistributed"; ++ } ++ ++ return "Unknown"; ++} ++ +const char *bgp_ls_ospf_route_type_str_json(enum bgp_ls_ospf_route_type route_type) +{ + switch (route_type) { @@ -3987,6 +3500,24 @@ index 0000000000..db93d2fc29 + return "Unknown"; +} + ++const char *bgp_ls_bgp_route_type_str_json(enum bgp_ls_bgp_route_type route_type) ++{ ++ switch (route_type) { ++ case BGP_LS_BGP_RT_LOCAL: ++ return "local"; ++ case BGP_LS_BGP_RT_ATTACHED: ++ return "attached"; ++ case BGP_LS_BGP_RT_EXTERNAL_BGP: ++ return "externalBgp"; ++ case BGP_LS_BGP_RT_INTERNAL_BGP: ++ return "internalBgp"; ++ case BGP_LS_BGP_RT_REDISTRIBUTED: ++ return "redistributed"; ++ } ++ ++ return "unknown"; ++} ++ +/* + * =========================================================================== + * Hash Key Functions @@ -3997,18 +3528,21 @@ index 0000000000..db93d2fc29 +static unsigned int bgp_ls_node_descriptor_hash(const struct bgp_ls_node_descriptor *desc, + uint32_t key) +{ -+ if (BGP_LS_TLV_CHECK(desc->present_tlvs, BGP_LS_NODE_DESC_AS_BIT)) ++ if (CHECK_FLAG(desc->present_tlvs, BGP_LS_NODE_DESC_AS_BIT)) + key = jhash_1word(desc->asn, key); + -+ if (BGP_LS_TLV_CHECK(desc->present_tlvs, BGP_LS_NODE_DESC_BGP_LS_ID_BIT)) ++ if (CHECK_FLAG(desc->present_tlvs, BGP_LS_NODE_DESC_BGP_LS_ID_BIT)) + key = jhash_1word(desc->bgp_ls_id, key); + -+ if (BGP_LS_TLV_CHECK(desc->present_tlvs, BGP_LS_NODE_DESC_OSPF_AREA_BIT)) ++ if (CHECK_FLAG(desc->present_tlvs, BGP_LS_NODE_DESC_OSPF_AREA_BIT)) + key = jhash_1word(desc->ospf_area_id, key); + -+ if (BGP_LS_TLV_CHECK(desc->present_tlvs, BGP_LS_NODE_DESC_IGP_ROUTER_BIT)) ++ if (CHECK_FLAG(desc->present_tlvs, BGP_LS_NODE_DESC_IGP_ROUTER_BIT)) + key = jhash(desc->igp_router_id, desc->igp_router_id_len, key); + ++ if (CHECK_FLAG(desc->present_tlvs, BGP_LS_NODE_DESC_BGP_ROUTER_ID_BIT)) ++ key = jhash_1word(desc->bgp_router_id.s_addr, key); ++ + return key; +} + @@ -4043,20 +3577,20 @@ index 0000000000..db93d2fc29 + key = bgp_ls_node_descriptor_hash(&link->remote_node, key); + + /* Hash link descriptor */ -+ if (BGP_LS_TLV_CHECK(link->link_desc.present_tlvs, BGP_LS_LINK_DESC_LINK_ID_BIT)) ++ if (CHECK_FLAG(link->link_desc.present_tlvs, BGP_LS_LINK_DESC_LINK_ID_BIT)) + key = jhash_2words(link->link_desc.link_local_id, link->link_desc.link_remote_id, + key); + -+ if (BGP_LS_TLV_CHECK(link->link_desc.present_tlvs, BGP_LS_LINK_DESC_IPV4_INTF_BIT)) ++ if (CHECK_FLAG(link->link_desc.present_tlvs, BGP_LS_LINK_DESC_IPV4_INTF_BIT)) + key = jhash_1word(link->link_desc.ipv4_intf_addr.s_addr, key); + -+ if (BGP_LS_TLV_CHECK(link->link_desc.present_tlvs, BGP_LS_LINK_DESC_IPV4_NEIGH_BIT)) ++ if (CHECK_FLAG(link->link_desc.present_tlvs, BGP_LS_LINK_DESC_IPV4_NEIGH_BIT)) + key = jhash_1word(link->link_desc.ipv4_neigh_addr.s_addr, key); + -+ if (BGP_LS_TLV_CHECK(link->link_desc.present_tlvs, BGP_LS_LINK_DESC_IPV6_INTF_BIT)) ++ if (CHECK_FLAG(link->link_desc.present_tlvs, BGP_LS_LINK_DESC_IPV6_INTF_BIT)) + key = jhash(&link->link_desc.ipv6_intf_addr, sizeof(struct in6_addr), key); + -+ if (BGP_LS_TLV_CHECK(link->link_desc.present_tlvs, BGP_LS_LINK_DESC_IPV6_NEIGH_BIT)) ++ if (CHECK_FLAG(link->link_desc.present_tlvs, BGP_LS_LINK_DESC_IPV6_NEIGH_BIT)) + key = jhash(&link->link_desc.ipv6_neigh_addr, sizeof(struct in6_addr), key); + + return key; @@ -4084,9 +3618,13 @@ index 0000000000..db93d2fc29 + key = jhash(&prefix->prefix_desc.prefix.u.prefix6, sizeof(struct in6_addr), key); + + /* Hash OSPF route type if present */ -+ if (BGP_LS_TLV_CHECK(prefix->prefix_desc.present_tlvs, BGP_LS_PREFIX_DESC_OSPF_ROUTE_BIT)) ++ if (CHECK_FLAG(prefix->prefix_desc.present_tlvs, BGP_LS_PREFIX_DESC_OSPF_ROUTE_BIT)) + key = jhash_1word(prefix->prefix_desc.ospf_route_type, key); + ++ /* Hash BGP route type if present */ ++ if (CHECK_FLAG(prefix->prefix_desc.present_tlvs, BGP_LS_PREFIX_DESC_BGP_ROUTE_TYPE_BIT)) ++ key = jhash_1word(prefix->prefix_desc.bgp_route_type, key); ++ + return key; +} + @@ -4126,77 +3664,77 @@ index 0000000000..db93d2fc29 + key = jhash_1word((uint32_t)(attr->present_tlvs >> 32), key); + key = jhash_1word((uint32_t)attr->present_tlvs, key); + -+ if (BGP_LS_TLV_CHECK(attr->present_tlvs, BGP_LS_ATTR_NODE_FLAGS_BIT)) ++ if (CHECK_FLAG(attr->present_tlvs, BGP_LS_ATTR_NODE_FLAGS_BIT)) + key = jhash_1word(attr->node_flags, key); + -+ if (BGP_LS_TLV_CHECK(attr->present_tlvs, BGP_LS_ATTR_NODE_NAME_BIT)) ++ if (CHECK_FLAG(attr->present_tlvs, BGP_LS_ATTR_NODE_NAME_BIT)) + key = jhash(attr->node_name, strlen(attr->node_name), key); + -+ if (BGP_LS_TLV_CHECK(attr->present_tlvs, BGP_LS_ATTR_ISIS_AREA_BIT)) ++ if (CHECK_FLAG(attr->present_tlvs, BGP_LS_ATTR_ISIS_AREA_BIT)) + key = jhash(attr->isis_area_id, attr->isis_area_id_len, key); + -+ if (BGP_LS_TLV_CHECK(attr->present_tlvs, BGP_LS_ATTR_SR_CAPABILITIES_BIT)) { ++ if (CHECK_FLAG(attr->present_tlvs, BGP_LS_ATTR_SR_CAPABILITIES_BIT)) { + key = jhash(&attr->srgb.flag, sizeof(attr->srgb.flag), key); + key = jhash(&attr->srgb.lower_bound, sizeof(attr->srgb.lower_bound), key); + key = jhash(&attr->srgb.range_size, sizeof(attr->srgb.range_size), key); + } + -+ if (BGP_LS_TLV_CHECK(attr->present_tlvs, BGP_LS_ATTR_NODE_MSD_BIT)) ++ if (CHECK_FLAG(attr->present_tlvs, BGP_LS_ATTR_NODE_MSD_BIT)) + key = jhash(&attr->msd, sizeof(attr->msd), key); + -+ if (BGP_LS_TLV_CHECK(attr->present_tlvs, BGP_LS_ATTR_IPV4_ROUTER_ID_LOCAL_BIT)) ++ if (CHECK_FLAG(attr->present_tlvs, BGP_LS_ATTR_IPV4_ROUTER_ID_LOCAL_BIT)) + key = jhash_1word(attr->ipv4_router_id_local.s_addr, key); + -+ if (BGP_LS_TLV_CHECK(attr->present_tlvs, BGP_LS_ATTR_IPV6_ROUTER_ID_LOCAL_BIT)) ++ if (CHECK_FLAG(attr->present_tlvs, BGP_LS_ATTR_IPV6_ROUTER_ID_LOCAL_BIT)) + key = jhash(&attr->ipv6_router_id_local, sizeof(struct in6_addr), key); + -+ if (BGP_LS_TLV_CHECK(attr->present_tlvs, BGP_LS_ATTR_IPV4_ROUTER_ID_REMOTE_BIT)) ++ if (CHECK_FLAG(attr->present_tlvs, BGP_LS_ATTR_IPV4_ROUTER_ID_REMOTE_BIT)) + key = jhash_1word(attr->ipv4_router_id_remote.s_addr, key); + -+ if (BGP_LS_TLV_CHECK(attr->present_tlvs, BGP_LS_ATTR_IPV6_ROUTER_ID_REMOTE_BIT)) ++ if (CHECK_FLAG(attr->present_tlvs, BGP_LS_ATTR_IPV6_ROUTER_ID_REMOTE_BIT)) + key = jhash(&attr->ipv6_router_id_remote, sizeof(struct in6_addr), key); + -+ if (BGP_LS_TLV_CHECK(attr->present_tlvs, BGP_LS_ATTR_ADMIN_GROUP_BIT)) ++ if (CHECK_FLAG(attr->present_tlvs, BGP_LS_ATTR_ADMIN_GROUP_BIT)) + key = jhash_1word(attr->admin_group, key); + -+ if (BGP_LS_TLV_CHECK(attr->present_tlvs, BGP_LS_ATTR_MAX_LINK_BW_BIT)) { ++ if (CHECK_FLAG(attr->present_tlvs, BGP_LS_ATTR_MAX_LINK_BW_BIT)) { + uint32_t bw; + + memcpy(&bw, &attr->max_link_bw, sizeof(uint32_t)); + key = jhash_1word(bw, key); + } + -+ if (BGP_LS_TLV_CHECK(attr->present_tlvs, BGP_LS_ATTR_MAX_RESV_BW_BIT)) { ++ if (CHECK_FLAG(attr->present_tlvs, BGP_LS_ATTR_MAX_RESV_BW_BIT)) { + uint32_t bw; + + memcpy(&bw, &attr->max_resv_bw, sizeof(uint32_t)); + key = jhash_1word(bw, key); + } + -+ if (BGP_LS_TLV_CHECK(attr->present_tlvs, BGP_LS_ATTR_UNRESV_BW_BIT)) ++ if (CHECK_FLAG(attr->present_tlvs, BGP_LS_ATTR_UNRESV_BW_BIT)) + key = jhash(attr->unreserved_bw, sizeof(attr->unreserved_bw), key); + -+ if (BGP_LS_TLV_CHECK(attr->present_tlvs, BGP_LS_ATTR_TE_METRIC_BIT)) ++ if (CHECK_FLAG(attr->present_tlvs, BGP_LS_ATTR_TE_METRIC_BIT)) + key = jhash_1word(attr->te_metric, key); + -+ if (BGP_LS_TLV_CHECK(attr->present_tlvs, BGP_LS_ATTR_LINK_PROTECTION_BIT)) ++ if (CHECK_FLAG(attr->present_tlvs, BGP_LS_ATTR_LINK_PROTECTION_BIT)) + key = jhash_1word(attr->link_protection, key); + -+ if (BGP_LS_TLV_CHECK(attr->present_tlvs, BGP_LS_ATTR_MPLS_PROTOCOL_BIT)) ++ if (CHECK_FLAG(attr->present_tlvs, BGP_LS_ATTR_MPLS_PROTOCOL_BIT)) + key = jhash_1word(attr->mpls_protocol_mask, key); + -+ if (BGP_LS_TLV_CHECK(attr->present_tlvs, BGP_LS_ATTR_IGP_METRIC_BIT)) ++ if (CHECK_FLAG(attr->present_tlvs, BGP_LS_ATTR_IGP_METRIC_BIT)) + key = jhash_1word(attr->igp_metric, key); + -+ if (BGP_LS_TLV_CHECK(attr->present_tlvs, BGP_LS_ATTR_SRLG_BIT)) { ++ if (CHECK_FLAG(attr->present_tlvs, BGP_LS_ATTR_SRLG_BIT)) { + for (int i = 0; i < attr->srlg_count; i++) + key = jhash_1word(attr->srlg_values[i], key); + } + -+ if (BGP_LS_TLV_CHECK(attr->present_tlvs, BGP_LS_ATTR_LINK_NAME_BIT)) ++ if (CHECK_FLAG(attr->present_tlvs, BGP_LS_ATTR_LINK_NAME_BIT)) + key = jhash(attr->link_name, strlen(attr->link_name), key); + -+ if (BGP_LS_TLV_CHECK(attr->present_tlvs, BGP_LS_ATTR_ADJ_SID_BIT)) { ++ if (CHECK_FLAG(attr->present_tlvs, BGP_LS_ATTR_ADJ_SID_BIT)) { + key = jhash(&attr->adj_sid_count, sizeof(attr->adj_sid_count), key); + for (int i = 0; i < attr->adj_sid_count; i++) { + key = jhash(&attr->adj_sid[i].sid, sizeof(attr->adj_sid[i].sid), key); @@ -4205,30 +3743,30 @@ index 0000000000..db93d2fc29 + } + } + -+ if (BGP_LS_TLV_CHECK(attr->present_tlvs, BGP_LS_ATTR_IGP_FLAGS_BIT)) ++ if (CHECK_FLAG(attr->present_tlvs, BGP_LS_ATTR_IGP_FLAGS_BIT)) + key = jhash_1word(attr->igp_flags, key); + -+ if (BGP_LS_TLV_CHECK(attr->present_tlvs, BGP_LS_ATTR_ROUTE_TAG_BIT)) { ++ if (CHECK_FLAG(attr->present_tlvs, BGP_LS_ATTR_ROUTE_TAG_BIT)) { + for (int i = 0; i < attr->route_tag_count; i++) + key = jhash_1word(attr->route_tags[i], key); + } + -+ if (BGP_LS_TLV_CHECK(attr->present_tlvs, BGP_LS_ATTR_EXTENDED_TAG_BIT)) { ++ if (CHECK_FLAG(attr->present_tlvs, BGP_LS_ATTR_EXTENDED_TAG_BIT)) { + for (int i = 0; i < attr->extended_tag_count; i++) { + key = jhash_1word((uint32_t)(attr->extended_tags[i] >> 32), key); + key = jhash_1word((uint32_t)attr->extended_tags[i], key); + } + } + -+ if (BGP_LS_TLV_CHECK(attr->present_tlvs, BGP_LS_ATTR_PREFIX_METRIC_BIT)) ++ if (CHECK_FLAG(attr->present_tlvs, BGP_LS_ATTR_PREFIX_METRIC_BIT)) + key = jhash_1word(attr->prefix_metric, key); + -+ if (BGP_LS_TLV_CHECK(attr->present_tlvs, BGP_LS_ATTR_OSPF_FWD_ADDR_BIT)) { ++ if (CHECK_FLAG(attr->present_tlvs, BGP_LS_ATTR_OSPF_FWD_ADDR_BIT)) { + key = jhash_1word(attr->ospf_fwd_addr.s_addr, key); + key = jhash(&attr->ospf_fwd_addr6, sizeof(struct in6_addr), key); + } + -+ if (BGP_LS_TLV_CHECK(attr->present_tlvs, BGP_LS_ATTR_PREFIX_SID_BIT)) ++ if (CHECK_FLAG(attr->present_tlvs, BGP_LS_ATTR_PREFIX_SID_BIT)) + key = jhash(&attr->prefix_sid, sizeof(attr->prefix_sid), key); + + if (attr->opaque_len > 0) @@ -4433,60 +3971,6 @@ index 0000000000..db93d2fc29 + */ + +/* -+ * Write TLV header (Type + Length) to stream -+ * -+ * Wire format (RFC 9552 Section 3.1): -+ * 0 1 2 3 -+ * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 -+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ -+ * | Type | Length | -+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ -+ * -+ * Returns number of bytes written, or -1 on error -+ */ -+static inline int stream_put_tlv_hdr(struct stream *s, uint16_t type, uint16_t length) -+{ -+ if (STREAM_WRITEABLE(s) < BGP_LS_TLV_HDR_SIZE) -+ return -1; -+ -+ stream_putw(s, type); -+ stream_putw(s, length); -+ return BGP_LS_TLV_HDR_SIZE; -+} -+ -+/* -+ * Put a complete TLV (Type + Length + Value) into stream -+ * Returns 0 on success, -1 on error -+ */ -+static inline int stream_put_tlv(struct stream *s, uint16_t type, uint16_t length, -+ const void *value) -+{ -+ if (stream_put_tlv_hdr(s, type, length) < 0) -+ return -1; -+ -+ if (length > 0) { -+ if (STREAM_WRITEABLE(s) < length) -+ return -1; -+ stream_put(s, value, length); -+ } -+ -+ return 0; -+} -+ -+static inline int stream_putf_tlv(struct stream *s, uint16_t type, const float value) -+{ -+ if (stream_put_tlv_hdr(s, type, 4) < 0) -+ return -1; -+ -+ if (STREAM_WRITEABLE(s) < 4) -+ return -1; -+ -+ stream_putf(s, value); -+ -+ return 0; -+} -+ -+/* + * Encode Node Descriptor to wire format (RFC 9552 Section 5.2.1) + * + * Wire format: @@ -4511,13 +3995,13 @@ index 0000000000..db93d2fc29 +int bgp_ls_encode_node_descriptor(struct stream *s, const struct bgp_ls_node_descriptor *desc, + uint16_t tlv_type) +{ -+ size_t len_pos, sub_tlv_start; -+ int written = 0; -+ int ret; ++ size_t start, len_pos, sub_tlv_start; + + if (!s || !desc) + return -1; + ++ start = stream_get_endp(s); ++ + if (STREAM_WRITEABLE(s) < BGP_LS_TLV_HDR_SIZE) + return -1; + @@ -4525,68 +4009,59 @@ index 0000000000..db93d2fc29 + stream_putw(s, tlv_type); + len_pos = stream_get_endp(s); + stream_putw(s, 0); /* Placeholder for length */ -+ written += BGP_LS_TLV_HDR_SIZE; + + sub_tlv_start = stream_get_endp(s); + + /* AS Number (TLV 512) */ -+ if (BGP_LS_TLV_CHECK(desc->present_tlvs, BGP_LS_NODE_DESC_AS_BIT)) { -+ ret = stream_put_tlv_hdr(s, BGP_LS_TLV_AS_NUMBER, BGP_LS_AS_NUMBER_SIZE); -+ if (ret < 0) -+ return -1; -+ written += ret; -+ -+ if (STREAM_WRITEABLE(s) < BGP_LS_AS_NUMBER_SIZE) ++ if (CHECK_FLAG(desc->present_tlvs, BGP_LS_NODE_DESC_AS_BIT)) { ++ if (STREAM_WRITEABLE(s) < BGP_LS_TLV_HDR_SIZE + BGP_LS_AS_NUMBER_SIZE) + return -1; ++ stream_putw(s, BGP_LS_TLV_AS_NUMBER); ++ stream_putw(s, BGP_LS_AS_NUMBER_SIZE); + stream_putl(s, desc->asn); -+ written += BGP_LS_AS_NUMBER_SIZE; + } + + /* BGP-LS Identifier (TLV 513) - deprecated but may be present */ -+ if (BGP_LS_TLV_CHECK(desc->present_tlvs, BGP_LS_NODE_DESC_BGP_LS_ID_BIT)) { -+ ret = stream_put_tlv_hdr(s, BGP_LS_TLV_BGP_LS_ID, BGP_LS_BGP_LS_ID_SIZE); -+ if (ret < 0) -+ return -1; -+ written += ret; -+ -+ if (STREAM_WRITEABLE(s) < BGP_LS_BGP_LS_ID_SIZE) ++ if (CHECK_FLAG(desc->present_tlvs, BGP_LS_NODE_DESC_BGP_LS_ID_BIT)) { ++ if (STREAM_WRITEABLE(s) < BGP_LS_TLV_HDR_SIZE + BGP_LS_BGP_LS_ID_SIZE) + return -1; ++ stream_putw(s, BGP_LS_TLV_BGP_LS_ID); ++ stream_putw(s, BGP_LS_BGP_LS_ID_SIZE); + stream_putl(s, desc->bgp_ls_id); -+ written += BGP_LS_BGP_LS_ID_SIZE; + } + + /* OSPF Area ID (TLV 514) */ -+ if (BGP_LS_TLV_CHECK(desc->present_tlvs, BGP_LS_NODE_DESC_OSPF_AREA_BIT)) { -+ ret = stream_put_tlv_hdr(s, BGP_LS_TLV_OSPF_AREA_ID, BGP_LS_OSPF_AREA_ID_SIZE); -+ if (ret < 0) -+ return -1; -+ written += ret; -+ -+ if (STREAM_WRITEABLE(s) < BGP_LS_OSPF_AREA_ID_SIZE) ++ if (CHECK_FLAG(desc->present_tlvs, BGP_LS_NODE_DESC_OSPF_AREA_BIT)) { ++ if (STREAM_WRITEABLE(s) < BGP_LS_TLV_HDR_SIZE + BGP_LS_OSPF_AREA_ID_SIZE) + return -1; ++ stream_putw(s, BGP_LS_TLV_OSPF_AREA_ID); ++ stream_putw(s, BGP_LS_OSPF_AREA_ID_SIZE); + stream_putl(s, desc->ospf_area_id); -+ written += BGP_LS_OSPF_AREA_ID_SIZE; + } + + /* IGP Router ID (TLV 515) - MANDATORY */ -+ if (BGP_LS_TLV_CHECK(desc->present_tlvs, BGP_LS_NODE_DESC_IGP_ROUTER_BIT)) { -+ ret = stream_put_tlv_hdr(s, BGP_LS_TLV_IGP_ROUTER_ID, desc->igp_router_id_len); -+ if (ret < 0) ++ if (CHECK_FLAG(desc->present_tlvs, BGP_LS_NODE_DESC_IGP_ROUTER_BIT)) { ++ if (STREAM_WRITEABLE(s) < ++ (size_t)BGP_LS_TLV_HDR_SIZE + (size_t)desc->igp_router_id_len) + return -1; -+ written += ret; ++ stream_putw(s, BGP_LS_TLV_IGP_ROUTER_ID); ++ stream_putw(s, desc->igp_router_id_len); ++ stream_put(s, desc->igp_router_id, desc->igp_router_id_len); ++ } + -+ if (STREAM_WRITEABLE(s) < desc->igp_router_id_len) ++ /* BGP Router ID (TLV 516) */ ++ if (CHECK_FLAG(desc->present_tlvs, BGP_LS_NODE_DESC_BGP_ROUTER_ID_BIT)) { ++ if (STREAM_WRITEABLE(s) < BGP_LS_TLV_HDR_SIZE + BGP_LS_BGP_ROUTER_ID_SIZE) + return -1; -+ stream_put(s, desc->igp_router_id, desc->igp_router_id_len); -+ written += desc->igp_router_id_len; ++ stream_putw(s, BGP_LS_TLV_BGP_ROUTER_ID); ++ stream_putw(s, BGP_LS_BGP_ROUTER_ID_SIZE); ++ stream_put_ipv4(s, desc->bgp_router_id.s_addr); + } + + /* Update length field */ -+ uint16_t sub_tlv_len = stream_get_endp(s) - sub_tlv_start; -+ -+ stream_putw_at(s, len_pos, sub_tlv_len); ++ stream_putw_at(s, len_pos, stream_get_endp(s) - sub_tlv_start); + -+ return written; ++ return stream_get_endp(s) - start; +} + +/* @@ -4611,109 +4086,81 @@ index 0000000000..db93d2fc29 + */ +int bgp_ls_encode_link_descriptor(struct stream *s, const struct bgp_ls_link_descriptor *desc) +{ -+ int written = 0; -+ int ret; ++ size_t start; + uint16_t i; + + if (!s || !desc) + return -1; + -+ /* Link Local/Remote Identifiers (TLV 258) */ -+ if (BGP_LS_TLV_CHECK(desc->present_tlvs, BGP_LS_LINK_DESC_LINK_ID_BIT)) { -+ ret = stream_put_tlv_hdr(s, BGP_LS_TLV_LINK_ID, BGP_LS_LINK_ID_SIZE); -+ if (ret < 0) -+ return -1; -+ written += ret; ++ start = stream_get_endp(s); + -+ if (STREAM_WRITEABLE(s) < BGP_LS_LINK_ID_SIZE) ++ /* Link Local/Remote Identifiers (TLV 258) */ ++ if (CHECK_FLAG(desc->present_tlvs, BGP_LS_LINK_DESC_LINK_ID_BIT)) { ++ if (STREAM_WRITEABLE(s) < BGP_LS_TLV_HDR_SIZE + BGP_LS_LINK_ID_SIZE) + return -1; ++ stream_putw(s, BGP_LS_TLV_LINK_ID); ++ stream_putw(s, BGP_LS_LINK_ID_SIZE); + stream_putl(s, desc->link_local_id); + stream_putl(s, desc->link_remote_id); -+ written += BGP_LS_LINK_ID_SIZE; + } + + /* IPv4 Interface Address (TLV 259) */ -+ if (BGP_LS_TLV_CHECK(desc->present_tlvs, BGP_LS_LINK_DESC_IPV4_INTF_BIT)) { -+ ret = stream_put_tlv_hdr(s, BGP_LS_TLV_IPV4_INTF_ADDR, BGP_LS_IPV4_ADDR_SIZE); -+ if (ret < 0) -+ return -1; -+ written += ret; -+ -+ if (STREAM_WRITEABLE(s) < BGP_LS_IPV4_ADDR_SIZE) ++ if (CHECK_FLAG(desc->present_tlvs, BGP_LS_LINK_DESC_IPV4_INTF_BIT)) { ++ if (STREAM_WRITEABLE(s) < BGP_LS_TLV_HDR_SIZE + BGP_LS_IPV4_ADDR_SIZE) + return -1; ++ stream_putw(s, BGP_LS_TLV_IPV4_INTF_ADDR); ++ stream_putw(s, BGP_LS_IPV4_ADDR_SIZE); + stream_put_ipv4(s, desc->ipv4_intf_addr.s_addr); -+ written += BGP_LS_IPV4_ADDR_SIZE; + } + + /* IPv4 Neighbor Address (TLV 260) */ -+ if (BGP_LS_TLV_CHECK(desc->present_tlvs, BGP_LS_LINK_DESC_IPV4_NEIGH_BIT)) { -+ ret = stream_put_tlv_hdr(s, BGP_LS_TLV_IPV4_NEIGH_ADDR, BGP_LS_IPV4_ADDR_SIZE); -+ if (ret < 0) -+ return -1; -+ written += ret; -+ -+ if (STREAM_WRITEABLE(s) < BGP_LS_IPV4_ADDR_SIZE) ++ if (CHECK_FLAG(desc->present_tlvs, BGP_LS_LINK_DESC_IPV4_NEIGH_BIT)) { ++ if (STREAM_WRITEABLE(s) < BGP_LS_TLV_HDR_SIZE + BGP_LS_IPV4_ADDR_SIZE) + return -1; ++ stream_putw(s, BGP_LS_TLV_IPV4_NEIGH_ADDR); ++ stream_putw(s, BGP_LS_IPV4_ADDR_SIZE); + stream_put_ipv4(s, desc->ipv4_neigh_addr.s_addr); -+ written += BGP_LS_IPV4_ADDR_SIZE; + } + + /* IPv6 Interface Address (TLV 261) */ -+ if (BGP_LS_TLV_CHECK(desc->present_tlvs, BGP_LS_LINK_DESC_IPV6_INTF_BIT)) { -+ ret = stream_put_tlv_hdr(s, BGP_LS_TLV_IPV6_INTF_ADDR, BGP_LS_IPV6_ADDR_SIZE); -+ if (ret < 0) -+ return -1; -+ written += ret; -+ -+ if (STREAM_WRITEABLE(s) < BGP_LS_IPV6_ADDR_SIZE) ++ if (CHECK_FLAG(desc->present_tlvs, BGP_LS_LINK_DESC_IPV6_INTF_BIT)) { ++ if (STREAM_WRITEABLE(s) < BGP_LS_TLV_HDR_SIZE + BGP_LS_IPV6_ADDR_SIZE) + return -1; ++ stream_putw(s, BGP_LS_TLV_IPV6_INTF_ADDR); ++ stream_putw(s, BGP_LS_IPV6_ADDR_SIZE); + stream_put(s, &desc->ipv6_intf_addr, BGP_LS_IPV6_ADDR_SIZE); -+ written += BGP_LS_IPV6_ADDR_SIZE; + } + + /* IPv6 Neighbor Address (TLV 262) */ -+ if (BGP_LS_TLV_CHECK(desc->present_tlvs, BGP_LS_LINK_DESC_IPV6_NEIGH_BIT)) { -+ ret = stream_put_tlv_hdr(s, BGP_LS_TLV_IPV6_NEIGH_ADDR, BGP_LS_IPV6_ADDR_SIZE); -+ if (ret < 0) -+ return -1; -+ written += ret; -+ -+ if (STREAM_WRITEABLE(s) < BGP_LS_IPV6_ADDR_SIZE) ++ if (CHECK_FLAG(desc->present_tlvs, BGP_LS_LINK_DESC_IPV6_NEIGH_BIT)) { ++ if (STREAM_WRITEABLE(s) < BGP_LS_TLV_HDR_SIZE + BGP_LS_IPV6_ADDR_SIZE) + return -1; ++ stream_putw(s, BGP_LS_TLV_IPV6_NEIGH_ADDR); ++ stream_putw(s, BGP_LS_IPV6_ADDR_SIZE); + stream_put(s, &desc->ipv6_neigh_addr, BGP_LS_IPV6_ADDR_SIZE); -+ written += BGP_LS_IPV6_ADDR_SIZE; + } + + /* Multi-Topology ID (TLV 263) */ -+ if (BGP_LS_TLV_CHECK(desc->present_tlvs, BGP_LS_LINK_DESC_MT_ID_BIT) && -+ desc->mt_id_count > 0) { -+ ret = stream_put_tlv_hdr(s, BGP_LS_TLV_MT_ID, -+ desc->mt_id_count * BGP_LS_MT_ID_SIZE); -+ if (ret < 0) -+ return -1; -+ written += ret; -+ -+ if (STREAM_WRITEABLE(s) < (size_t)desc->mt_id_count * BGP_LS_MT_ID_SIZE) ++ if (CHECK_FLAG(desc->present_tlvs, BGP_LS_LINK_DESC_MT_ID_BIT) && desc->mt_id_count > 0) { ++ if (STREAM_WRITEABLE(s) < ++ BGP_LS_TLV_HDR_SIZE + (size_t)desc->mt_id_count * BGP_LS_MT_ID_SIZE) + return -1; ++ stream_putw(s, BGP_LS_TLV_MT_ID); ++ stream_putw(s, desc->mt_id_count * BGP_LS_MT_ID_SIZE); + for (i = 0; i < desc->mt_id_count; i++) + stream_putw(s, desc->mt_id[i]); -+ written += desc->mt_id_count * BGP_LS_MT_ID_SIZE; + } + + /* Remote AS Number (TLV 264) */ -+ if (BGP_LS_TLV_CHECK(desc->present_tlvs, BGP_LS_LINK_DESC_REMOTE_AS_BIT)) { -+ ret = stream_put_tlv_hdr(s, BGP_LS_TLV_REMOTE_AS_NUMBER, 4); -+ if (ret < 0) -+ return -1; -+ written += ret; -+ -+ if (STREAM_WRITEABLE(s) < 4) ++ if (CHECK_FLAG(desc->present_tlvs, BGP_LS_LINK_DESC_REMOTE_AS_BIT)) { ++ if (STREAM_WRITEABLE(s) < BGP_LS_TLV_HDR_SIZE + 4) + return -1; ++ stream_putw(s, BGP_LS_TLV_REMOTE_AS_NUMBER); ++ stream_putw(s, 4); + stream_putl(s, desc->remote_asn); -+ written += 4; + } + -+ return written; ++ return stream_get_endp(s) - start; +} + +/* @@ -4735,74 +4182,66 @@ index 0000000000..db93d2fc29 + */ +int bgp_ls_encode_prefix_descriptor(struct stream *s, const struct bgp_ls_prefix_descriptor *desc) +{ -+ int written = 0; -+ int ret; ++ size_t start; + uint16_t i; + uint8_t prefix_len_bytes; + + if (!s || !desc) + return -1; + -+ /* Multi-Topology ID (TLV 263) */ -+ if (BGP_LS_TLV_CHECK(desc->present_tlvs, BGP_LS_PREFIX_DESC_MT_ID_BIT) && -+ desc->mt_id_count > 0) { -+ ret = stream_put_tlv_hdr(s, BGP_LS_TLV_MT_ID, -+ desc->mt_id_count * BGP_LS_MT_ID_SIZE); -+ if (ret < 0) -+ return -1; -+ written += ret; ++ start = stream_get_endp(s); + -+ if (STREAM_WRITEABLE(s) < (size_t)desc->mt_id_count * BGP_LS_MT_ID_SIZE) ++ /* Multi-Topology ID (TLV 263) */ ++ if (CHECK_FLAG(desc->present_tlvs, BGP_LS_PREFIX_DESC_MT_ID_BIT) && desc->mt_id_count > 0) { ++ if (STREAM_WRITEABLE(s) < ++ BGP_LS_TLV_HDR_SIZE + (size_t)desc->mt_id_count * BGP_LS_MT_ID_SIZE) + return -1; ++ stream_putw(s, BGP_LS_TLV_MT_ID); ++ stream_putw(s, desc->mt_id_count * BGP_LS_MT_ID_SIZE); + for (i = 0; i < desc->mt_id_count; i++) + stream_putw(s, desc->mt_id[i]); -+ written += desc->mt_id_count * BGP_LS_MT_ID_SIZE; + } + + /* OSPF Route Type (TLV 264) */ -+ if (BGP_LS_TLV_CHECK(desc->present_tlvs, BGP_LS_PREFIX_DESC_OSPF_ROUTE_BIT)) { -+ ret = stream_put_tlv_hdr(s, BGP_LS_TLV_OSPF_ROUTE_TYPE, -+ BGP_LS_OSPF_ROUTE_TYPE_SIZE); -+ if (ret < 0) -+ return -1; -+ written += ret; -+ -+ if (STREAM_WRITEABLE(s) < BGP_LS_OSPF_ROUTE_TYPE_SIZE) ++ if (CHECK_FLAG(desc->present_tlvs, BGP_LS_PREFIX_DESC_OSPF_ROUTE_BIT)) { ++ if (STREAM_WRITEABLE(s) < BGP_LS_TLV_HDR_SIZE + BGP_LS_OSPF_ROUTE_TYPE_SIZE) + return -1; ++ stream_putw(s, BGP_LS_TLV_OSPF_ROUTE_TYPE); ++ stream_putw(s, BGP_LS_OSPF_ROUTE_TYPE_SIZE); + stream_putc(s, desc->ospf_route_type); -+ written += BGP_LS_OSPF_ROUTE_TYPE_SIZE; + } + + /* IP Reachability Information (TLV 265) - MANDATORY */ + if (desc->prefix.family == AF_INET) { + prefix_len_bytes = (desc->prefix.prefixlen + 7) / 8; -+ ret = stream_put_tlv_hdr(s, BGP_LS_TLV_IP_REACH_INFO, -+ BGP_LS_PREFIX_LEN_SIZE + prefix_len_bytes); -+ if (ret < 0) -+ return -1; -+ written += ret; -+ -+ if (STREAM_WRITEABLE(s) < (size_t)BGP_LS_PREFIX_LEN_SIZE + prefix_len_bytes) ++ if (STREAM_WRITEABLE(s) < ++ BGP_LS_TLV_HDR_SIZE + (size_t)BGP_LS_PREFIX_LEN_SIZE + prefix_len_bytes) + return -1; ++ stream_putw(s, BGP_LS_TLV_IP_REACH_INFO); ++ stream_putw(s, BGP_LS_PREFIX_LEN_SIZE + prefix_len_bytes); + stream_putc(s, desc->prefix.prefixlen); + stream_put(s, &desc->prefix.u.prefix4, prefix_len_bytes); -+ written += BGP_LS_PREFIX_LEN_SIZE + prefix_len_bytes; + } else if (desc->prefix.family == AF_INET6) { + prefix_len_bytes = (desc->prefix.prefixlen + 7) / 8; -+ ret = stream_put_tlv_hdr(s, BGP_LS_TLV_IP_REACH_INFO, -+ BGP_LS_PREFIX_LEN_SIZE + prefix_len_bytes); -+ if (ret < 0) -+ return -1; -+ written += ret; -+ -+ if (STREAM_WRITEABLE(s) < (size_t)BGP_LS_PREFIX_LEN_SIZE + prefix_len_bytes) ++ if (STREAM_WRITEABLE(s) < ++ BGP_LS_TLV_HDR_SIZE + (size_t)BGP_LS_PREFIX_LEN_SIZE + prefix_len_bytes) + return -1; ++ stream_putw(s, BGP_LS_TLV_IP_REACH_INFO); ++ stream_putw(s, BGP_LS_PREFIX_LEN_SIZE + prefix_len_bytes); + stream_putc(s, desc->prefix.prefixlen); + stream_put(s, &desc->prefix.u.prefix6, prefix_len_bytes); -+ written += BGP_LS_PREFIX_LEN_SIZE + prefix_len_bytes; + } + -+ return written; ++ /* BGP Route Type */ ++ if (CHECK_FLAG(desc->present_tlvs, BGP_LS_PREFIX_DESC_BGP_ROUTE_TYPE_BIT)) { ++ if (STREAM_WRITEABLE(s) < BGP_LS_TLV_HDR_SIZE + BGP_LS_BGP_ROUTE_TYPE_SIZE) ++ return -1; ++ stream_putw(s, BGP_LS_TLV_BGP_ROUTE_TYPE); ++ stream_putw(s, BGP_LS_BGP_ROUTE_TYPE_SIZE); ++ stream_putc(s, desc->bgp_route_type); ++ } ++ ++ return stream_get_endp(s) - start; +} + +/* @@ -4824,31 +4263,28 @@ index 0000000000..db93d2fc29 + */ +int bgp_ls_encode_node_nlri(struct stream *s, const struct bgp_ls_node_nlri *nlri) +{ -+ int written = 0; -+ int ret; ++ size_t start; + + if (!s || !nlri) + return -1; + ++ start = stream_get_endp(s); ++ + /* Protocol-ID (1 byte) */ + if (STREAM_WRITEABLE(s) < BGP_LS_PROTOCOL_ID_SIZE) + return -1; + stream_putc(s, nlri->protocol_id); -+ written += BGP_LS_PROTOCOL_ID_SIZE; + + /* Identifier (8 bytes) */ + if (STREAM_WRITEABLE(s) < BGP_LS_IDENTIFIER_SIZE) + return -1; + stream_putq(s, nlri->identifier); -+ written += BGP_LS_IDENTIFIER_SIZE; + + /* Local Node Descriptors */ -+ ret = bgp_ls_encode_node_descriptor(s, &nlri->local_node, BGP_LS_TLV_LOCAL_NODE_DESC); -+ if (ret < 0) ++ if (bgp_ls_encode_node_descriptor(s, &nlri->local_node, BGP_LS_TLV_LOCAL_NODE_DESC) < 0) + return -1; -+ written += ret; + -+ return written; ++ return stream_get_endp(s) - start; +} + +/* @@ -4874,43 +4310,36 @@ index 0000000000..db93d2fc29 + */ +int bgp_ls_encode_link_nlri(struct stream *s, const struct bgp_ls_link_nlri *nlri) +{ -+ int written = 0; -+ int ret; ++ size_t start; + + if (!s || !nlri) + return -1; + ++ start = stream_get_endp(s); ++ + /* Protocol-ID (1 byte) */ + if (STREAM_WRITEABLE(s) < BGP_LS_PROTOCOL_ID_SIZE) + return -1; + stream_putc(s, nlri->protocol_id); -+ written += BGP_LS_PROTOCOL_ID_SIZE; + + /* Identifier (8 bytes) */ + if (STREAM_WRITEABLE(s) < BGP_LS_IDENTIFIER_SIZE) + return -1; + stream_putq(s, nlri->identifier); -+ written += BGP_LS_IDENTIFIER_SIZE; + + /* Local Node Descriptors */ -+ ret = bgp_ls_encode_node_descriptor(s, &nlri->local_node, BGP_LS_TLV_LOCAL_NODE_DESC); -+ if (ret < 0) ++ if (bgp_ls_encode_node_descriptor(s, &nlri->local_node, BGP_LS_TLV_LOCAL_NODE_DESC) < 0) + return -1; -+ written += ret; + + /* Remote Node Descriptors */ -+ ret = bgp_ls_encode_node_descriptor(s, &nlri->remote_node, BGP_LS_TLV_REMOTE_NODE_DESC); -+ if (ret < 0) ++ if (bgp_ls_encode_node_descriptor(s, &nlri->remote_node, BGP_LS_TLV_REMOTE_NODE_DESC) < 0) + return -1; -+ written += ret; + + /* Link Descriptors */ -+ ret = bgp_ls_encode_link_descriptor(s, &nlri->link_desc); -+ if (ret < 0) ++ if (bgp_ls_encode_link_descriptor(s, &nlri->link_desc) < 0) + return -1; -+ written += ret; + -+ return written; ++ return stream_get_endp(s) - start; +} + +/* @@ -4939,8 +4368,7 @@ index 0000000000..db93d2fc29 +int bgp_ls_encode_prefix_nlri(struct stream *s, const struct bgp_ls_prefix_nlri *nlri, + enum bgp_ls_nlri_type nlri_type) +{ -+ int written = 0; -+ int ret; ++ size_t start; + + if (!s || !nlri) + return -1; @@ -4952,31 +4380,27 @@ index 0000000000..db93d2fc29 + nlri->prefix_desc.prefix.family != AF_INET6)) + return -1; + ++ start = stream_get_endp(s); ++ + /* Protocol-ID (1 byte) */ + if (STREAM_WRITEABLE(s) < BGP_LS_PROTOCOL_ID_SIZE) + return -1; + stream_putc(s, nlri->protocol_id); -+ written += BGP_LS_PROTOCOL_ID_SIZE; + + /* Identifier (8 bytes) */ + if (STREAM_WRITEABLE(s) < BGP_LS_IDENTIFIER_SIZE) + return -1; + stream_putq(s, nlri->identifier); -+ written += BGP_LS_IDENTIFIER_SIZE; + + /* Local Node Descriptors */ -+ ret = bgp_ls_encode_node_descriptor(s, &nlri->local_node, BGP_LS_TLV_LOCAL_NODE_DESC); -+ if (ret < 0) ++ if (bgp_ls_encode_node_descriptor(s, &nlri->local_node, BGP_LS_TLV_LOCAL_NODE_DESC) < 0) + return -1; -+ written += ret; + + /* Prefix Descriptors */ -+ ret = bgp_ls_encode_prefix_descriptor(s, &nlri->prefix_desc); -+ if (ret < 0) ++ if (bgp_ls_encode_prefix_descriptor(s, &nlri->prefix_desc) < 0) + return -1; -+ written += ret; + -+ return written; ++ return stream_get_endp(s) - start; +} + +/* @@ -5004,8 +4428,7 @@ index 0000000000..db93d2fc29 + */ +int bgp_ls_encode_nlri(struct stream *s, const struct bgp_ls_nlri *nlri) +{ -+ size_t len_pos, value_start; -+ int written = 0; ++ size_t start, len_pos, value_start; + int ret = 0; + + if (!s || !nlri) @@ -5015,18 +4438,18 @@ index 0000000000..db93d2fc29 + if (!bgp_ls_nlri_validate(nlri)) + return -1; + ++ start = stream_get_endp(s); ++ + /* NLRI Type (2 bytes) */ + if (STREAM_WRITEABLE(s) < BGP_LS_NLRI_TYPE_SIZE) + return -1; + stream_putw(s, nlri->nlri_type); -+ written += BGP_LS_NLRI_TYPE_SIZE; + + /* Reserve space for NLRI Length */ + if (STREAM_WRITEABLE(s) < BGP_LS_NLRI_LENGTH_SIZE) + return -1; + len_pos = stream_get_endp(s); + stream_putw(s, 0); /* Placeholder */ -+ written += BGP_LS_NLRI_LENGTH_SIZE; + + value_start = stream_get_endp(s); + @@ -5052,14 +4475,10 @@ index 0000000000..db93d2fc29 + if (ret < 0) + return -1; + -+ written += ret; -+ + /* Update NLRI Length field */ -+ uint16_t nlri_len = stream_get_endp(s) - value_start; -+ -+ stream_putw_at(s, len_pos, nlri_len); ++ stream_putw_at(s, len_pos, stream_get_endp(s) - value_start); + -+ return written; ++ return stream_get_endp(s) - start; +} + +/* @@ -5079,137 +4498,172 @@ index 0000000000..db93d2fc29 + start_pos = stream_get_endp(s); + + /* Node Flag Bits (TLV 1024) */ -+ if (BGP_LS_TLV_CHECK(attr->present_tlvs, BGP_LS_ATTR_NODE_FLAGS_BIT)) { -+ if (stream_put_tlv(s, BGP_LS_ATTR_NODE_FLAG_BITS, 1, &attr->node_flags) < 0) ++ if (CHECK_FLAG(attr->present_tlvs, BGP_LS_ATTR_NODE_FLAGS_BIT)) { ++ if (STREAM_WRITEABLE(s) < BGP_LS_TLV_HDR_SIZE + 1) + return -1; ++ stream_putw(s, BGP_LS_ATTR_NODE_FLAG_BITS); ++ stream_putw(s, 1); ++ stream_putc(s, attr->node_flags); + } + + /* Node Name (TLV 1026) */ -+ if (BGP_LS_TLV_CHECK(attr->present_tlvs, BGP_LS_ATTR_NODE_NAME_BIT)) { ++ if (CHECK_FLAG(attr->present_tlvs, BGP_LS_ATTR_NODE_NAME_BIT)) { + if (attr->node_name) { + uint16_t len = strlen(attr->node_name); + -+ if (stream_put_tlv(s, BGP_LS_ATTR_NODE_NAME, len, attr->node_name) < 0) ++ if (STREAM_WRITEABLE(s) < (size_t)BGP_LS_TLV_HDR_SIZE + (size_t)len) + return -1; ++ stream_putw(s, BGP_LS_ATTR_NODE_NAME); ++ stream_putw(s, len); ++ stream_put(s, attr->node_name, len); + } + } + + /* IS-IS Area Identifier (TLV 1027) */ -+ if (BGP_LS_TLV_CHECK(attr->present_tlvs, BGP_LS_ATTR_ISIS_AREA_BIT)) { ++ if (CHECK_FLAG(attr->present_tlvs, BGP_LS_ATTR_ISIS_AREA_BIT)) { + if (attr->isis_area_id && attr->isis_area_id_len > 0) { -+ if (stream_put_tlv(s, BGP_LS_ATTR_ISIS_AREA_ID, attr->isis_area_id_len, -+ attr->isis_area_id) < 0) ++ if (STREAM_WRITEABLE(s) < ++ (size_t)BGP_LS_TLV_HDR_SIZE + (size_t)attr->isis_area_id_len) + return -1; ++ stream_putw(s, BGP_LS_ATTR_ISIS_AREA_ID); ++ stream_putw(s, attr->isis_area_id_len); ++ stream_put(s, attr->isis_area_id, attr->isis_area_id_len); + } + } + + /* SR Capabilities (TLV 1034) */ -+ if (BGP_LS_TLV_CHECK(attr->present_tlvs, BGP_LS_ATTR_SR_CAPABILITIES_BIT)) { -+ if (stream_put_tlv_hdr(s, BGP_LS_ATTR_SR_CAPABILITIES, 12) < 0) -+ return -1; -+ if (STREAM_WRITEABLE(s) < 12) ++ if (CHECK_FLAG(attr->present_tlvs, BGP_LS_ATTR_SR_CAPABILITIES_BIT)) { ++ if (STREAM_WRITEABLE(s) < (size_t)BGP_LS_TLV_HDR_SIZE + 12) + return -1; ++ stream_putw(s, BGP_LS_ATTR_SR_CAPABILITIES); ++ stream_putw(s, 12); + stream_putc(s, attr->srgb.flag); + stream_putc(s, 0); + stream_put3(s, attr->srgb.range_size); -+ stream_put_tlv_hdr(s, BGP_LS_ATTR_SID_LABEL, 3); ++ /* sub-TLV */ ++ stream_putw(s, BGP_LS_ATTR_SID_LABEL); ++ stream_putw(s, 3); + stream_put3(s, attr->srgb.lower_bound); + } + + /* Node MSD (TLV 266) */ -+ if (BGP_LS_TLV_CHECK(attr->present_tlvs, BGP_LS_ATTR_NODE_MSD_BIT)) { -+ if (stream_put_tlv_hdr(s, BGP_LS_ATTR_NODE_MSD, 2) < 0) -+ return -1; -+ if (STREAM_WRITEABLE(s) < 2) ++ if (CHECK_FLAG(attr->present_tlvs, BGP_LS_ATTR_NODE_MSD_BIT)) { ++ if (STREAM_WRITEABLE(s) < (size_t)BGP_LS_TLV_HDR_SIZE + 2) + return -1; ++ stream_putw(s, BGP_LS_ATTR_NODE_MSD); ++ stream_putw(s, 2); + stream_putc(s, BGP_LS_IGP_MSD_TYPE_BASE_MPLS); + stream_putc(s, attr->msd); + } + + /* IPv4 Router-ID of Local Node (TLV 1028) */ -+ if (BGP_LS_TLV_CHECK(attr->present_tlvs, BGP_LS_ATTR_IPV4_ROUTER_ID_LOCAL_BIT)) { -+ if (stream_put_tlv(s, BGP_LS_ATTR_IPV4_ROUTER_ID_LOCAL, 4, -+ &attr->ipv4_router_id_local) < 0) ++ if (CHECK_FLAG(attr->present_tlvs, BGP_LS_ATTR_IPV4_ROUTER_ID_LOCAL_BIT)) { ++ if (STREAM_WRITEABLE(s) < BGP_LS_TLV_HDR_SIZE + 4) + return -1; ++ stream_putw(s, BGP_LS_ATTR_IPV4_ROUTER_ID_LOCAL); ++ stream_putw(s, 4); ++ stream_put(s, &attr->ipv4_router_id_local, 4); + } + + /* IPv6 Router-ID of Local Node (TLV 1029) */ -+ if (BGP_LS_TLV_CHECK(attr->present_tlvs, BGP_LS_ATTR_IPV6_ROUTER_ID_LOCAL_BIT)) { -+ if (stream_put_tlv(s, BGP_LS_ATTR_IPV6_ROUTER_ID_LOCAL, 16, -+ &attr->ipv6_router_id_local) < 0) ++ if (CHECK_FLAG(attr->present_tlvs, BGP_LS_ATTR_IPV6_ROUTER_ID_LOCAL_BIT)) { ++ if (STREAM_WRITEABLE(s) < BGP_LS_TLV_HDR_SIZE + 16) + return -1; ++ stream_putw(s, BGP_LS_ATTR_IPV6_ROUTER_ID_LOCAL); ++ stream_putw(s, 16); ++ stream_put(s, &attr->ipv6_router_id_local, 16); + } + + /* IPv4 Router-ID of Remote Node (TLV 1030) */ -+ if (BGP_LS_TLV_CHECK(attr->present_tlvs, BGP_LS_ATTR_IPV4_ROUTER_ID_REMOTE_BIT)) { -+ if (stream_put_tlv(s, BGP_LS_ATTR_IPV4_ROUTER_ID_REMOTE, 4, -+ &attr->ipv4_router_id_remote) < 0) ++ if (CHECK_FLAG(attr->present_tlvs, BGP_LS_ATTR_IPV4_ROUTER_ID_REMOTE_BIT)) { ++ if (STREAM_WRITEABLE(s) < BGP_LS_TLV_HDR_SIZE + 4) + return -1; ++ stream_putw(s, BGP_LS_ATTR_IPV4_ROUTER_ID_REMOTE); ++ stream_putw(s, 4); ++ stream_put(s, &attr->ipv4_router_id_remote, 4); + } + + /* IPv6 Router-ID of Remote Node (TLV 1031) */ -+ if (BGP_LS_TLV_CHECK(attr->present_tlvs, BGP_LS_ATTR_IPV6_ROUTER_ID_REMOTE_BIT)) { -+ if (stream_put_tlv(s, BGP_LS_ATTR_IPV6_ROUTER_ID_REMOTE, 16, -+ &attr->ipv6_router_id_remote) < 0) ++ if (CHECK_FLAG(attr->present_tlvs, BGP_LS_ATTR_IPV6_ROUTER_ID_REMOTE_BIT)) { ++ if (STREAM_WRITEABLE(s) < BGP_LS_TLV_HDR_SIZE + 16) + return -1; ++ stream_putw(s, BGP_LS_ATTR_IPV6_ROUTER_ID_REMOTE); ++ stream_putw(s, 16); ++ stream_put(s, &attr->ipv6_router_id_remote, 16); + } + + /* Administrative Group (TLV 1088) */ -+ if (BGP_LS_TLV_CHECK(attr->present_tlvs, BGP_LS_ATTR_ADMIN_GROUP_BIT)) { ++ if (CHECK_FLAG(attr->present_tlvs, BGP_LS_ATTR_ADMIN_GROUP_BIT)) { + uint32_t admin_group_be = htonl(attr->admin_group); + -+ if (stream_put_tlv(s, BGP_LS_ATTR_ADMIN_GROUP, 4, &admin_group_be) < 0) ++ if (STREAM_WRITEABLE(s) < BGP_LS_TLV_HDR_SIZE + 4) + return -1; ++ stream_putw(s, BGP_LS_ATTR_ADMIN_GROUP); ++ stream_putw(s, 4); ++ stream_put(s, &admin_group_be, 4); + } + + /* Maximum Link Bandwidth (TLV 1089) - IEEE 754 floating point */ -+ if (BGP_LS_TLV_CHECK(attr->present_tlvs, BGP_LS_ATTR_MAX_LINK_BW_BIT)) { -+ if (stream_putf_tlv(s, BGP_LS_ATTR_MAX_LINK_BW, attr->max_link_bw) < 0) ++ if (CHECK_FLAG(attr->present_tlvs, BGP_LS_ATTR_MAX_LINK_BW_BIT)) { ++ if (STREAM_WRITEABLE(s) < BGP_LS_TLV_HDR_SIZE + 4) + return -1; ++ stream_putw(s, BGP_LS_ATTR_MAX_LINK_BW); ++ stream_putw(s, 4); ++ stream_putf(s, attr->max_link_bw); + } + + /* Maximum Reservable Bandwidth (TLV 1090) */ -+ if (BGP_LS_TLV_CHECK(attr->present_tlvs, BGP_LS_ATTR_MAX_RESV_BW_BIT)) { -+ if (stream_putf_tlv(s, BGP_LS_ATTR_MAX_RESV_BW, attr->max_resv_bw) < 0) ++ if (CHECK_FLAG(attr->present_tlvs, BGP_LS_ATTR_MAX_RESV_BW_BIT)) { ++ if (STREAM_WRITEABLE(s) < BGP_LS_TLV_HDR_SIZE + 4) + return -1; ++ stream_putw(s, BGP_LS_ATTR_MAX_RESV_BW); ++ stream_putw(s, 4); ++ stream_putf(s, attr->max_resv_bw); + } + + /* Unreserved Bandwidth (TLV 1091) - 8 priority levels */ -+ if (BGP_LS_TLV_CHECK(attr->present_tlvs, BGP_LS_ATTR_UNRESV_BW_BIT)) { -+ if (stream_put_tlv_hdr(s, BGP_LS_ATTR_UNRESV_BW, 32) < 0) ++ if (CHECK_FLAG(attr->present_tlvs, BGP_LS_ATTR_UNRESV_BW_BIT)) { ++ if (STREAM_WRITEABLE(s) < BGP_LS_TLV_HDR_SIZE + 32) + return -1; -+ for (int i = 0; i < BGP_LS_MAX_UNRESV_BW; i++) { -+ if (STREAM_WRITEABLE(s) < 4) -+ return -1; ++ stream_putw(s, BGP_LS_ATTR_UNRESV_BW); ++ stream_putw(s, 32); ++ for (int i = 0; i < BGP_LS_MAX_UNRESV_BW; i++) + stream_putf(s, attr->unreserved_bw[i]); -+ } + } + + /* TE Default Metric (TLV 1092) */ -+ if (BGP_LS_TLV_CHECK(attr->present_tlvs, BGP_LS_ATTR_TE_METRIC_BIT)) { ++ if (CHECK_FLAG(attr->present_tlvs, BGP_LS_ATTR_TE_METRIC_BIT)) { + uint32_t te_metric_be = htonl(attr->te_metric); + -+ if (stream_put_tlv(s, BGP_LS_ATTR_TE_DEFAULT_METRIC, 4, &te_metric_be) < 0) ++ if (STREAM_WRITEABLE(s) < BGP_LS_TLV_HDR_SIZE + 4) + return -1; ++ stream_putw(s, BGP_LS_ATTR_TE_DEFAULT_METRIC); ++ stream_putw(s, 4); ++ stream_put(s, &te_metric_be, 4); + } + + /* Link Protection Type (TLV 1093) */ -+ if (BGP_LS_TLV_CHECK(attr->present_tlvs, BGP_LS_ATTR_LINK_PROTECTION_BIT)) { ++ if (CHECK_FLAG(attr->present_tlvs, BGP_LS_ATTR_LINK_PROTECTION_BIT)) { + uint16_t protection_be = htons(attr->link_protection); + -+ if (stream_put_tlv(s, BGP_LS_ATTR_LINK_PROTECTION_TYPE, 2, &protection_be) < 0) ++ if (STREAM_WRITEABLE(s) < BGP_LS_TLV_HDR_SIZE + 2) + return -1; ++ stream_putw(s, BGP_LS_ATTR_LINK_PROTECTION_TYPE); ++ stream_putw(s, 2); ++ stream_put(s, &protection_be, 2); + } + + /* MPLS Protocol Mask (TLV 1094) */ -+ if (BGP_LS_TLV_CHECK(attr->present_tlvs, BGP_LS_ATTR_MPLS_PROTOCOL_BIT)) { -+ if (stream_put_tlv(s, BGP_LS_ATTR_MPLS_PROTOCOL_MASK, 1, -+ &attr->mpls_protocol_mask) < 0) ++ if (CHECK_FLAG(attr->present_tlvs, BGP_LS_ATTR_MPLS_PROTOCOL_BIT)) { ++ if (STREAM_WRITEABLE(s) < BGP_LS_TLV_HDR_SIZE + 1) + return -1; ++ stream_putw(s, BGP_LS_ATTR_MPLS_PROTOCOL_MASK); ++ stream_putw(s, 1); ++ stream_putc(s, attr->mpls_protocol_mask); + } + + /* IGP Metric (TLV 1095) - Variable length 1-3 bytes */ -+ if (BGP_LS_TLV_CHECK(attr->present_tlvs, BGP_LS_ATTR_IGP_METRIC_BIT)) { ++ if (CHECK_FLAG(attr->present_tlvs, BGP_LS_ATTR_IGP_METRIC_BIT)) { + uint8_t metric_buf[3]; + uint8_t len = attr->igp_metric_len; + @@ -5225,40 +4679,46 @@ index 0000000000..db93d2fc29 + metric_buf[1] = (attr->igp_metric >> 8) & 0xFF; + metric_buf[2] = attr->igp_metric & 0xFF; + } -+ if (stream_put_tlv(s, BGP_LS_ATTR_IGP_METRIC, len, metric_buf) < 0) ++ if (STREAM_WRITEABLE(s) < (size_t)BGP_LS_TLV_HDR_SIZE + (size_t)len) + return -1; ++ stream_putw(s, BGP_LS_ATTR_IGP_METRIC); ++ stream_putw(s, len); ++ stream_put(s, metric_buf, len); + } + } + + /* Shared Risk Link Group (TLV 1096) */ -+ if (BGP_LS_TLV_CHECK(attr->present_tlvs, BGP_LS_ATTR_SRLG_BIT)) { ++ if (CHECK_FLAG(attr->present_tlvs, BGP_LS_ATTR_SRLG_BIT)) { + if (attr->srlg_values && attr->srlg_count > 0) { + uint16_t srlg_len = attr->srlg_count * 4; + -+ if (stream_put_tlv_hdr(s, BGP_LS_ATTR_SRLG, srlg_len) < 0) ++ if (STREAM_WRITEABLE(s) < (size_t)BGP_LS_TLV_HDR_SIZE + (size_t)srlg_len) + return -1; ++ stream_putw(s, BGP_LS_ATTR_SRLG); ++ stream_putw(s, srlg_len); + for (int i = 0; i < attr->srlg_count; i++) { + uint32_t srlg_be = htonl(attr->srlg_values[i]); + -+ if (STREAM_WRITEABLE(s) < 4) -+ return -1; + stream_put(s, &srlg_be, 4); + } + } + } + + /* Link Name (TLV 1098) */ -+ if (BGP_LS_TLV_CHECK(attr->present_tlvs, BGP_LS_ATTR_LINK_NAME_BIT)) { ++ if (CHECK_FLAG(attr->present_tlvs, BGP_LS_ATTR_LINK_NAME_BIT)) { + if (attr->link_name) { + uint16_t len = strlen(attr->link_name); + -+ if (stream_put_tlv(s, BGP_LS_ATTR_LINK_NAME, len, attr->link_name) < 0) ++ if (STREAM_WRITEABLE(s) < (size_t)BGP_LS_TLV_HDR_SIZE + (size_t)len) + return -1; ++ stream_putw(s, BGP_LS_ATTR_LINK_NAME); ++ stream_putw(s, len); ++ stream_put(s, attr->link_name, len); + } + } + + /* Adjacency SID (TLV 1099) */ -+ if (BGP_LS_TLV_CHECK(attr->present_tlvs, BGP_LS_ATTR_ADJ_SID_BIT)) ++ if (CHECK_FLAG(attr->present_tlvs, BGP_LS_ATTR_ADJ_SID_BIT)) + for (int i = 0; i < attr->adj_sid_count; i++) { + int sid_len = bgp_ls_attr_adjacency_sid_len(attr->adj_sid[i].flags, + protocol_id); @@ -5267,169 +4727,193 @@ index 0000000000..db93d2fc29 + /* Should be impossible here */ + zlog_warn("BGP-LS: %s wrong combination of V-Flag and L-Flag for Adjacency SID", + __func__); -+ } else { -+ if (stream_put_tlv_hdr(s, BGP_LS_ATTR_ADJ_SID, sid_len + 4) < 0) -+ return -1; -+ if (STREAM_WRITEABLE(s) < (size_t)sid_len + 4) -+ return -1; -+ stream_putc(s, attr->adj_sid[i].flags); -+ stream_putc(s, attr->adj_sid[i].weight); -+ stream_putw(s, 0); -+ if (sid_len == 3) -+ stream_put3(s, attr->adj_sid[i].sid); -+ else -+ stream_putl(s, attr->adj_sid[i].sid); ++ continue; + } ++ ++ if (STREAM_WRITEABLE(s) < (size_t)BGP_LS_TLV_HDR_SIZE + (size_t)sid_len + 4) ++ return -1; ++ stream_putw(s, BGP_LS_ATTR_ADJ_SID); ++ stream_putw(s, sid_len + 4); ++ ++ stream_putc(s, attr->adj_sid[i].flags); ++ stream_putc(s, attr->adj_sid[i].weight); ++ stream_putw(s, 0); ++ if (sid_len == 3) ++ stream_put3(s, attr->adj_sid[i].sid); ++ else ++ stream_putl(s, attr->adj_sid[i].sid); + } + + /* Extended Admin Group (TLV 1173) */ -+ if (BGP_LS_TLV_CHECK(attr->present_tlvs, BGP_LS_ATTR_EXT_ADMIN_GROUP_BIT)) { ++ if (CHECK_FLAG(attr->present_tlvs, BGP_LS_ATTR_EXT_ADMIN_GROUP_BIT)) { + size_t nb_words = admin_group_nb_words(&attr->ext_admin_group); + + if (nb_words > 0) { + uint16_t ag_len = nb_words * 4; /* Each word is 4 bytes */ + -+ if (stream_put_tlv_hdr(s, BGP_LS_ATTR_EXTENDED_ADMIN_GROUP, ag_len) < 0) ++ if (STREAM_WRITEABLE(s) < (size_t)BGP_LS_TLV_HDR_SIZE + (size_t)ag_len) + return -1; -+ ++ stream_putw(s, BGP_LS_ATTR_EXTENDED_ADMIN_GROUP); ++ stream_putw(s, ag_len); + /* Encode each 32-bit word */ + for (size_t i = 0; i < nb_words; i++) { + uint32_t word = admin_group_get_offset(&attr->ext_admin_group, i); + uint32_t word_be = htonl(word); + -+ if (STREAM_WRITEABLE(s) < 4) -+ return -1; + stream_put(s, &word_be, 4); + } + } + } + + /* Unidirectional Link Delay (TLV 1114) */ -+ if (BGP_LS_TLV_CHECK(attr->present_tlvs, BGP_LS_ATTR_DELAY_BIT)) { ++ if (CHECK_FLAG(attr->present_tlvs, BGP_LS_ATTR_DELAY_BIT)) { + uint32_t delay_be = htonl(attr->delay); + -+ if (stream_put_tlv(s, BGP_LS_ATTR_UNIDIRECTIONAL_LINK_DELAY, 4, &delay_be) < 0) ++ if (STREAM_WRITEABLE(s) < BGP_LS_TLV_HDR_SIZE + 4) + return -1; ++ stream_putw(s, BGP_LS_ATTR_UNIDIRECTIONAL_LINK_DELAY); ++ stream_putw(s, 4); ++ stream_put(s, &delay_be, 4); + } + + /* Min/Max Unidirectional Link Delay (TLV 1115) */ -+ if (BGP_LS_TLV_CHECK(attr->present_tlvs, BGP_LS_ATTR_MIN_MAX_DELAY_BIT)) { ++ if (CHECK_FLAG(attr->present_tlvs, BGP_LS_ATTR_MIN_MAX_DELAY_BIT)) { + uint32_t min_delay_be = htonl(attr->min_delay); + uint32_t max_delay_be = htonl(attr->max_delay); + -+ if (stream_put_tlv_hdr(s, BGP_LS_ATTR_MIN_MAX_UNIDIRECTIONAL_LINK_DELAY, 8) < 0) -+ return -1; -+ -+ if (STREAM_WRITEABLE(s) < 8) ++ if (STREAM_WRITEABLE(s) < BGP_LS_TLV_HDR_SIZE + 8) + return -1; ++ stream_putw(s, BGP_LS_ATTR_MIN_MAX_UNIDIRECTIONAL_LINK_DELAY); ++ stream_putw(s, 8); + stream_put(s, &min_delay_be, 4); + stream_put(s, &max_delay_be, 4); + } + + /* Unidirectional Delay Variation (TLV 1116) */ -+ if (BGP_LS_TLV_CHECK(attr->present_tlvs, BGP_LS_ATTR_JITTER_BIT)) { ++ if (CHECK_FLAG(attr->present_tlvs, BGP_LS_ATTR_JITTER_BIT)) { + uint32_t jitter_be = htonl(attr->jitter); + -+ if (stream_put_tlv(s, BGP_LS_ATTR_UNIDIRECTIONAL_DELAY_VARIATION, 4, &jitter_be) < -+ 0) ++ if (STREAM_WRITEABLE(s) < BGP_LS_TLV_HDR_SIZE + 4) + return -1; ++ stream_putw(s, BGP_LS_ATTR_UNIDIRECTIONAL_DELAY_VARIATION); ++ stream_putw(s, 4); ++ stream_put(s, &jitter_be, 4); + } + + /* Unidirectional Packet Loss (TLV 1117) */ -+ if (BGP_LS_TLV_CHECK(attr->present_tlvs, BGP_LS_ATTR_PKT_LOSS_BIT)) { ++ if (CHECK_FLAG(attr->present_tlvs, BGP_LS_ATTR_PKT_LOSS_BIT)) { + uint32_t pkt_loss_be = htonl(attr->pkt_loss); + -+ if (stream_put_tlv(s, BGP_LS_ATTR_UNIDIRECTIONAL_LINK_LOSS, 4, &pkt_loss_be) < 0) ++ if (STREAM_WRITEABLE(s) < BGP_LS_TLV_HDR_SIZE + 4) + return -1; ++ stream_putw(s, BGP_LS_ATTR_UNIDIRECTIONAL_LINK_LOSS); ++ stream_putw(s, 4); ++ stream_put(s, &pkt_loss_be, 4); + } + + /* Unidirectional Residual Bandwidth (TLV 1118) */ -+ if (BGP_LS_TLV_CHECK(attr->present_tlvs, BGP_LS_ATTR_RESIDUAL_BW_BIT)) { -+ if (stream_putf_tlv(s, BGP_LS_ATTR_UNIDIRECTIONAL_RESIDUAL_BANDWIDTH, -+ attr->residual_bw) < 0) ++ if (CHECK_FLAG(attr->present_tlvs, BGP_LS_ATTR_RESIDUAL_BW_BIT)) { ++ if (STREAM_WRITEABLE(s) < BGP_LS_TLV_HDR_SIZE + 4) + return -1; ++ stream_putw(s, BGP_LS_ATTR_UNIDIRECTIONAL_RESIDUAL_BANDWIDTH); ++ stream_putw(s, 4); ++ stream_putf(s, attr->residual_bw); + } + + /* Unidirectional Available Bandwidth (TLV 1119) */ -+ if (BGP_LS_TLV_CHECK(attr->present_tlvs, BGP_LS_ATTR_AVAILABLE_BW_BIT)) { -+ if (stream_putf_tlv(s, BGP_LS_ATTR_UNIDIRECTIONAL_AVAILABLE_BANDWIDTH, -+ attr->available_bw) < 0) ++ if (CHECK_FLAG(attr->present_tlvs, BGP_LS_ATTR_AVAILABLE_BW_BIT)) { ++ if (STREAM_WRITEABLE(s) < BGP_LS_TLV_HDR_SIZE + 4) + return -1; ++ stream_putw(s, BGP_LS_ATTR_UNIDIRECTIONAL_AVAILABLE_BANDWIDTH); ++ stream_putw(s, 4); ++ stream_putf(s, attr->available_bw); + } + + /* Unidirectional Utilized Bandwidth (TLV 1120) */ -+ if (BGP_LS_TLV_CHECK(attr->present_tlvs, BGP_LS_ATTR_UTILIZED_BW_BIT)) { -+ if (stream_putf_tlv(s, BGP_LS_ATTR_UNIDIRECTIONAL_UTILIZED_BANDWIDTH, -+ attr->utilized_bw) < 0) ++ if (CHECK_FLAG(attr->present_tlvs, BGP_LS_ATTR_UTILIZED_BW_BIT)) { ++ if (STREAM_WRITEABLE(s) < BGP_LS_TLV_HDR_SIZE + 4) + return -1; ++ stream_putw(s, BGP_LS_ATTR_UNIDIRECTIONAL_UTILIZED_BANDWIDTH); ++ stream_putw(s, 4); ++ stream_putf(s, attr->utilized_bw); + } + + /* IGP Flags (TLV 1152) */ -+ if (BGP_LS_TLV_CHECK(attr->present_tlvs, BGP_LS_ATTR_IGP_FLAGS_BIT)) { -+ if (stream_put_tlv(s, BGP_LS_ATTR_IGP_FLAGS, 1, &attr->igp_flags) < 0) ++ if (CHECK_FLAG(attr->present_tlvs, BGP_LS_ATTR_IGP_FLAGS_BIT)) { ++ if (STREAM_WRITEABLE(s) < BGP_LS_TLV_HDR_SIZE + 1) + return -1; ++ stream_putw(s, BGP_LS_ATTR_IGP_FLAGS); ++ stream_putw(s, 1); ++ stream_putc(s, attr->igp_flags); + } + + /* Route Tags (TLV 1153) */ -+ if (BGP_LS_TLV_CHECK(attr->present_tlvs, BGP_LS_ATTR_ROUTE_TAG_BIT)) { ++ if (CHECK_FLAG(attr->present_tlvs, BGP_LS_ATTR_ROUTE_TAG_BIT)) { + if (attr->route_tags && attr->route_tag_count > 0) { + uint16_t tag_len = attr->route_tag_count * 4; + -+ if (stream_put_tlv_hdr(s, BGP_LS_ATTR_ROUTE_TAG, tag_len) < 0) ++ if (STREAM_WRITEABLE(s) < (size_t)BGP_LS_TLV_HDR_SIZE + (size_t)tag_len) + return -1; ++ stream_putw(s, BGP_LS_ATTR_ROUTE_TAG); ++ stream_putw(s, tag_len); + for (int i = 0; i < attr->route_tag_count; i++) { + uint32_t tag_be = htonl(attr->route_tags[i]); + -+ if (STREAM_WRITEABLE(s) < 4) -+ return -1; + stream_put(s, &tag_be, 4); + } + } + } + + /* Extended Tags (TLV 1154) */ -+ if (BGP_LS_TLV_CHECK(attr->present_tlvs, BGP_LS_ATTR_EXTENDED_TAG_BIT)) { ++ if (CHECK_FLAG(attr->present_tlvs, BGP_LS_ATTR_EXTENDED_TAG_BIT)) { + if (attr->extended_tags && attr->extended_tag_count > 0) { + uint16_t tag_len = attr->extended_tag_count * 8; + -+ if (stream_put_tlv_hdr(s, BGP_LS_ATTR_EXTENDED_TAG, tag_len) < 0) ++ if (STREAM_WRITEABLE(s) < (size_t)BGP_LS_TLV_HDR_SIZE + (size_t)tag_len) + return -1; ++ stream_putw(s, BGP_LS_ATTR_EXTENDED_TAG); ++ stream_putw(s, tag_len); + for (int i = 0; i < attr->extended_tag_count; i++) { + uint64_t tag_be = htonll(attr->extended_tags[i]); + -+ if (STREAM_WRITEABLE(s) < 8) -+ return -1; + stream_put(s, &tag_be, 8); + } + } + } + + /* Prefix Metric (TLV 1155) */ -+ if (BGP_LS_TLV_CHECK(attr->present_tlvs, BGP_LS_ATTR_PREFIX_METRIC_BIT)) { ++ if (CHECK_FLAG(attr->present_tlvs, BGP_LS_ATTR_PREFIX_METRIC_BIT)) { + uint32_t metric_be = htonl(attr->prefix_metric); + -+ if (stream_put_tlv(s, BGP_LS_ATTR_PREFIX_METRIC, 4, &metric_be) < 0) ++ if (STREAM_WRITEABLE(s) < BGP_LS_TLV_HDR_SIZE + 4) + return -1; ++ stream_putw(s, BGP_LS_ATTR_PREFIX_METRIC); ++ stream_putw(s, 4); ++ stream_put(s, &metric_be, 4); + } + + /* OSPF Forwarding Address (TLV 1156) - IPv4 or IPv6 */ -+ if (BGP_LS_TLV_CHECK(attr->present_tlvs, BGP_LS_ATTR_OSPF_FWD_ADDR_BIT)) { ++ if (CHECK_FLAG(attr->present_tlvs, BGP_LS_ATTR_OSPF_FWD_ADDR_BIT)) { + /* Check which address family is present */ + if (attr->ospf_fwd_addr.s_addr != 0) { + /* IPv4 forwarding address */ -+ if (stream_put_tlv(s, BGP_LS_ATTR_OSPF_FWD_ADDR, 4, &attr->ospf_fwd_addr) < -+ 0) ++ if (STREAM_WRITEABLE(s) < BGP_LS_TLV_HDR_SIZE + 4) + return -1; ++ stream_putw(s, BGP_LS_ATTR_OSPF_FWD_ADDR); ++ stream_putw(s, 4); ++ stream_put(s, &attr->ospf_fwd_addr, 4); + } else if (!IN6_IS_ADDR_UNSPECIFIED(&attr->ospf_fwd_addr6)) { + /* IPv6 forwarding address */ -+ if (stream_put_tlv(s, BGP_LS_ATTR_OSPF_FWD_ADDR, 16, -+ &attr->ospf_fwd_addr6) < 0) ++ if (STREAM_WRITEABLE(s) < BGP_LS_TLV_HDR_SIZE + 16) + return -1; ++ stream_putw(s, BGP_LS_ATTR_OSPF_FWD_ADDR); ++ stream_putw(s, 16); ++ stream_put(s, &attr->ospf_fwd_addr6, 16); + } + } + + /* Prefix SID (TLV 1158) */ -+ if (BGP_LS_TLV_CHECK(attr->present_tlvs, BGP_LS_ATTR_PREFIX_SID_BIT)) { ++ if (CHECK_FLAG(attr->present_tlvs, BGP_LS_ATTR_PREFIX_SID_BIT)) { + int sid_len = bgp_ls_attr_prefix_sid_len(attr->prefix_sid.sid_flag); + + if (sid_len == -1) { @@ -5437,10 +4921,10 @@ index 0000000000..db93d2fc29 + zlog_warn("BGP-LS: %s wrong combination of V-Flag and L-Flag for Prefix SID", + __func__); + } else { -+ if (stream_put_tlv_hdr(s, BGP_LS_ATTR_PREFIX_SID, 4 + sid_len) < 0) -+ return -1; -+ if (STREAM_WRITEABLE(s) < (size_t)4 + sid_len) ++ if (STREAM_WRITEABLE(s) < BGP_LS_TLV_HDR_SIZE + (size_t)4 + sid_len) + return -1; ++ stream_putw(s, BGP_LS_ATTR_PREFIX_SID); ++ stream_putw(s, 4 + sid_len); + stream_putc(s, attr->prefix_sid.sid_flag); + stream_putc(s, attr->prefix_sid.algo); + stream_putw(s, 0); /* Reserved = 0 */ @@ -5583,6 +5067,7 @@ index 0000000000..db93d2fc29 + size_t end_pos; + uint16_t sub_type, sub_len; + bool has_igp_router_id = false; ++ bool has_bgp_router_id = false; + + if (!s || !desc) + return -1; @@ -5600,7 +5085,7 @@ index 0000000000..db93d2fc29 + + switch (sub_type) { + case BGP_LS_TLV_AS_NUMBER: -+ if (BGP_LS_TLV_CHECK(desc->present_tlvs, BGP_LS_NODE_DESC_AS_BIT)) { ++ if (CHECK_FLAG(desc->present_tlvs, BGP_LS_NODE_DESC_AS_BIT)) { + flog_warn(EC_BGP_LS_PACKET, + "BGP-LS: duplicate AS Number TLV in node descriptor"); + return -1; @@ -5612,11 +5097,11 @@ index 0000000000..db93d2fc29 + return -1; + } + desc->asn = stream_getl(s); -+ BGP_LS_TLV_SET(desc->present_tlvs, BGP_LS_NODE_DESC_AS_BIT); ++ SET_FLAG(desc->present_tlvs, BGP_LS_NODE_DESC_AS_BIT); + break; + + case BGP_LS_TLV_BGP_LS_ID: -+ if (BGP_LS_TLV_CHECK(desc->present_tlvs, BGP_LS_NODE_DESC_BGP_LS_ID_BIT)) { ++ if (CHECK_FLAG(desc->present_tlvs, BGP_LS_NODE_DESC_BGP_LS_ID_BIT)) { + flog_warn(EC_BGP_LS_PACKET, + "BGP-LS: duplicate BGP-LS ID TLV in node descriptor"); + return -1; @@ -5628,11 +5113,11 @@ index 0000000000..db93d2fc29 + return -1; + } + desc->bgp_ls_id = stream_getl(s); -+ BGP_LS_TLV_SET(desc->present_tlvs, BGP_LS_NODE_DESC_BGP_LS_ID_BIT); ++ SET_FLAG(desc->present_tlvs, BGP_LS_NODE_DESC_BGP_LS_ID_BIT); + break; + + case BGP_LS_TLV_OSPF_AREA_ID: -+ if (BGP_LS_TLV_CHECK(desc->present_tlvs, BGP_LS_NODE_DESC_OSPF_AREA_BIT)) { ++ if (CHECK_FLAG(desc->present_tlvs, BGP_LS_NODE_DESC_OSPF_AREA_BIT)) { + flog_warn(EC_BGP_LS_PACKET, + "BGP-LS: duplicate OSPF Area ID TLV in node descriptor"); + return -1; @@ -5644,11 +5129,11 @@ index 0000000000..db93d2fc29 + return -1; + } + desc->ospf_area_id = stream_getl(s); -+ BGP_LS_TLV_SET(desc->present_tlvs, BGP_LS_NODE_DESC_OSPF_AREA_BIT); ++ SET_FLAG(desc->present_tlvs, BGP_LS_NODE_DESC_OSPF_AREA_BIT); + break; + + case BGP_LS_TLV_IGP_ROUTER_ID: -+ if (BGP_LS_TLV_CHECK(desc->present_tlvs, BGP_LS_NODE_DESC_IGP_ROUTER_BIT)) { ++ if (CHECK_FLAG(desc->present_tlvs, BGP_LS_NODE_DESC_IGP_ROUTER_BIT)) { + flog_warn(EC_BGP_LS_PACKET, + "BGP-LS: duplicate IGP Router-ID TLV in node descriptor"); + return -1; @@ -5662,10 +5147,26 @@ index 0000000000..db93d2fc29 + } + desc->igp_router_id_len = sub_len; + stream_get(desc->igp_router_id, s, sub_len); -+ BGP_LS_TLV_SET(desc->present_tlvs, BGP_LS_NODE_DESC_IGP_ROUTER_BIT); ++ SET_FLAG(desc->present_tlvs, BGP_LS_NODE_DESC_IGP_ROUTER_BIT); + has_igp_router_id = true; + break; + ++ case BGP_LS_TLV_BGP_ROUTER_ID: ++ if (CHECK_FLAG(desc->present_tlvs, BGP_LS_NODE_DESC_BGP_ROUTER_ID_BIT)) { ++ flog_warn(EC_BGP_LS_PACKET, ++ "BGP-LS: duplicate BGP Router-ID TLV in node descriptor"); ++ return -1; ++ } ++ if (sub_len != BGP_LS_BGP_ROUTER_ID_SIZE) { ++ flog_warn(EC_BGP_LS_PACKET, ++ "BGP-LS: Invalid BGP Router-ID TLV length %u", sub_len); ++ return -1; ++ } ++ desc->bgp_router_id.s_addr = stream_get_ipv4(s); ++ SET_FLAG(desc->present_tlvs, BGP_LS_NODE_DESC_BGP_ROUTER_ID_BIT); ++ has_bgp_router_id = true; ++ break; ++ + default: + /* Unknown TLV - skip but preserve (RFC 9552 Section 5.1) */ + flog_warn(EC_BGP_LS_PACKET, @@ -5677,9 +5178,9 @@ index 0000000000..db93d2fc29 + } + + /* Verify mandatory TLV present */ -+ if (!has_igp_router_id) { ++ if (!has_igp_router_id && !has_bgp_router_id) { + flog_warn(EC_BGP_LS_PACKET, -+ "BGP-LS: Mandatory IGP Router-ID TLV missing from Node Descriptor"); ++ "BGP-LS: Mandatory IGP Router-ID TLV or BGP Router-ID TLV missing from Node Descriptor"); + return -1; + } + @@ -5736,7 +5237,7 @@ index 0000000000..db93d2fc29 + + switch (tlv_type) { + case BGP_LS_TLV_LINK_ID: -+ if (BGP_LS_TLV_CHECK(desc->present_tlvs, BGP_LS_LINK_DESC_LINK_ID_BIT)) { ++ if (CHECK_FLAG(desc->present_tlvs, BGP_LS_LINK_DESC_LINK_ID_BIT)) { + flog_warn(EC_BGP_LS_PACKET, + "BGP-LS: duplicate Link ID TLV in link descriptor"); + goto error; @@ -5749,11 +5250,11 @@ index 0000000000..db93d2fc29 + } + desc->link_local_id = stream_getl(s); + desc->link_remote_id = stream_getl(s); -+ BGP_LS_TLV_SET(desc->present_tlvs, BGP_LS_LINK_DESC_LINK_ID_BIT); ++ SET_FLAG(desc->present_tlvs, BGP_LS_LINK_DESC_LINK_ID_BIT); + break; + + case BGP_LS_TLV_IPV4_INTF_ADDR: -+ if (BGP_LS_TLV_CHECK(desc->present_tlvs, BGP_LS_LINK_DESC_IPV4_INTF_BIT)) { ++ if (CHECK_FLAG(desc->present_tlvs, BGP_LS_LINK_DESC_IPV4_INTF_BIT)) { + flog_warn(EC_BGP_LS_PACKET, + "BGP-LS: duplicate IPv4 Interface Address TLV in link descriptor"); + goto error; @@ -5765,11 +5266,11 @@ index 0000000000..db93d2fc29 + goto error; + } + stream_get(&desc->ipv4_intf_addr.s_addr, s, BGP_LS_IPV4_ADDR_SIZE); -+ BGP_LS_TLV_SET(desc->present_tlvs, BGP_LS_LINK_DESC_IPV4_INTF_BIT); ++ SET_FLAG(desc->present_tlvs, BGP_LS_LINK_DESC_IPV4_INTF_BIT); + break; + + case BGP_LS_TLV_IPV4_NEIGH_ADDR: -+ if (BGP_LS_TLV_CHECK(desc->present_tlvs, BGP_LS_LINK_DESC_IPV4_NEIGH_BIT)) { ++ if (CHECK_FLAG(desc->present_tlvs, BGP_LS_LINK_DESC_IPV4_NEIGH_BIT)) { + flog_warn(EC_BGP_LS_PACKET, + "BGP-LS: duplicate IPv4 Neighbor Address TLV in link descriptor"); + goto error; @@ -5781,11 +5282,11 @@ index 0000000000..db93d2fc29 + goto error; + } + stream_get(&desc->ipv4_neigh_addr.s_addr, s, BGP_LS_IPV4_ADDR_SIZE); -+ BGP_LS_TLV_SET(desc->present_tlvs, BGP_LS_LINK_DESC_IPV4_NEIGH_BIT); ++ SET_FLAG(desc->present_tlvs, BGP_LS_LINK_DESC_IPV4_NEIGH_BIT); + break; + + case BGP_LS_TLV_IPV6_INTF_ADDR: -+ if (BGP_LS_TLV_CHECK(desc->present_tlvs, BGP_LS_LINK_DESC_IPV6_INTF_BIT)) { ++ if (CHECK_FLAG(desc->present_tlvs, BGP_LS_LINK_DESC_IPV6_INTF_BIT)) { + flog_warn(EC_BGP_LS_PACKET, + "BGP-LS: duplicate IPv6 Interface Address TLV in link descriptor"); + goto error; @@ -5797,11 +5298,11 @@ index 0000000000..db93d2fc29 + goto error; + } + stream_get(&desc->ipv6_intf_addr.s6_addr, s, BGP_LS_IPV6_ADDR_SIZE); -+ BGP_LS_TLV_SET(desc->present_tlvs, BGP_LS_LINK_DESC_IPV6_INTF_BIT); ++ SET_FLAG(desc->present_tlvs, BGP_LS_LINK_DESC_IPV6_INTF_BIT); + break; + + case BGP_LS_TLV_IPV6_NEIGH_ADDR: -+ if (BGP_LS_TLV_CHECK(desc->present_tlvs, BGP_LS_LINK_DESC_IPV6_NEIGH_BIT)) { ++ if (CHECK_FLAG(desc->present_tlvs, BGP_LS_LINK_DESC_IPV6_NEIGH_BIT)) { + flog_warn(EC_BGP_LS_PACKET, + "BGP-LS: duplicate IPv6 Neighbor Address TLV in link descriptor"); + goto error; @@ -5813,17 +5314,17 @@ index 0000000000..db93d2fc29 + goto error; + } + stream_get(&desc->ipv6_neigh_addr.s6_addr, s, BGP_LS_IPV6_ADDR_SIZE); -+ BGP_LS_TLV_SET(desc->present_tlvs, BGP_LS_LINK_DESC_IPV6_NEIGH_BIT); ++ SET_FLAG(desc->present_tlvs, BGP_LS_LINK_DESC_IPV6_NEIGH_BIT); + break; + + case BGP_LS_TLV_MT_ID: -+ if (BGP_LS_TLV_CHECK(desc->present_tlvs, BGP_LS_LINK_DESC_MT_ID_BIT)) { ++ if (CHECK_FLAG(desc->present_tlvs, BGP_LS_LINK_DESC_MT_ID_BIT)) { + flog_warn(EC_BGP_LS_PACKET, + "BGP-LS: duplicate MT-ID TLV in link descriptor"); + goto error; + } + /* Variable length: 2*n bytes where n is number of MT-IDs */ -+ if (tlv_len % 2 != 0 || tlv_len > BGP_LS_MAX_MT_ID * 2) { ++ if (tlv_len == 0 || tlv_len % 2 != 0 || tlv_len > BGP_LS_MAX_MT_ID * 2) { + flog_warn(EC_BGP_LS_PACKET, "BGP-LS: Invalid MT-ID TLV length %u", + tlv_len); + goto error; @@ -5832,11 +5333,11 @@ index 0000000000..db93d2fc29 + desc->mt_id = XCALLOC(MTYPE_BGP_LS_NLRI, tlv_len); + for (uint16_t i = 0; i < desc->mt_id_count; i++) + desc->mt_id[i] = stream_getw(s); -+ BGP_LS_TLV_SET(desc->present_tlvs, BGP_LS_LINK_DESC_MT_ID_BIT); ++ SET_FLAG(desc->present_tlvs, BGP_LS_LINK_DESC_MT_ID_BIT); + break; + + case BGP_LS_TLV_REMOTE_AS_NUMBER: -+ if (BGP_LS_TLV_CHECK(desc->present_tlvs, BGP_LS_LINK_DESC_REMOTE_AS_BIT)) { ++ if (CHECK_FLAG(desc->present_tlvs, BGP_LS_LINK_DESC_REMOTE_AS_BIT)) { + flog_warn(EC_BGP_LS_PACKET, + "BGP-LS: duplicate Remote AS Number TLV in link descriptor"); + goto error; @@ -5848,7 +5349,7 @@ index 0000000000..db93d2fc29 + goto error; + } + desc->remote_asn = stream_getl(s); -+ BGP_LS_TLV_SET(desc->present_tlvs, BGP_LS_LINK_DESC_REMOTE_AS_BIT); ++ SET_FLAG(desc->present_tlvs, BGP_LS_LINK_DESC_REMOTE_AS_BIT); + break; + + default: @@ -5916,13 +5417,13 @@ index 0000000000..db93d2fc29 + + switch (tlv_type) { + case BGP_LS_TLV_MT_ID: -+ if (BGP_LS_TLV_CHECK(desc->present_tlvs, BGP_LS_PREFIX_DESC_MT_ID_BIT)) { ++ if (CHECK_FLAG(desc->present_tlvs, BGP_LS_PREFIX_DESC_MT_ID_BIT)) { + flog_warn(EC_BGP_LS_PACKET, + "BGP-LS: duplicate MT-ID TLV in prefix descriptor"); + goto error; + } + /* Variable length: 2*n bytes where n is number of MT-IDs */ -+ if (tlv_len % 2 != 0 || tlv_len > BGP_LS_MAX_MT_ID * 2) { ++ if (tlv_len == 0 || tlv_len % 2 != 0 || tlv_len > BGP_LS_MAX_MT_ID * 2) { + flog_warn(EC_BGP_LS_PACKET, "BGP-LS: Invalid MT-ID TLV length %u", + tlv_len); + goto error; @@ -5931,12 +5432,11 @@ index 0000000000..db93d2fc29 + desc->mt_id = XCALLOC(MTYPE_BGP_LS_NLRI, tlv_len); + for (uint16_t i = 0; i < desc->mt_id_count; i++) + desc->mt_id[i] = stream_getw(s); -+ BGP_LS_TLV_SET(desc->present_tlvs, BGP_LS_PREFIX_DESC_MT_ID_BIT); ++ SET_FLAG(desc->present_tlvs, BGP_LS_PREFIX_DESC_MT_ID_BIT); + break; + + case BGP_LS_TLV_OSPF_ROUTE_TYPE: -+ if (BGP_LS_TLV_CHECK(desc->present_tlvs, -+ BGP_LS_PREFIX_DESC_OSPF_ROUTE_BIT)) { ++ if (CHECK_FLAG(desc->present_tlvs, BGP_LS_PREFIX_DESC_OSPF_ROUTE_BIT)) { + flog_warn(EC_BGP_LS_PACKET, + "BGP-LS: duplicate OSPF Route Type TLV in prefix descriptor"); + goto error; @@ -5947,11 +5447,11 @@ index 0000000000..db93d2fc29 + goto error; + } + desc->ospf_route_type = stream_getc(s); -+ BGP_LS_TLV_SET(desc->present_tlvs, BGP_LS_PREFIX_DESC_OSPF_ROUTE_BIT); ++ SET_FLAG(desc->present_tlvs, BGP_LS_PREFIX_DESC_OSPF_ROUTE_BIT); + break; + + case BGP_LS_TLV_IP_REACH_INFO: -+ if (BGP_LS_TLV_CHECK(desc->present_tlvs, BGP_LS_PREFIX_DESC_IP_REACH_BIT)) { ++ if (CHECK_FLAG(desc->present_tlvs, BGP_LS_PREFIX_DESC_IP_REACH_BIT)) { + flog_warn(EC_BGP_LS_PACKET, + "BGP-LS: duplicate IP Reachability Info TLV in prefix descriptor"); + goto error; @@ -6004,10 +5504,32 @@ index 0000000000..db93d2fc29 + expected_bytes); + } + -+ BGP_LS_TLV_SET(desc->present_tlvs, BGP_LS_PREFIX_DESC_IP_REACH_BIT); ++ SET_FLAG(desc->present_tlvs, BGP_LS_PREFIX_DESC_IP_REACH_BIT); + has_ip_reach = true; + break; + ++ case BGP_LS_TLV_BGP_ROUTE_TYPE: ++ if (CHECK_FLAG(desc->present_tlvs, BGP_LS_PREFIX_DESC_BGP_ROUTE_TYPE_BIT)) { ++ flog_warn(EC_BGP_LS_PACKET, ++ "BGP-LS: duplicate BGP Route Type TLV in prefix descriptor"); ++ goto error; ++ } ++ if (tlv_len != BGP_LS_BGP_ROUTE_TYPE_SIZE) { ++ flog_warn(EC_BGP_LS_PACKET, ++ "BGP-LS: Invalid BGP Route Type TLV length %u", tlv_len); ++ goto error; ++ } ++ desc->bgp_route_type = stream_getc(s); ++ if (desc->bgp_route_type < BGP_LS_BGP_RT_LOCAL || ++ desc->bgp_route_type > BGP_LS_BGP_RT_REDISTRIBUTED) { ++ flog_warn(EC_BGP_LS_PACKET, ++ "BGP-LS: Invalid BGP Route Type value %u", ++ desc->bgp_route_type); ++ goto error; ++ } ++ SET_FLAG(desc->present_tlvs, BGP_LS_PREFIX_DESC_BGP_ROUTE_TYPE_BIT); ++ break; ++ + default: + /* Unknown TLV - skip but preserve (RFC 9552 Section 5.1) */ + flog_warn(EC_BGP_LS_PACKET, @@ -6221,6 +5743,9 @@ index 0000000000..db93d2fc29 + if (bgp_ls_decode_link_descriptor(s, &nlri->nlri_data.link.link_desc, + link_desc_len) < 0) + return -1; ++ } else { ++ flog_warn(EC_BGP_LS_PACKET, "BGP-LS: Link NLRI has no Link Descriptor TLVs"); ++ return -1; + } + + /* Verify we consumed exactly nlri_length bytes */ @@ -6356,18 +5881,12 @@ index 0000000000..db93d2fc29 + if (!s || !nlri) + return -1; + -+ /* Read NLRI Type (2 bytes) */ -+ if (STREAM_READABLE(s) < 2) { -+ flog_warn(EC_BGP_LS_PACKET, "BGP-LS: Not enough data for NLRI type"); ++ /* Read NLRI Type + Length (4 bytes total) */ ++ if (STREAM_READABLE(s) < BGP_LS_NLRI_TYPE_SIZE + BGP_LS_NLRI_LENGTH_SIZE) { ++ flog_warn(EC_BGP_LS_PACKET, "BGP-LS: Not enough data for NLRI type and length"); + return -1; + } + nlri_type = stream_getw(s); -+ -+ /* Read NLRI Length (2 bytes) */ -+ if (STREAM_READABLE(s) < 2) { -+ flog_warn(EC_BGP_LS_PACKET, "BGP-LS: Not enough data for NLRI length"); -+ return -1; -+ } + nlri_length = stream_getw(s); + + /* Check if stream has enough data for NLRI */ @@ -6409,13 +5928,13 @@ index 0000000000..db93d2fc29 + return -1; + } + -+ if (BGP_LS_TLV_CHECK(attr->present_tlvs, BGP_LS_ATTR_NODE_FLAGS_BIT)) { ++ if (CHECK_FLAG(attr->present_tlvs, BGP_LS_ATTR_NODE_FLAGS_BIT)) { + flog_warn(EC_BGP_UPDATE_RCV, "BGP-LS: duplicate Node Flags TLV"); + return -1; + } + + attr->node_flags = stream_getc(s); -+ BGP_LS_TLV_SET(attr->present_tlvs, BGP_LS_ATTR_NODE_FLAGS_BIT); ++ SET_FLAG(attr->present_tlvs, BGP_LS_ATTR_NODE_FLAGS_BIT); + + /* Skip any extra bytes */ + if (length > 1) @@ -6433,7 +5952,15 @@ index 0000000000..db93d2fc29 + if (length == 0) + return 0; + -+ if (BGP_LS_TLV_CHECK(attr->present_tlvs, BGP_LS_ATTR_NODE_NAME_BIT)) { ++ if (length > BGP_LS_MAX_NODE_NAME_LEN) { ++ flog_warn(EC_BGP_UPDATE_RCV, ++ "BGP-LS: Node Name TLV length %u exceeds maximum %u, skipping TLV", ++ length, BGP_LS_MAX_NODE_NAME_LEN); ++ stream_forward_getp(s, length); ++ return 0; ++ } ++ ++ if (CHECK_FLAG(attr->present_tlvs, BGP_LS_ATTR_NODE_NAME_BIT)) { + flog_warn(EC_BGP_UPDATE_RCV, "BGP-LS: duplicate Node Name TLV"); + return -1; + } @@ -6442,7 +5969,24 @@ index 0000000000..db93d2fc29 + attr->node_name = XCALLOC(MTYPE_BGP_LS_ATTR, length + 1); + stream_get(attr->node_name, s, length); + attr->node_name[length] = '\0'; -+ BGP_LS_TLV_SET(attr->present_tlvs, BGP_LS_ATTR_NODE_NAME_BIT); ++ ++ /* ++ * Reject names containing embedded NUL or non-printable characters to ++ * prevent log spoofing and deduplication bypass (RFC 9552 Section 5.3.1.3 ++ * implies printable hostname). ++ */ ++ for (uint16_t i = 0; i < length; i++) { ++ if ((unsigned char)attr->node_name[i] < 0x20 || ++ (unsigned char)attr->node_name[i] > 0x7E) { ++ flog_warn(EC_BGP_UPDATE_RCV, ++ "BGP-LS: Node Name TLV contains non-printable character at byte %u, rejecting", ++ i); ++ XFREE(MTYPE_BGP_LS_ATTR, attr->node_name); ++ return -1; ++ } ++ } ++ ++ SET_FLAG(attr->present_tlvs, BGP_LS_ATTR_NODE_NAME_BIT); + + return 0; +} @@ -6459,7 +6003,7 @@ index 0000000000..db93d2fc29 + return -1; + } + -+ if (BGP_LS_TLV_CHECK(attr->present_tlvs, BGP_LS_ATTR_ISIS_AREA_BIT)) { ++ if (CHECK_FLAG(attr->present_tlvs, BGP_LS_ATTR_ISIS_AREA_BIT)) { + /* + * RFC 9552 Section 5.3.1.4 allows multiple IS-IS Area-ID TLVs + * to encode synonymous area addresses. We currently support @@ -6475,7 +6019,7 @@ index 0000000000..db93d2fc29 + attr->isis_area_id = XCALLOC(MTYPE_BGP_LS_ATTR, length); + stream_get(attr->isis_area_id, s, length); + attr->isis_area_id_len = length; -+ BGP_LS_TLV_SET(attr->present_tlvs, BGP_LS_ATTR_ISIS_AREA_BIT); ++ SET_FLAG(attr->present_tlvs, BGP_LS_ATTR_ISIS_AREA_BIT); + + return 0; +} @@ -6504,7 +6048,7 @@ index 0000000000..db93d2fc29 + * XXX Selection of lowest-numbered LSP for IS-IS is not implemented + * (it is SHOULD) + */ -+ if (BGP_LS_TLV_CHECK(attr->present_tlvs, BGP_LS_ATTR_SR_CAPABILITIES_BIT)) { ++ if (CHECK_FLAG(attr->present_tlvs, BGP_LS_ATTR_SR_CAPABILITIES_BIT)) { + flog_warn(EC_BGP_UPDATE_RCV, + "BGP-LS: Multiple range SR Capabilities TLV not supported, skipping duplicate"); + @@ -6535,7 +6079,7 @@ index 0000000000..db93d2fc29 + return -1; + } + attr->srgb.lower_bound = stream_get3(s); -+ BGP_LS_TLV_SET(attr->present_tlvs, BGP_LS_ATTR_SR_CAPABILITIES_BIT); ++ SET_FLAG(attr->present_tlvs, BGP_LS_ATTR_SR_CAPABILITIES_BIT); + + if (length > 12) { + flog_warn(EC_BGP_UPDATE_RCV, @@ -6568,7 +6112,7 @@ index 0000000000..db93d2fc29 + * originated by the same router, the procedures defined in [RFC7981] + * apply. + */ -+ if (BGP_LS_TLV_CHECK(attr->present_tlvs, BGP_LS_ATTR_NODE_MSD_BIT)) { ++ if (CHECK_FLAG(attr->present_tlvs, BGP_LS_ATTR_NODE_MSD_BIT)) { + flog_warn(EC_BGP_UPDATE_RCV, + "BGP-LS: Multiple Node MSD TLV received, skipping duplicate"); + stream_forward_getp(s, length); @@ -6600,7 +6144,7 @@ index 0000000000..db93d2fc29 + } + + attr->msd = msd; -+ BGP_LS_TLV_SET(attr->present_tlvs, BGP_LS_ATTR_NODE_MSD_BIT); ++ SET_FLAG(attr->present_tlvs, BGP_LS_ATTR_NODE_MSD_BIT); + + return 0; +} @@ -6617,13 +6161,13 @@ index 0000000000..db93d2fc29 + return -1; + } + -+ if (BGP_LS_TLV_CHECK(attr->present_tlvs, BGP_LS_ATTR_ADMIN_GROUP_BIT)) { ++ if (CHECK_FLAG(attr->present_tlvs, BGP_LS_ATTR_ADMIN_GROUP_BIT)) { + flog_warn(EC_BGP_UPDATE_RCV, "BGP-LS: duplicate Admin Group TLV"); + return -1; + } + + attr->admin_group = stream_getl(s); -+ BGP_LS_TLV_SET(attr->present_tlvs, BGP_LS_ATTR_ADMIN_GROUP_BIT); ++ SET_FLAG(attr->present_tlvs, BGP_LS_ATTR_ADMIN_GROUP_BIT); + + return 0; +} @@ -6642,7 +6186,7 @@ index 0000000000..db93d2fc29 + return -1; + } + -+ if (BGP_LS_TLV_CHECK(attr->present_tlvs, BGP_LS_ATTR_MAX_LINK_BW_BIT)) { ++ if (CHECK_FLAG(attr->present_tlvs, BGP_LS_ATTR_MAX_LINK_BW_BIT)) { + flog_warn(EC_BGP_UPDATE_RCV, "BGP-LS: duplicate Maximum Link Bandwidth TLV"); + return -1; + } @@ -6650,7 +6194,7 @@ index 0000000000..db93d2fc29 + /* Read as 32-bit IEEE floating point */ + bw_bits = stream_getl(s); + memcpy(&attr->max_link_bw, &bw_bits, 4); -+ BGP_LS_TLV_SET(attr->present_tlvs, BGP_LS_ATTR_MAX_LINK_BW_BIT); ++ SET_FLAG(attr->present_tlvs, BGP_LS_ATTR_MAX_LINK_BW_BIT); + + return 0; +} @@ -6669,14 +6213,14 @@ index 0000000000..db93d2fc29 + return -1; + } + -+ if (BGP_LS_TLV_CHECK(attr->present_tlvs, BGP_LS_ATTR_MAX_RESV_BW_BIT)) { ++ if (CHECK_FLAG(attr->present_tlvs, BGP_LS_ATTR_MAX_RESV_BW_BIT)) { + flog_warn(EC_BGP_UPDATE_RCV, "BGP-LS: duplicate Maximum Reservable Bandwidth TLV"); + return -1; + } + + bw_bits = stream_getl(s); + memcpy(&attr->max_resv_bw, &bw_bits, 4); -+ BGP_LS_TLV_SET(attr->present_tlvs, BGP_LS_ATTR_MAX_RESV_BW_BIT); ++ SET_FLAG(attr->present_tlvs, BGP_LS_ATTR_MAX_RESV_BW_BIT); + + return 0; +} @@ -6696,7 +6240,7 @@ index 0000000000..db93d2fc29 + return -1; + } + -+ if (BGP_LS_TLV_CHECK(attr->present_tlvs, BGP_LS_ATTR_UNRESV_BW_BIT)) { ++ if (CHECK_FLAG(attr->present_tlvs, BGP_LS_ATTR_UNRESV_BW_BIT)) { + flog_warn(EC_BGP_UPDATE_RCV, "BGP-LS: duplicate Unreserved Bandwidth TLV"); + return -1; + } @@ -6705,7 +6249,7 @@ index 0000000000..db93d2fc29 + bw_bits = stream_getl(s); + memcpy(&attr->unreserved_bw[i], &bw_bits, 4); + } -+ BGP_LS_TLV_SET(attr->present_tlvs, BGP_LS_ATTR_UNRESV_BW_BIT); ++ SET_FLAG(attr->present_tlvs, BGP_LS_ATTR_UNRESV_BW_BIT); + + return 0; +} @@ -6721,13 +6265,13 @@ index 0000000000..db93d2fc29 + return -1; + } + -+ if (BGP_LS_TLV_CHECK(attr->present_tlvs, BGP_LS_ATTR_TE_METRIC_BIT)) { ++ if (CHECK_FLAG(attr->present_tlvs, BGP_LS_ATTR_TE_METRIC_BIT)) { + flog_warn(EC_BGP_UPDATE_RCV, "BGP-LS: duplicate TE Default Metric TLV"); + return -1; + } + + attr->te_metric = stream_getl(s); -+ BGP_LS_TLV_SET(attr->present_tlvs, BGP_LS_ATTR_TE_METRIC_BIT); ++ SET_FLAG(attr->present_tlvs, BGP_LS_ATTR_TE_METRIC_BIT); + + return 0; +} @@ -6744,7 +6288,7 @@ index 0000000000..db93d2fc29 + return -1; + } + -+ if (BGP_LS_TLV_CHECK(attr->present_tlvs, BGP_LS_ATTR_IGP_METRIC_BIT)) { ++ if (CHECK_FLAG(attr->present_tlvs, BGP_LS_ATTR_IGP_METRIC_BIT)) { + flog_warn(EC_BGP_UPDATE_RCV, "BGP-LS: duplicate IGP Metric TLV"); + return -1; + } @@ -6753,7 +6297,7 @@ index 0000000000..db93d2fc29 + for (int i = 0; i < length; i++) + attr->igp_metric = (attr->igp_metric << 8) | stream_getc(s); + attr->igp_metric_len = length; -+ BGP_LS_TLV_SET(attr->present_tlvs, BGP_LS_ATTR_IGP_METRIC_BIT); ++ SET_FLAG(attr->present_tlvs, BGP_LS_ATTR_IGP_METRIC_BIT); + + return 0; +} @@ -6779,7 +6323,7 @@ index 0000000000..db93d2fc29 + return -1; + } + -+ if (BGP_LS_TLV_CHECK(attr->present_tlvs, BGP_LS_ATTR_SRLG_BIT)) { ++ if (CHECK_FLAG(attr->present_tlvs, BGP_LS_ATTR_SRLG_BIT)) { + flog_warn(EC_BGP_UPDATE_RCV, "BGP-LS: duplicate SRLG TLV"); + return -1; + } @@ -6788,7 +6332,7 @@ index 0000000000..db93d2fc29 + for (i = 0; i < count; i++) + attr->srlg_values[i] = stream_getl(s); + attr->srlg_count = count; -+ BGP_LS_TLV_SET(attr->present_tlvs, BGP_LS_ATTR_SRLG_BIT); ++ SET_FLAG(attr->present_tlvs, BGP_LS_ATTR_SRLG_BIT); + + return 0; +} @@ -6802,7 +6346,15 @@ index 0000000000..db93d2fc29 + if (length == 0) + return 0; + -+ if (BGP_LS_TLV_CHECK(attr->present_tlvs, BGP_LS_ATTR_LINK_NAME_BIT)) { ++ if (length > BGP_LS_MAX_LINK_NAME_LEN) { ++ flog_warn(EC_BGP_UPDATE_RCV, ++ "BGP-LS: Link Name TLV length %u exceeds maximum %u, skipping TLV", ++ length, BGP_LS_MAX_LINK_NAME_LEN); ++ stream_forward_getp(s, length); ++ return 0; ++ } ++ ++ if (CHECK_FLAG(attr->present_tlvs, BGP_LS_ATTR_LINK_NAME_BIT)) { + flog_warn(EC_BGP_UPDATE_RCV, "BGP-LS: duplicate Link Name TLV"); + return -1; + } @@ -6810,7 +6362,20 @@ index 0000000000..db93d2fc29 + attr->link_name = XCALLOC(MTYPE_BGP_LS_ATTR, length + 1); + stream_get(attr->link_name, s, length); + attr->link_name[length] = '\0'; -+ BGP_LS_TLV_SET(attr->present_tlvs, BGP_LS_ATTR_LINK_NAME_BIT); ++ ++ /* Reject names containing embedded NUL or non-printable characters. */ ++ for (uint16_t i = 0; i < length; i++) { ++ if ((unsigned char)attr->link_name[i] < 0x20 || ++ (unsigned char)attr->link_name[i] > 0x7E) { ++ flog_warn(EC_BGP_UPDATE_RCV, ++ "BGP-LS: Link Name TLV contains non-printable character at byte %u, rejecting", ++ i); ++ XFREE(MTYPE_BGP_LS_ATTR, attr->link_name); ++ return -1; ++ } ++ } ++ ++ SET_FLAG(attr->present_tlvs, BGP_LS_ATTR_LINK_NAME_BIT); + + return 0; +} @@ -6834,7 +6399,7 @@ index 0000000000..db93d2fc29 + flog_warn(EC_BGP_UPDATE_RCV, + "BGP-LS: Ignoring Adjacency SID, maximum %d is implemented", + BGP_LS_ADJ_MAX); -+ stream_forward_endp(s, length); ++ stream_forward_getp(s, length); + return 0; + } + @@ -6865,7 +6430,7 @@ index 0000000000..db93d2fc29 + attr->adj_sid[attr->adj_sid_count].sid = stream_getl(s); + attr->adj_sid_count++; + -+ BGP_LS_TLV_SET(attr->present_tlvs, BGP_LS_ATTR_ADJ_SID_BIT); ++ SET_FLAG(attr->present_tlvs, BGP_LS_ATTR_ADJ_SID_BIT); + + return 0; +} @@ -6885,7 +6450,7 @@ index 0000000000..db93d2fc29 + return -1; + } + -+ if (BGP_LS_TLV_CHECK(attr->present_tlvs, BGP_LS_ATTR_EXT_ADMIN_GROUP_BIT)) { ++ if (CHECK_FLAG(attr->present_tlvs, BGP_LS_ATTR_EXT_ADMIN_GROUP_BIT)) { + flog_warn(EC_BGP_UPDATE_RCV, "BGP-LS: duplicate Extended Admin Group TLV"); + return -1; + } @@ -6902,7 +6467,7 @@ index 0000000000..db93d2fc29 + for (size_t i = 0; i < nb_words; i++) + admin_group_bulk_set(&attr->ext_admin_group, stream_getl(s), i); + -+ BGP_LS_TLV_SET(attr->present_tlvs, BGP_LS_ATTR_EXT_ADMIN_GROUP_BIT); ++ SET_FLAG(attr->present_tlvs, BGP_LS_ATTR_EXT_ADMIN_GROUP_BIT); + + return 0; +} @@ -6918,13 +6483,13 @@ index 0000000000..db93d2fc29 + return -1; + } + -+ if (BGP_LS_TLV_CHECK(attr->present_tlvs, BGP_LS_ATTR_DELAY_BIT)) { ++ if (CHECK_FLAG(attr->present_tlvs, BGP_LS_ATTR_DELAY_BIT)) { + flog_warn(EC_BGP_UPDATE_RCV, "BGP-LS: duplicate Unidirectional Link Delay TLV"); + return -1; + } + + attr->delay = stream_getl(s); -+ BGP_LS_TLV_SET(attr->present_tlvs, BGP_LS_ATTR_DELAY_BIT); ++ SET_FLAG(attr->present_tlvs, BGP_LS_ATTR_DELAY_BIT); + + return 0; +} @@ -6940,7 +6505,7 @@ index 0000000000..db93d2fc29 + return -1; + } + -+ if (BGP_LS_TLV_CHECK(attr->present_tlvs, BGP_LS_ATTR_MIN_MAX_DELAY_BIT)) { ++ if (CHECK_FLAG(attr->present_tlvs, BGP_LS_ATTR_MIN_MAX_DELAY_BIT)) { + flog_warn(EC_BGP_UPDATE_RCV, + "BGP-LS: duplicate Min/Max Unidirectional Link Delay TLV"); + return -1; @@ -6948,7 +6513,7 @@ index 0000000000..db93d2fc29 + + attr->min_delay = stream_getl(s); + attr->max_delay = stream_getl(s); -+ BGP_LS_TLV_SET(attr->present_tlvs, BGP_LS_ATTR_MIN_MAX_DELAY_BIT); ++ SET_FLAG(attr->present_tlvs, BGP_LS_ATTR_MIN_MAX_DELAY_BIT); + + return 0; +} @@ -6964,14 +6529,14 @@ index 0000000000..db93d2fc29 + return -1; + } + -+ if (BGP_LS_TLV_CHECK(attr->present_tlvs, BGP_LS_ATTR_JITTER_BIT)) { ++ if (CHECK_FLAG(attr->present_tlvs, BGP_LS_ATTR_JITTER_BIT)) { + flog_warn(EC_BGP_UPDATE_RCV, + "BGP-LS: duplicate Unidirectional Delay Variation TLV"); + return -1; + } + + attr->jitter = stream_getl(s); -+ BGP_LS_TLV_SET(attr->present_tlvs, BGP_LS_ATTR_JITTER_BIT); ++ SET_FLAG(attr->present_tlvs, BGP_LS_ATTR_JITTER_BIT); + + return 0; +} @@ -6987,13 +6552,13 @@ index 0000000000..db93d2fc29 + return -1; + } + -+ if (BGP_LS_TLV_CHECK(attr->present_tlvs, BGP_LS_ATTR_PKT_LOSS_BIT)) { ++ if (CHECK_FLAG(attr->present_tlvs, BGP_LS_ATTR_PKT_LOSS_BIT)) { + flog_warn(EC_BGP_UPDATE_RCV, "BGP-LS: duplicate Unidirectional Link Loss TLV"); + return -1; + } + + attr->pkt_loss = stream_getl(s); -+ BGP_LS_TLV_SET(attr->present_tlvs, BGP_LS_ATTR_PKT_LOSS_BIT); ++ SET_FLAG(attr->present_tlvs, BGP_LS_ATTR_PKT_LOSS_BIT); + + return 0; +} @@ -7009,13 +6574,13 @@ index 0000000000..db93d2fc29 + return -1; + } + -+ if (BGP_LS_TLV_CHECK(attr->present_tlvs, BGP_LS_ATTR_RESIDUAL_BW_BIT)) { ++ if (CHECK_FLAG(attr->present_tlvs, BGP_LS_ATTR_RESIDUAL_BW_BIT)) { + flog_warn(EC_BGP_UPDATE_RCV, "BGP-LS: duplicate Residual Bandwidth TLV"); + return -1; + } + + stream_get(&attr->residual_bw, s, 4); -+ BGP_LS_TLV_SET(attr->present_tlvs, BGP_LS_ATTR_RESIDUAL_BW_BIT); ++ SET_FLAG(attr->present_tlvs, BGP_LS_ATTR_RESIDUAL_BW_BIT); + + return 0; +} @@ -7031,13 +6596,13 @@ index 0000000000..db93d2fc29 + return -1; + } + -+ if (BGP_LS_TLV_CHECK(attr->present_tlvs, BGP_LS_ATTR_AVAILABLE_BW_BIT)) { ++ if (CHECK_FLAG(attr->present_tlvs, BGP_LS_ATTR_AVAILABLE_BW_BIT)) { + flog_warn(EC_BGP_UPDATE_RCV, "BGP-LS: duplicate Available Bandwidth TLV"); + return -1; + } + + stream_get(&attr->available_bw, s, 4); -+ BGP_LS_TLV_SET(attr->present_tlvs, BGP_LS_ATTR_AVAILABLE_BW_BIT); ++ SET_FLAG(attr->present_tlvs, BGP_LS_ATTR_AVAILABLE_BW_BIT); + + return 0; +} @@ -7053,13 +6618,13 @@ index 0000000000..db93d2fc29 + return -1; + } + -+ if (BGP_LS_TLV_CHECK(attr->present_tlvs, BGP_LS_ATTR_UTILIZED_BW_BIT)) { ++ if (CHECK_FLAG(attr->present_tlvs, BGP_LS_ATTR_UTILIZED_BW_BIT)) { + flog_warn(EC_BGP_UPDATE_RCV, "BGP-LS: duplicate Utilized Bandwidth TLV"); + return -1; + } + + stream_get(&attr->utilized_bw, s, 4); -+ BGP_LS_TLV_SET(attr->present_tlvs, BGP_LS_ATTR_UTILIZED_BW_BIT); ++ SET_FLAG(attr->present_tlvs, BGP_LS_ATTR_UTILIZED_BW_BIT); + + return 0; +} @@ -7075,13 +6640,13 @@ index 0000000000..db93d2fc29 + return -1; + } + -+ if (BGP_LS_TLV_CHECK(attr->present_tlvs, BGP_LS_ATTR_IGP_FLAGS_BIT)) { ++ if (CHECK_FLAG(attr->present_tlvs, BGP_LS_ATTR_IGP_FLAGS_BIT)) { + flog_warn(EC_BGP_UPDATE_RCV, "BGP-LS: duplicate IGP Flags TLV"); + return -1; + } + + attr->igp_flags = stream_getc(s); -+ BGP_LS_TLV_SET(attr->present_tlvs, BGP_LS_ATTR_IGP_FLAGS_BIT); ++ SET_FLAG(attr->present_tlvs, BGP_LS_ATTR_IGP_FLAGS_BIT); + + if (length > 1) + stream_forward_getp(s, length - 1); @@ -7110,7 +6675,7 @@ index 0000000000..db93d2fc29 + return -1; + } + -+ if (BGP_LS_TLV_CHECK(attr->present_tlvs, BGP_LS_ATTR_ROUTE_TAG_BIT)) { ++ if (CHECK_FLAG(attr->present_tlvs, BGP_LS_ATTR_ROUTE_TAG_BIT)) { + flog_warn(EC_BGP_UPDATE_RCV, "BGP-LS: duplicate Route Tag TLV"); + return -1; + } @@ -7119,7 +6684,7 @@ index 0000000000..db93d2fc29 + for (i = 0; i < count; i++) + attr->route_tags[i] = stream_getl(s); + attr->route_tag_count = count; -+ BGP_LS_TLV_SET(attr->present_tlvs, BGP_LS_ATTR_ROUTE_TAG_BIT); ++ SET_FLAG(attr->present_tlvs, BGP_LS_ATTR_ROUTE_TAG_BIT); + + return 0; +} @@ -7136,13 +6701,13 @@ index 0000000000..db93d2fc29 + return -1; + } + -+ if (BGP_LS_TLV_CHECK(attr->present_tlvs, BGP_LS_ATTR_PREFIX_METRIC_BIT)) { ++ if (CHECK_FLAG(attr->present_tlvs, BGP_LS_ATTR_PREFIX_METRIC_BIT)) { + flog_warn(EC_BGP_UPDATE_RCV, "BGP-LS: duplicate Prefix Metric TLV"); + return -1; + } + + attr->prefix_metric = stream_getl(s); -+ BGP_LS_TLV_SET(attr->present_tlvs, BGP_LS_ATTR_PREFIX_METRIC_BIT); ++ SET_FLAG(attr->present_tlvs, BGP_LS_ATTR_PREFIX_METRIC_BIT); + + return 0; +} @@ -7159,7 +6724,7 @@ index 0000000000..db93d2fc29 + return -1; + } + -+ if (BGP_LS_TLV_CHECK(attr->present_tlvs, BGP_LS_ATTR_OSPF_FWD_ADDR_BIT)) { ++ if (CHECK_FLAG(attr->present_tlvs, BGP_LS_ATTR_OSPF_FWD_ADDR_BIT)) { + flog_warn(EC_BGP_UPDATE_RCV, "BGP-LS: duplicate OSPF Forwarding Address TLV"); + return -1; + } @@ -7171,7 +6736,7 @@ index 0000000000..db93d2fc29 + stream_get(&attr->ospf_fwd_addr6, s, 16); + } + -+ BGP_LS_TLV_SET(attr->present_tlvs, BGP_LS_ATTR_OSPF_FWD_ADDR_BIT); ++ SET_FLAG(attr->present_tlvs, BGP_LS_ATTR_OSPF_FWD_ADDR_BIT); + return 0; +} + @@ -7184,7 +6749,7 @@ index 0000000000..db93d2fc29 + return -1; + } + -+ if (BGP_LS_TLV_CHECK(attr->present_tlvs, BGP_LS_ATTR_IPV4_ROUTER_ID_LOCAL_BIT)) { ++ if (CHECK_FLAG(attr->present_tlvs, BGP_LS_ATTR_IPV4_ROUTER_ID_LOCAL_BIT)) { + /* + * RFC 9552 Section 5.3.1.4 allows multiple IPv4 Local Router-ID TLVs + * when a node has more than one auxiliary Router-ID. We currently @@ -7198,7 +6763,7 @@ index 0000000000..db93d2fc29 + } + + stream_get(&attr->ipv4_router_id_local, s, 4); -+ BGP_LS_TLV_SET(attr->present_tlvs, BGP_LS_ATTR_IPV4_ROUTER_ID_LOCAL_BIT); ++ SET_FLAG(attr->present_tlvs, BGP_LS_ATTR_IPV4_ROUTER_ID_LOCAL_BIT); + return 0; +} + @@ -7211,7 +6776,7 @@ index 0000000000..db93d2fc29 + return -1; + } + -+ if (BGP_LS_TLV_CHECK(attr->present_tlvs, BGP_LS_ATTR_IPV6_ROUTER_ID_LOCAL_BIT)) { ++ if (CHECK_FLAG(attr->present_tlvs, BGP_LS_ATTR_IPV6_ROUTER_ID_LOCAL_BIT)) { + /* + * RFC 9552 Section 5.3.1.4 allows multiple IPv6 Local Router-ID TLVs + * when a node has more than one auxiliary Router-ID. We currently @@ -7225,7 +6790,7 @@ index 0000000000..db93d2fc29 + } + + stream_get(&attr->ipv6_router_id_local, s, 16); -+ BGP_LS_TLV_SET(attr->present_tlvs, BGP_LS_ATTR_IPV6_ROUTER_ID_LOCAL_BIT); ++ SET_FLAG(attr->present_tlvs, BGP_LS_ATTR_IPV6_ROUTER_ID_LOCAL_BIT); + return 0; +} + @@ -7238,7 +6803,7 @@ index 0000000000..db93d2fc29 + return -1; + } + -+ if (BGP_LS_TLV_CHECK(attr->present_tlvs, BGP_LS_ATTR_IPV4_ROUTER_ID_REMOTE_BIT)) { ++ if (CHECK_FLAG(attr->present_tlvs, BGP_LS_ATTR_IPV4_ROUTER_ID_REMOTE_BIT)) { + /* + * RFC 9552 Section 5.3.2.1 allows multiple IPv4 Remote Router-ID TLVs + * when a node has more than one auxiliary Router-ID. We currently @@ -7252,7 +6817,7 @@ index 0000000000..db93d2fc29 + } + + stream_get(&attr->ipv4_router_id_remote, s, 4); -+ BGP_LS_TLV_SET(attr->present_tlvs, BGP_LS_ATTR_IPV4_ROUTER_ID_REMOTE_BIT); ++ SET_FLAG(attr->present_tlvs, BGP_LS_ATTR_IPV4_ROUTER_ID_REMOTE_BIT); + return 0; +} + @@ -7265,7 +6830,7 @@ index 0000000000..db93d2fc29 + return -1; + } + -+ if (BGP_LS_TLV_CHECK(attr->present_tlvs, BGP_LS_ATTR_IPV6_ROUTER_ID_REMOTE_BIT)) { ++ if (CHECK_FLAG(attr->present_tlvs, BGP_LS_ATTR_IPV6_ROUTER_ID_REMOTE_BIT)) { + /* + * RFC 9552 Section 5.3.2.1 allows multiple IPv6 Remote Router-ID TLVs + * when a node has more than one auxiliary Router-ID. We currently @@ -7279,7 +6844,7 @@ index 0000000000..db93d2fc29 + } + + stream_get(&attr->ipv6_router_id_remote, s, 16); -+ BGP_LS_TLV_SET(attr->present_tlvs, BGP_LS_ATTR_IPV6_ROUTER_ID_REMOTE_BIT); ++ SET_FLAG(attr->present_tlvs, BGP_LS_ATTR_IPV6_ROUTER_ID_REMOTE_BIT); + return 0; +} + @@ -7292,13 +6857,13 @@ index 0000000000..db93d2fc29 + return -1; + } + -+ if (BGP_LS_TLV_CHECK(attr->present_tlvs, BGP_LS_ATTR_LINK_PROTECTION_BIT)) { ++ if (CHECK_FLAG(attr->present_tlvs, BGP_LS_ATTR_LINK_PROTECTION_BIT)) { + flog_warn(EC_BGP_UPDATE_RCV, "BGP-LS: duplicate Link Protection Type TLV"); + return -1; + } + + attr->link_protection = stream_getw(s); -+ BGP_LS_TLV_SET(attr->present_tlvs, BGP_LS_ATTR_LINK_PROTECTION_BIT); ++ SET_FLAG(attr->present_tlvs, BGP_LS_ATTR_LINK_PROTECTION_BIT); + return 0; +} + @@ -7311,13 +6876,13 @@ index 0000000000..db93d2fc29 + return -1; + } + -+ if (BGP_LS_TLV_CHECK(attr->present_tlvs, BGP_LS_ATTR_MPLS_PROTOCOL_BIT)) { ++ if (CHECK_FLAG(attr->present_tlvs, BGP_LS_ATTR_MPLS_PROTOCOL_BIT)) { + flog_warn(EC_BGP_UPDATE_RCV, "BGP-LS: duplicate MPLS Protocol Mask TLV"); + return -1; + } + + attr->mpls_protocol_mask = stream_getc(s); -+ BGP_LS_TLV_SET(attr->present_tlvs, BGP_LS_ATTR_MPLS_PROTOCOL_BIT); ++ SET_FLAG(attr->present_tlvs, BGP_LS_ATTR_MPLS_PROTOCOL_BIT); + return 0; +} + @@ -7340,7 +6905,7 @@ index 0000000000..db93d2fc29 + return -1; + } + -+ if (BGP_LS_TLV_CHECK(attr->present_tlvs, BGP_LS_ATTR_EXTENDED_TAG_BIT)) { ++ if (CHECK_FLAG(attr->present_tlvs, BGP_LS_ATTR_EXTENDED_TAG_BIT)) { + flog_warn(EC_BGP_UPDATE_RCV, "BGP-LS: duplicate Extended Route Tag TLV"); + return -1; + } @@ -7351,7 +6916,7 @@ index 0000000000..db93d2fc29 + for (uint16_t i = 0; i < count; i++) + attr->extended_tags[i] = stream_getq(s); + -+ BGP_LS_TLV_SET(attr->present_tlvs, BGP_LS_ATTR_EXTENDED_TAG_BIT); ++ SET_FLAG(attr->present_tlvs, BGP_LS_ATTR_EXTENDED_TAG_BIT); + return 0; +} + @@ -7378,7 +6943,7 @@ index 0000000000..db93d2fc29 + * RFC8665 (OSPFv2), 5; RFC8666 (OSPFv3), 6: + * It MAY appear more than once in the parent TLV + */ -+ if (BGP_LS_TLV_CHECK(attr->present_tlvs, BGP_LS_ATTR_PREFIX_SID_BIT)) { ++ if (CHECK_FLAG(attr->present_tlvs, BGP_LS_ATTR_PREFIX_SID_BIT)) { + flog_warn(EC_BGP_UPDATE_RCV, + "BGP-LS: Only one Prefix SID per prefix is supported, ignoring another one"); + stream_forward_getp(s, length); @@ -7410,7 +6975,7 @@ index 0000000000..db93d2fc29 + attr->prefix_sid.sid = 0; + for (int i = 0; i < sid_len; i++) + attr->prefix_sid.sid = (attr->prefix_sid.sid << 8) | stream_getc(s); -+ BGP_LS_TLV_SET(attr->present_tlvs, BGP_LS_ATTR_PREFIX_SID_BIT); ++ SET_FLAG(attr->present_tlvs, BGP_LS_ATTR_PREFIX_SID_BIT); + + return 0; +} @@ -7623,8 +7188,8 @@ index 0000000000..db93d2fc29 +int bgp_ls_attr_process_with_protocol_id(struct bgp_ls_attr *attr, + enum bgp_ls_protocol_id protocol_id) +{ -+ if (BGP_LS_TLV_CHECK(attr->present_tlvs, BGP_LS_ATTR_ADJ_SID_BIT)) { -+ for (int i = attr->adj_sid_count - 1; i > 0; i--) { ++ if (CHECK_FLAG(attr->present_tlvs, BGP_LS_ATTR_ADJ_SID_BIT)) { ++ for (int i = attr->adj_sid_count - 1; i >= 0; i--) { + int sid_len = bgp_ls_attr_adjacency_sid_len(attr->adj_sid[i].flags, + protocol_id); + @@ -7635,6 +7200,7 @@ index 0000000000..db93d2fc29 + + memmove(&attr->adj_sid[i], &attr->adj_sid[i + 1], + (attr->adj_sid_count - i - 1) * sizeof(attr->adj_sid[0])); ++ attr->adj_sid_count--; + } + } + } @@ -7650,11 +7216,11 @@ index 0000000000..db93d2fc29 + json_object *json_ls_attr = json_object_new_object(); + + /* Node Name */ -+ if (BGP_LS_TLV_CHECK(ls_attr->present_tlvs, BGP_LS_ATTR_NODE_NAME_BIT)) ++ if (CHECK_FLAG(ls_attr->present_tlvs, BGP_LS_ATTR_NODE_NAME_BIT)) + json_object_string_add(json_ls_attr, "nodeName", + ls_attr->node_name ? ls_attr->node_name : "(null)"); + -+ if (BGP_LS_TLV_CHECK(ls_attr->present_tlvs, BGP_LS_ATTR_ISIS_AREA_BIT)) { ++ if (CHECK_FLAG(ls_attr->present_tlvs, BGP_LS_ATTR_ISIS_AREA_BIT)) { + struct iso_address addr; + + addr.addr_len = ls_attr->isis_area_id_len; @@ -7663,24 +7229,24 @@ index 0000000000..db93d2fc29 + } + + /* Local TE Router-ID (IPv4) */ -+ if (BGP_LS_TLV_CHECK(ls_attr->present_tlvs, BGP_LS_ATTR_IPV4_ROUTER_ID_LOCAL_BIT)) ++ if (CHECK_FLAG(ls_attr->present_tlvs, BGP_LS_ATTR_IPV4_ROUTER_ID_LOCAL_BIT)) + json_object_string_addf(json_ls_attr, "routerIdLocal", "%pI4", + &ls_attr->ipv4_router_id_local); + + /* Local TE Router-ID (IPv6) */ -+ if (BGP_LS_TLV_CHECK(ls_attr->present_tlvs, BGP_LS_ATTR_IPV6_ROUTER_ID_LOCAL_BIT)) ++ if (CHECK_FLAG(ls_attr->present_tlvs, BGP_LS_ATTR_IPV6_ROUTER_ID_LOCAL_BIT)) + json_object_string_addf(json_ls_attr, "routerIdLocalV6", "%pI6", + &ls_attr->ipv6_router_id_local); + + /* Link bandwidth */ -+ if (BGP_LS_TLV_CHECK(ls_attr->present_tlvs, BGP_LS_ATTR_MAX_LINK_BW_BIT)) ++ if (CHECK_FLAG(ls_attr->present_tlvs, BGP_LS_ATTR_MAX_LINK_BW_BIT)) + json_object_double_add(json_ls_attr, "maxLinkBandwidth", ls_attr->max_link_bw); + -+ if (BGP_LS_TLV_CHECK(ls_attr->present_tlvs, BGP_LS_ATTR_MAX_RESV_BW_BIT)) ++ if (CHECK_FLAG(ls_attr->present_tlvs, BGP_LS_ATTR_MAX_RESV_BW_BIT)) + json_object_double_add(json_ls_attr, "maxResvLinkBandwidth", + ls_attr->max_resv_bw); + -+ if (BGP_LS_TLV_CHECK(ls_attr->present_tlvs, BGP_LS_ATTR_UNRESV_BW_BIT)) { ++ if (CHECK_FLAG(ls_attr->present_tlvs, BGP_LS_ATTR_UNRESV_BW_BIT)) { + json_object *jbw = json_object_new_array(); + + json_object_object_add(json_ls_attr, "unreservedBandwidth", jbw); @@ -7694,15 +7260,15 @@ index 0000000000..db93d2fc29 + } + } + -+ if (BGP_LS_TLV_CHECK(ls_attr->present_tlvs, BGP_LS_ATTR_IGP_METRIC_BIT)) ++ if (CHECK_FLAG(ls_attr->present_tlvs, BGP_LS_ATTR_IGP_METRIC_BIT)) + json_object_int_add(json_ls_attr, "igpMetric", ls_attr->igp_metric); + + /* TE Default Metric */ -+ if (BGP_LS_TLV_CHECK(ls_attr->present_tlvs, BGP_LS_ATTR_TE_METRIC_BIT)) ++ if (CHECK_FLAG(ls_attr->present_tlvs, BGP_LS_ATTR_TE_METRIC_BIT)) + json_object_int_add(json_ls_attr, "teMetric", ls_attr->te_metric); + + /* SR Capabilities */ -+ if (BGP_LS_TLV_CHECK(ls_attr->present_tlvs, BGP_LS_ATTR_SR_CAPABILITIES_BIT)) { ++ if (CHECK_FLAG(ls_attr->present_tlvs, BGP_LS_ATTR_SR_CAPABILITIES_BIT)) { + json_object *jsrcap = json_object_new_object(); + + json_object_object_add(json_ls_attr, "srCapabilities", jsrcap); @@ -7712,24 +7278,31 @@ index 0000000000..db93d2fc29 + } + + /* Node MSD */ -+ if (BGP_LS_TLV_CHECK(ls_attr->present_tlvs, BGP_LS_ATTR_NODE_MSD_BIT)) -+ json_object_int_add(json_ls_attr, "msd", ls_attr->msd); ++ if (CHECK_FLAG(ls_attr->present_tlvs, BGP_LS_ATTR_NODE_MSD_BIT)) { ++ json_object *jma = json_object_new_array(); ++ json_object *jobj = json_object_new_object(); ++ ++ json_object_object_add(json_ls_attr, "msd", jma); ++ json_object_int_add(jobj, "type", BGP_LS_IGP_MSD_TYPE_BASE_MPLS); ++ json_object_int_add(jobj, "value", ls_attr->msd); ++ json_object_array_add(jma, jobj); ++ } + + /* Administrative Group */ -+ if (BGP_LS_TLV_CHECK(ls_attr->present_tlvs, BGP_LS_ATTR_ADMIN_GROUP_BIT)) ++ if (CHECK_FLAG(ls_attr->present_tlvs, BGP_LS_ATTR_ADMIN_GROUP_BIT)) + json_object_int_add(json_ls_attr, "adminGroup", ls_attr->admin_group); + + /* Link Protection Type */ -+ if (BGP_LS_TLV_CHECK(ls_attr->present_tlvs, BGP_LS_ATTR_LINK_PROTECTION_BIT)) ++ if (CHECK_FLAG(ls_attr->present_tlvs, BGP_LS_ATTR_LINK_PROTECTION_BIT)) + json_object_int_add(json_ls_attr, "linkProtection", ls_attr->link_protection); + + /* MPLS Protocol Mask */ -+ if (BGP_LS_TLV_CHECK(ls_attr->present_tlvs, BGP_LS_ATTR_MPLS_PROTOCOL_BIT)) ++ if (CHECK_FLAG(ls_attr->present_tlvs, BGP_LS_ATTR_MPLS_PROTOCOL_BIT)) + json_object_int_add(json_ls_attr, "mplsProtocolMask", + ls_attr->mpls_protocol_mask); + + /* SRLG */ -+ if (BGP_LS_TLV_CHECK(ls_attr->present_tlvs, BGP_LS_ATTR_SRLG_BIT)) { ++ if (CHECK_FLAG(ls_attr->present_tlvs, BGP_LS_ATTR_SRLG_BIT)) { + struct json_object *jsrlg = json_object_new_array(); + + json_object_object_add(json_ls_attr, "srlgs", jsrlg); @@ -7742,11 +7315,11 @@ index 0000000000..db93d2fc29 + } + + /* Link Name */ -+ if (BGP_LS_TLV_CHECK(ls_attr->present_tlvs, BGP_LS_ATTR_LINK_NAME_BIT)) ++ if (CHECK_FLAG(ls_attr->present_tlvs, BGP_LS_ATTR_LINK_NAME_BIT)) + json_object_string_add(json_ls_attr, "linkName", ls_attr->link_name); + + /* Adjacency SID */ -+ if (BGP_LS_TLV_CHECK(ls_attr->present_tlvs, BGP_LS_ATTR_ADJ_SID_BIT)) { ++ if (CHECK_FLAG(ls_attr->present_tlvs, BGP_LS_ATTR_ADJ_SID_BIT)) { + struct json_object *jadj = json_object_new_array(); + + json_object_object_add(json_ls_attr, "adjSids", jadj); @@ -7762,41 +7335,41 @@ index 0000000000..db93d2fc29 + } + + /* Performance Metrics - Link Delay */ -+ if (BGP_LS_TLV_CHECK(ls_attr->present_tlvs, BGP_LS_ATTR_DELAY_BIT)) ++ if (CHECK_FLAG(ls_attr->present_tlvs, BGP_LS_ATTR_DELAY_BIT)) + json_object_int_add(json_ls_attr, "delay", ls_attr->delay); + + /* Min/Max Delay */ -+ if (BGP_LS_TLV_CHECK(ls_attr->present_tlvs, BGP_LS_ATTR_MIN_MAX_DELAY_BIT)) { ++ if (CHECK_FLAG(ls_attr->present_tlvs, BGP_LS_ATTR_MIN_MAX_DELAY_BIT)) { + json_object_int_add(json_ls_attr, "minDelay", ls_attr->min_delay); + json_object_int_add(json_ls_attr, "maxDelay", ls_attr->max_delay); + } + + /* Delay Variation */ -+ if (BGP_LS_TLV_CHECK(ls_attr->present_tlvs, BGP_LS_ATTR_JITTER_BIT)) ++ if (CHECK_FLAG(ls_attr->present_tlvs, BGP_LS_ATTR_JITTER_BIT)) + json_object_int_add(json_ls_attr, "jitter", ls_attr->jitter); + + /* Packet Loss */ -+ if (BGP_LS_TLV_CHECK(ls_attr->present_tlvs, BGP_LS_ATTR_PKT_LOSS_BIT)) ++ if (CHECK_FLAG(ls_attr->present_tlvs, BGP_LS_ATTR_PKT_LOSS_BIT)) + json_object_double_add(json_ls_attr, "loss", ls_attr->pkt_loss * LOSS_PRECISION); + + /* Residual Bandwidth */ -+ if (BGP_LS_TLV_CHECK(ls_attr->present_tlvs, BGP_LS_ATTR_RESIDUAL_BW_BIT)) ++ if (CHECK_FLAG(ls_attr->present_tlvs, BGP_LS_ATTR_RESIDUAL_BW_BIT)) + json_object_double_add(json_ls_attr, "residualBandwidth", ls_attr->residual_bw); + + /* Available Bandwidth */ -+ if (BGP_LS_TLV_CHECK(ls_attr->present_tlvs, BGP_LS_ATTR_AVAILABLE_BW_BIT)) ++ if (CHECK_FLAG(ls_attr->present_tlvs, BGP_LS_ATTR_AVAILABLE_BW_BIT)) + json_object_double_add(json_ls_attr, "availableBandwidth", ls_attr->available_bw); + + /* Utilized Bandwidth */ -+ if (BGP_LS_TLV_CHECK(ls_attr->present_tlvs, BGP_LS_ATTR_UTILIZED_BW_BIT)) ++ if (CHECK_FLAG(ls_attr->present_tlvs, BGP_LS_ATTR_UTILIZED_BW_BIT)) + json_object_double_add(json_ls_attr, "utilizedBandwidth", ls_attr->utilized_bw); + + /* IGP Flags (for prefixes) */ -+ if (BGP_LS_TLV_CHECK(ls_attr->present_tlvs, BGP_LS_ATTR_IGP_FLAGS_BIT)) ++ if (CHECK_FLAG(ls_attr->present_tlvs, BGP_LS_ATTR_IGP_FLAGS_BIT)) + json_object_string_addf(json_ls_attr, "flags", "0x%x", ls_attr->igp_flags); + + /* Route Tags */ -+ if (BGP_LS_TLV_CHECK(ls_attr->present_tlvs, BGP_LS_ATTR_ROUTE_TAG_BIT)) { ++ if (CHECK_FLAG(ls_attr->present_tlvs, BGP_LS_ATTR_ROUTE_TAG_BIT)) { + struct json_object *jtags = json_object_new_array(); + + json_object_object_add(json_ls_attr, "tags", jtags); @@ -7809,7 +7382,7 @@ index 0000000000..db93d2fc29 + } + + /* Extended Tags */ -+ if (BGP_LS_TLV_CHECK(ls_attr->present_tlvs, BGP_LS_ATTR_EXTENDED_TAG_BIT)) { ++ if (CHECK_FLAG(ls_attr->present_tlvs, BGP_LS_ATTR_EXTENDED_TAG_BIT)) { + struct json_object *jtags = json_object_new_array(); + + json_object_object_add(json_ls_attr, "extendedTags", jtags); @@ -7822,21 +7395,21 @@ index 0000000000..db93d2fc29 + } + + /* Prefix Metric */ -+ if (BGP_LS_TLV_CHECK(ls_attr->present_tlvs, BGP_LS_ATTR_PREFIX_METRIC_BIT)) ++ if (CHECK_FLAG(ls_attr->present_tlvs, BGP_LS_ATTR_PREFIX_METRIC_BIT)) + json_object_int_add(json_ls_attr, "prefixMetric", ls_attr->prefix_metric); + + /* OSPF Forwarding Address (IPv4) */ -+ if (BGP_LS_TLV_CHECK(ls_attr->present_tlvs, BGP_LS_ATTR_OSPF_FWD_ADDR_BIT)) { ++ if (CHECK_FLAG(ls_attr->present_tlvs, BGP_LS_ATTR_OSPF_FWD_ADDR_BIT)) { + if (ls_attr->ospf_fwd_addr.s_addr != INADDR_ANY) + json_object_string_addf(json_ls_attr, "forwardingAddr", "%pI4", + &ls_attr->ospf_fwd_addr); + else if (!IN6_IS_ADDR_UNSPECIFIED(&ls_attr->ospf_fwd_addr6)) + json_object_string_addf(json_ls_attr, "forwardingAddrV6", "%pI6", -+ &ls_attr->ospf_fwd_addr6); ++ &ls_attr->ospf_fwd_addr6); + } + + /* Prefix SID */ -+ if (BGP_LS_TLV_CHECK(ls_attr->present_tlvs, BGP_LS_ATTR_PREFIX_SID_BIT)) { ++ if (CHECK_FLAG(ls_attr->present_tlvs, BGP_LS_ATTR_PREFIX_SID_BIT)) { + json_object *jpref = json_object_new_object(); + + json_object_object_add(json_ls_attr, "prefixSid", jpref); @@ -7876,14 +7449,14 @@ index 0000000000..db93d2fc29 + col = strlen(INIT_INDENT); + + /* Node Name */ -+ if (BGP_LS_TLV_CHECK(ls_attr->present_tlvs, BGP_LS_ATTR_NODE_NAME_BIT)) { ++ if (CHECK_FLAG(ls_attr->present_tlvs, BGP_LS_ATTR_NODE_NAME_BIT)) { + CHECK_WRAP(); + col += vty_out(vty, "Node Name: %s", + ls_attr->node_name ? ls_attr->node_name : "(null)"); + } + + /* IS-IS Area Identifier */ -+ if (BGP_LS_TLV_CHECK(ls_attr->present_tlvs, BGP_LS_ATTR_ISIS_AREA_BIT)) { ++ if (CHECK_FLAG(ls_attr->present_tlvs, BGP_LS_ATTR_ISIS_AREA_BIT)) { + struct iso_address addr; + + CHECK_WRAP(); @@ -7893,82 +7466,83 @@ index 0000000000..db93d2fc29 + } + + /* Local TE Router-ID (IPv4) */ -+ if (BGP_LS_TLV_CHECK(ls_attr->present_tlvs, BGP_LS_ATTR_IPV4_ROUTER_ID_LOCAL_BIT)) { ++ if (CHECK_FLAG(ls_attr->present_tlvs, BGP_LS_ATTR_IPV4_ROUTER_ID_LOCAL_BIT)) { + CHECK_WRAP(); + col += vty_out(vty, "Local TE Router-ID: %pI4", &ls_attr->ipv4_router_id_local); + } + + /* Local TE Router-ID (IPv6) */ -+ if (BGP_LS_TLV_CHECK(ls_attr->present_tlvs, BGP_LS_ATTR_IPV6_ROUTER_ID_LOCAL_BIT)) { ++ if (CHECK_FLAG(ls_attr->present_tlvs, BGP_LS_ATTR_IPV6_ROUTER_ID_LOCAL_BIT)) { + CHECK_WRAP(); + col += vty_out(vty, "Local TE Router-ID: %pI6", &ls_attr->ipv6_router_id_local); + } + + /* Link bandwidth */ -+ if (BGP_LS_TLV_CHECK(ls_attr->present_tlvs, BGP_LS_ATTR_MAX_LINK_BW_BIT)) { ++ if (CHECK_FLAG(ls_attr->present_tlvs, BGP_LS_ATTR_MAX_LINK_BW_BIT)) { + CHECK_WRAP(); + col += vty_out(vty, "Maximum Link BW (kbits/sec): %.0f", + ls_attr->max_link_bw / 1000.0); + } + -+ if (BGP_LS_TLV_CHECK(ls_attr->present_tlvs, BGP_LS_ATTR_MAX_RESV_BW_BIT)) { ++ if (CHECK_FLAG(ls_attr->present_tlvs, BGP_LS_ATTR_MAX_RESV_BW_BIT)) { + CHECK_WRAP(); + col += vty_out(vty, "Maximum Reserv Link BW (kbits/sec): %.0f", + ls_attr->max_resv_bw / 1000.0); + } + -+ if (BGP_LS_TLV_CHECK(ls_attr->present_tlvs, BGP_LS_ATTR_UNRESV_BW_BIT)) { ++ if (CHECK_FLAG(ls_attr->present_tlvs, BGP_LS_ATTR_UNRESV_BW_BIT)) { + CHECK_WRAP(); + col += vty_out(vty, "Maximum Unreserv Link BW (kbits/sec):"); + for (int j = 0; j < 8; j++) + col += vty_out(vty, " %.0f", ls_attr->unreserved_bw[j] / 1000.0); + } + -+ if (BGP_LS_TLV_CHECK(ls_attr->present_tlvs, BGP_LS_ATTR_IGP_METRIC_BIT)) { ++ if (CHECK_FLAG(ls_attr->present_tlvs, BGP_LS_ATTR_IGP_METRIC_BIT)) { + CHECK_WRAP(); + col += vty_out(vty, "metric: %u", ls_attr->igp_metric); + } + + /* TE Default Metric */ -+ if (BGP_LS_TLV_CHECK(ls_attr->present_tlvs, BGP_LS_ATTR_TE_METRIC_BIT)) { ++ if (CHECK_FLAG(ls_attr->present_tlvs, BGP_LS_ATTR_TE_METRIC_BIT)) { + CHECK_WRAP(); + col += vty_out(vty, "TE Default Metric: %u", ls_attr->te_metric); + } + + /* Administrative Group */ -+ if (BGP_LS_TLV_CHECK(ls_attr->present_tlvs, BGP_LS_ATTR_ADMIN_GROUP_BIT)) { ++ if (CHECK_FLAG(ls_attr->present_tlvs, BGP_LS_ATTR_ADMIN_GROUP_BIT)) { + CHECK_WRAP(); + col += vty_out(vty, "Administrative Group: 0x%08x", ls_attr->admin_group); + } + + /* Link Protection Type */ -+ if (BGP_LS_TLV_CHECK(ls_attr->present_tlvs, BGP_LS_ATTR_LINK_PROTECTION_BIT)) { ++ if (CHECK_FLAG(ls_attr->present_tlvs, BGP_LS_ATTR_LINK_PROTECTION_BIT)) { + CHECK_WRAP(); + vty_out(vty, "Link Protection Type: 0x%04x", ls_attr->link_protection); + } + + /* MPLS Protocol Mask */ -+ if (BGP_LS_TLV_CHECK(ls_attr->present_tlvs, BGP_LS_ATTR_MPLS_PROTOCOL_BIT)) { ++ if (CHECK_FLAG(ls_attr->present_tlvs, BGP_LS_ATTR_MPLS_PROTOCOL_BIT)) { + CHECK_WRAP(); + col += vty_out(vty, "MPLS Protocol Mask: 0x%02x", ls_attr->mpls_protocol_mask); + } + + /* SR Capabilities */ -+ if (BGP_LS_TLV_CHECK(ls_attr->present_tlvs, BGP_LS_ATTR_SR_CAPABILITIES_BIT)) { ++ if (CHECK_FLAG(ls_attr->present_tlvs, BGP_LS_ATTR_SR_CAPABILITIES_BIT)) { + CHECK_WRAP(); + col += vty_out(vty, "SR Capabilities: flags 0x%x range %d-%d", ls_attr->srgb.flag, + ls_attr->srgb.lower_bound, -+ ls_attr->srgb.lower_bound + ls_attr->srgb.range_size); ++ ls_attr->srgb.lower_bound + ls_attr->srgb.range_size - 1); + } + + /* Node MSD */ -+ if (BGP_LS_TLV_CHECK(ls_attr->present_tlvs, BGP_LS_ATTR_NODE_MSD_BIT)) { ++ if (CHECK_FLAG(ls_attr->present_tlvs, BGP_LS_ATTR_NODE_MSD_BIT)) { + CHECK_WRAP(); -+ col += vty_out(vty, "MSD: %d", ls_attr->msd); ++ col += vty_out(vty, "MSD: Type %d Value %d", BGP_LS_IGP_MSD_TYPE_BASE_MPLS, ++ ls_attr->msd); + } + + /* SRLG */ -+ if (BGP_LS_TLV_CHECK(ls_attr->present_tlvs, BGP_LS_ATTR_SRLG_BIT)) { ++ if (CHECK_FLAG(ls_attr->present_tlvs, BGP_LS_ATTR_SRLG_BIT)) { + CHECK_WRAP(); + col += vty_out(vty, "SRLG:"); + col += 5; @@ -7977,13 +7551,13 @@ index 0000000000..db93d2fc29 + } + + /* Link Name */ -+ if (BGP_LS_TLV_CHECK(ls_attr->present_tlvs, BGP_LS_ATTR_LINK_NAME_BIT)) { ++ if (CHECK_FLAG(ls_attr->present_tlvs, BGP_LS_ATTR_LINK_NAME_BIT)) { + CHECK_WRAP(); + col += vty_out(vty, "Link Name: %s", ls_attr->link_name); + } + + /* Adjacency SID */ -+ if (BGP_LS_TLV_CHECK(ls_attr->present_tlvs, BGP_LS_ATTR_ADJ_SID_BIT)) { ++ if (CHECK_FLAG(ls_attr->present_tlvs, BGP_LS_ATTR_ADJ_SID_BIT)) { + for (int i = 0; i < ls_attr->adj_sid_count; i++) { + CHECK_WRAP(); + col += vty_out(vty, "Adjacency-SID: %u Flags: 0x%x Weight: 0x%x", @@ -7993,58 +7567,58 @@ index 0000000000..db93d2fc29 + } + + /* Performance Metrics - Link Delay */ -+ if (BGP_LS_TLV_CHECK(ls_attr->present_tlvs, BGP_LS_ATTR_DELAY_BIT)) { ++ if (CHECK_FLAG(ls_attr->present_tlvs, BGP_LS_ATTR_DELAY_BIT)) { + CHECK_WRAP(); + col += vty_out(vty, "Link Delay: %u us", ls_attr->delay); + } + + /* Min/Max Delay */ -+ if (BGP_LS_TLV_CHECK(ls_attr->present_tlvs, BGP_LS_ATTR_MIN_MAX_DELAY_BIT)) { ++ if (CHECK_FLAG(ls_attr->present_tlvs, BGP_LS_ATTR_MIN_MAX_DELAY_BIT)) { + CHECK_WRAP(); + col += vty_out(vty, "Min Delay: %u us Max Delay: %u us", ls_attr->min_delay, + ls_attr->max_delay); + } + + /* Delay Variation */ -+ if (BGP_LS_TLV_CHECK(ls_attr->present_tlvs, BGP_LS_ATTR_JITTER_BIT)) { ++ if (CHECK_FLAG(ls_attr->present_tlvs, BGP_LS_ATTR_JITTER_BIT)) { + CHECK_WRAP(); + col += vty_out(vty, "Delay Variation: %u us", ls_attr->jitter); + } + + /* Packet Loss */ -+ if (BGP_LS_TLV_CHECK(ls_attr->present_tlvs, BGP_LS_ATTR_PKT_LOSS_BIT)) { ++ if (CHECK_FLAG(ls_attr->present_tlvs, BGP_LS_ATTR_PKT_LOSS_BIT)) { + CHECK_WRAP(); + col += vty_out(vty, "Packet Loss: %g (%%)", + (float)(ls_attr->pkt_loss * LOSS_PRECISION)); + } + + /* Residual Bandwidth */ -+ if (BGP_LS_TLV_CHECK(ls_attr->present_tlvs, BGP_LS_ATTR_RESIDUAL_BW_BIT)) { ++ if (CHECK_FLAG(ls_attr->present_tlvs, BGP_LS_ATTR_RESIDUAL_BW_BIT)) { + CHECK_WRAP(); + col += vty_out(vty, "Residual BW (kbits/sec): %.0f", ls_attr->residual_bw / 1000.0); + } + + /* Available Bandwidth */ -+ if (BGP_LS_TLV_CHECK(ls_attr->present_tlvs, BGP_LS_ATTR_AVAILABLE_BW_BIT)) { ++ if (CHECK_FLAG(ls_attr->present_tlvs, BGP_LS_ATTR_AVAILABLE_BW_BIT)) { + CHECK_WRAP(); + col += vty_out(vty, "Available BW (kbits/sec): %.0f", + ls_attr->available_bw / 1000.0); + } + + /* Utilized Bandwidth */ -+ if (BGP_LS_TLV_CHECK(ls_attr->present_tlvs, BGP_LS_ATTR_UTILIZED_BW_BIT)) { ++ if (CHECK_FLAG(ls_attr->present_tlvs, BGP_LS_ATTR_UTILIZED_BW_BIT)) { + CHECK_WRAP(); + col += vty_out(vty, "Utilized BW (kbits/sec): %.0f", ls_attr->utilized_bw / 1000.0); + } + + /* IGP Flags (for prefixes) */ -+ if (BGP_LS_TLV_CHECK(ls_attr->present_tlvs, BGP_LS_ATTR_IGP_FLAGS_BIT)) { ++ if (CHECK_FLAG(ls_attr->present_tlvs, BGP_LS_ATTR_IGP_FLAGS_BIT)) { + CHECK_WRAP(); + col += vty_out(vty, "IGP flags: 0x%02x", ls_attr->igp_flags); + } + + /* Route Tags */ -+ if (BGP_LS_TLV_CHECK(ls_attr->present_tlvs, BGP_LS_ATTR_ROUTE_TAG_BIT)) { ++ if (CHECK_FLAG(ls_attr->present_tlvs, BGP_LS_ATTR_ROUTE_TAG_BIT)) { + CHECK_WRAP(); + col += vty_out(vty, "Route tag:"); + for (int j = 0; j < ls_attr->route_tag_count; j++) @@ -8052,7 +7626,7 @@ index 0000000000..db93d2fc29 + } + + /* Extended Tags */ -+ if (BGP_LS_TLV_CHECK(ls_attr->present_tlvs, BGP_LS_ATTR_EXTENDED_TAG_BIT)) { ++ if (CHECK_FLAG(ls_attr->present_tlvs, BGP_LS_ATTR_EXTENDED_TAG_BIT)) { + CHECK_WRAP(); + col += vty_out(vty, "Extended tag:"); + for (int j = 0; j < ls_attr->extended_tag_count; j++) @@ -8060,13 +7634,13 @@ index 0000000000..db93d2fc29 + } + + /* Prefix Metric */ -+ if (BGP_LS_TLV_CHECK(ls_attr->present_tlvs, BGP_LS_ATTR_PREFIX_METRIC_BIT)) { ++ if (CHECK_FLAG(ls_attr->present_tlvs, BGP_LS_ATTR_PREFIX_METRIC_BIT)) { + CHECK_WRAP(); + col += vty_out(vty, "Metric: %u", ls_attr->prefix_metric); + } + + /* OSPF Forwarding Address (IPv4) */ -+ if (BGP_LS_TLV_CHECK(ls_attr->present_tlvs, BGP_LS_ATTR_OSPF_FWD_ADDR_BIT)) { ++ if (CHECK_FLAG(ls_attr->present_tlvs, BGP_LS_ATTR_OSPF_FWD_ADDR_BIT)) { + if (ls_attr->ospf_fwd_addr.s_addr != INADDR_ANY) { + CHECK_WRAP(); + col += vty_out(vty, "Forwarding addr: %pI4", &ls_attr->ospf_fwd_addr); @@ -8077,7 +7651,7 @@ index 0000000000..db93d2fc29 + } + + /* Prefix SID */ -+ if (BGP_LS_TLV_CHECK(ls_attr->present_tlvs, BGP_LS_ATTR_PREFIX_SID_BIT)) { ++ if (CHECK_FLAG(ls_attr->present_tlvs, BGP_LS_ATTR_PREFIX_SID_BIT)) { + CHECK_WRAP(); + col += vty_out(vty, "Prefix-SID: %u Flags 0x%x algo %hhu", ls_attr->prefix_sid.sid, + ls_attr->prefix_sid.sid_flag, ls_attr->prefix_sid.algo); @@ -8098,13 +7672,33 @@ index 0000000000..db93d2fc29 + */ +void bgp_ls_nlri_display(struct vty *vty, struct bgp_ls_nlri *nlri) +{ -+ char ipaddr_str[INET6_ADDRSTRLEN]; + const char *nlri_type_str = NULL; + const char *protocol_str = NULL; ++ enum bgp_ls_protocol_id protocol_id = BGP_LS_PROTO_RESERVED; ++ uint64_t identifier = 0; + + if (!nlri) + return; + ++ /* Extract common fields from the active union member */ ++ switch (nlri->nlri_type) { ++ case BGP_LS_NLRI_TYPE_NODE: ++ protocol_id = nlri->nlri_data.node.protocol_id; ++ identifier = nlri->nlri_data.node.identifier; ++ break; ++ case BGP_LS_NLRI_TYPE_LINK: ++ protocol_id = nlri->nlri_data.link.protocol_id; ++ identifier = nlri->nlri_data.link.identifier; ++ break; ++ case BGP_LS_NLRI_TYPE_IPV4_PREFIX: ++ case BGP_LS_NLRI_TYPE_IPV6_PREFIX: ++ protocol_id = nlri->nlri_data.prefix.protocol_id; ++ identifier = nlri->nlri_data.prefix.identifier; ++ break; ++ case BGP_LS_NLRI_TYPE_RESERVED: ++ break; ++ } ++ + /* Determine NLRI type string */ + switch (nlri->nlri_type) { + case BGP_LS_NLRI_TYPE_NODE: @@ -8125,7 +7719,7 @@ index 0000000000..db93d2fc29 + } + + /* Determine protocol string */ -+ switch (nlri->nlri_data.node.protocol_id) { ++ switch (protocol_id) { + case BGP_LS_PROTO_ISIS_L1: + protocol_str = "ISIS L1"; + break; @@ -8154,7 +7748,7 @@ index 0000000000..db93d2fc29 + + vty_out(vty, "NLRI Type: %s\n", nlri_type_str); + vty_out(vty, "Protocol: %s\n", protocol_str); -+ vty_out(vty, "Identifier: 0x%" PRIx64 "\n", nlri->nlri_data.node.identifier); ++ vty_out(vty, "Identifier: 0x%" PRIx64 "\n", identifier); + + /* Display Local Node Descriptor */ + vty_out(vty, "Local Node Descriptor:\n"); @@ -8170,13 +7764,13 @@ index 0000000000..db93d2fc29 + } + + if (local_node) { -+ if (BGP_LS_TLV_CHECK(local_node->present_tlvs, BGP_LS_NODE_DESC_AS_BIT)) ++ if (CHECK_FLAG(local_node->present_tlvs, BGP_LS_NODE_DESC_AS_BIT)) + vty_out(vty, "\tAS Number: %u\n", local_node->asn); -+ if (BGP_LS_TLV_CHECK(local_node->present_tlvs, BGP_LS_NODE_DESC_OSPF_AREA_BIT)) ++ if (CHECK_FLAG(local_node->present_tlvs, BGP_LS_NODE_DESC_OSPF_AREA_BIT)) + vty_out(vty, "\tArea ID: %pI4\n", (in_addr_t *)&local_node->ospf_area_id); -+ if (BGP_LS_TLV_CHECK(local_node->present_tlvs, BGP_LS_NODE_DESC_BGP_LS_ID_BIT)) ++ if (CHECK_FLAG(local_node->present_tlvs, BGP_LS_NODE_DESC_BGP_LS_ID_BIT)) + vty_out(vty, "\tBGP Identifier: %u\n", local_node->bgp_ls_id); -+ if (BGP_LS_TLV_CHECK(local_node->present_tlvs, BGP_LS_NODE_DESC_IGP_ROUTER_BIT)) { ++ if (CHECK_FLAG(local_node->present_tlvs, BGP_LS_NODE_DESC_IGP_ROUTER_BIT)) { + if (local_node->igp_router_id_len == 4) { + vty_out(vty, "\tRouter ID IPv4: %pI4\n", + (struct in_addr *)local_node->igp_router_id); @@ -8187,12 +7781,12 @@ index 0000000000..db93d2fc29 + local_node->igp_router_id[2], local_node->igp_router_id[3], + local_node->igp_router_id[4], local_node->igp_router_id[5], + local_node->igp_router_id[6] >> 1); -+ } else if (local_node->igp_router_id_len == 16) { -+ inet_ntop(AF_INET6, local_node->igp_router_id, ipaddr_str, -+ sizeof(ipaddr_str)); -+ vty_out(vty, "\tRouter ID IPv6: %s\n", ipaddr_str); -+ } ++ } else if (local_node->igp_router_id_len == 16) ++ vty_out(vty, "\tRouter ID IPv6: %pI6\n", ++ (struct in6_addr *)local_node->igp_router_id); + } ++ if (CHECK_FLAG(local_node->present_tlvs, BGP_LS_NODE_DESC_BGP_ROUTER_ID_BIT)) ++ vty_out(vty, "\tBGP Router Identifier: %pI4\n", &local_node->bgp_router_id); + } + + /* Display Remote Node Descriptor for Link NLRI */ @@ -8201,13 +7795,13 @@ index 0000000000..db93d2fc29 + + vty_out(vty, "Remote Node Descriptor:\n"); + -+ if (BGP_LS_TLV_CHECK(remote_node->present_tlvs, BGP_LS_NODE_DESC_AS_BIT)) ++ if (CHECK_FLAG(remote_node->present_tlvs, BGP_LS_NODE_DESC_AS_BIT)) + vty_out(vty, "\tAS Number: %u\n", remote_node->asn); -+ if (BGP_LS_TLV_CHECK(remote_node->present_tlvs, BGP_LS_NODE_DESC_OSPF_AREA_BIT)) ++ if (CHECK_FLAG(remote_node->present_tlvs, BGP_LS_NODE_DESC_OSPF_AREA_BIT)) + vty_out(vty, "\tArea ID: %pI4\n", (in_addr_t *)&remote_node->ospf_area_id); -+ if (BGP_LS_TLV_CHECK(remote_node->present_tlvs, BGP_LS_NODE_DESC_BGP_LS_ID_BIT)) ++ if (CHECK_FLAG(remote_node->present_tlvs, BGP_LS_NODE_DESC_BGP_LS_ID_BIT)) + vty_out(vty, "\tBGP Identifier: %u\n", remote_node->bgp_ls_id); -+ if (BGP_LS_TLV_CHECK(remote_node->present_tlvs, BGP_LS_NODE_DESC_IGP_ROUTER_BIT)) { ++ if (CHECK_FLAG(remote_node->present_tlvs, BGP_LS_NODE_DESC_IGP_ROUTER_BIT)) { + if (remote_node->igp_router_id_len == 4) { + vty_out(vty, "\tRouter ID IPv4: %pI4\n", + (struct in_addr *)remote_node->igp_router_id); @@ -8221,46 +7815,39 @@ index 0000000000..db93d2fc29 + remote_node->igp_router_id[4], + remote_node->igp_router_id[5], + remote_node->igp_router_id[6] >> 1); -+ } else if (remote_node->igp_router_id_len == 16) { -+ inet_ntop(AF_INET6, remote_node->igp_router_id, ipaddr_str, -+ sizeof(ipaddr_str)); -+ vty_out(vty, "\tRouter ID IPv6: %s\n", ipaddr_str); -+ } ++ } else if (remote_node->igp_router_id_len == 16) ++ vty_out(vty, "\tRouter ID IPv6: %pI6\n", ++ (struct in6_addr *)remote_node->igp_router_id); + } ++ if (CHECK_FLAG(remote_node->present_tlvs, BGP_LS_NODE_DESC_BGP_ROUTER_ID_BIT)) ++ vty_out(vty, "\tBGP Router Identifier: %pI4\n", ++ &remote_node->bgp_router_id); + + /* Display Link Descriptor */ + struct bgp_ls_link_descriptor *link_desc = &nlri->nlri_data.link.link_desc; + + vty_out(vty, "Link Descriptor:\n"); + -+ if (BGP_LS_TLV_CHECK(link_desc->present_tlvs, BGP_LS_LINK_DESC_LINK_ID_BIT)) { ++ if (CHECK_FLAG(link_desc->present_tlvs, BGP_LS_LINK_DESC_LINK_ID_BIT)) { + vty_out(vty, "\tLink ID: %u.%u\n", link_desc->link_local_id, + link_desc->link_remote_id); + } + -+ if (BGP_LS_TLV_CHECK(link_desc->present_tlvs, BGP_LS_LINK_DESC_IPV4_INTF_BIT)) { -+ inet_ntop(AF_INET, &link_desc->ipv4_intf_addr, ipaddr_str, -+ sizeof(ipaddr_str)); -+ vty_out(vty, "\tLocal Interface Address IPv4: %s\n", ipaddr_str); -+ } -+ if (BGP_LS_TLV_CHECK(link_desc->present_tlvs, BGP_LS_LINK_DESC_IPV4_NEIGH_BIT)) { -+ inet_ntop(AF_INET, &link_desc->ipv4_neigh_addr, ipaddr_str, -+ sizeof(ipaddr_str)); -+ vty_out(vty, "\tNeighbor Interface Address IPv4: %s\n", ipaddr_str); -+ } -+ if (BGP_LS_TLV_CHECK(link_desc->present_tlvs, BGP_LS_LINK_DESC_IPV6_INTF_BIT)) { -+ inet_ntop(AF_INET6, &link_desc->ipv6_intf_addr, ipaddr_str, -+ sizeof(ipaddr_str)); -+ vty_out(vty, "\tLocal Interface Address IPv6: %s\n", ipaddr_str); -+ } -+ if (BGP_LS_TLV_CHECK(link_desc->present_tlvs, BGP_LS_LINK_DESC_IPV6_NEIGH_BIT)) { -+ inet_ntop(AF_INET6, &link_desc->ipv6_neigh_addr, ipaddr_str, -+ sizeof(ipaddr_str)); -+ vty_out(vty, "\tNeighbor Interface Address IPv6: %s\n", ipaddr_str); -+ } ++ if (CHECK_FLAG(link_desc->present_tlvs, BGP_LS_LINK_DESC_IPV4_INTF_BIT)) ++ vty_out(vty, "\tLocal Interface Address IPv4: %pI4\n", ++ &link_desc->ipv4_intf_addr); ++ if (CHECK_FLAG(link_desc->present_tlvs, BGP_LS_LINK_DESC_IPV4_NEIGH_BIT)) ++ vty_out(vty, "\tNeighbor Interface Address IPv4: %pI4\n", ++ &link_desc->ipv4_neigh_addr); ++ if (CHECK_FLAG(link_desc->present_tlvs, BGP_LS_LINK_DESC_IPV6_INTF_BIT)) ++ vty_out(vty, "\tLocal Interface Address IPv6: %pI6\n", ++ &link_desc->ipv6_intf_addr); ++ if (CHECK_FLAG(link_desc->present_tlvs, BGP_LS_LINK_DESC_IPV6_NEIGH_BIT)) ++ vty_out(vty, "\tNeighbor Interface Address IPv6: %pI6\n", ++ &link_desc->ipv6_neigh_addr); + + /* Display Link Descriptor Multi-Topology info */ -+ if (BGP_LS_TLV_CHECK(link_desc->present_tlvs, BGP_LS_LINK_DESC_MT_ID_BIT)) { ++ if (CHECK_FLAG(link_desc->present_tlvs, BGP_LS_LINK_DESC_MT_ID_BIT)) { + vty_out(vty, "Multi-Topology:\n"); + for (uint8_t i = 0; i < link_desc->mt_id_count; i++) + vty_out(vty, "\tMT-ID: %u\n", link_desc->mt_id[i]); @@ -8280,7 +7867,7 @@ index 0000000000..db93d2fc29 + vty_out(vty, "\tPrefix: %s\n", prefix_str); + + /* OSPF Route Type */ -+ if (BGP_LS_TLV_CHECK(prefix_desc->present_tlvs, BGP_LS_PREFIX_DESC_OSPF_ROUTE_BIT)) { ++ if (CHECK_FLAG(prefix_desc->present_tlvs, BGP_LS_PREFIX_DESC_OSPF_ROUTE_BIT)) { + const char *ospf_rt_str = NULL; + + switch (prefix_desc->ospf_route_type) { @@ -8309,8 +7896,14 @@ index 0000000000..db93d2fc29 + vty_out(vty, "\tOSPF Route Type: %s\n", ospf_rt_str); + } + ++ /* BGP Route Type */ ++ if (CHECK_FLAG(prefix_desc->present_tlvs, BGP_LS_PREFIX_DESC_BGP_ROUTE_TYPE_BIT)) { ++ vty_out(vty, "\tBGP Route Type: %s\n", ++ bgp_ls_bgp_route_type_str(prefix_desc->bgp_route_type)); ++ } ++ + /* Multi-Topology */ -+ if (BGP_LS_TLV_CHECK(prefix_desc->present_tlvs, BGP_LS_PREFIX_DESC_MT_ID_BIT)) { ++ if (CHECK_FLAG(prefix_desc->present_tlvs, BGP_LS_PREFIX_DESC_MT_ID_BIT)) { + vty_out(vty, "Multi-Topology:\n"); + for (uint8_t i = 0; i < prefix_desc->mt_id_count; i++) + vty_out(vty, "\tMT-ID: %u\n", prefix_desc->mt_id[i]); @@ -8319,10 +7912,10 @@ index 0000000000..db93d2fc29 +} diff --git a/bgpd/bgp_ls_nlri.h b/bgpd/bgp_ls_nlri.h new file mode 100644 -index 0000000000..b491c78734 +index 0000000000..01c34e7ea4 --- /dev/null +++ b/bgpd/bgp_ls_nlri.h -@@ -0,0 +1,895 @@ +@@ -0,0 +1,910 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * BGP Link-State NLRI (RFC 9552) @@ -8388,6 +7981,7 @@ index 0000000000..b491c78734 + BGP_LS_TLV_BGP_LS_ID = 513, /* BGP-LS Identifier (deprecated) - RFC 9552, Section 5.2.1.4 */ + BGP_LS_TLV_OSPF_AREA_ID = 514, /* OSPF Area-ID - RFC 9552, Section 5.2.1.4 */ + BGP_LS_TLV_IGP_ROUTER_ID = 515, /* IGP Router-ID - RFC 9552, Section 5.2.1.4 */ ++ BGP_LS_TLV_BGP_ROUTER_ID = 516, /* BGP Router-ID - RFC 9086 */ +}; + +/* @@ -8411,6 +8005,7 @@ index 0000000000..b491c78734 +enum bgp_ls_prefix_descriptor_tlv { + BGP_LS_TLV_OSPF_ROUTE_TYPE = 264, /* OSPF Route Type - RFC 9552, Section 5.2.3.1 */ + BGP_LS_TLV_IP_REACH_INFO = 265, /* IP Reachability Information - RFC 9552, Section 5.2.3.2 */ ++ BGP_LS_TLV_BGP_ROUTE_TYPE = 535, /* BGP Route Type - draft-ietf-idr-bgp-ls-bgp-only-fabric */ +}; + +/* @@ -8427,6 +8022,18 @@ index 0000000000..b491c78734 +}; + +/* ++ * BGP Route Type Values ++ * draft-ietf-idr-bgp-ls-bgp-only-fabric-04, Section 4.3 ++ */ ++enum bgp_ls_bgp_route_type { ++ BGP_LS_BGP_RT_LOCAL = 1, /* Local interface prefix (e.g., Loopback) */ ++ BGP_LS_BGP_RT_ATTACHED = 2, /* Directly attached node's prefix (e.g., host) */ ++ BGP_LS_BGP_RT_EXTERNAL_BGP = 3, /* Prefix learned via EBGP */ ++ BGP_LS_BGP_RT_INTERNAL_BGP = 4, /* Prefix learned via IBGP */ ++ BGP_LS_BGP_RT_REDISTRIBUTED = 5, /* Prefix redistributed into BGP */ ++}; ++ ++/* + * BGP-LS Attribute TLV Types + * IANA: https://www.iana.org/assignments/bgp-ls-parameters/bgp-ls-parameters.xhtml#node-descriptor-link-descriptor-prefix-descriptor-attribute-tlv + */ @@ -8497,34 +8104,27 @@ index 0000000000..b491c78734 + * =========================================================================== + */ + -+/* -+ * TLV Presence Bitmask Macros -+ * Used to track which optional TLVs are present in descriptors -+ */ -+#define BGP_LS_TLV_SET(bitmap, bit) ((bitmap) |= (1ULL << (bit))) -+#define BGP_LS_TLV_CHECK(bitmap, bit) ((bitmap) & (1ULL << (bit))) -+#define BGP_LS_TLV_UNSET(bitmap, bit) ((bitmap) &= ~(1ULL << (bit))) -+#define BGP_LS_TLV_RESET(bitmap) ((bitmap) = 0) -+ +/* Bit positions for Node Descriptor TLVs */ -+#define BGP_LS_NODE_DESC_AS_BIT 0 -+#define BGP_LS_NODE_DESC_BGP_LS_ID_BIT 1 -+#define BGP_LS_NODE_DESC_OSPF_AREA_BIT 2 -+#define BGP_LS_NODE_DESC_IGP_ROUTER_BIT 3 ++#define BGP_LS_NODE_DESC_AS_BIT (1ULL << 0) ++#define BGP_LS_NODE_DESC_BGP_LS_ID_BIT (1ULL << 1) ++#define BGP_LS_NODE_DESC_OSPF_AREA_BIT (1ULL << 2) ++#define BGP_LS_NODE_DESC_IGP_ROUTER_BIT (1ULL << 3) ++#define BGP_LS_NODE_DESC_BGP_ROUTER_ID_BIT (1ULL << 4) + +/* Bit positions for Link Descriptor TLVs */ -+#define BGP_LS_LINK_DESC_LINK_ID_BIT 0 -+#define BGP_LS_LINK_DESC_IPV4_INTF_BIT 1 -+#define BGP_LS_LINK_DESC_IPV4_NEIGH_BIT 2 -+#define BGP_LS_LINK_DESC_IPV6_INTF_BIT 3 -+#define BGP_LS_LINK_DESC_IPV6_NEIGH_BIT 4 -+#define BGP_LS_LINK_DESC_MT_ID_BIT 5 -+#define BGP_LS_LINK_DESC_REMOTE_AS_BIT 6 ++#define BGP_LS_LINK_DESC_LINK_ID_BIT (1ULL << 0) ++#define BGP_LS_LINK_DESC_IPV4_INTF_BIT (1ULL << 1) ++#define BGP_LS_LINK_DESC_IPV4_NEIGH_BIT (1ULL << 2) ++#define BGP_LS_LINK_DESC_IPV6_INTF_BIT (1ULL << 3) ++#define BGP_LS_LINK_DESC_IPV6_NEIGH_BIT (1ULL << 4) ++#define BGP_LS_LINK_DESC_MT_ID_BIT (1ULL << 5) ++#define BGP_LS_LINK_DESC_REMOTE_AS_BIT (1ULL << 6) + +/* Bit positions for Prefix Descriptor TLVs */ -+#define BGP_LS_PREFIX_DESC_MT_ID_BIT 0 -+#define BGP_LS_PREFIX_DESC_OSPF_ROUTE_BIT 1 -+#define BGP_LS_PREFIX_DESC_IP_REACH_BIT 2 ++#define BGP_LS_PREFIX_DESC_MT_ID_BIT (1ULL << 0) ++#define BGP_LS_PREFIX_DESC_OSPF_ROUTE_BIT (1ULL << 1) ++#define BGP_LS_PREFIX_DESC_IP_REACH_BIT (1ULL << 2) ++#define BGP_LS_PREFIX_DESC_BGP_ROUTE_TYPE_BIT (1ULL << 3) + +/* Maximum number of MT-IDs per descriptor */ +#define BGP_LS_MAX_MT_ID 16 @@ -8564,6 +8164,8 @@ index 0000000000..b491c78734 +#define BGP_LS_OSPF_ROUTE_TYPE_SIZE 1 /* OSPF Route Type value */ +#define BGP_LS_MT_ID_SIZE 2 /* Multi-Topology ID (per entry) */ +#define BGP_LS_PREFIX_LEN_SIZE 1 /* IP prefix length field */ ++#define BGP_LS_BGP_ROUTER_ID_SIZE 4 /* BGP Router-ID value */ ++#define BGP_LS_BGP_ROUTE_TYPE_SIZE 1 /* BGP Route Type value */ + +/* + * IGP Metric can be 1, 2, or 3 bytes @@ -8583,50 +8185,52 @@ index 0000000000..b491c78734 +#define BGP_LS_MAX_UNRESV_BW 8 /* 8 priority classes */ +#define BGP_LS_MAX_ROUTE_TAGS 16 /* Maximum route tags */ +#define BGP_LS_MAX_EXT_ADMIN_GROUPS 256 /* Maximum number of admin groups in Extended Admin Group TLV */ ++#define BGP_LS_MAX_NODE_NAME_LEN 255 /* Maximum node name length */ ++#define BGP_LS_MAX_LINK_NAME_LEN 255 /* Maximum link name length */ + +/* + * Bit positions for attribute presence bitmasks + */ -+#define BGP_LS_ATTR_NODE_FLAGS_BIT 0 -+#define BGP_LS_ATTR_NODE_NAME_BIT 1 -+#define BGP_LS_ATTR_ISIS_AREA_BIT 2 -+#define BGP_LS_ATTR_SR_CAPABILITIES_BIT 3 -+#define BGP_LS_ATTR_SR_ALGORITHM_BIT 4 -+#define BGP_LS_ATTR_SR_LOCAL_BLOCK_BIT 5 -+#define BGP_LS_ATTR_NODE_MSD_BIT 6 -+#define BGP_LS_ATTR_IPV4_ROUTER_ID_LOCAL_BIT 7 -+#define BGP_LS_ATTR_IPV6_ROUTER_ID_LOCAL_BIT 8 -+#define BGP_LS_ATTR_IPV4_ROUTER_ID_REMOTE_BIT 9 -+#define BGP_LS_ATTR_IPV6_ROUTER_ID_REMOTE_BIT 10 -+#define BGP_LS_ATTR_ADMIN_GROUP_BIT 11 -+#define BGP_LS_ATTR_MAX_LINK_BW_BIT 12 -+#define BGP_LS_ATTR_MAX_RESV_BW_BIT 13 -+#define BGP_LS_ATTR_UNRESV_BW_BIT 14 -+#define BGP_LS_ATTR_TE_METRIC_BIT 15 -+#define BGP_LS_ATTR_LINK_PROTECTION_BIT 16 -+#define BGP_LS_ATTR_MPLS_PROTOCOL_BIT 17 -+#define BGP_LS_ATTR_IGP_METRIC_BIT 18 -+#define BGP_LS_ATTR_SRLG_BIT 19 -+#define BGP_LS_ATTR_LINK_NAME_BIT 20 -+#define BGP_LS_ATTR_ADJ_SID_BIT 21 -+#define BGP_LS_ATTR_LINK_MSD_BIT 22 -+#define BGP_LS_ATTR_EXT_ADMIN_GROUP_BIT 23 -+#define BGP_LS_ATTR_DELAY_BIT 24 -+#define BGP_LS_ATTR_MIN_MAX_DELAY_BIT 25 -+#define BGP_LS_ATTR_JITTER_BIT 26 -+#define BGP_LS_ATTR_PKT_LOSS_BIT 27 -+#define BGP_LS_ATTR_RESIDUAL_BW_BIT 28 -+#define BGP_LS_ATTR_AVAILABLE_BW_BIT 29 -+#define BGP_LS_ATTR_UTILIZED_BW_BIT 30 -+#define BGP_LS_ATTR_IGP_FLAGS_BIT 31 -+#define BGP_LS_ATTR_ROUTE_TAG_BIT 32 -+#define BGP_LS_ATTR_EXTENDED_TAG_BIT 33 -+#define BGP_LS_ATTR_PREFIX_METRIC_BIT 34 -+#define BGP_LS_ATTR_OSPF_FWD_ADDR_BIT 35 -+#define BGP_LS_ATTR_PREFIX_SID_BIT 36 -+#define BGP_LS_ATTR_RANGE_BIT 37 -+#define BGP_LS_ATTR_SID_LABEL_BIT 38 -+#define BGP_LS_ATTR_SRV6_LOCATOR_BIT 39 ++#define BGP_LS_ATTR_NODE_FLAGS_BIT (1ULL << 0) ++#define BGP_LS_ATTR_NODE_NAME_BIT (1ULL << 1) ++#define BGP_LS_ATTR_ISIS_AREA_BIT (1ULL << 2) ++#define BGP_LS_ATTR_SR_CAPABILITIES_BIT (1ULL << 3) ++#define BGP_LS_ATTR_SR_ALGORITHM_BIT (1ULL << 4) ++#define BGP_LS_ATTR_SR_LOCAL_BLOCK_BIT (1ULL << 5) ++#define BGP_LS_ATTR_NODE_MSD_BIT (1ULL << 6) ++#define BGP_LS_ATTR_IPV4_ROUTER_ID_LOCAL_BIT (1ULL << 7) ++#define BGP_LS_ATTR_IPV6_ROUTER_ID_LOCAL_BIT (1ULL << 8) ++#define BGP_LS_ATTR_IPV4_ROUTER_ID_REMOTE_BIT (1ULL << 9) ++#define BGP_LS_ATTR_IPV6_ROUTER_ID_REMOTE_BIT (1ULL << 10) ++#define BGP_LS_ATTR_ADMIN_GROUP_BIT (1ULL << 11) ++#define BGP_LS_ATTR_MAX_LINK_BW_BIT (1ULL << 12) ++#define BGP_LS_ATTR_MAX_RESV_BW_BIT (1ULL << 13) ++#define BGP_LS_ATTR_UNRESV_BW_BIT (1ULL << 14) ++#define BGP_LS_ATTR_TE_METRIC_BIT (1ULL << 15) ++#define BGP_LS_ATTR_LINK_PROTECTION_BIT (1ULL << 16) ++#define BGP_LS_ATTR_MPLS_PROTOCOL_BIT (1ULL << 17) ++#define BGP_LS_ATTR_IGP_METRIC_BIT (1ULL << 18) ++#define BGP_LS_ATTR_SRLG_BIT (1ULL << 19) ++#define BGP_LS_ATTR_LINK_NAME_BIT (1ULL << 20) ++#define BGP_LS_ATTR_ADJ_SID_BIT (1ULL << 21) ++#define BGP_LS_ATTR_LINK_MSD_BIT (1ULL << 22) ++#define BGP_LS_ATTR_EXT_ADMIN_GROUP_BIT (1ULL << 23) ++#define BGP_LS_ATTR_DELAY_BIT (1ULL << 24) ++#define BGP_LS_ATTR_MIN_MAX_DELAY_BIT (1ULL << 25) ++#define BGP_LS_ATTR_JITTER_BIT (1ULL << 26) ++#define BGP_LS_ATTR_PKT_LOSS_BIT (1ULL << 27) ++#define BGP_LS_ATTR_RESIDUAL_BW_BIT (1ULL << 28) ++#define BGP_LS_ATTR_AVAILABLE_BW_BIT (1ULL << 29) ++#define BGP_LS_ATTR_UTILIZED_BW_BIT (1ULL << 30) ++#define BGP_LS_ATTR_IGP_FLAGS_BIT (1ULL << 31) ++#define BGP_LS_ATTR_ROUTE_TAG_BIT (1ULL << 32) ++#define BGP_LS_ATTR_EXTENDED_TAG_BIT (1ULL << 33) ++#define BGP_LS_ATTR_PREFIX_METRIC_BIT (1ULL << 34) ++#define BGP_LS_ATTR_OSPF_FWD_ADDR_BIT (1ULL << 35) ++#define BGP_LS_ATTR_PREFIX_SID_BIT (1ULL << 36) ++#define BGP_LS_ATTR_RANGE_BIT (1ULL << 37) ++#define BGP_LS_ATTR_SID_LABEL_BIT (1ULL << 38) ++#define BGP_LS_ATTR_SRV6_LOCATOR_BIT (1ULL << 39) + +/* + * Node Flag Bits (TLV 1024) @@ -8687,6 +8291,7 @@ index 0000000000..b491c78734 + uint32_t ospf_area_id; /* OSPF Area ID */ + uint8_t igp_router_id_len; /* Length of IGP Router ID (4-16 bytes) */ + uint8_t igp_router_id[BGP_LS_IGP_ROUTER_ID_MAX_SIZE]; /* IGP Router ID (ISIS, OSPF, Direct, or Static configuration) */ ++ struct in_addr bgp_router_id; /* BGP Router-ID (TLV 516) */ +}; + +/* @@ -8715,6 +8320,7 @@ index 0000000000..b491c78734 + uint8_t mt_id_count; /* Number of Multi-Topology IDs */ + uint16_t *mt_id; /* Multi-Topology IDs */ + enum bgp_ls_ospf_route_type ospf_route_type; /* OSPF Route Type */ ++ enum bgp_ls_bgp_route_type bgp_route_type; /* BGP Route Type */ + struct prefix prefix; /* IP prefix (IPv4 or IPv6) */ +}; + @@ -9022,9 +8628,11 @@ index 0000000000..b491c78734 +extern const char *bgp_ls_link_descriptor_tlv_str(enum bgp_ls_link_descriptor_tlv tlv_type); +extern const char *bgp_ls_prefix_descriptor_tlv_str(enum bgp_ls_prefix_descriptor_tlv tlv_type); +extern const char *bgp_ls_ospf_route_type_str(enum bgp_ls_ospf_route_type route_type); ++extern const char *bgp_ls_bgp_route_type_str(enum bgp_ls_bgp_route_type route_type); + +/* Json conversion helpers */ +extern const char *bgp_ls_ospf_route_type_str_json(enum bgp_ls_ospf_route_type route_type); ++extern const char *bgp_ls_bgp_route_type_str_json(enum bgp_ls_bgp_route_type route_type); + +/* + * =========================================================================== @@ -9220,10 +8828,10 @@ index 0000000000..b491c78734 +#endif /* _FRR_BGP_LS_NLRI_H */ diff --git a/bgpd/bgp_ls_ted.c b/bgpd/bgp_ls_ted.c new file mode 100644 -index 0000000000..0750dd690a +index 0000000000..6a0eb49907 --- /dev/null +++ b/bgpd/bgp_ls_ted.c -@@ -0,0 +1,1403 @@ +@@ -0,0 +1,1381 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * BGP Link-State Traffic Engineering Database (RFC 9552) @@ -9265,7 +8873,7 @@ index 0000000000..0750dd690a + /* Node Flag Bits (TLV 1024) */ + if (CHECK_FLAG(ls_node->flags, LS_NODE_FLAG)) { + attr->node_flags = ls_node->node_flag; -+ attr->present_tlvs |= (1ULL << BGP_LS_ATTR_NODE_FLAGS_BIT); ++ SET_FLAG(attr->present_tlvs, BGP_LS_ATTR_NODE_FLAGS_BIT); + } + + /* Node Name (TLV 1026) */ @@ -9274,7 +8882,7 @@ index 0000000000..0750dd690a + + attr->node_name = XCALLOC(MTYPE_BGP_LS_ATTR, name_len + 1); + memcpy(attr->node_name, ls_node->name, name_len + 1); -+ attr->present_tlvs |= (1ULL << BGP_LS_ATTR_NODE_NAME_BIT); ++ SET_FLAG(attr->present_tlvs, BGP_LS_ATTR_NODE_NAME_BIT); + } + + /* IS-IS Area Identifier (TLV 1027) */ @@ -9282,7 +8890,7 @@ index 0000000000..0750dd690a + attr->isis_area_id_len = ls_node->isis_area_id_len; + attr->isis_area_id = XCALLOC(MTYPE_BGP_LS_ATTR, attr->isis_area_id_len); + memcpy(attr->isis_area_id, ls_node->isis_area_id, attr->isis_area_id_len); -+ attr->present_tlvs |= (1ULL << BGP_LS_ATTR_ISIS_AREA_BIT); ++ SET_FLAG(attr->present_tlvs, BGP_LS_ATTR_ISIS_AREA_BIT); + } + + /* SR Capabilities (TLV 1034) */ @@ -9290,25 +8898,25 @@ index 0000000000..0750dd690a + attr->srgb.flag = ls_node->srgb.flag; + attr->srgb.lower_bound = ls_node->srgb.lower_bound; + attr->srgb.range_size = ls_node->srgb.range_size; -+ attr->present_tlvs |= (1ULL << BGP_LS_ATTR_SR_CAPABILITIES_BIT); ++ SET_FLAG(attr->present_tlvs, BGP_LS_ATTR_SR_CAPABILITIES_BIT); + } + + /* Node MSD (TLV 266) */ + if (CHECK_FLAG(ls_node->flags, LS_NODE_MSD)) { + attr->msd = ls_node->msd; -+ attr->present_tlvs |= (1ULL << BGP_LS_ATTR_NODE_MSD_BIT); ++ SET_FLAG(attr->present_tlvs, BGP_LS_ATTR_NODE_MSD_BIT); + } + + /* IPv4 Router-ID (TLV 1028) */ + if (CHECK_FLAG(ls_node->flags, LS_NODE_ROUTER_ID)) { + attr->ipv4_router_id_local = ls_node->router_id; -+ attr->present_tlvs |= (1ULL << BGP_LS_ATTR_IPV4_ROUTER_ID_LOCAL_BIT); ++ SET_FLAG(attr->present_tlvs, BGP_LS_ATTR_IPV4_ROUTER_ID_LOCAL_BIT); + } + + /* IPv6 Router-ID (TLV 1029) */ + if (CHECK_FLAG(ls_node->flags, LS_NODE_ROUTER_ID6)) { + attr->ipv6_router_id_local = ls_node->router_id6; -+ attr->present_tlvs |= (1ULL << BGP_LS_ATTR_IPV6_ROUTER_ID_LOCAL_BIT); ++ SET_FLAG(attr->present_tlvs, BGP_LS_ATTR_IPV6_ROUTER_ID_LOCAL_BIT); + } + + return 0; @@ -9346,7 +8954,7 @@ index 0000000000..0750dd690a + attr->adj_sid[attr->adj_sid_count].weight = ls_attr->adj_sid[index].weight; + attr->adj_sid_count++; + -+ attr->present_tlvs |= (1ULL << BGP_LS_ATTR_ADJ_SID_BIT); ++ SET_FLAG(attr->present_tlvs, BGP_LS_ATTR_ADJ_SID_BIT); +} + +/* @@ -9361,32 +8969,32 @@ index 0000000000..0750dd690a + /* Administrative Group (TLV 1088) */ + if (CHECK_FLAG(ls_attr->flags, LS_ATTR_ADM_GRP)) { + attr->admin_group = ls_attr->standard.admin_group; -+ attr->present_tlvs |= (1ULL << BGP_LS_ATTR_ADMIN_GROUP_BIT); ++ SET_FLAG(attr->present_tlvs, BGP_LS_ATTR_ADMIN_GROUP_BIT); + } + + /* Maximum Link Bandwidth (TLV 1089) */ + if (CHECK_FLAG(ls_attr->flags, LS_ATTR_MAX_BW)) { + attr->max_link_bw = ls_attr->standard.max_bw; -+ attr->present_tlvs |= (1ULL << BGP_LS_ATTR_MAX_LINK_BW_BIT); ++ SET_FLAG(attr->present_tlvs, BGP_LS_ATTR_MAX_LINK_BW_BIT); + } + + /* Maximum Reservable Bandwidth (TLV 1090) */ + if (CHECK_FLAG(ls_attr->flags, LS_ATTR_MAX_RSV_BW)) { + attr->max_resv_bw = ls_attr->standard.max_rsv_bw; -+ attr->present_tlvs |= (1ULL << BGP_LS_ATTR_MAX_RESV_BW_BIT); ++ SET_FLAG(attr->present_tlvs, BGP_LS_ATTR_MAX_RESV_BW_BIT); + } + + /* Unreserved Bandwidth (TLV 1091) */ + if (CHECK_FLAG(ls_attr->flags, LS_ATTR_UNRSV_BW)) { + for (int i = 0; i < BGP_LS_MAX_UNRESV_BW; i++) + attr->unreserved_bw[i] = ls_attr->standard.unrsv_bw[i]; -+ attr->present_tlvs |= (1ULL << BGP_LS_ATTR_UNRESV_BW_BIT); ++ SET_FLAG(attr->present_tlvs, BGP_LS_ATTR_UNRESV_BW_BIT); + } + + /* TE Default Metric (TLV 1092) */ + if (CHECK_FLAG(ls_attr->flags, LS_ATTR_TE_METRIC)) { + attr->te_metric = ls_attr->standard.te_metric; -+ attr->present_tlvs |= (1ULL << BGP_LS_ATTR_TE_METRIC_BIT); ++ SET_FLAG(attr->present_tlvs, BGP_LS_ATTR_TE_METRIC_BIT); + } + + /* IGP Metric (TLV 1095) */ @@ -9399,7 +9007,7 @@ index 0000000000..0750dd690a + else + attr->igp_metric_len = 3; + attr->igp_metric = ls_attr->metric; -+ attr->present_tlvs |= (1ULL << BGP_LS_ATTR_IGP_METRIC_BIT); ++ SET_FLAG(attr->present_tlvs, BGP_LS_ATTR_IGP_METRIC_BIT); + } + + /* Shared Risk Link Group (TLV 1096) */ @@ -9413,7 +9021,7 @@ index 0000000000..0750dd690a + attr->srlg_values = XCALLOC(MTYPE_BGP_LS_ATTR, count * sizeof(uint32_t)); + for (uint8_t i = 0; i < count; i++) + attr->srlg_values[i] = ls_attr->srlgs[i]; -+ attr->present_tlvs |= (1ULL << BGP_LS_ATTR_SRLG_BIT); ++ SET_FLAG(attr->present_tlvs, BGP_LS_ATTR_SRLG_BIT); + } + + /* Link Name (TLV 1098) */ @@ -9422,7 +9030,7 @@ index 0000000000..0750dd690a + + attr->link_name = XCALLOC(MTYPE_BGP_LS_ATTR, name_len + 1); + memcpy(attr->link_name, ls_attr->name, name_len + 1); -+ attr->present_tlvs |= (1ULL << BGP_LS_ATTR_LINK_NAME_BIT); ++ SET_FLAG(attr->present_tlvs, BGP_LS_ATTR_LINK_NAME_BIT); + } + + /* Adjacency SID (TLV 1099) */ @@ -9434,62 +9042,62 @@ index 0000000000..0750dd690a + /* Remote IPv4 Router-ID (TLV 1030) */ + if (CHECK_FLAG(ls_attr->flags, LS_ATTR_REMOTE_ADDR)) { + attr->ipv4_router_id_remote = ls_attr->standard.remote_addr; -+ attr->present_tlvs |= (1ULL << BGP_LS_ATTR_IPV4_ROUTER_ID_REMOTE_BIT); ++ SET_FLAG(attr->present_tlvs, BGP_LS_ATTR_IPV4_ROUTER_ID_REMOTE_BIT); + } + + /* Remote IPv6 Router-ID (TLV 1031) */ + if (CHECK_FLAG(ls_attr->flags, LS_ATTR_REMOTE_ADDR6)) { + attr->ipv6_router_id_remote = ls_attr->standard.remote_addr6; -+ attr->present_tlvs |= (1ULL << BGP_LS_ATTR_IPV6_ROUTER_ID_REMOTE_BIT); ++ SET_FLAG(attr->present_tlvs, BGP_LS_ATTR_IPV6_ROUTER_ID_REMOTE_BIT); + } + + /* Extended Admin Group (TLV 1093) */ + if (CHECK_FLAG(ls_attr->flags, LS_ATTR_EXT_ADM_GRP)) { + admin_group_copy(&attr->ext_admin_group, &ls_attr->ext_admin_group); -+ attr->present_tlvs |= (1ULL << BGP_LS_ATTR_EXT_ADMIN_GROUP_BIT); ++ SET_FLAG(attr->present_tlvs, BGP_LS_ATTR_EXT_ADMIN_GROUP_BIT); + } + + /* Unidirectional Link Delay (TLV 1114) */ + if (CHECK_FLAG(ls_attr->flags, LS_ATTR_DELAY)) { + attr->delay = ls_attr->extended.delay; -+ attr->present_tlvs |= (1ULL << BGP_LS_ATTR_DELAY_BIT); ++ SET_FLAG(attr->present_tlvs, BGP_LS_ATTR_DELAY_BIT); + } + + /* Min/Max Unidirectional Link Delay (TLV 1115) */ + if (CHECK_FLAG(ls_attr->flags, LS_ATTR_MIN_MAX_DELAY)) { + attr->min_delay = ls_attr->extended.min_delay; + attr->max_delay = ls_attr->extended.max_delay; -+ attr->present_tlvs |= (1ULL << BGP_LS_ATTR_MIN_MAX_DELAY_BIT); ++ SET_FLAG(attr->present_tlvs, BGP_LS_ATTR_MIN_MAX_DELAY_BIT); + } + + /* Unidirectional Delay Variation (TLV 1116) */ + if (CHECK_FLAG(ls_attr->flags, LS_ATTR_JITTER)) { + attr->jitter = ls_attr->extended.jitter; -+ attr->present_tlvs |= (1ULL << BGP_LS_ATTR_JITTER_BIT); ++ SET_FLAG(attr->present_tlvs, BGP_LS_ATTR_JITTER_BIT); + } + + /* Unidirectional Packet Loss (TLV 1117) */ + if (CHECK_FLAG(ls_attr->flags, LS_ATTR_PACKET_LOSS)) { + attr->pkt_loss = ls_attr->extended.pkt_loss; -+ attr->present_tlvs |= (1ULL << BGP_LS_ATTR_PKT_LOSS_BIT); ++ SET_FLAG(attr->present_tlvs, BGP_LS_ATTR_PKT_LOSS_BIT); + } + + /* Unidirectional Residual Bandwidth (TLV 1118) */ + if (CHECK_FLAG(ls_attr->flags, LS_ATTR_RSV_BW)) { + attr->residual_bw = ls_attr->extended.rsv_bw; -+ attr->present_tlvs |= (1ULL << BGP_LS_ATTR_RESIDUAL_BW_BIT); ++ SET_FLAG(attr->present_tlvs, BGP_LS_ATTR_RESIDUAL_BW_BIT); + } + + /* Unidirectional Available Bandwidth (TLV 1119) */ + if (CHECK_FLAG(ls_attr->flags, LS_ATTR_AVA_BW)) { + attr->available_bw = ls_attr->extended.ava_bw; -+ attr->present_tlvs |= (1ULL << BGP_LS_ATTR_AVAILABLE_BW_BIT); ++ SET_FLAG(attr->present_tlvs, BGP_LS_ATTR_AVAILABLE_BW_BIT); + } + + /* Unidirectional Utilized Bandwidth (TLV 1120) */ + if (CHECK_FLAG(ls_attr->flags, LS_ATTR_USE_BW)) { + attr->utilized_bw = ls_attr->extended.used_bw; -+ attr->present_tlvs |= (1ULL << BGP_LS_ATTR_UTILIZED_BW_BIT); ++ SET_FLAG(attr->present_tlvs, BGP_LS_ATTR_UTILIZED_BW_BIT); + } + + return 0; @@ -9512,7 +9120,7 @@ index 0000000000..0750dd690a + /* IGP Flags (TLV 1152) */ + if (CHECK_FLAG(ls_prefix->flags, LS_PREF_IGP_FLAG)) { + attr->igp_flags = ls_prefix->igp_flag; -+ attr->present_tlvs |= (1ULL << BGP_LS_ATTR_IGP_FLAGS_BIT); ++ SET_FLAG(attr->present_tlvs, BGP_LS_ATTR_IGP_FLAGS_BIT); + } + + /* Route Tags (TLV 1153) - single tag */ @@ -9520,7 +9128,7 @@ index 0000000000..0750dd690a + attr->route_tag_count = 1; + attr->route_tags = XCALLOC(MTYPE_BGP_LS_ATTR, sizeof(uint32_t)); + attr->route_tags[0] = ls_prefix->route_tag; -+ attr->present_tlvs |= (1ULL << BGP_LS_ATTR_ROUTE_TAG_BIT); ++ SET_FLAG(attr->present_tlvs, BGP_LS_ATTR_ROUTE_TAG_BIT); + } + + /* Extended Tags (TLV 1154) - single extended tag */ @@ -9528,13 +9136,13 @@ index 0000000000..0750dd690a + attr->extended_tag_count = 1; + attr->extended_tags = XCALLOC(MTYPE_BGP_LS_ATTR, sizeof(uint64_t)); + attr->extended_tags[0] = ls_prefix->extended_tag; -+ attr->present_tlvs |= (1ULL << BGP_LS_ATTR_EXTENDED_TAG_BIT); ++ SET_FLAG(attr->present_tlvs, BGP_LS_ATTR_EXTENDED_TAG_BIT); + } + + /* Prefix Metric (TLV 1155) */ + if (CHECK_FLAG(ls_prefix->flags, LS_PREF_METRIC)) { + attr->prefix_metric = ls_prefix->metric; -+ attr->present_tlvs |= (1ULL << BGP_LS_ATTR_PREFIX_METRIC_BIT); ++ SET_FLAG(attr->present_tlvs, BGP_LS_ATTR_PREFIX_METRIC_BIT); + } + + /* Prefix-SID (TLV 1158) */ @@ -9546,7 +9154,7 @@ index 0000000000..0750dd690a + attr->prefix_sid.sid = ls_prefix->sr.sid; + attr->prefix_sid.sid_flag = ls_prefix->sr.sid_flag; + attr->prefix_sid.algo = ls_prefix->sr.algo; -+ attr->present_tlvs |= (1ULL << BGP_LS_ATTR_PREFIX_SID_BIT); ++ SET_FLAG(attr->present_tlvs, BGP_LS_ATTR_PREFIX_SID_BIT); + } + } + @@ -9615,21 +9223,19 @@ index 0000000000..0750dd690a + /* Set Local Node Descriptor */ + nlri.nlri_data.node.local_node.igp_router_id_len = router_id_len; + memcpy(nlri.nlri_data.node.local_node.igp_router_id, router_id, router_id_len); -+ BGP_LS_TLV_SET(nlri.nlri_data.node.local_node.present_tlvs, -+ BGP_LS_NODE_DESC_IGP_ROUTER_BIT); ++ SET_FLAG(nlri.nlri_data.node.local_node.present_tlvs, BGP_LS_NODE_DESC_IGP_ROUTER_BIT); + + /* Set AS Number if available */ + if (CHECK_FLAG(vertex->node->flags, LS_NODE_AS_NUMBER)) { + nlri.nlri_data.node.local_node.asn = vertex->node->as_number; -+ BGP_LS_TLV_SET(nlri.nlri_data.node.local_node.present_tlvs, -+ BGP_LS_NODE_DESC_AS_BIT); ++ SET_FLAG(nlri.nlri_data.node.local_node.present_tlvs, BGP_LS_NODE_DESC_AS_BIT); + } + + /* Set OSPF Area ID if OSPF */ + if (protocol_id == BGP_LS_PROTO_OSPFV2 || protocol_id == BGP_LS_PROTO_OSPFV3) { + nlri.nlri_data.node.local_node.ospf_area_id = area_id; -+ BGP_LS_TLV_SET(nlri.nlri_data.node.local_node.present_tlvs, -+ BGP_LS_NODE_DESC_OSPF_AREA_BIT); ++ SET_FLAG(nlri.nlri_data.node.local_node.present_tlvs, ++ BGP_LS_NODE_DESC_OSPF_AREA_BIT); + } + + /* Populate BGP-LS attributes from Link State vertex */ @@ -9687,21 +9293,19 @@ index 0000000000..0750dd690a + /* Set Local Node Descriptor */ + nlri.nlri_data.node.local_node.igp_router_id_len = router_id_len; + memcpy(nlri.nlri_data.node.local_node.igp_router_id, router_id, router_id_len); -+ BGP_LS_TLV_SET(nlri.nlri_data.node.local_node.present_tlvs, -+ BGP_LS_NODE_DESC_IGP_ROUTER_BIT); ++ SET_FLAG(nlri.nlri_data.node.local_node.present_tlvs, BGP_LS_NODE_DESC_IGP_ROUTER_BIT); + + /* Set AS Number if available */ + if (CHECK_FLAG(vertex->node->flags, LS_NODE_AS_NUMBER)) { + nlri.nlri_data.node.local_node.asn = vertex->node->as_number; -+ BGP_LS_TLV_SET(nlri.nlri_data.node.local_node.present_tlvs, -+ BGP_LS_NODE_DESC_AS_BIT); ++ SET_FLAG(nlri.nlri_data.node.local_node.present_tlvs, BGP_LS_NODE_DESC_AS_BIT); + } + + /* Set OSPF Area ID if OSPF */ + if (protocol_id == BGP_LS_PROTO_OSPFV2 || protocol_id == BGP_LS_PROTO_OSPFV3) { + nlri.nlri_data.node.local_node.ospf_area_id = area_id; -+ BGP_LS_TLV_SET(nlri.nlri_data.node.local_node.present_tlvs, -+ BGP_LS_NODE_DESC_OSPF_AREA_BIT); ++ SET_FLAG(nlri.nlri_data.node.local_node.present_tlvs, ++ BGP_LS_NODE_DESC_OSPF_AREA_BIT); + } + + /* Withdraw from RIB */ @@ -9758,89 +9362,83 @@ index 0000000000..0750dd690a + /* Set Local Node Descriptor */ + nlri.nlri_data.link.local_node.igp_router_id_len = local_router_id_len; + memcpy(nlri.nlri_data.link.local_node.igp_router_id, local_router_id, local_router_id_len); -+ BGP_LS_TLV_SET(nlri.nlri_data.link.local_node.present_tlvs, -+ BGP_LS_NODE_DESC_IGP_ROUTER_BIT); ++ SET_FLAG(nlri.nlri_data.link.local_node.present_tlvs, BGP_LS_NODE_DESC_IGP_ROUTER_BIT); + + /* Set AS Number for Local Node if available */ + if (edge->source && edge->source->node && + CHECK_FLAG(edge->source->node->flags, LS_NODE_AS_NUMBER)) { + nlri.nlri_data.link.local_node.asn = edge->source->node->as_number; -+ BGP_LS_TLV_SET(nlri.nlri_data.link.local_node.present_tlvs, -+ BGP_LS_NODE_DESC_AS_BIT); ++ SET_FLAG(nlri.nlri_data.link.local_node.present_tlvs, BGP_LS_NODE_DESC_AS_BIT); + } + + /* Set Remote Node Descriptor */ + nlri.nlri_data.link.remote_node.igp_router_id_len = remote_router_id_len; + memcpy(nlri.nlri_data.link.remote_node.igp_router_id, remote_router_id, + remote_router_id_len); -+ BGP_LS_TLV_SET(nlri.nlri_data.link.remote_node.present_tlvs, -+ BGP_LS_NODE_DESC_IGP_ROUTER_BIT); ++ SET_FLAG(nlri.nlri_data.link.remote_node.present_tlvs, BGP_LS_NODE_DESC_IGP_ROUTER_BIT); + + /* Set AS Number for Remote Node if available */ + if (edge->destination && edge->destination->node && + CHECK_FLAG(edge->destination->node->flags, LS_NODE_AS_NUMBER)) { + nlri.nlri_data.link.remote_node.asn = edge->destination->node->as_number; -+ BGP_LS_TLV_SET(nlri.nlri_data.link.remote_node.present_tlvs, -+ BGP_LS_NODE_DESC_AS_BIT); ++ SET_FLAG(nlri.nlri_data.link.remote_node.present_tlvs, BGP_LS_NODE_DESC_AS_BIT); + } + + /* Set OSPF Area ID if OSPF */ + if (protocol_id == BGP_LS_PROTO_OSPFV2 || protocol_id == BGP_LS_PROTO_OSPFV3) { + nlri.nlri_data.link.local_node.ospf_area_id = area_id; -+ BGP_LS_TLV_SET(nlri.nlri_data.link.local_node.present_tlvs, -+ BGP_LS_NODE_DESC_OSPF_AREA_BIT); ++ SET_FLAG(nlri.nlri_data.link.local_node.present_tlvs, ++ BGP_LS_NODE_DESC_OSPF_AREA_BIT); + nlri.nlri_data.link.remote_node.ospf_area_id = area_id; -+ BGP_LS_TLV_SET(nlri.nlri_data.link.remote_node.present_tlvs, -+ BGP_LS_NODE_DESC_OSPF_AREA_BIT); ++ SET_FLAG(nlri.nlri_data.link.remote_node.present_tlvs, ++ BGP_LS_NODE_DESC_OSPF_AREA_BIT); + } + + /* Populate Link Descriptor from Link State edge attributes */ + /* Link Local/Remote Identifiers (TLV 258) */ + if (CHECK_FLAG(edge->attributes->flags, LS_ATTR_LOCAL_ID)) { + nlri.nlri_data.link.link_desc.link_local_id = edge->attributes->standard.local_id; -+ BGP_LS_TLV_SET(nlri.nlri_data.link.link_desc.present_tlvs, -+ BGP_LS_LINK_DESC_LINK_ID_BIT); ++ SET_FLAG(nlri.nlri_data.link.link_desc.present_tlvs, BGP_LS_LINK_DESC_LINK_ID_BIT); + } + + if (CHECK_FLAG(edge->attributes->flags, LS_ATTR_NEIGH_ID)) { + nlri.nlri_data.link.link_desc.link_remote_id = edge->attributes->standard.remote_id; -+ BGP_LS_TLV_SET(nlri.nlri_data.link.link_desc.present_tlvs, -+ BGP_LS_LINK_DESC_LINK_ID_BIT); ++ SET_FLAG(nlri.nlri_data.link.link_desc.present_tlvs, BGP_LS_LINK_DESC_LINK_ID_BIT); + } + + /* IPv4 Interface Address (TLV 259) */ + if (CHECK_FLAG(edge->attributes->flags, LS_ATTR_LOCAL_ADDR)) { + nlri.nlri_data.link.link_desc.ipv4_intf_addr = edge->attributes->standard.local; -+ BGP_LS_TLV_SET(nlri.nlri_data.link.link_desc.present_tlvs, -+ BGP_LS_LINK_DESC_IPV4_INTF_BIT); ++ SET_FLAG(nlri.nlri_data.link.link_desc.present_tlvs, ++ BGP_LS_LINK_DESC_IPV4_INTF_BIT); + } + + /* IPv4 Neighbor Address (TLV 260) */ + if (CHECK_FLAG(edge->attributes->flags, LS_ATTR_NEIGH_ADDR)) { + nlri.nlri_data.link.link_desc.ipv4_neigh_addr = edge->attributes->standard.remote; -+ BGP_LS_TLV_SET(nlri.nlri_data.link.link_desc.present_tlvs, -+ BGP_LS_LINK_DESC_IPV4_NEIGH_BIT); ++ SET_FLAG(nlri.nlri_data.link.link_desc.present_tlvs, ++ BGP_LS_LINK_DESC_IPV4_NEIGH_BIT); + } + + /* IPv6 Interface Address (TLV 261) */ + if (CHECK_FLAG(edge->attributes->flags, LS_ATTR_LOCAL_ADDR6)) { + nlri.nlri_data.link.link_desc.ipv6_intf_addr = edge->attributes->standard.local6; -+ BGP_LS_TLV_SET(nlri.nlri_data.link.link_desc.present_tlvs, -+ BGP_LS_LINK_DESC_IPV6_INTF_BIT); ++ SET_FLAG(nlri.nlri_data.link.link_desc.present_tlvs, ++ BGP_LS_LINK_DESC_IPV6_INTF_BIT); + } + + /* IPv6 Neighbor Address (TLV 262) */ + if (CHECK_FLAG(edge->attributes->flags, LS_ATTR_NEIGH_ADDR6)) { + nlri.nlri_data.link.link_desc.ipv6_neigh_addr = edge->attributes->standard.remote6; -+ BGP_LS_TLV_SET(nlri.nlri_data.link.link_desc.present_tlvs, -+ BGP_LS_LINK_DESC_IPV6_NEIGH_BIT); ++ SET_FLAG(nlri.nlri_data.link.link_desc.present_tlvs, ++ BGP_LS_LINK_DESC_IPV6_NEIGH_BIT); + } + + /* Remote AS Number (TLV 264) */ + if (CHECK_FLAG(edge->attributes->flags, LS_ATTR_REMOTE_AS)) { + nlri.nlri_data.link.link_desc.remote_asn = edge->attributes->standard.remote_as; -+ BGP_LS_TLV_SET(nlri.nlri_data.link.link_desc.present_tlvs, -+ BGP_LS_LINK_DESC_REMOTE_AS_BIT); ++ SET_FLAG(nlri.nlri_data.link.link_desc.present_tlvs, ++ BGP_LS_LINK_DESC_REMOTE_AS_BIT); + } + + /* Populate BGP-LS attributes from Link State edge */ @@ -9901,89 +9499,83 @@ index 0000000000..0750dd690a + /* Set Local Node Descriptor */ + nlri.nlri_data.link.local_node.igp_router_id_len = local_router_id_len; + memcpy(nlri.nlri_data.link.local_node.igp_router_id, local_router_id, local_router_id_len); -+ BGP_LS_TLV_SET(nlri.nlri_data.link.local_node.present_tlvs, -+ BGP_LS_NODE_DESC_IGP_ROUTER_BIT); ++ SET_FLAG(nlri.nlri_data.link.local_node.present_tlvs, BGP_LS_NODE_DESC_IGP_ROUTER_BIT); + + /* Set AS Number for Local Node if available */ + if (edge->source && edge->source->node && + CHECK_FLAG(edge->source->node->flags, LS_NODE_AS_NUMBER)) { + nlri.nlri_data.link.local_node.asn = edge->source->node->as_number; -+ BGP_LS_TLV_SET(nlri.nlri_data.link.local_node.present_tlvs, -+ BGP_LS_NODE_DESC_AS_BIT); ++ SET_FLAG(nlri.nlri_data.link.local_node.present_tlvs, BGP_LS_NODE_DESC_AS_BIT); + } + + /* Set Remote Node Descriptor */ + nlri.nlri_data.link.remote_node.igp_router_id_len = remote_router_id_len; + memcpy(nlri.nlri_data.link.remote_node.igp_router_id, remote_router_id, + remote_router_id_len); -+ BGP_LS_TLV_SET(nlri.nlri_data.link.remote_node.present_tlvs, -+ BGP_LS_NODE_DESC_IGP_ROUTER_BIT); ++ SET_FLAG(nlri.nlri_data.link.remote_node.present_tlvs, BGP_LS_NODE_DESC_IGP_ROUTER_BIT); + + /* Set AS Number for Remote Node if available */ + if (edge->destination && edge->destination->node && + CHECK_FLAG(edge->destination->node->flags, LS_NODE_AS_NUMBER)) { + nlri.nlri_data.link.remote_node.asn = edge->destination->node->as_number; -+ BGP_LS_TLV_SET(nlri.nlri_data.link.remote_node.present_tlvs, -+ BGP_LS_NODE_DESC_AS_BIT); ++ SET_FLAG(nlri.nlri_data.link.remote_node.present_tlvs, BGP_LS_NODE_DESC_AS_BIT); + } + + /* Set OSPF Area ID if OSPF */ + if (protocol_id == BGP_LS_PROTO_OSPFV2 || protocol_id == BGP_LS_PROTO_OSPFV3) { + nlri.nlri_data.link.local_node.ospf_area_id = area_id; -+ BGP_LS_TLV_SET(nlri.nlri_data.link.local_node.present_tlvs, -+ BGP_LS_NODE_DESC_OSPF_AREA_BIT); ++ SET_FLAG(nlri.nlri_data.link.local_node.present_tlvs, ++ BGP_LS_NODE_DESC_OSPF_AREA_BIT); + nlri.nlri_data.link.remote_node.ospf_area_id = area_id; -+ BGP_LS_TLV_SET(nlri.nlri_data.link.remote_node.present_tlvs, -+ BGP_LS_NODE_DESC_OSPF_AREA_BIT); ++ SET_FLAG(nlri.nlri_data.link.remote_node.present_tlvs, ++ BGP_LS_NODE_DESC_OSPF_AREA_BIT); + } + + /* Populate Link Descriptor from Link State edge attributes */ + /* Link Local/Remote Identifiers (TLV 258) */ + if (CHECK_FLAG(edge->attributes->flags, LS_ATTR_LOCAL_ID)) { + nlri.nlri_data.link.link_desc.link_local_id = edge->attributes->standard.local_id; -+ BGP_LS_TLV_SET(nlri.nlri_data.link.link_desc.present_tlvs, -+ BGP_LS_LINK_DESC_LINK_ID_BIT); ++ SET_FLAG(nlri.nlri_data.link.link_desc.present_tlvs, BGP_LS_LINK_DESC_LINK_ID_BIT); + } + + if (CHECK_FLAG(edge->attributes->flags, LS_ATTR_NEIGH_ID)) { + nlri.nlri_data.link.link_desc.link_remote_id = edge->attributes->standard.remote_id; -+ BGP_LS_TLV_SET(nlri.nlri_data.link.link_desc.present_tlvs, -+ BGP_LS_LINK_DESC_LINK_ID_BIT); ++ SET_FLAG(nlri.nlri_data.link.link_desc.present_tlvs, BGP_LS_LINK_DESC_LINK_ID_BIT); + } + + /* IPv4 Interface Address (TLV 259) */ + if (CHECK_FLAG(edge->attributes->flags, LS_ATTR_LOCAL_ADDR)) { + nlri.nlri_data.link.link_desc.ipv4_intf_addr = edge->attributes->standard.local; -+ BGP_LS_TLV_SET(nlri.nlri_data.link.link_desc.present_tlvs, -+ BGP_LS_LINK_DESC_IPV4_INTF_BIT); ++ SET_FLAG(nlri.nlri_data.link.link_desc.present_tlvs, ++ BGP_LS_LINK_DESC_IPV4_INTF_BIT); + } + + /* IPv4 Neighbor Address (TLV 260) */ + if (CHECK_FLAG(edge->attributes->flags, LS_ATTR_NEIGH_ADDR)) { + nlri.nlri_data.link.link_desc.ipv4_neigh_addr = edge->attributes->standard.remote; -+ BGP_LS_TLV_SET(nlri.nlri_data.link.link_desc.present_tlvs, -+ BGP_LS_LINK_DESC_IPV4_NEIGH_BIT); ++ SET_FLAG(nlri.nlri_data.link.link_desc.present_tlvs, ++ BGP_LS_LINK_DESC_IPV4_NEIGH_BIT); + } + + /* IPv6 Interface Address (TLV 261) */ + if (CHECK_FLAG(edge->attributes->flags, LS_ATTR_LOCAL_ADDR6)) { + nlri.nlri_data.link.link_desc.ipv6_intf_addr = edge->attributes->standard.local6; -+ BGP_LS_TLV_SET(nlri.nlri_data.link.link_desc.present_tlvs, -+ BGP_LS_LINK_DESC_IPV6_INTF_BIT); ++ SET_FLAG(nlri.nlri_data.link.link_desc.present_tlvs, ++ BGP_LS_LINK_DESC_IPV6_INTF_BIT); + } + + /* IPv6 Neighbor Address (TLV 262) */ + if (CHECK_FLAG(edge->attributes->flags, LS_ATTR_NEIGH_ADDR6)) { + nlri.nlri_data.link.link_desc.ipv6_neigh_addr = edge->attributes->standard.remote6; -+ BGP_LS_TLV_SET(nlri.nlri_data.link.link_desc.present_tlvs, -+ BGP_LS_LINK_DESC_IPV6_NEIGH_BIT); ++ SET_FLAG(nlri.nlri_data.link.link_desc.present_tlvs, ++ BGP_LS_LINK_DESC_IPV6_NEIGH_BIT); + } + + /* Remote AS Number (TLV 264) */ + if (CHECK_FLAG(edge->attributes->flags, LS_ATTR_REMOTE_AS)) { + nlri.nlri_data.link.link_desc.remote_asn = edge->attributes->standard.remote_as; -+ BGP_LS_TLV_SET(nlri.nlri_data.link.link_desc.present_tlvs, -+ BGP_LS_LINK_DESC_REMOTE_AS_BIT); ++ SET_FLAG(nlri.nlri_data.link.link_desc.present_tlvs, ++ BGP_LS_LINK_DESC_REMOTE_AS_BIT); + } + + /* Withdraw from RIB */ @@ -10047,29 +9639,26 @@ index 0000000000..0750dd690a + /* Set Local Node Descriptor */ + nlri.nlri_data.prefix.local_node.igp_router_id_len = router_id_len; + memcpy(nlri.nlri_data.prefix.local_node.igp_router_id, router_id, router_id_len); -+ BGP_LS_TLV_SET(nlri.nlri_data.prefix.local_node.present_tlvs, -+ BGP_LS_NODE_DESC_IGP_ROUTER_BIT); ++ SET_FLAG(nlri.nlri_data.prefix.local_node.present_tlvs, BGP_LS_NODE_DESC_IGP_ROUTER_BIT); + + /* Set AS Number if available */ + if (subnet->vertex && subnet->vertex->node && + CHECK_FLAG(subnet->vertex->node->flags, LS_NODE_AS_NUMBER)) { + nlri.nlri_data.prefix.local_node.asn = subnet->vertex->node->as_number; -+ BGP_LS_TLV_SET(nlri.nlri_data.prefix.local_node.present_tlvs, -+ BGP_LS_NODE_DESC_AS_BIT); ++ SET_FLAG(nlri.nlri_data.prefix.local_node.present_tlvs, BGP_LS_NODE_DESC_AS_BIT); + } + + /* Set OSPF Area ID if OSPF */ + if (protocol_id == BGP_LS_PROTO_OSPFV2 || protocol_id == BGP_LS_PROTO_OSPFV3) { + nlri.nlri_data.prefix.local_node.ospf_area_id = area_id; -+ BGP_LS_TLV_SET(nlri.nlri_data.prefix.local_node.present_tlvs, -+ BGP_LS_NODE_DESC_OSPF_AREA_BIT); ++ SET_FLAG(nlri.nlri_data.prefix.local_node.present_tlvs, ++ BGP_LS_NODE_DESC_OSPF_AREA_BIT); + } + + /* Set Prefix Descriptor */ + nlri.nlri_data.prefix.prefix_desc.prefix = *prefix; + apply_mask(&nlri.nlri_data.prefix.prefix_desc.prefix); -+ BGP_LS_TLV_SET(nlri.nlri_data.prefix.prefix_desc.present_tlvs, -+ BGP_LS_PREFIX_DESC_IP_REACH_BIT); ++ SET_FLAG(nlri.nlri_data.prefix.prefix_desc.present_tlvs, BGP_LS_PREFIX_DESC_IP_REACH_BIT); + + /* Populate BGP-LS attributes from Link State subnet */ + ls_attr = bgp_ls_attr_alloc(); @@ -10137,28 +9726,25 @@ index 0000000000..0750dd690a + /* Set Local Node Descriptor */ + nlri.nlri_data.prefix.local_node.igp_router_id_len = router_id_len; + memcpy(nlri.nlri_data.prefix.local_node.igp_router_id, router_id, router_id_len); -+ BGP_LS_TLV_SET(nlri.nlri_data.prefix.local_node.present_tlvs, -+ BGP_LS_NODE_DESC_IGP_ROUTER_BIT); ++ SET_FLAG(nlri.nlri_data.prefix.local_node.present_tlvs, BGP_LS_NODE_DESC_IGP_ROUTER_BIT); + + /* Set AS Number if available */ + if (subnet->vertex && subnet->vertex->node && + CHECK_FLAG(subnet->vertex->node->flags, LS_NODE_AS_NUMBER)) { + nlri.nlri_data.prefix.local_node.asn = subnet->vertex->node->as_number; -+ BGP_LS_TLV_SET(nlri.nlri_data.prefix.local_node.present_tlvs, -+ BGP_LS_NODE_DESC_AS_BIT); ++ SET_FLAG(nlri.nlri_data.prefix.local_node.present_tlvs, BGP_LS_NODE_DESC_AS_BIT); + } + + /* Set OSPF Area ID if OSPF */ + if (protocol_id == BGP_LS_PROTO_OSPFV2 || protocol_id == BGP_LS_PROTO_OSPFV3) { + nlri.nlri_data.prefix.local_node.ospf_area_id = area_id; -+ BGP_LS_TLV_SET(nlri.nlri_data.prefix.local_node.present_tlvs, -+ BGP_LS_NODE_DESC_OSPF_AREA_BIT); ++ SET_FLAG(nlri.nlri_data.prefix.local_node.present_tlvs, ++ BGP_LS_NODE_DESC_OSPF_AREA_BIT); + } + + /* Set Prefix Descriptor */ + nlri.nlri_data.prefix.prefix_desc.prefix = *prefix; -+ BGP_LS_TLV_SET(nlri.nlri_data.prefix.prefix_desc.present_tlvs, -+ BGP_LS_PREFIX_DESC_IP_REACH_BIT); ++ SET_FLAG(nlri.nlri_data.prefix.prefix_desc.present_tlvs, BGP_LS_PREFIX_DESC_IP_REACH_BIT); + + /* Withdraw from RIB */ + ret = bgp_ls_withdraw(bgp, &nlri); @@ -10860,10 +10446,10 @@ index 0000000000..9404523c13 + +#endif /* _FRR_BGP_LS_TED_H */ diff --git a/bgpd/bgp_open.c b/bgpd/bgp_open.c -index 7d6a0b2c24..f9e0a15bfe 100644 +index dce2a1990c..c39e71adf2 100644 --- a/bgpd/bgp_open.c +++ b/bgpd/bgp_open.c -@@ -157,6 +157,11 @@ void bgp_capability_vty_out(struct vty *vty, struct peer *peer, bool use_json, +@@ -158,6 +158,11 @@ void bgp_capability_vty_out(struct vty *vty, struct peer *peer, bool use_json, "capabilityErrorMultiProtocolAfi", "L2VPN"); break; @@ -10875,7 +10461,7 @@ index 7d6a0b2c24..f9e0a15bfe 100644 case AFI_UNSPEC: case AFI_MAX: json_object_int_add( -@@ -208,6 +213,11 @@ void bgp_capability_vty_out(struct vty *vty, struct peer *peer, bool use_json, +@@ -209,6 +214,11 @@ void bgp_capability_vty_out(struct vty *vty, struct peer *peer, bool use_json, "capabilityErrorMultiProtocolSafi", "flowspec"); break; @@ -10887,7 +10473,7 @@ index 7d6a0b2c24..f9e0a15bfe 100644 case SAFI_UNSPEC: case SAFI_MAX: json_object_int_add( -@@ -229,6 +239,9 @@ void bgp_capability_vty_out(struct vty *vty, struct peer *peer, bool use_json, +@@ -230,6 +240,9 @@ void bgp_capability_vty_out(struct vty *vty, struct peer *peer, bool use_json, case AFI_L2VPN: vty_out(vty, "AFI L2VPN, "); break; @@ -10897,7 +10483,7 @@ index 7d6a0b2c24..f9e0a15bfe 100644 case AFI_UNSPEC: case AFI_MAX: vty_out(vty, "AFI Unknown %d, ", -@@ -257,6 +270,9 @@ void bgp_capability_vty_out(struct vty *vty, struct peer *peer, bool use_json, +@@ -258,6 +271,9 @@ void bgp_capability_vty_out(struct vty *vty, struct peer *peer, bool use_json, case SAFI_EVPN: vty_out(vty, "SAFI EVPN"); break; @@ -10907,7 +10493,7 @@ index 7d6a0b2c24..f9e0a15bfe 100644 case SAFI_UNSPEC: case SAFI_MAX: vty_out(vty, "SAFI Unknown %d ", -@@ -1511,7 +1527,8 @@ int bgp_open_option_parse(struct peer *peer, uint16_t length, +@@ -1529,7 +1545,8 @@ int bgp_open_option_parse(struct peer *peer, struct peer_connection *connection, && !peer->afc_nego[AFI_IP6][SAFI_MPLS_VPN] && !peer->afc_nego[AFI_IP6][SAFI_ENCAP] && !peer->afc_nego[AFI_IP6][SAFI_FLOWSPEC] @@ -10918,7 +10504,7 @@ index 7d6a0b2c24..f9e0a15bfe 100644 "%s [Error] Configured AFI/SAFIs do not overlap with received MP capabilities", peer->host); diff --git a/bgpd/bgp_packet.c b/bgpd/bgp_packet.c -index 2157f2a02c..f41890e230 100644 +index dfb03c6cec..fe1a652d1b 100644 --- a/bgpd/bgp_packet.c +++ b/bgpd/bgp_packet.c @@ -50,6 +50,7 @@ @@ -10939,20 +10525,19 @@ index 2157f2a02c..f41890e230 100644 return BGP_NLRI_PARSE_ERROR; } diff --git a/bgpd/bgp_route.c b/bgpd/bgp_route.c -index 0db68920a0..157f5ec333 100644 +index 351f01ef40..0ad77f1486 100644 --- a/bgpd/bgp_route.c +++ b/bgpd/bgp_route.c -@@ -64,6 +64,9 @@ - #include "bgpd/bgp_network.h" - #include "bgpd/bgp_trace.h" +@@ -67,6 +67,8 @@ #include "bgpd/bgp_rpki.h" -+#include "bgpd/bgp_bfd.h" + #include "bgpd/bgp_srv6.h" + #include "bgpd/bgp_bfd.h" +#include "bgpd/bgp_ls_nlri.h" +#include "bgpd/bgp_ls.h" #ifdef ENABLE_BGP_VNC #include "bgpd/rfapi/rfapi_backend.h" -@@ -81,6 +84,10 @@ +@@ -84,6 +86,10 @@ #include "bgpd/bgp_route_clippy.c" @@ -10963,7 +10548,253 @@ index 0db68920a0..157f5ec333 100644 static bool bgp_attr_nexthop_same(const struct attr *attr1, const struct attr *attr2, afi_t afi) { afi_t nh_afi1 = BGP_ATTR_NH_AFI(afi, attr1); -@@ -10256,6 +10263,18 @@ static void route_vty_out_route(struct bgp_dest *dest, const struct prefix *p, +@@ -5356,6 +5362,8 @@ static void bgp_rib_withdraw(const struct prefix *p, struct bgp_dest *dest, stru + if (advertise_type5_routes_multipath(peer->bgp, afi) && is_route_injectable_into_evpn(pi)) + bgp_evpn_unexport_type5_route(peer->bgp, dest, pi, afi, safi); + ++ bgp_ls_withdraw_bgp_prefix(peer->bgp, afi, safi, dest, pi); ++ + bgp_rib_remove(dest, pi, peer, afi, safi); + } + +@@ -6161,6 +6169,8 @@ void bgp_update(struct peer *peer, const struct prefix *p, uint32_t addpath_id, + force_evpn_export = true; + } + ++ bgp_ls_withdraw_bgp_prefix(bgp, afi, safi, dest, pi); ++ + /* Special handling for EVPN update of an existing route. If the + * extended community or nexthop attribute has changed, we need + * to un-import +@@ -6249,6 +6259,8 @@ void bgp_update(struct peer *peer, const struct prefix *p, uint32_t addpath_id, + bgp_evpn_export_type5_route(bgp, dest, pi, afi, safi); + } + ++ bgp_ls_originate_bgp_prefix(bgp, afi, safi, dest, pi); ++ + /* Update bgp route dampening information. */ + if (get_active_bdc_from_pi(pi, afi, safi) && + peer->sort == BGP_PEER_EBGP) { +@@ -6330,6 +6342,10 @@ void bgp_update(struct peer *peer, const struct prefix *p, uint32_t addpath_id, + vpn_leak_to_vrf_update(bgp, pi, prd, peer); + } + ++ if (safi == SAFI_UNICAST && bgp->inst_type == BGP_INSTANCE_TYPE_DEFAULT && ++ bgp->ls_info && bgp->ls_info->enable_distribution) ++ bgp_ls_originate_bgp_prefix(bgp, afi, safi, dest, pi); ++ + #ifdef ENABLE_BGP_VNC + if (SAFI_MPLS_VPN == safi) { + mpls_label_t label_decoded = decode_label(label); +@@ -6414,6 +6430,8 @@ void bgp_update(struct peer *peer, const struct prefix *p, uint32_t addpath_id, + if (advertise_type5_routes_multipath(bgp, afi) && is_route_injectable_into_evpn(new)) + bgp_evpn_export_type5_route(bgp, dest, new, afi, safi); + ++ bgp_ls_originate_bgp_prefix(bgp, afi, safi, dest, new); ++ + hook_call(bgp_process, bgp, afi, safi, dest, peer, false); + + /* Process change. */ +@@ -6441,6 +6459,10 @@ void bgp_update(struct peer *peer, const struct prefix *p, uint32_t addpath_id, + } + #endif + ++ if (safi == SAFI_UNICAST && bgp->inst_type == BGP_INSTANCE_TYPE_DEFAULT && bgp->ls_info && ++ bgp->ls_info->enable_distribution) ++ bgp_ls_originate_bgp_prefix(bgp, afi, safi, dest, new); ++ + if (p_evpn) + evpn_overlay_free(p_evpn); + return; +@@ -6480,6 +6502,8 @@ filtered: + if (advertise_type5_routes_multipath(bgp, afi) && is_route_injectable_into_evpn(pi)) + bgp_evpn_unexport_type5_route(bgp, dest, pi, afi, safi); + ++ bgp_ls_withdraw_bgp_prefix(bgp, afi, safi, dest, pi); ++ + if (SAFI_UNICAST == safi + && (bgp->inst_type == BGP_INSTANCE_TYPE_VRF + || bgp->inst_type == BGP_INSTANCE_TYPE_DEFAULT)) { +@@ -7029,6 +7053,8 @@ static wq_item_status bgp_clear_route_node(struct work_queue *wq, void *data) + if (advertise_type5_routes_multipath(bgp, afi) && + is_route_injectable_into_evpn(pi)) + bgp_evpn_unexport_type5_route(bgp, dest, pi, afi, safi); ++ ++ bgp_ls_withdraw_bgp_prefix(bgp, afi, safi, dest, pi); + /* Handle withdraw for VRF route-leaking and L3VPN */ + if (SAFI_UNICAST == safi + && (bgp->inst_type == BGP_INSTANCE_TYPE_VRF || +@@ -7257,6 +7283,8 @@ static void clearing_clear_one_pi(struct bgp_table *table, struct bgp_dest *dest + /* If this is a route exported to EVPN, process for un-export */ + if (advertise_type5_routes_multipath(bgp, afi) && is_route_injectable_into_evpn(pi)) + bgp_evpn_unexport_type5_route(bgp, dest, pi, afi, safi); ++ ++ bgp_ls_withdraw_bgp_prefix(bgp, afi, safi, dest, pi); + /* Handle withdraw for VRF route-leaking and L3VPN */ + if (SAFI_UNICAST == safi + && (bgp->inst_type == BGP_INSTANCE_TYPE_VRF || +@@ -7811,6 +7839,8 @@ void bgp_clear_stale_route(struct peer *peer, afi_t afi, safi_t safi) + bgp_evpn_unexport_type5_route(peer->bgp, dest, pi, afi, + safi); + ++ bgp_ls_withdraw_bgp_prefix(peer->bgp, afi, safi, dest, pi); ++ + bgp_rib_remove(dest, pi, peer, afi, safi); + } + } +@@ -7942,6 +7972,8 @@ static void bgp_cleanup_table(struct bgp *bgp, struct bgp_table *table, afi_t af + is_route_injectable_into_evpn(pi)) + bgp_evpn_unexport_type5_route(bgp, dest, pi, afi, safi); + ++ bgp_ls_withdraw_bgp_prefix(bgp, afi, safi, dest, pi); ++ + if (CHECK_FLAG(pi->flags, BGP_PATH_SELECTED) && + bgp_zebra_announce_eligible(pi)) { + if (bgp_fibupd_safi(safi)) { +@@ -8436,6 +8468,8 @@ void bgp_static_update(struct bgp *bgp, const struct prefix *p, + is_route_injectable_into_evpn(pi)) + bgp_evpn_export_type5_route(bgp, dest, pi, afi, safi); + ++ bgp_ls_originate_bgp_prefix(bgp, afi, safi, dest, pi); ++ + bgp_dest_unlock_node(dest); + aspath_unintern(&attr.aspath); + return; +@@ -8488,6 +8522,8 @@ void bgp_static_update(struct bgp *bgp, const struct prefix *p, + if (advertise_type5_routes_multipath(bgp, afi) && is_route_injectable_into_evpn(new)) + bgp_evpn_export_type5_route(bgp, dest, new, afi, safi); + ++ bgp_ls_originate_bgp_prefix(bgp, afi, safi, dest, new); ++ + #ifdef ENABLE_BGP_VNC + if (safi == SAFI_MPLS_VPN || safi == SAFI_ENCAP || safi == SAFI_EVPN) + rfapiProcessUpdate(new->peer, NULL, p, &bgp_static->prd, +@@ -8538,6 +8574,8 @@ void bgp_static_withdraw(struct bgp *bgp, const struct prefix *p, afi_t afi, + if (advertise_type5_routes_multipath(bgp, afi) && is_route_injectable_into_evpn(pi)) + bgp_evpn_unexport_type5_route(bgp, dest, pi, afi, safi); + ++ bgp_ls_withdraw_bgp_prefix(bgp, afi, safi, dest, pi); ++ + bgp_aggregate_decrement(bgp, p, pi, afi, safi); + bgp_unlink_nexthop(pi); + bgp_path_info_mark_for_delete(dest, pi); +@@ -9313,6 +9351,7 @@ static void bgp_aggregate_install( + if (advertise_type5_routes_multipath(bgp, afi) && + is_route_injectable_into_evpn(new)) + bgp_evpn_export_type5_route(bgp, dest, new, afi, safi); ++ bgp_ls_originate_bgp_prefix(bgp, afi, safi, dest, new); + } else { + uninstall_aggregate_route: + /* Withdraw the aggregate route from routing table. */ +@@ -9329,6 +9368,8 @@ static void bgp_aggregate_install( + if (advertise_type5_routes_multipath(bgp, afi) && + is_route_injectable_into_evpn(pi)) + bgp_evpn_unexport_type5_route(bgp, dest, pi, afi, safi); ++ ++ bgp_ls_withdraw_bgp_prefix(bgp, afi, safi, dest, pi); + } + } + +@@ -9444,6 +9485,8 @@ void bgp_aggregate_toggle_suppressed(struct bgp_aggregate *aggregate, + is_route_injectable_into_evpn(pi)) + bgp_evpn_unexport_type5_route(bgp, dest, pi, afi, + safi); ++ ++ bgp_ls_withdraw_bgp_prefix(bgp, afi, safi, dest, pi); + } + continue; + } +@@ -9458,6 +9501,8 @@ void bgp_aggregate_toggle_suppressed(struct bgp_aggregate *aggregate, + if (advertise_type5_routes_multipath(bgp, afi) && + is_route_injectable_into_evpn(pi)) + bgp_evpn_unexport_type5_route(bgp, dest, pi, afi, safi); ++ ++ bgp_ls_withdraw_bgp_prefix(bgp, afi, safi, dest, pi); + } + } + } +@@ -9600,6 +9645,8 @@ bool bgp_aggregate_route(struct bgp *bgp, const struct prefix *p, afi_t afi, + is_route_injectable_into_evpn(pi)) + bgp_evpn_unexport_type5_route(bgp, dest, pi, afi, + safi); ++ ++ bgp_ls_withdraw_bgp_prefix(bgp, afi, safi, dest, pi); + } + } + +@@ -9626,6 +9673,8 @@ bool bgp_aggregate_route(struct bgp *bgp, const struct prefix *p, afi_t afi, + is_route_injectable_into_evpn(pi)) + bgp_evpn_unexport_type5_route(bgp, dest, pi, afi, + safi); ++ ++ bgp_ls_withdraw_bgp_prefix(bgp, afi, safi, dest, pi); + } + } + +@@ -9784,6 +9833,8 @@ void bgp_aggregate_delete(struct bgp *bgp, const struct prefix *p, afi_t afi, + is_route_injectable_into_evpn(pi)) + bgp_evpn_export_type5_route(bgp, dest, pi, afi, + safi); ++ ++ bgp_ls_originate_bgp_prefix(bgp, afi, safi, dest, pi); + } + } + +@@ -9998,6 +10049,8 @@ static void bgp_remove_route_from_aggregate(struct bgp *bgp, afi_t afi, + if (advertise_type5_routes_multipath(bgp, afi) && + is_route_injectable_into_evpn(pi)) + bgp_evpn_export_type5_route(bgp, pi->net, pi, afi, safi); ++ ++ bgp_ls_originate_bgp_prefix(bgp, afi, safi, pi->net, pi); + } + + if (aggregate->suppress_map_name && AGGREGATE_MED_VALID(aggregate) +@@ -10010,6 +10063,8 @@ static void bgp_remove_route_from_aggregate(struct bgp *bgp, afi_t afi, + if (advertise_type5_routes_multipath(bgp, afi) && + is_route_injectable_into_evpn(pi)) + bgp_evpn_export_type5_route(bgp, pi->net, pi, afi, safi); ++ ++ bgp_ls_originate_bgp_prefix(bgp, afi, safi, pi->net, pi); + } + + /* +@@ -10696,6 +10751,7 @@ void bgp_redistribute_add(struct bgp *bgp, struct prefix *p, + is_route_injectable_into_evpn(bpi)) + bgp_evpn_export_type5_route(bgp, bn, bpi, afi, + SAFI_UNICAST); ++ bgp_ls_originate_bgp_prefix(bgp, afi, SAFI_UNICAST, bn, bpi); + return; + } + } +@@ -10718,6 +10774,9 @@ void bgp_redistribute_add(struct bgp *bgp, struct prefix *p, + if (advertise_type5_routes_multipath(bgp, afi) && + is_route_injectable_into_evpn(new)) + bgp_evpn_export_type5_route(bgp, bn, new, afi, SAFI_UNICAST); ++ ++ if (bgp && bgp->ls_info && bgp->ls_info->enable_distribution) ++ bgp_ls_originate_bgp_prefix(bgp, afi, SAFI_UNICAST, bn, new); + } + + /* Unintern original. */ +@@ -10754,6 +10813,8 @@ void bgp_redistribute_delete(struct bgp *bgp, struct prefix *p, uint8_t type, + if (advertise_type5_routes_multipath(bgp, afi) && + is_route_injectable_into_evpn(pi)) + bgp_evpn_unexport_type5_route(bgp, dest, pi, afi, SAFI_UNICAST); ++ ++ bgp_ls_withdraw_bgp_prefix(bgp, afi, SAFI_UNICAST, dest, pi); + bgp_aggregate_decrement(bgp, p, pi, afi, SAFI_UNICAST); + bgp_path_info_mark_for_delete(dest, pi); + bgp_process(bgp, dest, pi, afi, SAFI_UNICAST); +@@ -10791,6 +10852,8 @@ void bgp_redistribute_withdraw(struct bgp *bgp, afi_t afi, int type, + if (advertise_type5_routes_multipath(bgp, afi) && + is_route_injectable_into_evpn(pi)) + bgp_evpn_unexport_type5_route(bgp, dest, pi, afi, SAFI_UNICAST); ++ ++ bgp_ls_withdraw_bgp_prefix(bgp, afi, SAFI_UNICAST, dest, pi); + bgp_aggregate_decrement(bgp, bgp_dest_get_prefix(dest), + pi, afi, SAFI_UNICAST); + bgp_path_info_mark_for_delete(dest, pi); +@@ -10838,6 +10901,18 @@ static void route_vty_out_route(struct bgp_dest *dest, const struct prefix *p, s json ? NLRI_STRING_FORMAT_JSON_SIMPLE : NLRI_STRING_FORMAT_MIN, json); @@ -10982,7 +10813,7 @@ index 0db68920a0..157f5ec333 100644 } else { if (!json) len = vty_out(vty, "%pFX", p); -@@ -10665,7 +10684,8 @@ void route_vty_out(struct vty *vty, const struct prefix *p, +@@ -11265,7 +11340,8 @@ void route_vty_out(struct vty *vty, const struct prefix *p, struct bgp_path_info vty_out(vty, "%*s", len, " "); } } @@ -10992,7 +10823,7 @@ index 0db68920a0..157f5ec333 100644 if (json_paths) { json_nexthop_global = json_object_new_object(); -@@ -11005,6 +11025,9 @@ void route_vty_out_tmp(struct vty *vty, struct bgp *bgp, struct bgp_dest *dest, +@@ -11604,6 +11680,9 @@ void route_vty_out_tmp(struct vty *vty, struct bgp *bgp, struct bgp_dest *dest, json_object_string_addf( json_net, "nextHop", "%pI4", &attr->mp_nexthop_global_in); @@ -11002,7 +10833,7 @@ index 0db68920a0..157f5ec333 100644 } if (use_bgp_med_value(attr, bgp)) { -@@ -11050,6 +11073,11 @@ void route_vty_out_tmp(struct vty *vty, struct bgp *bgp, struct bgp_dest *dest, +@@ -11649,6 +11728,11 @@ void route_vty_out_tmp(struct vty *vty, struct bgp *bgp, struct bgp_dest *dest, vty_out(vty, "\n%*s", 38, " "); else vty_out(vty, "%*s", len, " "); @@ -11014,7 +10845,7 @@ index 0db68920a0..157f5ec333 100644 } if (use_bgp_med_value(attr, bgp)) { -@@ -11587,6 +11615,7 @@ void route_vty_out_detail(struct vty *vty, struct bgp *bgp, struct bgp_dest *bn, +@@ -12184,6 +12268,7 @@ void route_vty_out_detail(struct vty *vty, struct bgp *bgp, struct bgp_dest *bn, json_object *json_string = NULL; json_object *json_int = NULL; json_object *json_adv_to = NULL; @@ -11022,7 +10853,7 @@ index 0db68920a0..157f5ec333 100644 int first = 0; struct listnode *node, *nnode; struct peer *peer; -@@ -11854,6 +11883,21 @@ void route_vty_out_detail(struct vty *vty, struct bgp *bgp, struct bgp_dest *bn, +@@ -12449,6 +12534,21 @@ void route_vty_out_detail(struct vty *vty, struct bgp *bgp, struct bgp_dest *bn, if (json_paths) json_object_string_add(json_nexthop_global, "afi", "ipv4"); @@ -11044,7 +10875,7 @@ index 0db68920a0..157f5ec333 100644 } else { if (json_paths) { bool ll_nexthop = IN6_IS_ADDR_LINKLOCAL(&attr->mp_nexthop_global); -@@ -12697,6 +12741,15 @@ void route_vty_out_detail(struct vty *vty, struct bgp *bgp, struct bgp_dest *bn, +@@ -13307,6 +13407,15 @@ void route_vty_out_detail(struct vty *vty, struct bgp *bgp, struct bgp_dest *bn, } } @@ -11060,7 +10891,7 @@ index 0db68920a0..157f5ec333 100644 /* Output some debug about internal state of the dest flags */ if (json_paths) { if (CHECK_FLAG(bn->flags, BGP_NODE_PROCESS_SCHEDULED)) -@@ -13084,6 +13137,8 @@ static int bgp_show_table(struct vty *vty, struct bgp *bgp, afi_t afi, safi_t sa +@@ -13694,6 +13803,8 @@ static int bgp_show_table(struct vty *vty, struct bgp *bgp, afi_t afi, safi_t sa vty_out(vty, BGP_SHOW_NCODE_HEADER); vty_out(vty, BGP_SHOW_OCODE_HEADER); vty_out(vty, BGP_SHOW_RPKI_HEADER); @@ -11069,7 +10900,7 @@ index 0db68920a0..157f5ec333 100644 } if (type == bgp_show_type_dampend_paths || type == bgp_show_type_damp_neighbor) -@@ -13165,6 +13220,14 @@ static int bgp_show_table(struct vty *vty, struct bgp *bgp, afi_t afi, safi_t sa +@@ -13775,6 +13886,14 @@ static int bgp_show_table(struct vty *vty, struct bgp *bgp, afi_t afi, safi_t sa vty_out(vty, ",\"%s/%d\": ", retstr, dest_p->u.prefix_flowspec .prefixlen); @@ -11084,7 +10915,7 @@ index 0db68920a0..157f5ec333 100644 } else { if (first) vty_out(vty, "\"%pFX\": ", dest_p); -@@ -13473,6 +13536,33 @@ void route_vty_out_detail_header(struct vty *vty, struct bgp *bgp, +@@ -14090,6 +14209,33 @@ void route_vty_out_detail_header(struct vty *vty, struct bgp *bgp, : ""); bgp_evpn_route2json((struct prefix_evpn *)p, json); } @@ -11118,7 +10949,7 @@ index 0db68920a0..157f5ec333 100644 } else { if (!json) { vty_out(vty, -@@ -13734,6 +13824,7 @@ const struct prefix_rd *bgp_rd_from_dest(const struct bgp_dest *dest, +@@ -14363,6 +14509,7 @@ const struct prefix_rd *bgp_rd_from_dest(const struct bgp_dest *dest, case SAFI_ENCAP: case SAFI_EVPN: return (struct prefix_rd *)(bgp_dest_get_prefix(dest)); @@ -11126,7 +10957,7 @@ index 0db68920a0..157f5ec333 100644 case SAFI_UNSPEC: case SAFI_UNICAST: case SAFI_MULTICAST: -@@ -14652,6 +14743,65 @@ DEFPY(show_ip_bgp, show_ip_bgp_cmd, +@@ -15281,6 +15428,65 @@ DEFPY(show_ip_bgp, show_ip_bgp_cmd, return CMD_SUCCESS; } @@ -11192,7 +11023,7 @@ index 0db68920a0..157f5ec333 100644 DEFUN (show_ip_bgp_route, show_ip_bgp_route_cmd, "show [ip] bgp [<view|vrf> VIEWVRFNAME] ["BGP_AFI_CMD_STR" ["BGP_SAFI_WITH_LABEL_CMD_STR"]]<A.B.C.D|A.B.C.D/M|X:X::X:X|X:X::X:X/M> [internal] [<bestpath|multipath>] [rpki <valid|invalid|notfound>] [json]", -@@ -15004,6 +15154,8 @@ static void bgp_table_stats_walker(struct event *t) +@@ -15632,6 +15838,8 @@ static void bgp_table_stats_walker(struct event *t) case AFI_L2VPN: space = EVPN_ROUTE_PREFIXLEN; break; @@ -11201,7 +11032,7 @@ index 0db68920a0..157f5ec333 100644 case AFI_UNSPEC: case AFI_MAX: return; -@@ -15266,6 +15418,8 @@ static int bgp_table_stats_single(struct vty *vty, struct bgp *bgp, afi_t afi, +@@ -15894,6 +16102,8 @@ static int bgp_table_stats_single(struct vty *vty, struct bgp *bgp, afi_t afi, case AFI_L2VPN: bitlen = EVPN_ROUTE_PREFIXLEN; break; @@ -11210,7 +11041,7 @@ index 0db68920a0..157f5ec333 100644 case AFI_UNSPEC: case AFI_MAX: break; -@@ -15634,6 +15788,24 @@ DEFUN (show_bgp_l2vpn_evpn_route_prefix, +@@ -16262,6 +16472,24 @@ DEFUN (show_bgp_l2vpn_evpn_route_prefix, BGP_PATH_SHOW_ALL, RPKI_NOT_BEING_USED, use_json(argc, argv), 0); } @@ -11235,7 +11066,7 @@ index 0db68920a0..157f5ec333 100644 static void show_adj_route_header(struct vty *vty, struct peer *peer, struct bgp_table *table, int *header1, int *header2, json_object *json, bool wide, -@@ -17658,6 +17830,7 @@ void bgp_route_init(void) +@@ -18310,6 +18538,7 @@ void bgp_route_init(void) install_element(VIEW_NODE, &show_bgp_afi_vpn_rd_route_cmd); install_element(VIEW_NODE, &show_bgp_l2vpn_evpn_route_prefix_cmd); @@ -11243,7 +11074,7 @@ index 0db68920a0..157f5ec333 100644 /* BGP dampening clear commands */ install_element(ENABLE_NODE, &clear_ip_bgp_dampening_cmd); -@@ -17743,6 +17916,9 @@ void bgp_route_init(void) +@@ -18395,6 +18624,9 @@ void bgp_route_init(void) install_element(VIEW_NODE, &show_ip_bgp_large_community_list_cmd); install_element(VIEW_NODE, &show_ip_bgp_large_community_cmd); @@ -11254,10 +11085,10 @@ index 0db68920a0..157f5ec333 100644 install_element(VIEW_NODE, &show_ip_bgp_vrf_afi_safi_routes_detailed_cmd); diff --git a/bgpd/bgp_route.h b/bgpd/bgp_route.h -index e9f9368a9a..26d69e31c1 100644 +index 8c26de1b45..9cd5d26b6a 100644 --- a/bgpd/bgp_route.h +++ b/bgpd/bgp_route.h -@@ -68,6 +68,16 @@ enum bgp_show_adj_route_type { +@@ -71,6 +71,16 @@ enum bgp_show_adj_route_type { "RPKI validation codes: V valid, I invalid, N Not found\n\n" #define BGP_SHOW_HEADER " Network Next Hop Metric LocPrf Weight Path\n" #define BGP_SHOW_HEADER_WIDE " Network Next Hop Metric LocPrf Weight Path\n" @@ -11275,22 +11106,21 @@ index e9f9368a9a..26d69e31c1 100644 /* Maximum number of sids we can process or send with a prefix. */ #define BGP_MAX_SIDS 6 diff --git a/bgpd/bgp_table.c b/bgpd/bgp_table.c -index ac84ec77a9..354ca8167d 100644 +index 17b417895f..6ed11ec69e 100644 --- a/bgpd/bgp_table.c +++ b/bgpd/bgp_table.c -@@ -17,6 +17,7 @@ - #include "bgpd/bgp_table.h" +@@ -18,6 +18,7 @@ #include "bgp_addpath.h" #include "bgp_trace.h" + #include "bgp_mpath.h" +#include "bgp_ls.h" void bgp_table_lock(struct bgp_table *rt) { -@@ -88,6 +89,13 @@ inline struct bgp_dest *bgp_dest_unlock_node(struct bgp_dest *dest) - &dest->tx_addpath, rt->afi, - rt->safi); - } -+ +@@ -99,6 +100,12 @@ inline struct bgp_dest *bgp_dest_unlock_node(struct bgp_dest *dest) + if (dest->mpath) + bgp_path_info_mpath_free(&dest->mpath); + + if (dest->ls_nlri) { + if (rt->bgp && rt->bgp->ls_info) + bgp_ls_nlri_hash_del(&rt->bgp->ls_info->nlri_hash, dest->ls_nlri); @@ -11301,23 +11131,23 @@ index ac84ec77a9..354ca8167d 100644 dest = NULL; rn->info = NULL; diff --git a/bgpd/bgp_table.h b/bgpd/bgp_table.h -index c3964433e6..4bb76705d5 100644 +index 153be63df6..7a11a46448 100644 --- a/bgpd/bgp_table.h +++ b/bgpd/bgp_table.h -@@ -85,6 +85,8 @@ struct bgp_dest { +@@ -94,6 +94,8 @@ struct bgp_dest { mpls_label_t local_label; + struct bgp_ls_nlri *ls_nlri; + + struct bgp_attr_srv6_l3service *srv6_unicast; + uint16_t flags; - #define BGP_NODE_PROCESS_SCHEDULED (1 << 0) - #define BGP_NODE_USER_CLEAR (1 << 1) diff --git a/bgpd/bgp_updgrp.c b/bgpd/bgp_updgrp.c -index b680cdda27..c6c8ed62e1 100644 +index f92e19ad69..cf7bd498c5 100644 --- a/bgpd/bgp_updgrp.c +++ b/bgpd/bgp_updgrp.c -@@ -467,6 +467,10 @@ static unsigned int updgrp_hash_key_make(const void *p) +@@ -473,6 +473,10 @@ static unsigned int updgrp_hash_key_make(const void *p) (CHECK_FLAG(peer->af_flags[afi][safi], PEER_FLAG_NEXTHOP_LOCAL_UNCHANGED))) key = jhash(&peer->nexthop.v6_global, IPV6_MAX_BYTELEN, key); @@ -11329,18 +11159,18 @@ index b680cdda27..c6c8ed62e1 100644 * ANY NEW ITEMS THAT ARE ADDED TO THE key, ENSURE DEBUG * STATEMENT STAYS UP TO DATE diff --git a/bgpd/bgp_updgrp_packet.c b/bgpd/bgp_updgrp_packet.c -index 449173d93f..315bacb2b0 100644 +index e8a0997601..e4066f1f9a 100644 --- a/bgpd/bgp_updgrp_packet.c +++ b/bgpd/bgp_updgrp_packet.c -@@ -42,6 +42,7 @@ - #include "bgpd/bgp_mplsvpn.h" +@@ -43,6 +43,7 @@ #include "bgpd/bgp_label.h" #include "bgpd/bgp_addpath.h" + #include "bgpd/bgp_trace.h" +#include "bgpd/bgp_ls_nlri.h" /******************** * PRIVATE FUNCTIONS -@@ -684,6 +685,7 @@ struct bpacket *subgroup_update_packet(struct update_subgroup *subgrp) +@@ -685,6 +686,7 @@ struct bpacket *subgroup_update_packet(struct update_subgroup *subgrp) mpls_label_t *label_pnt = NULL; mpls_label_t labels[BGP_MAX_LABELS] = { MPLS_INVALID_LABEL }; uint8_t num_labels = 0; @@ -11348,17 +11178,17 @@ index 449173d93f..315bacb2b0 100644 if (!subgrp) return NULL; -@@ -758,8 +760,7 @@ struct bpacket *subgroup_update_packet(struct update_subgroup *subgrp) - * attr. */ +@@ -760,8 +762,7 @@ struct bpacket *subgroup_update_packet(struct update_subgroup *subgrp) total_attr_len = bgp_packet_attribute(NULL, peer, s, adv->baa->attr, &vecarr, NULL, afi, safi, from, NULL, -- NULL, 0, 0, 0, path); + NULL, 0, dest->srv6_unicast, 0, 0, +- path); - -+ NULL, 0, 0, 0, path, dest->ls_nlri); ++ path, dest->ls_nlri); space_remaining = STREAM_CONCAT_REMAIN(s, snlri, STREAM_SIZE(s)) - BGP_MAX_PACKET_SIZE_OVERFLOW; -@@ -829,7 +830,39 @@ struct bpacket *subgroup_update_packet(struct update_subgroup *subgrp) +@@ -831,7 +832,39 @@ struct bpacket *subgroup_update_packet(struct update_subgroup *subgrp) labels[0] = bgp_mplsvpn_nh_label_bind_get_label(path); label_pnt = &labels[0]; num_labels = 1; @@ -11399,7 +11229,7 @@ index 449173d93f..315bacb2b0 100644 num_labels = BGP_PATH_INFO_NUM_LABELS(path); label_pnt = num_labels -@@ -842,10 +875,9 @@ struct bpacket *subgroup_update_packet(struct update_subgroup *subgrp) +@@ -844,10 +877,9 @@ struct bpacket *subgroup_update_packet(struct update_subgroup *subgrp) snlri, peer, afi, safi, &vecarr, adv->baa->attr); @@ -11413,7 +11243,7 @@ index 449173d93f..315bacb2b0 100644 } num_pfx++; -@@ -960,6 +992,7 @@ struct bpacket *subgroup_withdraw_packet(struct update_subgroup *subgrp) +@@ -962,6 +994,7 @@ struct bpacket *subgroup_withdraw_packet(struct update_subgroup *subgrp) int addpath_overhead = 0; uint32_t addpath_tx_id = 0; const struct prefix_rd *prd = NULL; @@ -11421,7 +11251,7 @@ index 449173d93f..315bacb2b0 100644 if (!subgrp) -@@ -1000,6 +1033,53 @@ struct bpacket *subgroup_withdraw_packet(struct update_subgroup *subgrp) +@@ -1002,6 +1035,53 @@ struct bpacket *subgroup_withdraw_packet(struct update_subgroup *subgrp) } else first_time = 0; @@ -11474,8 +11304,8 @@ index 449173d93f..315bacb2b0 100644 + if (afi == AFI_IP && safi == SAFI_UNICAST && !peer_cap_enhe(peer, afi, safi)) - stream_put_prefix_addpath(s, dest_p, addpath_capable, -@@ -1009,6 +1089,16 @@ struct bpacket *subgroup_withdraw_packet(struct update_subgroup *subgrp) + bgp_attr_stream_put_prefix_addpath(s, dest_p, addpath_capable, +@@ -1011,6 +1091,16 @@ struct bpacket *subgroup_withdraw_packet(struct update_subgroup *subgrp) prd = (struct prefix_rd *)bgp_dest_get_prefix( dest->pdest); @@ -11492,8 +11322,8 @@ index 449173d93f..315bacb2b0 100644 /* If first time, format the MP_UNREACH header */ if (first_time) { -@@ -1036,9 +1126,8 @@ struct bpacket *subgroup_withdraw_packet(struct update_subgroup *subgrp) - iana_safi2str(pkt_safi)); +@@ -1042,9 +1132,8 @@ struct bpacket *subgroup_withdraw_packet(struct update_subgroup *subgrp) + iana_safi2str(pkt_safi)); } - bgp_packet_mpunreach_prefix(s, dest_p, afi, safi, prd, @@ -11504,16 +11334,16 @@ index 449173d93f..315bacb2b0 100644 } num_pfx++; -@@ -1168,7 +1257,7 @@ void subgroup_default_update_packet(struct update_subgroup *subgrp, +@@ -1184,7 +1273,7 @@ void subgroup_default_update_packet(struct update_subgroup *subgrp, stream_putw(s, 0); total_attr_len = bgp_packet_attribute(NULL, peer, s, attr, &vecarr, &p, afi, safi, from, - NULL, &label, num_labels, addpath_capable, + NULL, &label, num_labels, 0, addpath_capable, - BGP_ADDPATH_TX_ID_FOR_DEFAULT_ORIGINATE, NULL); + BGP_ADDPATH_TX_ID_FOR_DEFAULT_ORIGINATE, NULL, NULL); /* Set Total Path Attribute Length. */ stream_putw_at(s, pos, total_attr_len); -@@ -1267,9 +1356,8 @@ void subgroup_default_withdraw_packet(struct update_subgroup *subgrp) +@@ -1281,9 +1370,8 @@ void subgroup_default_withdraw_packet(struct update_subgroup *subgrp) stream_putw(s, 0); mp_start = stream_get_endp(s); mplen_pos = bgp_packet_mpunreach_start(s, afi, safi); @@ -11526,10 +11356,18 @@ index 449173d93f..315bacb2b0 100644 /* Set the mp_unreach attr's length */ bgp_packet_mpunreach_end(s, mplen_pos); diff --git a/bgpd/bgp_vty.c b/bgpd/bgp_vty.c -index 5669baa87a..ebad07dac8 100644 +index fc78a59f3d..ea8d7f62e2 100644 --- a/bgpd/bgp_vty.c +++ b/bgpd/bgp_vty.c -@@ -170,6 +170,7 @@ static enum node_type bgp_node_type(afi_t afi, safi_t safi) +@@ -70,6 +70,7 @@ + #ifdef ENABLE_BGP_VNC + #include "bgpd/rfapi/bgp_rfapi_cfg.h" + #endif ++#include "bgpd/bgp_ls.h" + + FRR_CFG_DEFAULT_BOOL(BGP_IMPORT_CHECK, + { +@@ -174,6 +175,7 @@ static enum node_type bgp_node_type(afi_t afi, safi_t safi) return BGP_VPNV4_NODE; case SAFI_FLOWSPEC: return BGP_FLOWSPECV4_NODE; @@ -11537,7 +11375,7 @@ index 5669baa87a..ebad07dac8 100644 case SAFI_UNSPEC: case SAFI_ENCAP: case SAFI_EVPN: -@@ -190,6 +191,7 @@ static enum node_type bgp_node_type(afi_t afi, safi_t safi) +@@ -194,6 +196,7 @@ static enum node_type bgp_node_type(afi_t afi, safi_t safi) return BGP_VPNV6_NODE; case SAFI_FLOWSPEC: return BGP_FLOWSPECV6_NODE; @@ -11545,7 +11383,7 @@ index 5669baa87a..ebad07dac8 100644 case SAFI_UNSPEC: case SAFI_ENCAP: case SAFI_EVPN: -@@ -200,6 +202,8 @@ static enum node_type bgp_node_type(afi_t afi, safi_t safi) +@@ -204,6 +207,8 @@ static enum node_type bgp_node_type(afi_t afi, safi_t safi) break; case AFI_L2VPN: return BGP_EVPN_NODE; @@ -11554,7 +11392,7 @@ index 5669baa87a..ebad07dac8 100644 case AFI_UNSPEC: case AFI_MAX: // We should never be here but to clarify the switch statement.. -@@ -241,6 +245,9 @@ static const char *get_afi_safi_vty_str(afi_t afi, safi_t safi) +@@ -245,6 +250,9 @@ static const char *get_afi_safi_vty_str(afi_t afi, safi_t safi) } else if (afi == AFI_L2VPN) { if (safi == SAFI_EVPN) return "L2VPN EVPN"; @@ -11564,7 +11402,7 @@ index 5669baa87a..ebad07dac8 100644 } return "Unknown"; -@@ -283,6 +290,9 @@ static const char *get_afi_safi_json_str(afi_t afi, safi_t safi) +@@ -287,6 +295,9 @@ static const char *get_afi_safi_json_str(afi_t afi, safi_t safi) } else if (afi == AFI_L2VPN) { if (safi == SAFI_EVPN) return "l2VpnEvpn"; @@ -11574,7 +11412,7 @@ index 5669baa87a..ebad07dac8 100644 } return "Unknown"; -@@ -419,6 +429,9 @@ afi_t bgp_node_afi(struct vty *vty) +@@ -442,6 +453,9 @@ afi_t bgp_node_afi(struct vty *vty) case BGP_EVPN_NODE: afi = AFI_L2VPN; break; @@ -11584,7 +11422,7 @@ index 5669baa87a..ebad07dac8 100644 default: afi = AFI_IP; break; -@@ -451,6 +464,9 @@ safi_t bgp_node_safi(struct vty *vty) +@@ -474,6 +488,9 @@ safi_t bgp_node_safi(struct vty *vty) case BGP_FLOWSPECV6_NODE: safi = SAFI_FLOWSPEC; break; @@ -11594,7 +11432,7 @@ index 5669baa87a..ebad07dac8 100644 default: safi = SAFI_UNICAST; break; -@@ -581,6 +597,7 @@ static const char *get_bgp_default_af_flag(afi_t afi, safi_t safi) +@@ -604,6 +621,7 @@ static const char *get_bgp_default_af_flag(afi_t afi, safi_t safi) return "ipv4-labeled-unicast"; case SAFI_FLOWSPEC: return "ipv4-flowspec"; @@ -11602,7 +11440,7 @@ index 5669baa87a..ebad07dac8 100644 case SAFI_UNSPEC: case SAFI_EVPN: case SAFI_MAX: -@@ -601,6 +618,7 @@ static const char *get_bgp_default_af_flag(afi_t afi, safi_t safi) +@@ -624,6 +642,7 @@ static const char *get_bgp_default_af_flag(afi_t afi, safi_t safi) return "ipv6-labeled-unicast"; case SAFI_FLOWSPEC: return "ipv6-flowspec"; @@ -11610,7 +11448,7 @@ index 5669baa87a..ebad07dac8 100644 case SAFI_UNSPEC: case SAFI_EVPN: case SAFI_MAX: -@@ -611,6 +629,7 @@ static const char *get_bgp_default_af_flag(afi_t afi, safi_t safi) +@@ -634,6 +653,7 @@ static const char *get_bgp_default_af_flag(afi_t afi, safi_t safi) switch (safi) { case SAFI_EVPN: return "l2vpn-evpn"; @@ -11618,7 +11456,7 @@ index 5669baa87a..ebad07dac8 100644 case SAFI_UNICAST: case SAFI_MULTICAST: case SAFI_MPLS_VPN: -@@ -622,6 +641,22 @@ static const char *get_bgp_default_af_flag(afi_t afi, safi_t safi) +@@ -645,6 +665,22 @@ static const char *get_bgp_default_af_flag(afi_t afi, safi_t safi) return "unknown-afi/safi"; } break; @@ -11641,7 +11479,7 @@ index 5669baa87a..ebad07dac8 100644 case AFI_UNSPEC: case AFI_MAX: return "unknown-afi/safi"; -@@ -11211,6 +11246,18 @@ DEFUN_NOSH (address_family_evpn, +@@ -11427,6 +11463,18 @@ DEFUN_NOSH (address_family_evpn, return CMD_SUCCESS; } @@ -11660,7 +11498,7 @@ index 5669baa87a..ebad07dac8 100644 DEFUN_NOSH (bgp_segment_routing_srv6, bgp_segment_routing_srv6_cmd, "segment-routing srv6", -@@ -11450,7 +11497,8 @@ DEFUN_NOSH (exit_address_family, +@@ -11665,7 +11713,8 @@ DEFUN_NOSH (exit_address_family, || vty->node == BGP_IPV6L_NODE || vty->node == BGP_VPNV6_NODE || vty->node == BGP_EVPN_NODE || vty->node == BGP_FLOWSPECV4_NODE @@ -11670,7 +11508,249 @@ index 5669baa87a..ebad07dac8 100644 vty->node = BGP_NODE; return CMD_SUCCESS; } -@@ -19816,6 +19864,9 @@ static void bgp_config_write_family(struct vty *vty, struct bgp *bgp, afi_t afi, +@@ -18967,6 +19016,228 @@ DEFPY(bgp_retain_route_target, bgp_retain_route_target_cmd, + return CMD_SUCCESS; + } + ++DEFPY(bgp_ls_distribute_bgp_fabric, ++ bgp_ls_distribute_bgp_fabric_cmd, ++ "distribute bgp-fabric-link-state [instance-id WORD$instance_id_str]", ++ "Distribute BGP link-state topology information\n" ++ "Enable BGP fabric link-state topology distribution\n" ++ "BGP-LS instance identifier\n" ++ "Instance ID value\n") ++{ ++ VTY_DECLVAR_CONTEXT(bgp, bgp); ++ uint64_t instance_id = 0; ++ char *endp = NULL; ++ ++ if (!bgp->ls_info) { ++ vty_out(vty, "%% BGP-LS not initialized\n"); ++ return CMD_WARNING; ++ } ++ ++ if (instance_id_str) { ++ errno = 0; ++ instance_id = strtoull(instance_id_str, &endp, 10); ++ if (errno == ERANGE || endp == instance_id_str || *endp != '\0') { ++ vty_out(vty, "%% Invalid instance-id\n"); ++ return CMD_WARNING_CONFIG_FAILED; ++ } ++ } ++ ++ if (bgp->ls_info->enable_distribution && bgp->ls_info->instance_id == instance_id) ++ return CMD_SUCCESS; ++ ++ /* ++ * If already enabled with a different instance-id, withdraw all ++ * existing NLRIs before re-exporting with the new instance-id. ++ */ ++ if (bgp->ls_info->enable_distribution && bgp->ls_info->instance_id != instance_id) ++ bgp_ls_withdraw_all(bgp); ++ ++ bgp->ls_info->instance_id = instance_id; ++ bgp->ls_info->enable_distribution = true; ++ ++ if (bgp_ls_export_bgp_topology(bgp) != 0) { ++ vty_out(vty, "%% Failed to export BGP topology\n"); ++ return CMD_WARNING; ++ } ++ ++ if (BGP_DEBUG(linkstate, LINKSTATE)) ++ vty_out(vty, ++ "BGP-LS: BGP fabric topology export enabled (instance-id %" PRIu64 ")\n", ++ instance_id); ++ ++ return CMD_SUCCESS; ++} ++ ++DEFPY(no_bgp_ls_distribute_bgp_fabric, ++ no_bgp_ls_distribute_bgp_fabric_cmd, ++ "no distribute bgp-fabric-link-state [instance-id WORD$instance_id_str]", ++ NO_STR ++ "Distribute BGP link-state topology information\n" ++ "Disable BGP fabric link-state topology distribution\n" ++ "BGP-LS instance identifier\n" ++ "Instance ID value\n") ++{ ++ VTY_DECLVAR_CONTEXT(bgp, bgp); ++ char *endp = NULL; ++ ++ if (instance_id_str) { ++ errno = 0; ++ strtoull(instance_id_str, &endp, 10); ++ if (errno == ERANGE || endp == instance_id_str || *endp != '\0') { ++ vty_out(vty, "%% Invalid instance-id\n"); ++ return CMD_WARNING_CONFIG_FAILED; ++ } ++ } ++ ++ if (bgp->ls_info) { ++ if (!bgp->ls_info->enable_distribution) ++ return CMD_SUCCESS; ++ ++ bgp->ls_info->enable_distribution = false; ++ bgp->ls_info->instance_id = 0; ++ bgp_ls_withdraw_all(bgp); ++ } ++ ++ if (BGP_DEBUG(linkstate, LINKSTATE)) ++ vty_out(vty, "BGP-LS: BGP fabric topology export disabled\n"); ++ ++ return CMD_SUCCESS; ++} ++ ++DEFPY(neighbor_ls_local_link_id, ++ neighbor_ls_local_link_id_cmd, ++ "neighbor <A.B.C.D|X:X::X:X|WORD>$peer_str local-link-id (1-4294967295)$link_id", ++ NEIGHBOR_STR ++ NEIGHBOR_ADDR_STR2 ++ "Configure local link ID for BGP-LS topology\n" ++ "Link identifier value\n") ++{ ++ VTY_DECLVAR_CONTEXT(bgp, bgp); ++ struct peer *peer; ++ ++ peer = peer_and_group_lookup_vty(vty, peer_str); ++ if (!peer) ++ return CMD_WARNING_CONFIG_FAILED; ++ ++ /* If link ID unchanged, nothing to do. */ ++ if (CHECK_FLAG(peer->flags, PEER_FLAG_LS_LOCAL_LINK_ID) && ++ peer->ls_local_link_id == link_id) ++ return CMD_SUCCESS; ++ ++ /* Withdraw the existing link NLRI before changing the key. */ ++ if (bgp->ls_info && bgp->ls_info->enable_distribution) ++ bgp_ls_withdraw_bgp_link(bgp, peer); ++ ++ peer->ls_local_link_id = link_id; ++ SET_FLAG(peer->flags, PEER_FLAG_LS_LOCAL_LINK_ID); ++ ++ /* Re-originate with the new local link ID. */ ++ if (bgp->ls_info && bgp->ls_info->enable_distribution) ++ bgp_ls_originate_bgp_link(bgp, peer); ++ ++ return CMD_SUCCESS; ++} ++ ++DEFPY(no_neighbor_ls_local_link_id, ++ no_neighbor_ls_local_link_id_cmd, ++ "no neighbor <A.B.C.D|X:X::X:X|WORD>$peer_str local-link-id [(1-4294967295)$link_id]", ++ NO_STR ++ NEIGHBOR_STR ++ NEIGHBOR_ADDR_STR2 ++ "Configure local link ID for BGP-LS topology\n" ++ "Link identifier value\n") ++{ ++ VTY_DECLVAR_CONTEXT(bgp, bgp); ++ struct peer *peer; ++ ++ peer = peer_and_group_lookup_vty(vty, peer_str); ++ if (!peer) ++ return CMD_WARNING_CONFIG_FAILED; ++ ++ if (!CHECK_FLAG(peer->flags, PEER_FLAG_LS_LOCAL_LINK_ID)) ++ return CMD_SUCCESS; ++ ++ /* Withdraw the existing link NLRI before clearing the key. */ ++ if (bgp->ls_info && bgp->ls_info->enable_distribution) ++ bgp_ls_withdraw_bgp_link(bgp, peer); ++ ++ peer->ls_local_link_id = 0; ++ UNSET_FLAG(peer->flags, PEER_FLAG_LS_LOCAL_LINK_ID); ++ ++ /* Re-originate using the fallback local link ID (ifindex). */ ++ if (bgp->ls_info && bgp->ls_info->enable_distribution) ++ bgp_ls_originate_bgp_link(bgp, peer); ++ ++ return CMD_SUCCESS; ++} ++ ++DEFPY(neighbor_ls_remote_link_id, ++ neighbor_ls_remote_link_id_cmd, ++ "neighbor <A.B.C.D|X:X::X:X|WORD>$peer_str remote-link-id (1-4294967295)$link_id", ++ NEIGHBOR_STR ++ NEIGHBOR_ADDR_STR2 ++ "Configure remote link ID for BGP-LS topology\n" ++ "Link identifier value\n") ++{ ++ VTY_DECLVAR_CONTEXT(bgp, bgp); ++ struct peer *peer; ++ ++ peer = peer_and_group_lookup_vty(vty, peer_str); ++ if (!peer) ++ return CMD_WARNING_CONFIG_FAILED; ++ ++ /* If link ID unchanged, nothing to do. */ ++ if (CHECK_FLAG(peer->flags, PEER_FLAG_LS_REMOTE_LINK_ID) && ++ peer->ls_remote_link_id == link_id) ++ return CMD_SUCCESS; ++ ++ /* Withdraw the existing link NLRI before changing the key. */ ++ if (bgp->ls_info && bgp->ls_info->enable_distribution) ++ bgp_ls_withdraw_bgp_link(bgp, peer); ++ ++ peer->ls_remote_link_id = link_id; ++ SET_FLAG(peer->flags, PEER_FLAG_LS_REMOTE_LINK_ID); ++ ++ /* Re-originate with the new remote link ID. */ ++ if (bgp->ls_info && bgp->ls_info->enable_distribution) ++ bgp_ls_originate_bgp_link(bgp, peer); ++ ++ return CMD_SUCCESS; ++} ++ ++DEFPY(no_neighbor_ls_remote_link_id, ++ no_neighbor_ls_remote_link_id_cmd, ++ "no neighbor <A.B.C.D|X:X::X:X|WORD>$peer_str remote-link-id [(1-4294967295)$link_id]", ++ NO_STR ++ NEIGHBOR_STR ++ NEIGHBOR_ADDR_STR2 ++ "Configure remote link ID for BGP-LS topology\n" ++ "Link identifier value\n") ++{ ++ VTY_DECLVAR_CONTEXT(bgp, bgp); ++ struct peer *peer; ++ ++ peer = peer_and_group_lookup_vty(vty, peer_str); ++ if (!peer) ++ return CMD_WARNING_CONFIG_FAILED; ++ ++ if (!CHECK_FLAG(peer->flags, PEER_FLAG_LS_REMOTE_LINK_ID)) ++ return CMD_SUCCESS; ++ ++ /* Withdraw the existing link NLRI before clearing the key. */ ++ if (bgp->ls_info && bgp->ls_info->enable_distribution) ++ bgp_ls_withdraw_bgp_link(bgp, peer); ++ ++ peer->ls_remote_link_id = 0; ++ UNSET_FLAG(peer->flags, PEER_FLAG_LS_REMOTE_LINK_ID); ++ ++ /* Re-originate using the fallback remote link ID (0). */ ++ if (bgp->ls_info && bgp->ls_info->enable_distribution) ++ bgp_ls_originate_bgp_link(bgp, peer); ++ ++ return CMD_SUCCESS; ++} ++ + static void bgp_config_write_redistribute(struct vty *vty, struct bgp *bgp, + afi_t afi, safi_t safi) + { +@@ -19545,6 +19816,12 @@ static void bgp_config_write_peer_global(struct vty *vty, struct bgp *bgp, + if (peergroup_flag_check(peer, PEER_FLAG_IP_TRANSPARENT)) + vty_out(vty, " neighbor %s ip-transparent\n", addr); + ++ /* BGP-LS link identifiers */ ++ if (CHECK_FLAG(peer->flags, PEER_FLAG_LS_LOCAL_LINK_ID)) ++ vty_out(vty, " neighbor %s local-link-id %u\n", addr, peer->ls_local_link_id); ++ if (CHECK_FLAG(peer->flags, PEER_FLAG_LS_REMOTE_LINK_ID)) ++ vty_out(vty, " neighbor %s remote-link-id %u\n", addr, peer->ls_remote_link_id); ++ + /* advertisement-interval */ + if (peergroup_flag_check(peer, PEER_FLAG_ROUTEADV)) + vty_out(vty, " neighbor %s advertisement-interval %u\n", addr, +@@ -20070,9 +20347,22 @@ static void bgp_config_write_family(struct vty *vty, struct bgp *bgp, afi_t afi, } else if (afi == AFI_L2VPN) { if (safi == SAFI_EVPN) vty_frame(vty, "l2vpn evpn"); @@ -11680,7 +11760,20 @@ index 5669baa87a..ebad07dac8 100644 } vty_frame(vty, "\n"); -@@ -20458,6 +20509,9 @@ int bgp_config_write(struct vty *vty) ++ /* BGP-only fabric distribution */ ++ if (afi == AFI_BGP_LS && safi == SAFI_BGP_LS && bgp->ls_info && ++ bgp->ls_info->enable_distribution) { ++ if (bgp->ls_info->instance_id != 0) ++ vty_out(vty, " distribute bgp-fabric-link-state instance-id %" PRIu64 "\n", ++ bgp->ls_info->instance_id); ++ else ++ vty_out(vty, " distribute bgp-fabric-link-state\n"); ++ } ++ + bgp_config_write_distance(vty, bgp, afi, safi); + + bgp_config_write_network(vty, bgp, afi, safi); +@@ -20733,6 +21023,9 @@ int bgp_config_write(struct vty *vty) /* EVPN configuration. */ bgp_config_write_family(vty, bgp, AFI_L2VPN, SAFI_EVPN); @@ -11690,7 +11783,7 @@ index 5669baa87a..ebad07dac8 100644 hook_call(bgp_inst_config_write, bgp, vty); #ifdef ENABLE_BGP_VNC -@@ -20582,6 +20636,14 @@ static struct cmd_node bgp_srv6_node = { +@@ -20857,6 +21150,14 @@ static struct cmd_node bgp_srv6_node = { .prompt = "%s(config-router-srv6)# ", }; @@ -11705,7 +11798,7 @@ index 5669baa87a..ebad07dac8 100644 static void community_list_vty(void); static void bgp_ac_peergroup(vector comps, struct cmd_token *token) -@@ -20901,6 +20963,7 @@ void bgp_vty_init(void) +@@ -21176,6 +21477,7 @@ void bgp_vty_init(void) install_node(&bgp_flowspecv4_node); install_node(&bgp_flowspecv6_node); install_node(&bgp_srv6_node); @@ -11713,7 +11806,7 @@ index 5669baa87a..ebad07dac8 100644 /* Install default VTY commands to new nodes. */ install_default(BGP_NODE); -@@ -20917,6 +20980,7 @@ void bgp_vty_init(void) +@@ -21192,6 +21494,7 @@ void bgp_vty_init(void) install_default(BGP_EVPN_NODE); install_default(BGP_EVPN_VNI_NODE); install_default(BGP_SRV6_NODE); @@ -11721,7 +11814,7 @@ index 5669baa87a..ebad07dac8 100644 /* "global bgp inq-limit command */ install_element(CONFIG_NODE, &bgp_inq_limit_cmd); -@@ -21303,6 +21367,7 @@ void bgp_vty_init(void) +@@ -21583,6 +21886,7 @@ void bgp_vty_init(void) install_element(BGP_FLOWSPECV4_NODE, &neighbor_activate_cmd); install_element(BGP_FLOWSPECV6_NODE, &neighbor_activate_cmd); install_element(BGP_EVPN_NODE, &neighbor_activate_cmd); @@ -11729,7 +11822,7 @@ index 5669baa87a..ebad07dac8 100644 /* "no neighbor activate" commands. */ install_element(BGP_NODE, &no_neighbor_activate_hidden_cmd); -@@ -21317,6 +21382,7 @@ void bgp_vty_init(void) +@@ -21597,6 +21901,7 @@ void bgp_vty_init(void) install_element(BGP_FLOWSPECV4_NODE, &no_neighbor_activate_cmd); install_element(BGP_FLOWSPECV6_NODE, &no_neighbor_activate_cmd); install_element(BGP_EVPN_NODE, &no_neighbor_activate_cmd); @@ -11737,7 +11830,16 @@ index 5669baa87a..ebad07dac8 100644 /* "neighbor peer-group" set commands. */ install_element(BGP_NODE, &neighbor_set_peer_group_cmd); -@@ -22362,6 +22428,7 @@ void bgp_vty_init(void) +@@ -22421,6 +22726,8 @@ void bgp_vty_init(void) + install_element(BGP_FLOWSPECV6_NODE, &no_neighbor_route_map_cmd); + install_element(BGP_EVPN_NODE, &neighbor_route_map_cmd); + install_element(BGP_EVPN_NODE, &no_neighbor_route_map_cmd); ++ install_element(BGP_LS_NODE, &neighbor_route_map_cmd); ++ install_element(BGP_LS_NODE, &no_neighbor_route_map_cmd); + + /* "neighbor unsuppress-map" commands. */ + install_element(BGP_NODE, &neighbor_unsuppress_map_hidden_cmd); +@@ -22642,6 +22949,7 @@ void bgp_vty_init(void) #endif /* KEEP_OLD_VPN_COMMANDS */ install_element(BGP_NODE, &address_family_evpn_cmd); @@ -11745,7 +11847,7 @@ index 5669baa87a..ebad07dac8 100644 /* "exit-address-family" command. */ install_element(BGP_IPV4_NODE, &exit_address_family_cmd); -@@ -22375,6 +22442,7 @@ void bgp_vty_init(void) +@@ -22655,6 +22963,7 @@ void bgp_vty_init(void) install_element(BGP_FLOWSPECV4_NODE, &exit_address_family_cmd); install_element(BGP_FLOWSPECV6_NODE, &exit_address_family_cmd); install_element(BGP_EVPN_NODE, &exit_address_family_cmd); @@ -11753,19 +11855,34 @@ index 5669baa87a..ebad07dac8 100644 /* BGP retain all route-target */ install_element(BGP_VPNV4_NODE, &bgp_retain_route_target_cmd); +@@ -22829,6 +23138,14 @@ void bgp_vty_init(void) + install_element(BGP_IPV4_NODE, &neighbor_encap_srv6_cmd); + install_element(BGP_NODE, &no_bgp_sid_vpn_export_cmd); + ++ /* BGP-LS commands */ ++ install_element(BGP_LS_NODE, &bgp_ls_distribute_bgp_fabric_cmd); ++ install_element(BGP_LS_NODE, &no_bgp_ls_distribute_bgp_fabric_cmd); ++ install_element(BGP_NODE, &neighbor_ls_local_link_id_cmd); ++ install_element(BGP_NODE, &no_neighbor_ls_local_link_id_cmd); ++ install_element(BGP_NODE, &neighbor_ls_remote_link_id_cmd); ++ install_element(BGP_NODE, &no_neighbor_ls_remote_link_id_cmd); ++ + bgp_vty_if_init(); + } + diff --git a/bgpd/bgp_zebra.c b/bgpd/bgp_zebra.c -index 1d33883e87..1b0464dbfb 100644 +index d1034ff42a..cda7ad659f 100644 --- a/bgpd/bgp_zebra.c +++ b/bgpd/bgp_zebra.c -@@ -54,6 +54,7 @@ - #include "bgpd/bgp_trace.h" +@@ -56,6 +56,7 @@ #include "bgpd/bgp_community.h" #include "bgpd/bgp_lcommunity.h" + #include "bgpd/bgp_srv6.h" +#include "bgpd/bgp_ls_ted.h" /* All information about zebra. */ struct zclient *bgp_zclient = NULL; -@@ -4007,6 +4008,35 @@ static int bgp_zebra_process_srv6_locator_delete(ZAPI_CALLBACK_ARGS) +@@ -4148,6 +4149,35 @@ static int bgp_zebra_process_srv6_locator_delete(ZAPI_CALLBACK_ARGS) return 0; } @@ -11801,7 +11918,7 @@ index 1d33883e87..1b0464dbfb 100644 static zclient_handler *const bgp_handlers[] = { [ZEBRA_ROUTER_ID_UPDATE] = bgp_router_id_update, [ZEBRA_INTERFACE_ADDRESS_ADD] = bgp_interface_address_add, -@@ -4035,9 +4065,9 @@ static zclient_handler *const bgp_handlers[] = { +@@ -4176,9 +4206,9 @@ static zclient_handler *const bgp_handlers[] = { [ZEBRA_ROUTE_NOTIFY_OWNER] = bgp_zebra_route_notify_owner, [ZEBRA_SRV6_LOCATOR_ADD] = bgp_zebra_process_srv6_locator_add, [ZEBRA_SRV6_LOCATOR_DELETE] = bgp_zebra_process_srv6_locator_delete, @@ -11814,19 +11931,39 @@ index 1d33883e87..1b0464dbfb 100644 static int bgp_if_new_hook(struct interface *ifp) diff --git a/bgpd/bgpd.c b/bgpd/bgpd.c -index 0000ebd862..7e1f45e4c1 100644 +index 116bef9339..a8e74fa748 100644 --- a/bgpd/bgpd.c +++ b/bgpd/bgpd.c -@@ -79,6 +79,8 @@ - #include "bgpd/bgp_evpn_mh.h" +@@ -80,6 +80,8 @@ #include "bgpd/bgp_mac.h" #include "bgpd/bgp_trace.h" + #include "bgpd/bgp_srv6.h" +#include "bgpd/bgp_ls.h" +#include "bgpd/bgp_ls_ted.h" DEFINE_MTYPE_STATIC(BGPD, PEER_TX_SHUTDOWN_MSG, "Peer shutdown message (TX)"); DEFINE_QOBJ_TYPE(bgp_master); -@@ -2545,6 +2547,19 @@ static int peer_activate_af(struct peer *peer, afi_t afi, safi_t safi) +@@ -340,6 +342,9 @@ static int bgp_router_id_set(struct bgp *bgp, const struct in_addr *id, + + vpn_handle_router_id_update(bgp, true, is_config); + ++ if (bgp && bgp->ls_info && bgp->ls_info->enable_distribution) ++ bgp_ls_withdraw_all(bgp); ++ + hook_call(bgp_routerid_update, bgp, true); + + IPV4_ADDR_COPY(&bgp->router_id, id); +@@ -359,6 +364,9 @@ static int bgp_router_id_set(struct bgp *bgp, const struct in_addr *id, + + vpn_handle_router_id_update(bgp, false, is_config); + ++ if (bgp && bgp->ls_info && bgp->ls_info->enable_distribution) ++ bgp_ls_export_bgp_topology(bgp); ++ + hook_call(bgp_routerid_update, bgp, false); + return 0; + } +@@ -2585,6 +2593,19 @@ static int peer_activate_af(struct peer *peer, afi_t afi, safi_t safi) peer_notify_config_change(other->connection); } @@ -11846,7 +11983,7 @@ index 0000ebd862..7e1f45e4c1 100644 return 0; } -@@ -2609,6 +2624,24 @@ int peer_activate(struct peer *peer, afi_t afi, safi_t safi) +@@ -2649,6 +2670,24 @@ int peer_activate(struct peer *peer, afi_t afi, safi_t safi) /* connect to table manager */ bgp_zebra_init_tm_connect(bgp); } @@ -11871,7 +12008,7 @@ index 0000ebd862..7e1f45e4c1 100644 return ret; } -@@ -2655,6 +2688,11 @@ static bool non_peergroup_deactivate_af(struct peer *peer, afi_t afi, +@@ -2695,6 +2734,11 @@ static bool non_peergroup_deactivate_af(struct peer *peer, afi_t afi, peer_notify_config_change(peer->connection); } @@ -11883,7 +12020,7 @@ index 0000ebd862..7e1f45e4c1 100644 return false; } -@@ -2705,6 +2743,36 @@ int peer_deactivate(struct peer *peer, afi_t afi, safi_t safi) +@@ -2745,6 +2789,36 @@ int peer_deactivate(struct peer *peer, afi_t afi, safi_t safi) bgp->allocate_mpls_labels[afi][safi_check] = 0; bgp_recalculate_afi_safi_bestpaths(bgp, afi, safi_check); } @@ -11920,15 +12057,25 @@ index 0000ebd862..7e1f45e4c1 100644 return ret; } -@@ -3720,6 +3788,7 @@ peer_init: +@@ -3767,6 +3841,7 @@ peer_init: bgp_evpn_vrf_es_init(bgp); bgp_pbr_init(bgp); bgp_srv6_init(bgp); + bgp_ls_init(bgp); } - /*initilize global GR FSM */ -@@ -4501,6 +4570,7 @@ void bgp_free(struct bgp *bgp) + /*initialize global GR FSM */ +@@ -3780,6 +3855,9 @@ peer_init: + bgp_peer_conn_errlist_init(&bgp->peer_conn_errlist); + bgp_clearing_info_init(&bgp->clearing_list); + ++ if (bgp && bgp->ls_info && bgp->ls_info->enable_distribution) ++ bgp_ls_originate_bgp_node(bgp); ++ + return bgp; + } + +@@ -4556,6 +4634,7 @@ void bgp_free(struct bgp *bgp) bgp_evpn_cleanup(bgp); bgp_pbr_cleanup(bgp); @@ -11936,7 +12083,7 @@ index 0000ebd862..7e1f45e4c1 100644 for (afi = AFI_IP; afi < AFI_MAX; afi++) { enum vpn_policy_direction dir; -@@ -4879,7 +4949,8 @@ enum bgp_peer_active peer_active(struct peer_connection *connection) +@@ -4931,7 +5010,8 @@ enum bgp_peer_active peer_active(struct peer_connection *connection) || peer->afc[AFI_IP6][SAFI_MPLS_VPN] || peer->afc[AFI_IP6][SAFI_ENCAP] || peer->afc[AFI_IP6][SAFI_FLOWSPEC] @@ -11946,7 +12093,7 @@ index 0000ebd862..7e1f45e4c1 100644 return BGP_PEER_ACTIVE; return BGP_PEER_AF_UNCONFIGURED; -@@ -4900,7 +4971,8 @@ bool peer_active_nego(struct peer *peer) +@@ -4952,7 +5032,8 @@ bool peer_active_nego(struct peer *peer) || peer->afc_nego[AFI_IP6][SAFI_MPLS_VPN] || peer->afc_nego[AFI_IP6][SAFI_ENCAP] || peer->afc_nego[AFI_IP6][SAFI_FLOWSPEC] @@ -11957,7 +12104,7 @@ index 0000ebd862..7e1f45e4c1 100644 return false; } diff --git a/bgpd/bgpd.h b/bgpd/bgpd.h -index c863ed2eeb..b1887d9222 100644 +index 406a2232bf..777ae9d80c 100644 --- a/bgpd/bgpd.h +++ b/bgpd/bgpd.h @@ -95,6 +95,7 @@ enum bgp_af_index { @@ -11968,7 +12115,7 @@ index c863ed2eeb..b1887d9222 100644 BGP_AF_MAX }; -@@ -728,6 +729,9 @@ struct bgp { +@@ -810,6 +811,9 @@ struct bgp { /* BGP routing information base. */ struct bgp_table *rib[AFI_MAX][SAFI_MAX]; @@ -11978,16 +12125,38 @@ index c863ed2eeb..b1887d9222 100644 /* BGP table route-map. */ struct bgp_rmap table_map[AFI_MAX][SAFI_MAX]; -@@ -1773,6 +1777,8 @@ struct peer { - #define PEER_FLAG_CONFIG_DAMPENING (1ULL << 31) +@@ -1795,6 +1799,10 @@ struct peer { + #define PEER_FLAG_IP_TRANSPARENT (1ULL << 45) /* ip-transparent */ + #define PEER_FLAG_RPKI_STRICT (1ULL << 46) /* RPKI strict mode */ + #define PEER_FLAG_CAPABILITY_SOFT_VERSION_NEW (1ULL << 47) ++#define PEER_FLAG_REMOTE_AS (1ULL << 48) /* remote-as override */ ++/* BGP-LS per-peer link identifiers configured */ ++#define PEER_FLAG_LS_LOCAL_LINK_ID (1ULL << 49) ++#define PEER_FLAG_LS_REMOTE_LINK_ID (1ULL << 50) + + /* + *GR-Disabled mode means unset PEER_FLAG_GRACEFUL_RESTART +@@ -1870,6 +1878,8 @@ struct peer { #define PEER_FLAG_CONFIG_ENCAPSULATION_SRV6 (1ULL << 32) - #define PEER_FLAG_CONFIG_ENCAPSULATION_MPLS (1ULL << 33) + #define PEER_FLAG_CONFIG_ENCAPSULATION_SRV6_RELAX (1ULL << 33) + #define PEER_FLAG_CONFIG_ENCAPSULATION_MPLS (1ULL << 34) +#define PEER_FLAG_BGP_LS_IPV4 (1ULL << 35) +#define PEER_FLAG_BGP_LS_IPV6 (1ULL << 36) #define PEER_FLAG_ACCEPT_OWN (1ULL << 63) enum bgp_addpath_strat addpath_type[AFI_MAX][SAFI_MAX]; -@@ -2229,6 +2235,7 @@ struct bgp_nlri { +@@ -2204,6 +2214,10 @@ struct peer { + + bool shut_during_cfg; + ++ /* BGP-LS per-peer link identifiers (draft-ietf-idr-bgp-ls-bgp-only-fabric) */ ++ uint32_t ls_local_link_id; ++ uint32_t ls_remote_link_id; ++ + #define BGP_ATTR_MAX 255 + /* Path attributes discard */ + bool discard_attrs[BGP_ATTR_MAX + 1]; +@@ -2327,6 +2341,7 @@ struct bgp_nlri { #define BGP_ATTR_ENCAP 23 #define BGP_ATTR_IPV6_EXT_COMMUNITIES 25 #define BGP_ATTR_AIGP 26 @@ -11995,7 +12164,7 @@ index c863ed2eeb..b1887d9222 100644 #define BGP_ATTR_LARGE_COMMUNITIES 32 #define BGP_ATTR_OTC 35 #define BGP_ATTR_NHC 39 -@@ -2834,6 +2841,7 @@ static inline int afindex(afi_t afi, safi_t safi) +@@ -2931,6 +2946,7 @@ static inline int afindex(afi_t afi, safi_t safi) return BGP_AF_IPV4_ENCAP; case SAFI_FLOWSPEC: return BGP_AF_IPV4_FLOWSPEC; @@ -12003,7 +12172,7 @@ index c863ed2eeb..b1887d9222 100644 case SAFI_EVPN: case SAFI_UNSPEC: case SAFI_MAX: -@@ -2854,6 +2862,7 @@ static inline int afindex(afi_t afi, safi_t safi) +@@ -2951,6 +2967,7 @@ static inline int afindex(afi_t afi, safi_t safi) return BGP_AF_IPV6_ENCAP; case SAFI_FLOWSPEC: return BGP_AF_IPV6_FLOWSPEC; @@ -12011,36 +12180,38 @@ index c863ed2eeb..b1887d9222 100644 case SAFI_EVPN: case SAFI_UNSPEC: case SAFI_MAX: -@@ -2864,11 +2873,28 @@ static inline int afindex(afi_t afi, safi_t safi) +@@ -2961,6 +2978,7 @@ static inline int afindex(afi_t afi, safi_t safi) switch (safi) { case SAFI_EVPN: return BGP_AF_L2VPN_EVPN; + case SAFI_BGP_LS: + case SAFI_UNICAST: + case SAFI_MULTICAST: + case SAFI_LABELED_UNICAST: +@@ -2972,6 +2990,22 @@ static inline int afindex(afi_t afi, safi_t safi) + return BGP_AF_MAX; + } + break; ++ case AFI_BGP_LS: ++ switch (safi) { ++ case SAFI_BGP_LS: ++ return BGP_AF_BGP_LS; + case SAFI_UNICAST: + case SAFI_MULTICAST: + case SAFI_LABELED_UNICAST: + case SAFI_MPLS_VPN: + case SAFI_ENCAP: ++ case SAFI_EVPN: + case SAFI_FLOWSPEC: + case SAFI_UNSPEC: + case SAFI_MAX: + return BGP_AF_MAX; + } + break; -+ case AFI_BGP_LS: -+ switch (safi) { -+ case SAFI_BGP_LS: -+ return BGP_AF_BGP_LS; - case SAFI_UNICAST: - case SAFI_MULTICAST: - case SAFI_LABELED_UNICAST: - case SAFI_MPLS_VPN: - case SAFI_ENCAP: -+ case SAFI_EVPN: - case SAFI_FLOWSPEC: - case SAFI_UNSPEC: - case SAFI_MAX: -@@ -2921,7 +2947,8 @@ static inline int peer_group_af_configured(struct peer_group *group) + case AFI_UNSPEC: + case AFI_MAX: + return BGP_AF_MAX; +@@ -3018,7 +3052,8 @@ static inline int peer_group_af_configured(struct peer_group *group) || peer->afc[AFI_IP6][SAFI_MPLS_VPN] || peer->afc[AFI_IP6][SAFI_ENCAP] || peer->afc[AFI_IP6][SAFI_FLOWSPEC] @@ -12051,7 +12222,7 @@ index c863ed2eeb..b1887d9222 100644 return 0; } diff --git a/bgpd/rfapi/rfapi_import.c b/bgpd/rfapi/rfapi_import.c -index 7f23df799a..c73390b629 100644 +index 0acd3fb0a0..b1667ca891 100644 --- a/bgpd/rfapi/rfapi_import.c +++ b/bgpd/rfapi/rfapi_import.c @@ -233,6 +233,7 @@ void rfapiCheckRefcount(struct agg_node *rn, safi_t safi, int lockoffset) @@ -12115,7 +12286,7 @@ index 83e8afa0e5..45ada52202 100644 case SAFI_UNICAST: case SAFI_MULTICAST: diff --git a/bgpd/subdir.am b/bgpd/subdir.am -index c59a669b38..c04b3548a1 100644 +index 07a9df979c..3e179075d6 100644 --- a/bgpd/subdir.am +++ b/bgpd/subdir.am @@ -51,6 +51,9 @@ bgpd_libbgp_a_SOURCES = \ @@ -12128,7 +12299,7 @@ index c59a669b38..c04b3548a1 100644 bgpd/bgp_mac.c \ bgpd/bgp_memory.c \ bgpd/bgp_mpath.c \ -@@ -135,6 +138,9 @@ noinst_HEADERS += \ +@@ -136,6 +139,9 @@ noinst_HEADERS += \ bgpd/bgp_label.h \ bgpd/bgp_labelpool.h \ bgpd/bgp_lcommunity.h \ @@ -12140,10 +12311,10 @@ index c59a669b38..c04b3548a1 100644 bgpd/bgp_mpath.h \ diff --git a/doc/user/bgp-linkstate.rst b/doc/user/bgp-linkstate.rst new file mode 100644 -index 0000000000..ba44a0f00f +index 0000000000..5a65c7add0 --- /dev/null +++ b/doc/user/bgp-linkstate.rst -@@ -0,0 +1,207 @@ +@@ -0,0 +1,274 @@ +.. _bgp-link-state: + +BGP Link-State @@ -12214,6 +12385,73 @@ index 0000000000..ba44a0f00f + neighbor 192.0.2.2 activate + exit-address-family + ++BGP-only Fabric Topology Distribution ++^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ++ ++In a BGP-only fabric, topology is derived from BGP sessions and BGP routing ++state rather than from IGP protocols. ++ ++This behavior aligns with the IETF document ++`draft-ietf-idr-bgp-ls-bgp-only-fabric ++<https://datatracker.ietf.org/doc/draft-ietf-idr-bgp-ls-bgp-only-fabric/>`_. ++ ++FRR uses BGP-LS to advertise Node, Link, and Prefix topology for the fabric. ++ ++Use the following command under the link-state address family to enable ++export of BGP-only fabric topology: ++ ++.. clicmd:: distribute bgp-fabric-link-state [instance-id WORD] ++ ++ Enable BGP-LS export for BGP-only fabrics. When enabled, FRR originates ++ BGP-LS Node, Link, and Prefix NLRIs from BGP adjacency and BGP RIB data. ++ ++ This command is configured under ``address-family link-state link-state``. ++ ++ ``instance-id`` is optional and sets the BGP-LS identifier in originated ++ NLRIs. If omitted, FRR uses ``0``. ++ ++Example for a BGP-only fabric speaker with default instance-id: ++ ++.. code-block:: frr ++ ++ router bgp 65001 ++ neighbor 10.255.1.2 remote-as 65000 ++ ! ++ address-family link-state link-state ++ distribute bgp-fabric-link-state ++ neighbor 10.255.1.2 activate ++ exit-address-family ++ ++Example for a BGP-only fabric speaker with explicit instance-id: ++ ++.. code-block:: frr ++ ++ router bgp 65001 ++ neighbor 10.255.1.2 remote-as 65000 ++ ! ++ address-family link-state link-state ++ distribute bgp-fabric-link-state instance-id 100 ++ neighbor 10.255.1.2 activate ++ exit-address-family ++ ++Per-neighbor Link Identifier Configuration ++^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ++ ++The following commands let you override local and remote link identifiers ++carried in BGP-LS Link NLRIs for a specific BGP neighbor. These commands are ++configured under ``router bgp`` and accept 32-bit values. ++ ++.. clicmd:: neighbor <A.B.C.D|X:X::X:X|WORD> local-link-id (1-4294967295) ++ ++ Set the local link identifier in the neighbor's Link NLRI. ++ If unset, FRR uses the interface kernel ifindex (a unique per-interface ++ identifier) as the fallback value. ++ ++.. clicmd:: neighbor <A.B.C.D|X:X::X:X|WORD> remote-link-id (1-4294967295) ++ ++ Set the remote link identifier in the neighbor's Link NLRI. ++ If unset, FRR uses ``0`` as fallback. ++ +Displaying BGP-LS Information +------------------------------ + @@ -12353,10 +12591,10 @@ index 0000000000..ba44a0f00f + topology information between domains via BGP. \ No newline at end of file diff --git a/doc/user/bgp.rst b/doc/user/bgp.rst -index 3b8fc9fbc3..b0e880a62e 100644 +index 4bd4c7f019..121ceeb437 100644 --- a/doc/user/bgp.rst +++ b/doc/user/bgp.rst -@@ -5751,6 +5751,8 @@ Show command json output: +@@ -5811,6 +5811,8 @@ Show command json output: .. include:: flowspec.rst @@ -12365,543 +12603,36 @@ index 3b8fc9fbc3..b0e880a62e 100644 .. [bgp-route-osci-cond] McPherson, D. and Gill, V. and Walton, D., "Border Gateway Protocol (BGP) Persistent Route Oscillation Condition", IETF RFC3345 .. [stable-flexible-ibgp] Flavel, A. and M. Roughan, "Stable and flexible iBGP", ACM SIGCOMM 2009 .. [ibgp-correctness] Griffin, T. and G. Wilfong, "On the correctness of IBGP configuration", ACM SIGCOMM 2002 -diff --git a/isisd/isis_lsp.c b/isisd/isis_lsp.c -index 1a1140bc5d..9a18fc4596 100644 ---- a/isisd/isis_lsp.c -+++ b/isisd/isis_lsp.c -@@ -1075,7 +1075,7 @@ static void lsp_build(struct isis_lsp *lsp, struct isis_area *area) - - lsp_add_auth(lsp); - -- isis_tlvs_add_area_addresses(lsp->tlvs, area->area_addrs); -+ isis_tlvs_add_area_addresses(lsp->tlvs, &area->area_addrs); - - /* Protocols Supported */ - if (area->ip_circuits > 0 || area->ipv6_circuits > 0) { -diff --git a/isisd/isis_nb_config.c b/isisd/isis_nb_config.c -index 6bd3b39bc7..5deab4985d 100644 ---- a/isisd/isis_nb_config.c -+++ b/isisd/isis_nb_config.c -@@ -139,7 +139,6 @@ int isis_instance_area_address_create(struct nb_cb_create_args *args) - { - struct isis_area *area; - struct iso_address addr, *addrr = NULL, *addrp = NULL; -- struct listnode *node; - struct sysid_iter iter; - uint8_t buff[255]; - const char *net_title = yang_dnode_get_string(args->dnode, NULL); -@@ -192,11 +191,9 @@ int isis_instance_area_address_create(struct nb_cb_create_args *args) - area->isis->sysid_set = 1; - } else { - /* check that we don't already have this address */ -- for (ALL_LIST_ELEMENTS_RO(area->area_addrs, node, -- addrp)) { -- if ((addrp->addr_len + ISIS_SYS_ID_LEN -- + ISIS_NSEL_LEN) -- != (addrr->addr_len)) -+ frr_each (iso_address_list, &area->area_addrs, addrp) { -+ if ((addrp->addr_len + ISIS_SYS_ID_LEN + ISIS_NSEL_LEN) != -+ (addrr->addr_len)) - continue; - if (!memcmp(addrp->area_addr, addrr->area_addr, - addrr->addr_len)) { -@@ -208,11 +205,10 @@ int isis_instance_area_address_create(struct nb_cb_create_args *args) - - /*Forget the systemID part of the address */ - addrr->addr_len -= (ISIS_SYS_ID_LEN + ISIS_NSEL_LEN); -- assert(area->area_addrs); /* to silence scan-build sillyness */ -- listnode_add(area->area_addrs, addrr); -+ iso_address_list_add_tail(&area->area_addrs, addrr); - - /* only now we can safely generate our LSPs for this area */ -- if (listcount(area->area_addrs) > 0) { -+ if (iso_address_list_count(&area->area_addrs) > 0) { - if (area->is_type & IS_LEVEL_1) - lsp_generate(area, IS_LEVEL_1); - if (area->is_type & IS_LEVEL_2) -@@ -227,7 +223,6 @@ int isis_instance_area_address_create(struct nb_cb_create_args *args) - int isis_instance_area_address_destroy(struct nb_cb_destroy_args *args) - { - struct iso_address addr, *addrp = NULL; -- struct listnode *node; - uint8_t buff[255]; - struct isis_area *area; - const char *net_title; -@@ -243,21 +238,20 @@ int isis_instance_area_address_destroy(struct nb_cb_destroy_args *args) - memcpy(addr.area_addr, buff, (int)addr.addr_len); - area = nb_running_get_entry(args->dnode, NULL, true); - -- for (ALL_LIST_ELEMENTS_RO(area->area_addrs, node, addrp)) { -- if ((addrp->addr_len + ISIS_SYS_ID_LEN + 1) == addr.addr_len -- && !memcmp(addrp->area_addr, addr.area_addr, addr.addr_len)) -+ frr_each (iso_address_list, &area->area_addrs, addrp) { -+ if ((addrp->addr_len + ISIS_SYS_ID_LEN + 1) == addr.addr_len && -+ !memcmp(addrp->area_addr, addr.area_addr, addr.addr_len)) - break; - } - if (!addrp) - return NB_ERR_INCONSISTENCY; - -- listnode_delete(area->area_addrs, addrp); -+ iso_address_list_del(&area->area_addrs, addrp); - /* - * Last area address - reset the SystemID for this router - */ -- if (!memcmp(addrp->area_addr + addrp->addr_len, area->isis->sysid, -- ISIS_SYS_ID_LEN) && -- listcount(area->area_addrs) == 0) { -+ if (!memcmp(addrp->area_addr + addrp->addr_len, area->isis->sysid, ISIS_SYS_ID_LEN) && -+ iso_address_list_count(&area->area_addrs) == 0) { - for (ALL_LIST_ELEMENTS_RO(area->circuit_list, cnode, circuit)) - for (lvl = IS_LEVEL_1; lvl <= IS_LEVEL_2; ++lvl) { - if (circuit->u.bc.is_dr[lvl - 1]) -diff --git a/isisd/isis_pdu.c b/isisd/isis_pdu.c -index 1a3142e72a..6df061a2f8 100644 ---- a/isisd/isis_pdu.c -+++ b/isisd/isis_pdu.c -@@ -163,7 +163,7 @@ static int process_p2p_hello(struct iih_info *iih) - (iih->circuit->is_type_config == IS_LEVEL_1_AND_2) && - (iih->circ_type == IS_LEVEL_1))) { - if (!isis_tlvs_area_addresses_match(iih->tlvs, -- iih->circuit->area -+ &iih->circuit->area - ->area_addrs)) { - if (IS_DEBUG_ADJ_PACKETS) { - zlog_debug("ISIS-Adj (%s): Rcvd P2P IIH from (%s), cir type %s, cir id %u, length %u", -@@ -201,7 +201,7 @@ static int process_p2p_hello(struct iih_info *iih) - iih->calculated_type = IS_LEVEL_1_AND_2; - - if (!isis_tlvs_area_addresses_match(iih->tlvs, -- iih->circuit->area -+ &iih->circuit->area - ->area_addrs)) { - iih->calculated_type = IS_LEVEL_2; - } -@@ -277,7 +277,7 @@ static int process_p2p_hello(struct iih_info *iih) - - /* 8.2.5.2 a) a match was detected */ - if (isis_tlvs_area_addresses_match(iih->tlvs, -- iih->circuit->area->area_addrs)) { -+ &iih->circuit->area->area_addrs)) { - /* 8.2.5.2 a) 2) If the calculated type is L1 - table 5 */ - if (iih->calculated_type == IS_LEVEL_1) { - switch (iih->circ_type) { -@@ -362,7 +362,7 @@ static int process_p2p_hello(struct iih_info *iih) - } - } - /* 8.2.5.2 b) if no match was detected */ -- else if (listcount(iih->circuit->area->area_addrs) > 0) { -+ else if (iso_address_list_count(&iih->circuit->area->area_addrs) > 0) { - if (iih->calculated_type == IS_LEVEL_1) { - /* 8.2.5.2 b) 1) is_type L1 and adj is not up */ - if (adj->adj_state != ISIS_ADJ_UP) { -@@ -789,10 +789,10 @@ static int process_hello(uint8_t pdu_type, struct isis_circuit *circuit, - } - - if (!p2p_hello -- && (listcount(circuit->area->area_addrs) == 0 -+ && (iso_address_list_count(&circuit->area->area_addrs) == 0 - || (level == ISIS_LEVEL1 - && !isis_tlvs_area_addresses_match( -- iih.tlvs, circuit->area->area_addrs)))) { -+ iih.tlvs, &circuit->area->area_addrs)))) { - if (IS_DEBUG_ADJ_PACKETS) { - zlog_debug( - "ISIS-Adj (%s): Area mismatch, level %d IIH on %s", -@@ -1952,12 +1952,12 @@ int send_hello(struct isis_circuit *circuit, int level) - - isis_tlvs_add_auth(tlvs, &circuit->passwd); - -- if (!listcount(circuit->area->area_addrs)) { -+ if (!iso_address_list_count(&circuit->area->area_addrs)) { - isis_free_tlvs(tlvs); - return ISIS_WARNING; - } - -- isis_tlvs_add_area_addresses(tlvs, circuit->area->area_addrs); -+ isis_tlvs_add_area_addresses(tlvs, &circuit->area->area_addrs); - - if (circuit->circ_type == CIRCUIT_T_BROADCAST) { - isis_tlvs_add_lan_neighbors( -diff --git a/isisd/isis_snmp.c b/isisd/isis_snmp.c -index 24320e9ed9..624890d5ad 100644 ---- a/isisd/isis_snmp.c -+++ b/isisd/isis_snmp.c -@@ -839,7 +839,6 @@ static int isis_snmp_area_addr_lookup_exact(oid *oid_idx, size_t oid_idx_len, - size_t addr_len; - struct isis_area *area = NULL; - struct iso_address *addr = NULL; -- struct listnode *addr_node; - struct isis *isis = isis_lookup_by_vrfid(VRF_DEFAULT); - - if (isis == NULL) -@@ -861,7 +860,7 @@ static int isis_snmp_area_addr_lookup_exact(oid *oid_idx, size_t oid_idx_len, - return 0; - } - -- for (ALL_LIST_ELEMENTS_RO(area->area_addrs, addr_node, addr)) { -+ frr_each (iso_address_list, &area->area_addrs, addr) { - if (addr->addr_len != addr_len) - continue; - -@@ -889,7 +888,6 @@ static int isis_snmp_area_addr_lookup_next(oid *oid_idx, size_t oid_idx_len, - struct isis_area *area = NULL; - struct iso_address *found_addr = NULL; - struct iso_address *addr = NULL; -- struct listnode *addr_node; - struct isis *isis = isis_lookup_by_vrfid(VRF_DEFAULT); - - if (isis == NULL) -@@ -908,7 +906,7 @@ static int isis_snmp_area_addr_lookup_next(oid *oid_idx, size_t oid_idx_len, - if (!res) - return 0; - -- for (ALL_LIST_ELEMENTS_RO(area->area_addrs, addr_node, addr)) { -+ frr_each (iso_address_list, &area->area_addrs, addr) { - if (addr->addr_len < addr_len) - continue; - diff --git a/isisd/isis_te.c b/isisd/isis_te.c -index cf92ae46e9..61fad6e8ee 100644 +index 84504086e2..0c5c870f68 100644 --- a/isisd/isis_te.c +++ b/isisd/isis_te.c -@@ -762,6 +762,17 @@ static struct ls_vertex *lsp_to_vertex(struct ls_ted *ted, struct isis_lsp *lsp) - vertex->status == NEW ? "Create" : "Found", vertex->key, - print_sys_hostname(old->adv.id.iso.sys_id)); +@@ -716,6 +716,7 @@ static struct ls_vertex *lsp_to_vertex(struct ls_ted *ted, struct isis_lsp *lsp) + struct ls_node *old, lnode = {}; + struct isis_tlvs *tlvs; + const struct in_addr inaddr_any = { .s_addr = INADDR_ANY }; ++ struct isis_area_address *addr; -+ if (iso_address_list_count(&lsp->area->area_addrs)) { -+ struct iso_address *addr = iso_address_list_first(&lsp->area->area_addrs); -+ -+ lnode.isis_area_id_len = addr->addr_len; -+ memcpy(lnode.isis_area_id, addr->area_addr, addr->addr_len); -+ SET_FLAG(lnode.flags, LS_NODE_ISIS_AREA_ID); -+ if (iso_address_list_count(&lsp->area->area_addrs) > 1) -+ zlog_warn("%s: Only one IS-IS Area ID is supported, ignoring others", -+ __func__); -+ } -+ + /* Sanity check */ + if (!ted || !lsp) +@@ -744,6 +745,15 @@ static struct ls_vertex *lsp_to_vertex(struct ls_ted *ted, struct isis_lsp *lsp) /* Fulfill Link State Node information */ tlvs = lsp->tlvs; if (tlvs) { -diff --git a/isisd/isis_tlvs.c b/isisd/isis_tlvs.c -index d29d917145..0e0d4cfd60 100644 ---- a/isisd/isis_tlvs.c -+++ b/isisd/isis_tlvs.c -@@ -7461,15 +7461,12 @@ void isis_tlvs_add_auth(struct isis_tlvs *tlvs, struct isis_passwd *passwd) - append_item(&tlvs->isis_auth, (struct isis_item *)auth); - } - --void isis_tlvs_add_area_addresses(struct isis_tlvs *tlvs, -- struct list *addresses) -+void isis_tlvs_add_area_addresses(struct isis_tlvs *tlvs, struct iso_address_list_head *addresses) - { -- struct listnode *node; - struct iso_address *area_addr; - -- for (ALL_LIST_ELEMENTS_RO(addresses, node, area_addr)) { -- struct isis_area_address *a = -- XCALLOC(MTYPE_ISIS_TLV, sizeof(*a)); -+ frr_each (iso_address_list, addresses, area_addr) { -+ struct isis_area_address *a = XCALLOC(MTYPE_ISIS_TLV, sizeof(*a)); - - a->len = area_addr->addr_len; - memcpy(a->addr, area_addr->area_addr, ISO_ADDR_SIZE); -@@ -7660,20 +7657,17 @@ int isis_tlvs_auth_is_valid(struct isis_tlvs *tlvs, struct isis_passwd *passwd, - return ISIS_AUTH_FAILURE; - } - --bool isis_tlvs_area_addresses_match(struct isis_tlvs *tlvs, -- struct list *addresses) -+bool isis_tlvs_area_addresses_match(struct isis_tlvs *tlvs, struct iso_address_list_head *addresses) - { - struct isis_area_address *addr_head; - - addr_head = (struct isis_area_address *)tlvs->area_addresses.head; -- for (struct isis_area_address *addr = addr_head; addr; -- addr = addr->next) { -- struct listnode *node; -+ for (struct isis_area_address *addr = addr_head; addr; addr = addr->next) { - struct iso_address *a; - -- for (ALL_LIST_ELEMENTS_RO(addresses, node, a)) { -- if (a->addr_len == addr->len -- && !memcmp(a->area_addr, addr->addr, addr->len)) -+ frr_each (iso_address_list, addresses, a) { -+ if (a->addr_len == addr->len && -+ !memcmp(a->area_addr, addr->addr, addr->len)) - return true; - } - } -diff --git a/isisd/isis_tlvs.h b/isisd/isis_tlvs.h -index 5798d318f2..f1bcde0830 100644 ---- a/isisd/isis_tlvs.h -+++ b/isisd/isis_tlvs.h -@@ -17,7 +17,7 @@ - #include "prefix.h" - #include "flex_algo.h" - #include "affinitymap.h" -- -+#include "lib/iso.h" - - #include "lib/srv6.h" - -@@ -778,14 +778,11 @@ struct list *isis_fragment_tlvs(struct isis_tlvs *tlvs, size_t size); - #endif - - void isis_tlvs_add_auth(struct isis_tlvs *tlvs, struct isis_passwd *passwd); --void isis_tlvs_add_area_addresses(struct isis_tlvs *tlvs, -- struct list *addresses); --void isis_tlvs_add_lan_neighbors(struct isis_tlvs *tlvs, -- struct list *neighbors); --void isis_tlvs_set_protocols_supported(struct isis_tlvs *tlvs, -- struct nlpids *nlpids); --void isis_tlvs_add_mt_router_info(struct isis_tlvs *tlvs, uint16_t mtid, -- bool overload, bool attached); -+void isis_tlvs_add_area_addresses(struct isis_tlvs *tlvs, struct iso_address_list_head *addresses); -+void isis_tlvs_add_lan_neighbors(struct isis_tlvs *tlvs, struct list *neighbors); -+void isis_tlvs_set_protocols_supported(struct isis_tlvs *tlvs, struct nlpids *nlpids); -+void isis_tlvs_add_mt_router_info(struct isis_tlvs *tlvs, uint16_t mtid, bool overload, -+ bool attached); - void isis_tlvs_add_ipv4_address(struct isis_tlvs *tlvs, struct in_addr *addr); - void isis_tlvs_add_ipv4_addresses(struct isis_tlvs *tlvs, - struct list *addresses); -@@ -796,7 +793,7 @@ void isis_tlvs_add_global_ipv6_addresses(struct isis_tlvs *tlvs, - int isis_tlvs_auth_is_valid(struct isis_tlvs *tlvs, struct isis_passwd *passwd, - struct stream *stream, bool is_lsp); - bool isis_tlvs_area_addresses_match(struct isis_tlvs *tlvs, -- struct list *addresses); -+ struct iso_address_list_head *addresses); - struct isis_adjacency; - void isis_tlvs_to_adj(struct isis_tlvs *tlvs, struct isis_adjacency *adj, - bool *changed); -diff --git a/isisd/isis_zebra.c b/isisd/isis_zebra.c -index 9389d6665a..2b8bc73eda 100644 ---- a/isisd/isis_zebra.c -+++ b/isisd/isis_zebra.c -@@ -68,7 +68,7 @@ static int isis_router_id_update_zebra(ZAPI_CALLBACK_ARGS) - - isis->router_id = router_id.u.prefix4.s_addr; - for (ALL_LIST_ELEMENTS_RO(isis->area_list, node, area)) -- if (listcount(area->area_addrs) > 0) -+ if (iso_address_list_count(&area->area_addrs) > 0) - lsp_regenerate_schedule(area, area->is_type, 0); - - return 0; -diff --git a/isisd/isisd.c b/isisd/isisd.c -index 78fbbe13b1..0de8793a50 100644 ---- a/isisd/isisd.c -+++ b/isisd/isisd.c -@@ -353,8 +353,7 @@ struct isis_area *isis_area_create(const char *area_tag, const char *vrf_name) - - area->circuit_list = list_new(); - area->adjacency_list = list_new(); -- area->area_addrs = list_new(); -- area->area_addrs->del = isis_area_address_delete; -+ iso_address_list_init(&area->area_addrs); - - if (!CHECK_FLAG(im->options, F_ISIS_UNIT_TEST)) - event_add_timer(master, lsp_tick, area, 1, &area->t_tick); -@@ -507,8 +506,8 @@ struct isis_area *isis_area_lookup_by_sysid(const uint8_t *sysid) - return NULL; - - for (ALL_LIST_ELEMENTS_RO(isis->area_list, node, area)) { -- if (listcount(area->area_addrs) > 0) { -- addr = listgetdata(listhead(area->area_addrs)); -+ addr = iso_address_list_first(&area->area_addrs); -+ if (addr != NULL) { - if (!memcmp(addr->area_addr + addr->addr_len, sysid, - ISIS_SYS_ID_LEN)) - return area; -@@ -593,8 +592,8 @@ void isis_area_destroy(struct isis_area *area) - if (!CHECK_FLAG(im->options, F_ISIS_UNIT_TEST)) - isis_redist_area_finish(area); - -- if (listcount(area->area_addrs) > 0) { -- addr = listgetdata(listhead(area->area_addrs)); -+ addr = iso_address_list_first(&area->area_addrs); -+ if (addr != NULL) { - if (!memcmp(addr->area_addr + addr->addr_len, area->isis->sysid, - ISIS_SYS_ID_LEN)) { - memset(area->isis->sysid, 0, ISIS_SYS_ID_LEN); -@@ -602,7 +601,9 @@ void isis_area_destroy(struct isis_area *area) - } - } - -- list_delete(&area->area_addrs); -+ while ((addr = iso_address_list_pop(&area->area_addrs))) -+ XFREE(MTYPE_ISIS_AREA_ADDR, addr); -+ iso_address_list_fini(&area->area_addrs); - - for (int i = SPF_PREFIX_PRIO_CRITICAL; i <= SPF_PREFIX_PRIO_MEDIUM; - i++) { -@@ -884,12 +885,11 @@ int area_net_title(struct vty *vty, const char *net_title) - VTY_DECLVAR_CONTEXT(isis_area, area); - struct iso_address *addr; - struct iso_address *addrp; -- struct listnode *node; - - uint8_t buff[255]; - - /* We check that we are not over the maximal number of addresses */ -- if (listcount(area->area_addrs) >= area->isis->max_area_addrs) { -+ if (iso_address_list_count(&area->area_addrs) >= area->isis->max_area_addrs) { - vty_out(vty, - "Maximum of area addresses (%d) already reached \n", - area->isis->max_area_addrs); -@@ -940,7 +940,7 @@ int area_net_title(struct vty *vty, const char *net_title) - } - - /* now we see that we don't already have this address */ -- for (ALL_LIST_ELEMENTS_RO(area->area_addrs, node, addrp)) { -+ frr_each (iso_address_list, &area->area_addrs, addrp) { - if ((addrp->addr_len + ISIS_SYS_ID_LEN + ISIS_NSEL_LEN) - != (addr->addr_len)) - continue; -@@ -956,10 +956,10 @@ int area_net_title(struct vty *vty, const char *net_title) - * Forget the systemID part of the address - */ - addr->addr_len -= (ISIS_SYS_ID_LEN + ISIS_NSEL_LEN); -- listnode_add(area->area_addrs, addr); -+ iso_address_list_add_tail(&area->area_addrs, addr); - - /* only now we can safely generate our LSPs for this area */ -- if (listcount(area->area_addrs) > 0) { -+ if (iso_address_list_count(&area->area_addrs) > 0) { - if (area->is_type & IS_LEVEL_1) - lsp_generate(area, IS_LEVEL_1); - if (area->is_type & IS_LEVEL_2) -@@ -973,7 +973,6 @@ int area_clear_net_title(struct vty *vty, const char *net_title) - { - VTY_DECLVAR_CONTEXT(isis_area, area); - struct iso_address addr, *addrp = NULL; -- struct listnode *node; - uint8_t buff[255]; - - addr.addr_len = dotformat2buff(buff, net_title); -@@ -986,7 +985,7 @@ int area_clear_net_title(struct vty *vty, const char *net_title) - - memcpy(addr.area_addr, buff, (int)addr.addr_len); - -- for (ALL_LIST_ELEMENTS_RO(area->area_addrs, node, addrp)) -+ frr_each (iso_address_list, &area->area_addrs, addrp) - if ((addrp->addr_len + ISIS_SYS_ID_LEN + 1) == addr.addr_len - && !memcmp(addrp->area_addr, addr.area_addr, addr.addr_len)) - break; -@@ -997,13 +996,13 @@ int area_clear_net_title(struct vty *vty, const char *net_title) - return CMD_ERR_NO_MATCH; - } - -- listnode_delete(area->area_addrs, addrp); -+ iso_address_list_del(&area->area_addrs, addrp); - XFREE(MTYPE_ISIS_AREA_ADDR, addrp); - - /* - * Last area address - reset the SystemID for this router - */ -- if (listcount(area->area_addrs) == 0) { -+ if (iso_address_list_count(&area->area_addrs) == 0) { - memset(area->isis->sysid, 0, ISIS_SYS_ID_LEN); - area->isis->sysid_set = 0; - if (IS_DEBUG_EVENTS) -@@ -2388,7 +2387,7 @@ static void common_isis_summary_json(struct json_object *json, - int level; - json_object *vrf_json, *areas_json, *area_json, *tx_pdu_json, *rx_pdu_json, *levels_json, - *level_json; -- struct listnode *node, *node2; -+ struct listnode *node; - struct isis_area *area; - time_t cur; - char uptime[MONOTIME_STRLEN]; -@@ -2423,10 +2422,9 @@ static void common_isis_summary_json(struct json_object *json, - : stier); - } - -- if (listcount(area->area_addrs) > 0) { -+ { - struct iso_address *area_addr; -- for (ALL_LIST_ELEMENTS_RO(area->area_addrs, node2, -- area_addr)) -+ frr_each (iso_address_list, &area->area_addrs, area_addr) - json_object_string_addf(area_json, "net", - "%pISl", area_addr); - } -@@ -2495,7 +2493,7 @@ static void common_isis_summary_json(struct json_object *json, - - static void common_isis_summary_vty(struct vty *vty, struct isis *isis) - { -- struct listnode *node, *node2; -+ struct listnode *node; - struct isis_area *area; - int level; - -@@ -2523,10 +2521,9 @@ static void common_isis_summary_vty(struct vty *vty, struct isis *isis) - vty_out(vty, " Tier: %hhu\n", tier); - } - -- if (listcount(area->area_addrs) > 0) { -+ if (iso_address_list_count(&area->area_addrs) > 0) { - struct iso_address *area_addr; -- for (ALL_LIST_ELEMENTS_RO(area->area_addrs, node2, -- area_addr)) -+ frr_each (iso_address_list, &area->area_addrs, area_addr) - vty_out(vty, " Net: %pISl\n", area_addr); - } - -@@ -3275,7 +3272,7 @@ void isis_area_is_type_set(struct isis_area *area, int is_type) - - spftree_area_init(area); - -- if (listcount(area->area_addrs) > 0) { -+ if (iso_address_list_count(&area->area_addrs) > 0) { - if (is_type & IS_LEVEL_1) - lsp_generate(area, IS_LEVEL_1); - if (is_type & IS_LEVEL_2) -@@ -3332,7 +3329,7 @@ void isis_area_overload_on_startup_set(struct isis_area *area, - - void config_end_lsp_generate(struct isis_area *area) - { -- if (listcount(area->area_addrs) > 0) { -+ if (iso_address_list_count(&area->area_addrs) > 0) { - if (CHECK_FLAG(area->is_type, IS_LEVEL_1)) - lsp_generate(area, IS_LEVEL_1); - if (CHECK_FLAG(area->is_type, IS_LEVEL_2)) -@@ -3548,7 +3545,7 @@ static int isis_config_write(struct vty *vty) - { - int write = 0; - struct isis_area *area; -- struct listnode *node, *node2, *inode; -+ struct listnode *node, *inode; - struct isis *isis; - - if (!im) { -@@ -3562,10 +3559,9 @@ static int isis_config_write(struct vty *vty) - vty_out(vty, "router " PROTO_NAME " %s\n", area->area_tag); - write++; - /* ISIS - Net */ -- if (listcount(area->area_addrs) > 0) { -+ if (iso_address_list_count(&area->area_addrs) > 0) { - struct iso_address *area_addr; -- for (ALL_LIST_ELEMENTS_RO(area->area_addrs, -- node2, area_addr)) { -+ frr_each (iso_address_list, &area->area_addrs, area_addr) { - vty_out(vty, " net %pISl\n", area_addr); - write++; - } -diff --git a/isisd/isisd.h b/isisd/isisd.h -index cd50d43a18..f43164da4d 100644 ---- a/isisd/isisd.h -+++ b/isisd/isisd.h -@@ -174,7 +174,7 @@ struct isis_area { - /* identifies the routing instance */ - char *area_tag; - /* area addresses for this area */ -- struct list *area_addrs; -+ struct iso_address_list_head area_addrs; - uint16_t max_lsp_lifetime[ISIS_LEVELS]; - char is_type; /* level-1 level-1-2 or level-2-only */ - /* are we overloaded? */ ++ addr = (struct isis_area_address *)tlvs->area_addresses.head; ++ if (addr) { ++ lnode.isis_area_id_len = addr->len; ++ memcpy(lnode.isis_area_id, addr->addr, addr->len); ++ SET_FLAG(lnode.flags, LS_NODE_ISIS_AREA_ID); ++ if (addr->next) ++ zlog_warn("%s: Only one IS-IS Area ID is supported, ignoring others", ++ __func__); ++ } + if (tlvs->te_router_id) { + IPV4_ADDR_COPY(&lnode.router_id, tlvs->te_router_id); + SET_FLAG(lnode.flags, LS_NODE_ROUTER_ID); diff --git a/lib/command.h b/lib/command.h -index d5119fd97c..ce3d86b659 100644 +index fe6bca11f9..ce9d894855 100644 --- a/lib/command.h +++ b/lib/command.h @@ -120,6 +120,7 @@ enum node_type { @@ -12968,37 +12699,6 @@ index b9c19cc3d5..4d4687c4c4 100644 case SAFI_UNSPEC: case SAFI_MAX: return IANA_SAFI_RESERVED; -diff --git a/lib/iso.h b/lib/iso.h -index bdc9d39031..8e9df8cee6 100644 ---- a/lib/iso.h -+++ b/lib/iso.h -@@ -11,16 +11,26 @@ - #define LIB_ISO_H_ - - #include "compiler.h" -+#include "typesafe.h" - - /* len of "xx.xxxx.xxxx.xxxx.xxxx.xxxx.xxxx.xxxx.xxxx.xxxx.xx" + '\0' */ - #define ISO_ADDR_STRLEN 51 - #define ISO_ADDR_MIN 8 - #define ISO_ADDR_SIZE 20 -+ -+/* Predeclare typesafe list for isis_area->area_addrs */ -+PREDECL_DLIST(iso_address_list); -+ - struct iso_address { - uint8_t addr_len; - uint8_t area_addr[ISO_ADDR_SIZE]; -+ -+ /* Typesafe list linkage for isis_area->area_addrs */ -+ struct iso_address_list_item item; - }; - -+DECLARE_DLIST(iso_address_list, struct iso_address, item); -+ - /* len of "xxxx.xxxx.xxxx.xx-xx" + '\0' */ - #define ISO_SYSID_STRLEN 21 - diff --git a/lib/link_state.c b/lib/link_state.c index 3603407edb..8a9acc51ee 100644 --- a/lib/link_state.c @@ -13073,17 +12773,17 @@ index 3603407edb..8a9acc51ee 100644 void ls_show_vertex(struct ls_vertex *vertex, struct vty *vty, diff --git a/lib/link_state.h b/lib/link_state.h -index 7169926a1a..14b8deef2d 100644 +index 7169926a1a..7f0ba2f336 100644 --- a/lib/link_state.h +++ b/lib/link_state.h -@@ -48,6 +48,7 @@ struct zclient; - /* Link State Common definitions */ - #define MAX_NAME_LENGTH 256 - #define ISO_SYS_ID_LEN 6 -+#define ISO_ADDR_SIZE 20 +@@ -13,6 +13,7 @@ + #define _FRR_LINK_STATE_H_ - /* Type of Node */ - enum ls_node_type { + #include "admin_group.h" ++#include "iso.h" + #include "typesafe.h" + + #ifdef __cplusplus @@ -107,12 +108,15 @@ extern int ls_node_id_same(struct ls_node_id i1, struct ls_node_id i2); #define LS_NODE_SRLB 0x0080 #define LS_NODE_MSD 0x0100 @@ -13101,7 +12801,7 @@ index 7169926a1a..14b8deef2d 100644 struct in6_addr router_id6; /* IPv6 Router ID */ uint8_t node_flag; /* IS-IS or OSPF Node flag */ diff --git a/lib/prefix.c b/lib/prefix.c -index 40277f0609..17a3129df6 100644 +index fb66e39f38..1f7e28c8ae 100644 --- a/lib/prefix.c +++ b/lib/prefix.c @@ -132,6 +132,8 @@ const char *afi2str_lower(afi_t afi) @@ -13132,7 +12832,7 @@ index 40277f0609..17a3129df6 100644 case SAFI_MAX: return "unknown"; diff --git a/lib/zebra.h b/lib/zebra.h -index 46d3c2f660..ca137058b7 100644 +index 81b762b593..3985b80f78 100644 --- a/lib/zebra.h +++ b/lib/zebra.h @@ -167,7 +167,8 @@ typedef enum { @@ -13182,10 +12882,10 @@ index c8b5c1e814..e1cd2f038e 100644 DEBUGD(&pbr_dbg_zebra, "%s: Asked to delete unsupported route type: L2VPN", diff --git a/staticd/static_vty.c b/staticd/static_vty.c -index 837a65bcb7..e7362570e4 100644 +index eab3faff6b..84c2ea2187 100644 --- a/staticd/static_vty.c +++ b/staticd/static_vty.c -@@ -138,6 +138,7 @@ static int static_route_nb_run(struct vty *vty, struct static_route_args *args) +@@ -139,6 +139,7 @@ static int static_route_nb_run(struct vty *vty, struct static_route_args *args) assert(!!str2prefix(args->source, &src)); break; case AFI_L2VPN: @@ -13194,7 +12894,7 @@ index 837a65bcb7..e7362570e4 100644 case AFI_MAX: break; diff --git a/tests/bgpd/test_peer_attr.c b/tests/bgpd/test_peer_attr.c -index ec2ea88282..fecaaa2d39 100644 +index 31dba17d42..9337f9b450 100644 --- a/tests/bgpd/test_peer_attr.c +++ b/tests/bgpd/test_peer_attr.c @@ -663,6 +663,8 @@ static const char *str_from_afi(afi_t afi) @@ -13206,25 +12906,12 @@ index ec2ea88282..fecaaa2d39 100644 case AFI_MAX: case AFI_UNSPEC: return "bad-value"; -diff --git a/tests/isisd/test_isis_spf.c b/tests/isisd/test_isis_spf.c -index bbafe7204e..d834a8de57 100644 ---- a/tests/isisd/test_isis_spf.c -+++ b/tests/isisd/test_isis_spf.c -@@ -261,7 +261,7 @@ static int test_run(struct vty *vty, const struct isis_topology *topology, - addr->addr_len = dotformat2buff(buff, net_title); - memcpy(addr->area_addr, buff, addr->addr_len); - addr->addr_len -= (ISIS_SYS_ID_LEN + ISIS_NSEL_LEN); -- listnode_add(area->area_addrs, addr); -+ iso_address_list_add_tail(&area->area_addrs, addr); - if (test_topology_load(topology, area, area->lspdb) != 0) { - vty_out(vty, "%% Failed to load topology\n"); - return CMD_WARNING; diff --git a/tests/topotests/bgp_link_state/__init__.py b/tests/topotests/bgp_link_state/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/topotests/bgp_link_state/r1/frr.conf b/tests/topotests/bgp_link_state/r1/frr.conf new file mode 100644 -index 0000000000..70f46ed496 +index 0000000000..3c1ee57dee --- /dev/null +++ b/tests/topotests/bgp_link_state/r1/frr.conf @@ -0,0 +1,57 @@ @@ -13265,7 +12952,7 @@ index 0000000000..70f46ed496 + isis network point-to-point +! +router isis 1 -+ net 49.0000.0000.0000.0001.00 ++ net 49.0001.0000.0000.0001.00 + is-type level-2-only + topology ipv6-unicast + redistribute ipv4 static level-2 @@ -14855,7 +14542,7 @@ index 0000000000..d1df9a5102 +} diff --git a/tests/topotests/bgp_link_state/r2/frr.conf b/tests/topotests/bgp_link_state/r2/frr.conf new file mode 100644 -index 0000000000..05fe6646b3 +index 0000000000..dd243592cd --- /dev/null +++ b/tests/topotests/bgp_link_state/r2/frr.conf @@ -0,0 +1,72 @@ @@ -14910,7 +14597,7 @@ index 0000000000..05fe6646b3 + isis passive +! +router isis 1 -+ net 49.0000.0000.0000.0002.00 ++ net 49.0002.0000.0000.0002.00 + is-type level-2-only + topology ipv6-unicast + redistribute ipv4 static level-2 @@ -14957,7 +14644,7 @@ index 0000000000..199f548177 +} diff --git a/tests/topotests/bgp_link_state/r3/frr.conf b/tests/topotests/bgp_link_state/r3/frr.conf new file mode 100644 -index 0000000000..e4212eb0d4 +index 0000000000..f53feafd77 --- /dev/null +++ b/tests/topotests/bgp_link_state/r3/frr.conf @@ -0,0 +1,56 @@ @@ -14997,7 +14684,7 @@ index 0000000000..e4212eb0d4 + isis network point-to-point +! +router isis 1 -+ net 49.0000.0000.0000.0003.00 ++ net 49.0003.0000.0000.0003.00 + is-type level-2-only + topology ipv6-unicast + redistribute ipv4 static level-2 @@ -15043,7 +14730,7 @@ index 0000000000..c6ef44912f +} diff --git a/tests/topotests/bgp_link_state/r4/frr.conf b/tests/topotests/bgp_link_state/r4/frr.conf new file mode 100644 -index 0000000000..273af2299a +index 0000000000..168b41d839 --- /dev/null +++ b/tests/topotests/bgp_link_state/r4/frr.conf @@ -0,0 +1,54 @@ @@ -15082,7 +14769,7 @@ index 0000000000..273af2299a + isis network point-to-point +! +router isis 1 -+ net 49.0000.0000.0000.0004.00 ++ net 49.0004.0000.0000.0004.00 + is-type level-2-only + topology ipv6-unicast + redistribute ipv4 static level-2 @@ -15164,10 +14851,10 @@ index 0000000000..4ce4cb631b +} diff --git a/tests/topotests/bgp_link_state/rr/bgp_ls_attrs_node4.json b/tests/topotests/bgp_link_state/rr/bgp_ls_attrs_node4.json new file mode 100644 -index 0000000000..59bf79196f +index 0000000000..35487d195a --- /dev/null +++ b/tests/topotests/bgp_link_state/rr/bgp_ls_attrs_node4.json -@@ -0,0 +1,15 @@ +@@ -0,0 +1,20 @@ +{ + "paths": [ + { @@ -15177,8 +14864,13 @@ index 0000000000..59bf79196f + "rangeSize": 4000, + "flags": "0xc0" + }, -+ "msd": 7, -+ "isisAreaId": "49.0000" ++ "msd": [ ++ { ++ "type": 1, ++ "value": 7 ++ } ++ ], ++ "isisAreaId": "49.0004" + } + } + ] @@ -17816,8 +17508,3274 @@ index 0000000000..97444690ba + +# Get current working directory +CWD = os.path.dirname(os.path.realpath(__file__)) +diff --git a/tests/topotests/bgp_link_state_bgp_fabric/r1/frr.conf b/tests/topotests/bgp_link_state_bgp_fabric/r1/frr.conf +new file mode 100644 +index 0000000000..50a27f3634 +--- /dev/null ++++ b/tests/topotests/bgp_link_state_bgp_fabric/r1/frr.conf +@@ -0,0 +1,50 @@ ++! ++hostname r1 ++! ++! Router 1: AS65001, IPv4 iBGP with r2 ++! ++! debug bgp updates ++! debug bgp link-state ++! debug bgp zebra ++! ++interface lo ++ ip address 10.1.1.1/32 ++! ++interface r1-eth1 ++ description Link to r2 ++ ip address 172.16.1.1/30 ++! ++interface r1-eth0 ++ description Point-to-point link to RR ++ ip address 10.255.1.1/30 ++! ++! Static route for BGP-LS Prefix NLRI ++ip route 192.0.2.0/24 Null0 ++! ++router bgp 65001 ++ bgp router-id 1.1.1.1 ++ no bgp ebgp-requires-policy ++ no bgp default ipv4-unicast ++ ! ++ ! iBGP neighbor (r2) ++ neighbor 172.16.1.2 remote-as 65001 ++ neighbor 172.16.1.2 update-source r1-eth1 ++ ! ++ ! BGP-LS speaker ++ neighbor 10.255.1.2 remote-as 65000 ++ ! ++ ! IPv4 unicast for iBGP session ++ address-family ipv4 unicast ++ neighbor 172.16.1.2 activate ++ redistribute local ++ redistribute connected ++ redistribute static ++ exit-address-family ++ ! ++ ! Link-State address family - export BGP topology to BGP-LS speaker ++ address-family link-state link-state ++ distribute bgp-fabric-link-state ++ neighbor 10.255.1.2 activate ++ exit-address-family ++! ++end +diff --git a/tests/topotests/bgp_link_state_bgp_fabric/r2/frr.conf b/tests/topotests/bgp_link_state_bgp_fabric/r2/frr.conf +new file mode 100644 +index 0000000000..fe61bebda7 +--- /dev/null ++++ b/tests/topotests/bgp_link_state_bgp_fabric/r2/frr.conf +@@ -0,0 +1,47 @@ ++! ++hostname r2 ++! ++! Router 2: AS65001, IPv4 iBGP with r1 ++! ++! debug bgp updates ++! debug bgp link-state ++! debug bgp zebra ++! ++interface lo ++ ip address 10.2.2.2/32 ++! ++interface r2-eth1 ++ description Link to r1 ++ ip address 172.16.1.2/30 ++! ++interface r2-eth0 ++ description Point-to-point link to RR ++ ip address 10.255.2.1/30 ++! ++router bgp 65001 ++ bgp router-id 2.2.2.2 ++ no bgp ebgp-requires-policy ++ no bgp default ipv4-unicast ++ ! ++ ! iBGP neighbor (r1) ++ neighbor 172.16.1.1 remote-as 65001 ++ neighbor 172.16.1.1 update-source r2-eth1 ++ ! ++ ! BGP-LS speaker ++ neighbor 10.255.2.2 remote-as 65000 ++ ! ++ ! IPv4 unicast for iBGP session ++ address-family ipv4 unicast ++ neighbor 172.16.1.1 activate ++ redistribute local ++ redistribute connected ++ redistribute static ++ exit-address-family ++ ! ++ ! Link-State address family - export BGP topology to BGP-LS speaker ++ address-family link-state link-state ++ distribute bgp-fabric-link-state ++ neighbor 10.255.2.2 activate ++ exit-address-family ++! ++end +diff --git a/tests/topotests/bgp_link_state_bgp_fabric/r3/frr.conf b/tests/topotests/bgp_link_state_bgp_fabric/r3/frr.conf +new file mode 100644 +index 0000000000..f737096892 +--- /dev/null ++++ b/tests/topotests/bgp_link_state_bgp_fabric/r3/frr.conf +@@ -0,0 +1,55 @@ ++! ++hostname r3 ++! ++! Router 3: AS65002, IPv6 eBGP with r4 ++! ++! debug bgp updates ++! debug bgp link-state ++! debug bgp zebra ++! ++interface lo ++ ip address 10.3.3.3/32 ++! ++interface r3-eth1 ++ description Link to r4 ++ ipv6 address 2001:db8:1::1/64 ++! ++interface r3-eth0 ++ description Point-to-point link to RR ++ ipv6 address 2001:db9:3::1/126 ++! ++! Static route for BGP-LS Prefix NLRI ++ipv6 route 2001:db9:1:124::/64 Null0 ++! ++router bgp 65002 ++ bgp router-id 3.3.3.3 ++ no bgp ebgp-requires-policy ++ no bgp default ipv4-unicast ++ ! ++ ! eBGP neighbor (r4) ++ neighbor 2001:db8:1::2 remote-as 65003 ++ ! ++ ! BGP-LS speaker ++ neighbor 2001:db9:3::2 remote-as 65000 ++ ! ++ address-family ipv4 unicast ++ redistribute local ++ redistribute connected ++ redistribute static ++ exit-address-family ++ ! ++ ! IPv6 unicast for eBGP session ++ address-family ipv6 unicast ++ neighbor 2001:db8:1::2 activate ++ redistribute local ++ redistribute connected ++ redistribute static ++ exit-address-family ++ ! ++ ! Link-State address family - export BGP topology to BGP-LS speaker ++ address-family link-state link-state ++ distribute bgp-fabric-link-state ++ neighbor 2001:db9:3::2 activate ++ exit-address-family ++! ++end +diff --git a/tests/topotests/bgp_link_state_bgp_fabric/r4/frr.conf b/tests/topotests/bgp_link_state_bgp_fabric/r4/frr.conf +new file mode 100644 +index 0000000000..e87a51a0a3 +--- /dev/null ++++ b/tests/topotests/bgp_link_state_bgp_fabric/r4/frr.conf +@@ -0,0 +1,54 @@ ++! ++hostname r4 ++! ++! Router 4: AS65003, IPv6 eBGP with r3 ++! ++! debug bgp updates ++! debug bgp link-state ++! debug bgp zebra ++! ++interface lo ++ ip address 10.4.4.4/32 ++! ++interface r4-eth1 ++ description Link to r3 ++ ipv6 address 2001:db8:1::2/64 ++! ++interface r4-eth0 ++ description Point-to-p\oint link to RR ++ ipv6 address 2001:db9:4::1/126 ++! ++! ++router bgp 65003 ++ bgp router-id 4.4.4.4 ++ no bgp ebgp-requires-policy ++ no bgp default ipv4-unicast ++ ! ++ ! eBGP neighbor (r3) ++ neighbor 2001:db8:1::1 remote-as 65002 ++ ! ++ ! BGP-LS speaker ++ neighbor 2001:db9:4::2 remote-as 65000 ++ ! ++ ! IPv4 unicast for static route ++ address-family ipv4 unicast ++ redistribute local ++ redistribute connected ++ redistribute static ++ exit-address-family ++ ! ++ ! IPv6 unicast for eBGP session ++ address-family ipv6 unicast ++ neighbor 2001:db8:1::1 activate ++ redistribute local ++ redistribute connected ++ redistribute static ++ exit-address-family ++ ! ++ ! Link-State address family - export BGP topology to BGP-LS speaker ++ address-family link-state link-state ++ distribute bgp-fabric-link-state ++ neighbor 2001:db9:4::2 activate ++ exit-address-family ++! ++end +diff --git a/tests/topotests/bgp_link_state_bgp_fabric/rr/expected_bgp_ls.json b/tests/topotests/bgp_link_state_bgp_fabric/rr/expected_bgp_ls.json +new file mode 100644 +index 0000000000..a2d81743ea +--- /dev/null ++++ b/tests/topotests/bgp_link_state_bgp_fabric/rr/expected_bgp_ls.json +@@ -0,0 +1,1785 @@ ++{ ++ "vrfId": 0, ++ "vrfName": "default", ++ "routerId": "192.0.2.254", ++ "defaultLocPrf": 100, ++ "localAS": 65000, ++ "routes": { ++ "[V][B][I0x0][N[c65001][q1.1.1.1]]": [ ++ { ++ "valid": true, ++ "bestpath": true, ++ "selectionReason": "First path received", ++ "pathFrom": "external", ++ "nlriStr": "[V][B][I0x0][N[c65001][q1.1.1.1]]", ++ "nlri": { ++ "nlriType": "node", ++ "protocolId": 7, ++ "identifier": 0, ++ "localNodeDescriptors": { ++ "asn": 65001, ++ "bgpRouterId": "1.1.1.1" ++ } ++ }, ++ "weight": 0, ++ "peerId": "10.255.1.1", ++ "path": "65001", ++ "origin": "incomplete" ++ } ++ ], ++ "[V][B][I0x0][N[c65001][q2.2.2.2]]": [ ++ { ++ "valid": true, ++ "bestpath": true, ++ "selectionReason": "First path received", ++ "pathFrom": "external", ++ "nlriStr": "[V][B][I0x0][N[c65001][q2.2.2.2]]", ++ "nlri": { ++ "nlriType": "node", ++ "protocolId": 7, ++ "identifier": 0, ++ "localNodeDescriptors": { ++ "asn": 65001, ++ "bgpRouterId": "2.2.2.2" ++ } ++ }, ++ "weight": 0, ++ "peerId": "10.255.2.1", ++ "path": "65001", ++ "origin": "incomplete" ++ } ++ ], ++ "[V][B][I0x0][N[c65002][q3.3.3.3]]": [ ++ { ++ "valid": true, ++ "bestpath": true, ++ "selectionReason": "First path received", ++ "pathFrom": "external", ++ "nlriStr": "[V][B][I0x0][N[c65002][q3.3.3.3]]", ++ "nlri": { ++ "nlriType": "node", ++ "protocolId": 7, ++ "identifier": 0, ++ "localNodeDescriptors": { ++ "asn": 65002, ++ "bgpRouterId": "3.3.3.3" ++ } ++ }, ++ "weight": 0, ++ "peerId": "2001:db9:3::1", ++ "path": "65002", ++ "origin": "incomplete", ++ "nexthops": [ ++ { ++ "ip": "2001:db9:3::1", ++ "hostname": "r3", ++ "afi": "ipv6", ++ "scope": "global", ++ "linkLocalOnly": false, ++ "length": 16, ++ "used": true ++ } ++ ] ++ } ++ ], ++ "[V][B][I0x0][N[c65003][q4.4.4.4]]": [ ++ { ++ "valid": true, ++ "bestpath": true, ++ "selectionReason": "First path received", ++ "pathFrom": "external", ++ "nlriStr": "[V][B][I0x0][N[c65003][q4.4.4.4]]", ++ "nlri": { ++ "nlriType": "node", ++ "protocolId": 7, ++ "identifier": 0, ++ "localNodeDescriptors": { ++ "asn": 65003, ++ "bgpRouterId": "4.4.4.4" ++ } ++ }, ++ "weight": 0, ++ "peerId": "2001:db9:4::1", ++ "path": "65003", ++ "origin": "incomplete", ++ "nexthops": [ ++ { ++ "ip": "2001:db9:4::1", ++ "hostname": "r4", ++ "afi": "ipv6", ++ "scope": "global", ++ "linkLocalOnly": false, ++ "length": 16, ++ "used": true ++ } ++ ] ++ } ++ ], ++ "[T][B][I0x0][N[c65001][q1.1.1.1]][P[p10.1.1.1/32]][br0x02]": [ ++ { ++ "valid": true, ++ "bestpath": true, ++ "selectionReason": "First path received", ++ "pathFrom": "external", ++ "nlriStr": ++ "[T][B][I0x0][N[c65001][q1.1.1.1]][P[p10.1.1.1/32]][br0x02]", ++ "nlri": { ++ "nlriType": "ipv4Prefix", ++ "protocolId": 7, ++ "identifier": 0, ++ "localNodeDescriptors": { ++ "asn": 65001, ++ "bgpRouterId": "1.1.1.1" ++ }, ++ "prefixDescriptors": { ++ "ipReachabilityInformation": "10.1.1.1/32", ++ "bgpRouteType": "attached" ++ } ++ }, ++ "weight": 0, ++ "peerId": "10.255.1.1", ++ "path": "65001", ++ "origin": "incomplete" ++ } ++ ], ++ "[T][B][I0x0][N[c65001][q1.1.1.1]][P[p10.255.1.0/30]][br0x02]": [ ++ { ++ "valid": true, ++ "bestpath": true, ++ "selectionReason": "First path received", ++ "pathFrom": "external", ++ "nlriStr": ++ "[T][B][I0x0][N[c65001][q1.1.1.1]][P[p10.255.1.0/30]][br0x02]", ++ "nlri": { ++ "nlriType": "ipv4Prefix", ++ "protocolId": 7, ++ "identifier": 0, ++ "localNodeDescriptors": { ++ "asn": 65001, ++ "bgpRouterId": "1.1.1.1" ++ }, ++ "prefixDescriptors": { ++ "ipReachabilityInformation": "10.255.1.0/30", ++ "bgpRouteType": "attached" ++ } ++ }, ++ "weight": 0, ++ "peerId": "10.255.1.1", ++ "path": "65001", ++ "origin": "incomplete" ++ } ++ ], ++ "[T][B][I0x0][N[c65001][q1.1.1.1]][P[p10.255.1.1/32]][br0x01]": [ ++ { ++ "valid": true, ++ "bestpath": true, ++ "selectionReason": "First path received", ++ "pathFrom": "external", ++ "nlriStr": ++ "[T][B][I0x0][N[c65001][q1.1.1.1]][P[p10.255.1.1/32]][br0x01]", ++ "nlri": { ++ "nlriType": "ipv4Prefix", ++ "protocolId": 7, ++ "identifier": 0, ++ "localNodeDescriptors": { ++ "asn": 65001, ++ "bgpRouterId": "1.1.1.1" ++ }, ++ "prefixDescriptors": { ++ "ipReachabilityInformation": "10.255.1.1/32", ++ "bgpRouteType": "local" ++ } ++ }, ++ "weight": 0, ++ "peerId": "10.255.1.1", ++ "path": "65001", ++ "origin": "incomplete" ++ } ++ ], ++ "[T][B][I0x0][N[c65001][q1.1.1.1]][P[p172.16.1.0/30]][br0x02]": [ ++ { ++ "valid": true, ++ "bestpath": true, ++ "selectionReason": "First path received", ++ "pathFrom": "external", ++ "nlriStr": ++ "[T][B][I0x0][N[c65001][q1.1.1.1]][P[p172.16.1.0/30]][br0x02]", ++ "nlri": { ++ "nlriType": "ipv4Prefix", ++ "protocolId": 7, ++ "identifier": 0, ++ "localNodeDescriptors": { ++ "asn": 65001, ++ "bgpRouterId": "1.1.1.1" ++ }, ++ "prefixDescriptors": { ++ "ipReachabilityInformation": "172.16.1.0/30", ++ "bgpRouteType": "attached" ++ } ++ }, ++ "weight": 0, ++ "peerId": "10.255.1.1", ++ "path": "65001", ++ "origin": "incomplete" ++ } ++ ], ++ "[T][B][I0x0][N[c65001][q1.1.1.1]][P[p172.16.1.1/32]][br0x01]": [ ++ { ++ "valid": true, ++ "bestpath": true, ++ "selectionReason": "First path received", ++ "pathFrom": "external", ++ "nlriStr": ++ "[T][B][I0x0][N[c65001][q1.1.1.1]][P[p172.16.1.1/32]][br0x01]", ++ "nlri": { ++ "nlriType": "ipv4Prefix", ++ "protocolId": 7, ++ "identifier": 0, ++ "localNodeDescriptors": { ++ "asn": 65001, ++ "bgpRouterId": "1.1.1.1" ++ }, ++ "prefixDescriptors": { ++ "ipReachabilityInformation": "172.16.1.1/32", ++ "bgpRouteType": "local" ++ } ++ }, ++ "weight": 0, ++ "peerId": "10.255.1.1", ++ "path": "65001", ++ "origin": "incomplete" ++ } ++ ], ++ "[T][B][I0x0][N[c65001][q1.1.1.1]][P[p192.0.2.0/24]][br0x05]": [ ++ { ++ "valid": true, ++ "bestpath": true, ++ "selectionReason": "First path received", ++ "pathFrom": "external", ++ "nlriStr": ++ "[T][B][I0x0][N[c65001][q1.1.1.1]][P[p192.0.2.0/24]][br0x05]", ++ "nlri": { ++ "nlriType": "ipv4Prefix", ++ "protocolId": 7, ++ "identifier": 0, ++ "localNodeDescriptors": { ++ "asn": 65001, ++ "bgpRouterId": "1.1.1.1" ++ }, ++ "prefixDescriptors": { ++ "ipReachabilityInformation": "192.0.2.0/24", ++ "bgpRouteType": "redistributed" ++ } ++ }, ++ "weight": 0, ++ "peerId": "10.255.1.1", ++ "path": "65001", ++ "origin": "incomplete" ++ } ++ ], ++ "[T][B][I0x0][N[c65001][q2.2.2.2]][P[p10.2.2.2/32]][br0x02]": [ ++ { ++ "valid": true, ++ "bestpath": true, ++ "selectionReason": "First path received", ++ "pathFrom": "external", ++ "nlriStr": ++ "[T][B][I0x0][N[c65001][q2.2.2.2]][P[p10.2.2.2/32]][br0x02]", ++ "nlri": { ++ "nlriType": "ipv4Prefix", ++ "protocolId": 7, ++ "identifier": 0, ++ "localNodeDescriptors": { ++ "asn": 65001, ++ "bgpRouterId": "2.2.2.2" ++ }, ++ "prefixDescriptors": { ++ "ipReachabilityInformation": "10.2.2.2/32", ++ "bgpRouteType": "attached" ++ } ++ }, ++ "weight": 0, ++ "peerId": "10.255.2.1", ++ "path": "65001", ++ "origin": "incomplete" ++ } ++ ], ++ "[T][B][I0x0][N[c65001][q2.2.2.2]][P[p10.255.2.0/30]][br0x02]": [ ++ { ++ "valid": true, ++ "bestpath": true, ++ "selectionReason": "First path received", ++ "pathFrom": "external", ++ "nlriStr": ++ "[T][B][I0x0][N[c65001][q2.2.2.2]][P[p10.255.2.0/30]][br0x02]", ++ "nlri": { ++ "nlriType": "ipv4Prefix", ++ "protocolId": 7, ++ "identifier": 0, ++ "localNodeDescriptors": { ++ "asn": 65001, ++ "bgpRouterId": "2.2.2.2" ++ }, ++ "prefixDescriptors": { ++ "ipReachabilityInformation": "10.255.2.0/30", ++ "bgpRouteType": "attached" ++ } ++ }, ++ "weight": 0, ++ "peerId": "10.255.2.1", ++ "path": "65001", ++ "origin": "incomplete" ++ } ++ ], ++ "[T][B][I0x0][N[c65001][q2.2.2.2]][P[p10.255.2.1/32]][br0x01]": [ ++ { ++ "valid": true, ++ "bestpath": true, ++ "selectionReason": "First path received", ++ "pathFrom": "external", ++ "nlriStr": ++ "[T][B][I0x0][N[c65001][q2.2.2.2]][P[p10.255.2.1/32]][br0x01]", ++ "nlri": { ++ "nlriType": "ipv4Prefix", ++ "protocolId": 7, ++ "identifier": 0, ++ "localNodeDescriptors": { ++ "asn": 65001, ++ "bgpRouterId": "2.2.2.2" ++ }, ++ "prefixDescriptors": { ++ "ipReachabilityInformation": "10.255.2.1/32", ++ "bgpRouteType": "local" ++ } ++ }, ++ "weight": 0, ++ "peerId": "10.255.2.1", ++ "path": "65001", ++ "origin": "incomplete" ++ } ++ ], ++ "[T][B][I0x0][N[c65001][q2.2.2.2]][P[p172.16.1.0/30]][br0x02]": [ ++ { ++ "valid": true, ++ "bestpath": true, ++ "selectionReason": "First path received", ++ "pathFrom": "external", ++ "nlriStr": ++ "[T][B][I0x0][N[c65001][q2.2.2.2]][P[p172.16.1.0/30]][br0x02]", ++ "nlri": { ++ "nlriType": "ipv4Prefix", ++ "protocolId": 7, ++ "identifier": 0, ++ "localNodeDescriptors": { ++ "asn": 65001, ++ "bgpRouterId": "2.2.2.2" ++ }, ++ "prefixDescriptors": { ++ "ipReachabilityInformation": "172.16.1.0/30", ++ "bgpRouteType": "attached" ++ } ++ }, ++ "weight": 0, ++ "peerId": "10.255.2.1", ++ "path": "65001", ++ "origin": "incomplete" ++ } ++ ], ++ "[T][B][I0x0][N[c65001][q2.2.2.2]][P[p172.16.1.2/32]][br0x01]": [ ++ { ++ "valid": true, ++ "bestpath": true, ++ "selectionReason": "First path received", ++ "pathFrom": "external", ++ "nlriStr": ++ "[T][B][I0x0][N[c65001][q2.2.2.2]][P[p172.16.1.2/32]][br0x01]", ++ "nlri": { ++ "nlriType": "ipv4Prefix", ++ "protocolId": 7, ++ "identifier": 0, ++ "localNodeDescriptors": { ++ "asn": 65001, ++ "bgpRouterId": "2.2.2.2" ++ }, ++ "prefixDescriptors": { ++ "ipReachabilityInformation": "172.16.1.2/32", ++ "bgpRouteType": "local" ++ } ++ }, ++ "weight": 0, ++ "peerId": "10.255.2.1", ++ "path": "65001", ++ "origin": "incomplete" ++ } ++ ], ++ "[T][B][I0x0][N[c65001][q1.1.1.1]][P[p10.2.2.2/32]][br0x04]": [ ++ { ++ "valid": true, ++ "bestpath": true, ++ "selectionReason": "First path received", ++ "pathFrom": "external", ++ "nlriStr": ++ "[T][B][I0x0][N[c65001][q1.1.1.1]][P[p10.2.2.2/32]][br0x04]", ++ "nlri": { ++ "nlriType": "ipv4Prefix", ++ "protocolId": 7, ++ "identifier": 0, ++ "localNodeDescriptors": { ++ "asn": 65001, ++ "bgpRouterId": "1.1.1.1" ++ }, ++ "prefixDescriptors": { ++ "ipReachabilityInformation": "10.2.2.2/32", ++ "bgpRouteType": "internalBgp" ++ } ++ }, ++ "weight": 0, ++ "peerId": "10.255.1.1", ++ "path": "65001", ++ "origin": "incomplete" ++ } ++ ], ++ "[T][B][I0x0][N[c65001][q1.1.1.1]][P[p10.255.2.0/30]][br0x04]": [ ++ { ++ "valid": true, ++ "bestpath": true, ++ "selectionReason": "First path received", ++ "pathFrom": "external", ++ "nlriStr": ++ "[T][B][I0x0][N[c65001][q1.1.1.1]][P[p10.255.2.0/30]][br0x04]", ++ "nlri": { ++ "nlriType": "ipv4Prefix", ++ "protocolId": 7, ++ "identifier": 0, ++ "localNodeDescriptors": { ++ "asn": 65001, ++ "bgpRouterId": "1.1.1.1" ++ }, ++ "prefixDescriptors": { ++ "ipReachabilityInformation": "10.255.2.0/30", ++ "bgpRouteType": "internalBgp" ++ } ++ }, ++ "weight": 0, ++ "peerId": "10.255.1.1", ++ "path": "65001", ++ "origin": "incomplete" ++ } ++ ], ++ "[T][B][I0x0][N[c65001][q1.1.1.1]][P[p10.255.2.1/32]][br0x04]": [ ++ { ++ "valid": true, ++ "bestpath": true, ++ "selectionReason": "First path received", ++ "pathFrom": "external", ++ "nlriStr": ++ "[T][B][I0x0][N[c65001][q1.1.1.1]][P[p10.255.2.1/32]][br0x04]", ++ "nlri": { ++ "nlriType": "ipv4Prefix", ++ "protocolId": 7, ++ "identifier": 0, ++ "localNodeDescriptors": { ++ "asn": 65001, ++ "bgpRouterId": "1.1.1.1" ++ }, ++ "prefixDescriptors": { ++ "ipReachabilityInformation": "10.255.2.1/32", ++ "bgpRouteType": "internalBgp" ++ } ++ }, ++ "weight": 0, ++ "peerId": "10.255.1.1", ++ "path": "65001", ++ "origin": "incomplete" ++ } ++ ], ++ "[T][B][I0x0][N[c65001][q1.1.1.1]][P[p172.16.1.0/30]][br0x04]": [ ++ { ++ "valid": true, ++ "bestpath": true, ++ "selectionReason": "First path received", ++ "pathFrom": "external", ++ "nlriStr": ++ "[T][B][I0x0][N[c65001][q1.1.1.1]][P[p172.16.1.0/30]][br0x04]", ++ "nlri": { ++ "nlriType": "ipv4Prefix", ++ "protocolId": 7, ++ "identifier": 0, ++ "localNodeDescriptors": { ++ "asn": 65001, ++ "bgpRouterId": "1.1.1.1" ++ }, ++ "prefixDescriptors": { ++ "ipReachabilityInformation": "172.16.1.0/30", ++ "bgpRouteType": "internalBgp" ++ } ++ }, ++ "weight": 0, ++ "peerId": "10.255.1.1", ++ "path": "65001", ++ "origin": "incomplete" ++ } ++ ], ++ "[T][B][I0x0][N[c65001][q1.1.1.1]][P[p172.16.1.2/32]][br0x04]": [ ++ { ++ "valid": true, ++ "bestpath": true, ++ "selectionReason": "First path received", ++ "pathFrom": "external", ++ "nlriStr": ++ "[T][B][I0x0][N[c65001][q1.1.1.1]][P[p172.16.1.2/32]][br0x04]", ++ "nlri": { ++ "nlriType": "ipv4Prefix", ++ "protocolId": 7, ++ "identifier": 0, ++ "localNodeDescriptors": { ++ "asn": 65001, ++ "bgpRouterId": "1.1.1.1" ++ }, ++ "prefixDescriptors": { ++ "ipReachabilityInformation": "172.16.1.2/32", ++ "bgpRouteType": "internalBgp" ++ } ++ }, ++ "weight": 0, ++ "peerId": "10.255.1.1", ++ "path": "65001", ++ "origin": "incomplete" ++ } ++ ], ++ "[T][B][I0x0][N[c65001][q2.2.2.2]][P[p10.1.1.1/32]][br0x04]": [ ++ { ++ "valid": true, ++ "bestpath": true, ++ "selectionReason": "First path received", ++ "pathFrom": "external", ++ "nlriStr": ++ "[T][B][I0x0][N[c65001][q2.2.2.2]][P[p10.1.1.1/32]][br0x04]", ++ "nlri": { ++ "nlriType": "ipv4Prefix", ++ "protocolId": 7, ++ "identifier": 0, ++ "localNodeDescriptors": { ++ "asn": 65001, ++ "bgpRouterId": "2.2.2.2" ++ }, ++ "prefixDescriptors": { ++ "ipReachabilityInformation": "10.1.1.1/32", ++ "bgpRouteType": "internalBgp" ++ } ++ }, ++ "weight": 0, ++ "peerId": "10.255.2.1", ++ "path": "65001", ++ "origin": "incomplete" ++ } ++ ], ++ "[T][B][I0x0][N[c65001][q2.2.2.2]][P[p10.255.1.0/30]][br0x04]": [ ++ { ++ "valid": true, ++ "bestpath": true, ++ "selectionReason": "First path received", ++ "pathFrom": "external", ++ "nlriStr": ++ "[T][B][I0x0][N[c65001][q2.2.2.2]][P[p10.255.1.0/30]][br0x04]", ++ "nlri": { ++ "nlriType": "ipv4Prefix", ++ "protocolId": 7, ++ "identifier": 0, ++ "localNodeDescriptors": { ++ "asn": 65001, ++ "bgpRouterId": "2.2.2.2" ++ }, ++ "prefixDescriptors": { ++ "ipReachabilityInformation": "10.255.1.0/30", ++ "bgpRouteType": "internalBgp" ++ } ++ }, ++ "weight": 0, ++ "peerId": "10.255.2.1", ++ "path": "65001", ++ "origin": "incomplete" ++ } ++ ], ++ "[T][B][I0x0][N[c65001][q2.2.2.2]][P[p10.255.1.1/32]][br0x04]": [ ++ { ++ "valid": true, ++ "bestpath": true, ++ "selectionReason": "First path received", ++ "pathFrom": "external", ++ "nlriStr": ++ "[T][B][I0x0][N[c65001][q2.2.2.2]][P[p10.255.1.1/32]][br0x04]", ++ "nlri": { ++ "nlriType": "ipv4Prefix", ++ "protocolId": 7, ++ "identifier": 0, ++ "localNodeDescriptors": { ++ "asn": 65001, ++ "bgpRouterId": "2.2.2.2" ++ }, ++ "prefixDescriptors": { ++ "ipReachabilityInformation": "10.255.1.1/32", ++ "bgpRouteType": "internalBgp" ++ } ++ }, ++ "weight": 0, ++ "peerId": "10.255.2.1", ++ "path": "65001", ++ "origin": "incomplete" ++ } ++ ], ++ "[T][B][I0x0][N[c65001][q2.2.2.2]][P[p172.16.1.0/30]][br0x04]": [ ++ { ++ "valid": true, ++ "bestpath": true, ++ "selectionReason": "First path received", ++ "pathFrom": "external", ++ "nlriStr": ++ "[T][B][I0x0][N[c65001][q2.2.2.2]][P[p172.16.1.0/30]][br0x04]", ++ "nlri": { ++ "nlriType": "ipv4Prefix", ++ "protocolId": 7, ++ "identifier": 0, ++ "localNodeDescriptors": { ++ "asn": 65001, ++ "bgpRouterId": "2.2.2.2" ++ }, ++ "prefixDescriptors": { ++ "ipReachabilityInformation": "172.16.1.0/30", ++ "bgpRouteType": "internalBgp" ++ } ++ }, ++ "weight": 0, ++ "peerId": "10.255.2.1", ++ "path": "65001", ++ "origin": "incomplete" ++ } ++ ], ++ "[T][B][I0x0][N[c65001][q2.2.2.2]][P[p172.16.1.1/32]][br0x04]": [ ++ { ++ "valid": true, ++ "bestpath": true, ++ "selectionReason": "First path received", ++ "pathFrom": "external", ++ "nlriStr": ++ "[T][B][I0x0][N[c65001][q2.2.2.2]][P[p172.16.1.1/32]][br0x04]", ++ "nlri": { ++ "nlriType": "ipv4Prefix", ++ "protocolId": 7, ++ "identifier": 0, ++ "localNodeDescriptors": { ++ "asn": 65001, ++ "bgpRouterId": "2.2.2.2" ++ }, ++ "prefixDescriptors": { ++ "ipReachabilityInformation": "172.16.1.1/32", ++ "bgpRouteType": "internalBgp" ++ } ++ }, ++ "weight": 0, ++ "peerId": "10.255.2.1", ++ "path": "65001", ++ "origin": "incomplete" ++ } ++ ], ++ "[T][B][I0x0][N[c65001][q2.2.2.2]][P[p192.0.2.0/24]][br0x04]": [ ++ { ++ "valid": true, ++ "bestpath": true, ++ "selectionReason": "First path received", ++ "pathFrom": "external", ++ "nlriStr": ++ "[T][B][I0x0][N[c65001][q2.2.2.2]][P[p192.0.2.0/24]][br0x04]", ++ "nlri": { ++ "nlriType": "ipv4Prefix", ++ "protocolId": 7, ++ "identifier": 0, ++ "localNodeDescriptors": { ++ "asn": 65001, ++ "bgpRouterId": "2.2.2.2" ++ }, ++ "prefixDescriptors": { ++ "ipReachabilityInformation": "192.0.2.0/24", ++ "bgpRouteType": "internalBgp" ++ } ++ }, ++ "weight": 0, ++ "peerId": "10.255.2.1", ++ "path": "65001", ++ "origin": "incomplete" ++ } ++ ], ++ "[T][B][I0x0][N[c65002][q3.3.3.3]][P[p10.3.3.3/32]][br0x02]": [ ++ { ++ "valid": true, ++ "bestpath": true, ++ "selectionReason": "First path received", ++ "pathFrom": "external", ++ "nlriStr": ++ "[T][B][I0x0][N[c65002][q3.3.3.3]][P[p10.3.3.3/32]][br0x02]", ++ "nlri": { ++ "nlriType": "ipv4Prefix", ++ "protocolId": 7, ++ "identifier": 0, ++ "localNodeDescriptors": { ++ "asn": 65002, ++ "bgpRouterId": "3.3.3.3" ++ }, ++ "prefixDescriptors": { ++ "ipReachabilityInformation": "10.3.3.3/32", ++ "bgpRouteType": "attached" ++ } ++ }, ++ "weight": 0, ++ "peerId": "2001:db9:3::1", ++ "path": "65002", ++ "origin": "incomplete", ++ "nexthops": [ ++ { ++ "ip": "2001:db9:3::1", ++ "hostname": "r3", ++ "afi": "ipv6", ++ "scope": "global", ++ "linkLocalOnly": false, ++ "length": 16, ++ "used": true ++ } ++ ] ++ } ++ ], ++ "[T][B][I0x0][N[c65002][q3.3.3.3]][P[p2001:db9:1:124::/64]][br0x05]": [ ++ { ++ "valid": true, ++ "bestpath": true, ++ "selectionReason": "First path received", ++ "pathFrom": "external", ++ "nlriStr": ++ "[T][B][I0x0][N[c65002][q3.3.3.3]][P[p2001:db9:1:124::/64]][br0x05]", ++ "nlri": { ++ "nlriType": "ipv6Prefix", ++ "protocolId": 7, ++ "identifier": 0, ++ "localNodeDescriptors": { ++ "asn": 65002, ++ "bgpRouterId": "3.3.3.3" ++ }, ++ "prefixDescriptors": { ++ "ipReachabilityInformation": "2001:db9:1:124::/64", ++ "bgpRouteType": "redistributed" ++ } ++ }, ++ "weight": 0, ++ "peerId": "2001:db9:3::1", ++ "path": "65002", ++ "origin": "incomplete", ++ "nexthops": [ ++ { ++ "ip": "2001:db9:3::1", ++ "hostname": "r3", ++ "afi": "ipv6", ++ "scope": "global", ++ "linkLocalOnly": false, ++ "length": 16, ++ "used": true ++ } ++ ] ++ } ++ ], ++ "[T][B][I0x0][N[c65002][q3.3.3.3]][P[p2001:db8:1::/64]][br0x02]": [ ++ { ++ "valid": true, ++ "bestpath": true, ++ "selectionReason": "First path received", ++ "pathFrom": "external", ++ "nlriStr": ++ "[T][B][I0x0][N[c65002][q3.3.3.3]][P[p2001:db8:1::/64]][br0x02]", ++ "nlri": { ++ "nlriType": "ipv6Prefix", ++ "protocolId": 7, ++ "identifier": 0, ++ "localNodeDescriptors": { ++ "asn": 65002, ++ "bgpRouterId": "3.3.3.3" ++ }, ++ "prefixDescriptors": { ++ "ipReachabilityInformation": "2001:db8:1::/64", ++ "bgpRouteType": "attached" ++ } ++ }, ++ "weight": 0, ++ "peerId": "2001:db9:3::1", ++ "path": "65002", ++ "origin": "incomplete", ++ "nexthops": [ ++ { ++ "ip": "2001:db9:3::1", ++ "hostname": "r3", ++ "afi": "ipv6", ++ "scope": "global", ++ "linkLocalOnly": false, ++ "length": 16, ++ "used": true ++ } ++ ] ++ } ++ ], ++ "[T][B][I0x0][N[c65002][q3.3.3.3]][P[p2001:db8:1::1/128]][br0x01]": [ ++ { ++ "valid": true, ++ "bestpath": true, ++ "selectionReason": "First path received", ++ "pathFrom": "external", ++ "nlriStr": ++ "[T][B][I0x0][N[c65002][q3.3.3.3]][P[p2001:db8:1::1/128]][br0x01]", ++ "nlri": { ++ "nlriType": "ipv6Prefix", ++ "protocolId": 7, ++ "identifier": 0, ++ "localNodeDescriptors": { ++ "asn": 65002, ++ "bgpRouterId": "3.3.3.3" ++ }, ++ "prefixDescriptors": { ++ "ipReachabilityInformation": "2001:db8:1::1/128", ++ "bgpRouteType": "local" ++ } ++ }, ++ "weight": 0, ++ "peerId": "2001:db9:3::1", ++ "path": "65002", ++ "origin": "incomplete", ++ "nexthops": [ ++ { ++ "ip": "2001:db9:3::1", ++ "hostname": "r3", ++ "afi": "ipv6", ++ "scope": "global", ++ "linkLocalOnly": false, ++ "length": 16, ++ "used": true ++ } ++ ] ++ } ++ ], ++ "[T][B][I0x0][N[c65002][q3.3.3.3]][P[p2001:db9:3::/126]][br0x02]": [ ++ { ++ "valid": true, ++ "bestpath": true, ++ "selectionReason": "First path received", ++ "pathFrom": "external", ++ "nlriStr": ++ "[T][B][I0x0][N[c65002][q3.3.3.3]][P[p2001:db9:3::/126]][br0x02]", ++ "nlri": { ++ "nlriType": "ipv6Prefix", ++ "protocolId": 7, ++ "identifier": 0, ++ "localNodeDescriptors": { ++ "asn": 65002, ++ "bgpRouterId": "3.3.3.3" ++ }, ++ "prefixDescriptors": { ++ "ipReachabilityInformation": "2001:db9:3::/126", ++ "bgpRouteType": "attached" ++ } ++ }, ++ "weight": 0, ++ "peerId": "2001:db9:3::1", ++ "path": "65002", ++ "origin": "incomplete", ++ "nexthops": [ ++ { ++ "ip": "2001:db9:3::1", ++ "hostname": "r3", ++ "afi": "ipv6", ++ "scope": "global", ++ "linkLocalOnly": false, ++ "length": 16, ++ "used": true ++ } ++ ] ++ } ++ ], ++ "[T][B][I0x0][N[c65002][q3.3.3.3]][P[p2001:db9:3::1/128]][br0x01]": [ ++ { ++ "valid": true, ++ "bestpath": true, ++ "selectionReason": "First path received", ++ "pathFrom": "external", ++ "nlriStr": ++ "[T][B][I0x0][N[c65002][q3.3.3.3]][P[p2001:db9:3::1/128]][br0x01]", ++ "nlri": { ++ "nlriType": "ipv6Prefix", ++ "protocolId": 7, ++ "identifier": 0, ++ "localNodeDescriptors": { ++ "asn": 65002, ++ "bgpRouterId": "3.3.3.3" ++ }, ++ "prefixDescriptors": { ++ "ipReachabilityInformation": "2001:db9:3::1/128", ++ "bgpRouteType": "local" ++ } ++ }, ++ "weight": 0, ++ "peerId": "2001:db9:3::1", ++ "path": "65002", ++ "origin": "incomplete", ++ "nexthops": [ ++ { ++ "ip": "2001:db9:3::1", ++ "hostname": "r3", ++ "afi": "ipv6", ++ "scope": "global", ++ "linkLocalOnly": false, ++ "length": 16, ++ "used": true ++ } ++ ] ++ } ++ ], ++ "[T][B][I0x0][N[c65002][q3.3.3.3]][P[p2001:db9:4::/126]][br0x03]": [ ++ { ++ "valid": true, ++ "bestpath": true, ++ "selectionReason": "First path received", ++ "pathFrom": "external", ++ "nlriStr": ++ "[T][B][I0x0][N[c65002][q3.3.3.3]][P[p2001:db9:4::/126]][br0x03]", ++ "nlri": { ++ "nlriType": "ipv6Prefix", ++ "protocolId": 7, ++ "identifier": 0, ++ "localNodeDescriptors": { ++ "asn": 65002, ++ "bgpRouterId": "3.3.3.3" ++ }, ++ "prefixDescriptors": { ++ "ipReachabilityInformation": "2001:db9:4::/126", ++ "bgpRouteType": "externalBgp" ++ } ++ }, ++ "weight": 0, ++ "peerId": "2001:db9:3::1", ++ "path": "65002", ++ "origin": "incomplete", ++ "nexthops": [ ++ { ++ "ip": "2001:db9:3::1", ++ "hostname": "r3", ++ "afi": "ipv6", ++ "scope": "global", ++ "linkLocalOnly": false, ++ "length": 16, ++ "used": true ++ } ++ ] ++ } ++ ], ++ "[T][B][I0x0][N[c65002][q3.3.3.3]][P[p2001:db9:4::1/128]][br0x03]": [ ++ { ++ "valid": true, ++ "bestpath": true, ++ "selectionReason": "First path received", ++ "pathFrom": "external", ++ "nlriStr": ++ "[T][B][I0x0][N[c65002][q3.3.3.3]][P[p2001:db9:4::1/128]][br0x03]", ++ "nlri": { ++ "nlriType": "ipv6Prefix", ++ "protocolId": 7, ++ "identifier": 0, ++ "localNodeDescriptors": { ++ "asn": 65002, ++ "bgpRouterId": "3.3.3.3" ++ }, ++ "prefixDescriptors": { ++ "ipReachabilityInformation": "2001:db9:4::1/128", ++ "bgpRouteType": "externalBgp" ++ } ++ }, ++ "weight": 0, ++ "peerId": "2001:db9:3::1", ++ "path": "65002", ++ "origin": "incomplete", ++ "nexthops": [ ++ { ++ "ip": "2001:db9:3::1", ++ "hostname": "r3", ++ "afi": "ipv6", ++ "scope": "global", ++ "linkLocalOnly": false, ++ "length": 16, ++ "used": true ++ } ++ ] ++ } ++ ], ++ "[T][B][I0x0][N[c65002][q3.3.3.3]][P[p2001:db8:1::/64]][br0x03]": [ ++ { ++ "valid": true, ++ "bestpath": true, ++ "selectionReason": "First path received", ++ "pathFrom": "external", ++ "nlriStr": ++ "[T][B][I0x0][N[c65002][q3.3.3.3]][P[p2001:db8:1::/64]][br0x03]", ++ "nlri": { ++ "nlriType": "ipv6Prefix", ++ "protocolId": 7, ++ "identifier": 0, ++ "localNodeDescriptors": { ++ "asn": 65002, ++ "bgpRouterId": "3.3.3.3" ++ }, ++ "prefixDescriptors": { ++ "ipReachabilityInformation": "2001:db8:1::/64", ++ "bgpRouteType": "externalBgp" ++ } ++ }, ++ "weight": 0, ++ "peerId": "2001:db9:3::1", ++ "path": "65002", ++ "origin": "incomplete", ++ "nexthops": [ ++ { ++ "ip": "2001:db9:3::1", ++ "hostname": "r3", ++ "afi": "ipv6", ++ "scope": "global", ++ "linkLocalOnly": false, ++ "length": 16, ++ "used": true ++ } ++ ] ++ } ++ ], ++ "[T][B][I0x0][N[c65002][q3.3.3.3]][P[p2001:db8:1::2/128]][br0x03]": [ ++ { ++ "valid": true, ++ "bestpath": true, ++ "selectionReason": "First path received", ++ "pathFrom": "external", ++ "nlriStr": ++ "[T][B][I0x0][N[c65002][q3.3.3.3]][P[p2001:db8:1::2/128]][br0x03]", ++ "nlri": { ++ "nlriType": "ipv6Prefix", ++ "protocolId": 7, ++ "identifier": 0, ++ "localNodeDescriptors": { ++ "asn": 65002, ++ "bgpRouterId": "3.3.3.3" ++ }, ++ "prefixDescriptors": { ++ "ipReachabilityInformation": "2001:db8:1::2/128", ++ "bgpRouteType": "externalBgp" ++ } ++ }, ++ "weight": 0, ++ "peerId": "2001:db9:3::1", ++ "path": "65002", ++ "origin": "incomplete", ++ "nexthops": [ ++ { ++ "ip": "2001:db9:3::1", ++ "hostname": "r3", ++ "afi": "ipv6", ++ "scope": "global", ++ "linkLocalOnly": false, ++ "length": 16, ++ "used": true ++ } ++ ] ++ } ++ ], ++ "[T][B][I0x0][N[c65003][q4.4.4.4]][P[p10.4.4.4/32]][br0x02]": [ ++ { ++ "valid": true, ++ "bestpath": true, ++ "selectionReason": "First path received", ++ "pathFrom": "external", ++ "nlriStr": ++ "[T][B][I0x0][N[c65003][q4.4.4.4]][P[p10.4.4.4/32]][br0x02]", ++ "nlri": { ++ "nlriType": "ipv4Prefix", ++ "protocolId": 7, ++ "identifier": 0, ++ "localNodeDescriptors": { ++ "asn": 65003, ++ "bgpRouterId": "4.4.4.4" ++ }, ++ "prefixDescriptors": { ++ "ipReachabilityInformation": "10.4.4.4/32", ++ "bgpRouteType": "attached" ++ } ++ }, ++ "weight": 0, ++ "peerId": "2001:db9:4::1", ++ "path": "65003", ++ "origin": "incomplete", ++ "nexthops": [ ++ { ++ "ip": "2001:db9:4::1", ++ "hostname": "r4", ++ "afi": "ipv6", ++ "scope": "global", ++ "linkLocalOnly": false, ++ "length": 16, ++ "used": true ++ } ++ ] ++ } ++ ], ++ "[T][B][I0x0][N[c65003][q4.4.4.4]][P[p2001:db9:4::/126]][br0x02]": [ ++ { ++ "valid": true, ++ "bestpath": true, ++ "selectionReason": "First path received", ++ "pathFrom": "external", ++ "nlriStr": ++ "[T][B][I0x0][N[c65003][q4.4.4.4]][P[p2001:db9:4::/126]][br0x02]", ++ "nlri": { ++ "nlriType": "ipv6Prefix", ++ "protocolId": 7, ++ "identifier": 0, ++ "localNodeDescriptors": { ++ "asn": 65003, ++ "bgpRouterId": "4.4.4.4" ++ }, ++ "prefixDescriptors": { ++ "ipReachabilityInformation": "2001:db9:4::/126", ++ "bgpRouteType": "attached" ++ } ++ }, ++ "weight": 0, ++ "peerId": "2001:db9:4::1", ++ "path": "65003", ++ "origin": "incomplete", ++ "nexthops": [ ++ { ++ "ip": "2001:db9:4::1", ++ "hostname": "r4", ++ "afi": "ipv6", ++ "scope": "global", ++ "linkLocalOnly": false, ++ "length": 16, ++ "used": true ++ } ++ ] ++ } ++ ], ++ "[T][B][I0x0][N[c65003][q4.4.4.4]][P[p2001:db9:4::1/128]][br0x01]": [ ++ { ++ "valid": true, ++ "bestpath": true, ++ "selectionReason": "First path received", ++ "pathFrom": "external", ++ "nlriStr": ++ "[T][B][I0x0][N[c65003][q4.4.4.4]][P[p2001:db9:4::1/128]][br0x01]", ++ "nlri": { ++ "nlriType": "ipv6Prefix", ++ "protocolId": 7, ++ "identifier": 0, ++ "localNodeDescriptors": { ++ "asn": 65003, ++ "bgpRouterId": "4.4.4.4" ++ }, ++ "prefixDescriptors": { ++ "ipReachabilityInformation": "2001:db9:4::1/128", ++ "bgpRouteType": "local" ++ } ++ }, ++ "weight": 0, ++ "peerId": "2001:db9:4::1", ++ "path": "65003", ++ "origin": "incomplete", ++ "nexthops": [ ++ { ++ "ip": "2001:db9:4::1", ++ "hostname": "r4", ++ "afi": "ipv6", ++ "scope": "global", ++ "linkLocalOnly": false, ++ "length": 16, ++ "used": true ++ } ++ ] ++ } ++ ], ++ "[T][B][I0x0][N[c65003][q4.4.4.4]][P[p2001:db8:1::/64]][br0x02]": [ ++ { ++ "valid": true, ++ "bestpath": true, ++ "selectionReason": "First path received", ++ "pathFrom": "external", ++ "nlriStr": ++ "[T][B][I0x0][N[c65003][q4.4.4.4]][P[p2001:db8:1::/64]][br0x02]", ++ "nlri": { ++ "nlriType": "ipv6Prefix", ++ "protocolId": 7, ++ "identifier": 0, ++ "localNodeDescriptors": { ++ "asn": 65003, ++ "bgpRouterId": "4.4.4.4" ++ }, ++ "prefixDescriptors": { ++ "ipReachabilityInformation": "2001:db8:1::/64", ++ "bgpRouteType": "attached" ++ } ++ }, ++ "weight": 0, ++ "peerId": "2001:db9:4::1", ++ "path": "65003", ++ "origin": "incomplete", ++ "nexthops": [ ++ { ++ "ip": "2001:db9:4::1", ++ "hostname": "r4", ++ "afi": "ipv6", ++ "scope": "global", ++ "linkLocalOnly": false, ++ "length": 16, ++ "used": true ++ } ++ ] ++ } ++ ], ++ "[T][B][I0x0][N[c65003][q4.4.4.4]][P[p2001:db8:1::2/128]][br0x01]": [ ++ { ++ "valid": true, ++ "bestpath": true, ++ "selectionReason": "First path received", ++ "pathFrom": "external", ++ "nlriStr": ++ "[T][B][I0x0][N[c65003][q4.4.4.4]][P[p2001:db8:1::2/128]][br0x01]", ++ "nlri": { ++ "nlriType": "ipv6Prefix", ++ "protocolId": 7, ++ "identifier": 0, ++ "localNodeDescriptors": { ++ "asn": 65003, ++ "bgpRouterId": "4.4.4.4" ++ }, ++ "prefixDescriptors": { ++ "ipReachabilityInformation": "2001:db8:1::2/128", ++ "bgpRouteType": "local" ++ } ++ }, ++ "weight": 0, ++ "peerId": "2001:db9:4::1", ++ "path": "65003", ++ "origin": "incomplete", ++ "nexthops": [ ++ { ++ "ip": "2001:db9:4::1", ++ "hostname": "r4", ++ "afi": "ipv6", ++ "scope": "global", ++ "linkLocalOnly": false, ++ "length": 16, ++ "used": true ++ } ++ ] ++ } ++ ], ++ "[T][B][I0x0][N[c65003][q4.4.4.4]][P[p2001:db9:1:124::/64]][br0x03]": [ ++ { ++ "valid": true, ++ "bestpath": true, ++ "selectionReason": "First path received", ++ "pathFrom": "external", ++ "nlriStr": ++ "[T][B][I0x0][N[c65003][q4.4.4.4]][P[p2001:db9:1:124::/64]][br0x03]", ++ "nlri": { ++ "nlriType": "ipv6Prefix", ++ "protocolId": 7, ++ "identifier": 0, ++ "localNodeDescriptors": { ++ "asn": 65003, ++ "bgpRouterId": "4.4.4.4" ++ }, ++ "prefixDescriptors": { ++ "ipReachabilityInformation": "2001:db9:1:124::/64", ++ "bgpRouteType": "externalBgp" ++ } ++ }, ++ "weight": 0, ++ "peerId": "2001:db9:4::1", ++ "path": "65003", ++ "origin": "incomplete", ++ "nexthops": [ ++ { ++ "ip": "2001:db9:4::1", ++ "hostname": "r4", ++ "afi": "ipv6", ++ "scope": "global", ++ "linkLocalOnly": false, ++ "length": 16, ++ "used": true ++ } ++ ] ++ } ++ ], ++ "[T][B][I0x0][N[c65003][q4.4.4.4]][P[p2001:db9:3::/126]][br0x03]": [ ++ { ++ "valid": true, ++ "bestpath": true, ++ "selectionReason": "First path received", ++ "pathFrom": "external", ++ "nlriStr": ++ "[T][B][I0x0][N[c65003][q4.4.4.4]][P[p2001:db9:3::/126]][br0x03]", ++ "nlri": { ++ "nlriType": "ipv6Prefix", ++ "protocolId": 7, ++ "identifier": 0, ++ "localNodeDescriptors": { ++ "asn": 65003, ++ "bgpRouterId": "4.4.4.4" ++ }, ++ "prefixDescriptors": { ++ "ipReachabilityInformation": "2001:db9:3::/126", ++ "bgpRouteType": "externalBgp" ++ } ++ }, ++ "weight": 0, ++ "peerId": "2001:db9:4::1", ++ "path": "65003", ++ "origin": "incomplete", ++ "nexthops": [ ++ { ++ "ip": "2001:db9:4::1", ++ "hostname": "r4", ++ "afi": "ipv6", ++ "scope": "global", ++ "linkLocalOnly": false, ++ "length": 16, ++ "used": true ++ } ++ ] ++ } ++ ], ++ "[T][B][I0x0][N[c65003][q4.4.4.4]][P[p2001:db9:3::1/128]][br0x03]": [ ++ { ++ "valid": true, ++ "bestpath": true, ++ "selectionReason": "First path received", ++ "pathFrom": "external", ++ "nlriStr": ++ "[T][B][I0x0][N[c65003][q4.4.4.4]][P[p2001:db9:3::1/128]][br0x03]", ++ "nlri": { ++ "nlriType": "ipv6Prefix", ++ "protocolId": 7, ++ "identifier": 0, ++ "localNodeDescriptors": { ++ "asn": 65003, ++ "bgpRouterId": "4.4.4.4" ++ }, ++ "prefixDescriptors": { ++ "ipReachabilityInformation": "2001:db9:3::1/128", ++ "bgpRouteType": "externalBgp" ++ } ++ }, ++ "weight": 0, ++ "peerId": "2001:db9:4::1", ++ "path": "65003", ++ "origin": "incomplete", ++ "nexthops": [ ++ { ++ "ip": "2001:db9:4::1", ++ "hostname": "r4", ++ "afi": "ipv6", ++ "scope": "global", ++ "linkLocalOnly": false, ++ "length": 16, ++ "used": true ++ } ++ ] ++ } ++ ], ++ "[T][B][I0x0][N[c65003][q4.4.4.4]][P[p2001:db8:1::/64]][br0x03]": [ ++ { ++ "valid": true, ++ "bestpath": true, ++ "selectionReason": "First path received", ++ "pathFrom": "external", ++ "nlriStr": ++ "[T][B][I0x0][N[c65003][q4.4.4.4]][P[p2001:db8:1::/64]][br0x03]", ++ "nlri": { ++ "nlriType": "ipv6Prefix", ++ "protocolId": 7, ++ "identifier": 0, ++ "localNodeDescriptors": { ++ "asn": 65003, ++ "bgpRouterId": "4.4.4.4" ++ }, ++ "prefixDescriptors": { ++ "ipReachabilityInformation": "2001:db8:1::/64", ++ "bgpRouteType": "externalBgp" ++ } ++ }, ++ "weight": 0, ++ "peerId": "2001:db9:4::1", ++ "path": "65003", ++ "origin": "incomplete", ++ "nexthops": [ ++ { ++ "ip": "2001:db9:4::1", ++ "hostname": "r4", ++ "afi": "ipv6", ++ "scope": "global", ++ "linkLocalOnly": false, ++ "length": 16, ++ "used": true ++ } ++ ] ++ } ++ ], ++ "[T][B][I0x0][N[c65003][q4.4.4.4]][P[p2001:db8:1::1/128]][br0x03]": [ ++ { ++ "valid": true, ++ "bestpath": true, ++ "selectionReason": "First path received", ++ "pathFrom": "external", ++ "nlriStr": ++ "[T][B][I0x0][N[c65003][q4.4.4.4]][P[p2001:db8:1::1/128]][br0x03]", ++ "nlri": { ++ "nlriType": "ipv6Prefix", ++ "protocolId": 7, ++ "identifier": 0, ++ "localNodeDescriptors": { ++ "asn": 65003, ++ "bgpRouterId": "4.4.4.4" ++ }, ++ "prefixDescriptors": { ++ "ipReachabilityInformation": "2001:db8:1::1/128", ++ "bgpRouteType": "externalBgp" ++ } ++ }, ++ "weight": 0, ++ "peerId": "2001:db9:4::1", ++ "path": "65003", ++ "origin": "incomplete", ++ "nexthops": [ ++ { ++ "ip": "2001:db9:4::1", ++ "hostname": "r4", ++ "afi": "ipv6", ++ "scope": "global", ++ "linkLocalOnly": false, ++ "length": 16, ++ "used": true ++ } ++ ] ++ } ++ ], ++ "[E][B][I0x0][N[c65001][q1.1.1.1]][R[c65000][q192.0.2.254]][L[l2/0][i10.255.1.1][n10.255.1.2]]": [ ++ { ++ "valid": true, ++ "bestpath": true, ++ "selectionReason": "First path received", ++ "pathFrom": "external", ++ "nlriStr": ++ "[E][B][I0x0][N[c65001][q1.1.1.1]][R[c65000][q192.0.2.254]][L[l2/0][i10.255.1.1][n10.255.1.2]]", ++ "nlri": { ++ "nlriType": "link", ++ "protocolId": 7, ++ "identifier": 0, ++ "localNodeDescriptors": { ++ "asn": 65001, ++ "bgpRouterId": "1.1.1.1" ++ }, ++ "remoteNodeDescriptors": { ++ "asn": 65000, ++ "bgpRouterId": "192.0.2.254" ++ }, ++ "linkDescriptors": { ++ "linkLocalId": 2, ++ "linkRemoteId": 0, ++ "ipv4InterfaceAddress": "10.255.1.1", ++ "ipv4NeighborAddress": "10.255.1.2" ++ } ++ }, ++ "weight": 0, ++ "peerId": "10.255.1.1", ++ "path": "65001", ++ "origin": "incomplete" ++ } ++ ], ++ "[E][B][I0x0][N[c65001][q1.1.1.1]][R[c65001][q2.2.2.2]][L[l3/0][i172.16.1.1][n172.16.1.2]]": [ ++ { ++ "valid": true, ++ "bestpath": true, ++ "selectionReason": "First path received", ++ "pathFrom": "external", ++ "nlriStr": ++ "[E][B][I0x0][N[c65001][q1.1.1.1]][R[c65001][q2.2.2.2]][L[l3/0][i172.16.1.1][n172.16.1.2]]", ++ "nlri": { ++ "nlriType": "link", ++ "protocolId": 7, ++ "identifier": 0, ++ "localNodeDescriptors": { ++ "asn": 65001, ++ "bgpRouterId": "1.1.1.1" ++ }, ++ "remoteNodeDescriptors": { ++ "asn": 65001, ++ "bgpRouterId": "2.2.2.2" ++ }, ++ "linkDescriptors": { ++ "linkLocalId": 3, ++ "linkRemoteId": 0, ++ "ipv4InterfaceAddress": "172.16.1.1", ++ "ipv4NeighborAddress": "172.16.1.2" ++ } ++ }, ++ "weight": 0, ++ "peerId": "10.255.1.1", ++ "path": "65001", ++ "origin": "incomplete" ++ } ++ ], ++ "[E][B][I0x0][N[c65001][q2.2.2.2]][R[c65000][q192.0.2.254]][L[l2/0][i10.255.2.1][n10.255.2.2]]": [ ++ { ++ "valid": true, ++ "bestpath": true, ++ "selectionReason": "First path received", ++ "pathFrom": "external", ++ "nlriStr": ++ "[E][B][I0x0][N[c65001][q2.2.2.2]][R[c65000][q192.0.2.254]][L[l2/0][i10.255.2.1][n10.255.2.2]]", ++ "nlri": { ++ "nlriType": "link", ++ "protocolId": 7, ++ "identifier": 0, ++ "localNodeDescriptors": { ++ "asn": 65001, ++ "bgpRouterId": "2.2.2.2" ++ }, ++ "remoteNodeDescriptors": { ++ "asn": 65000, ++ "bgpRouterId": "192.0.2.254" ++ }, ++ "linkDescriptors": { ++ "linkLocalId": 2, ++ "linkRemoteId": 0, ++ "ipv4InterfaceAddress": "10.255.2.1", ++ "ipv4NeighborAddress": "10.255.2.2" ++ } ++ }, ++ "weight": 0, ++ "peerId": "10.255.2.1", ++ "path": "65001", ++ "origin": "incomplete" ++ } ++ ], ++ "[E][B][I0x0][N[c65001][q2.2.2.2]][R[c65001][q1.1.1.1]][L[l3/0][i172.16.1.2][n172.16.1.1]]": [ ++ { ++ "valid": true, ++ "bestpath": true, ++ "selectionReason": "First path received", ++ "pathFrom": "external", ++ "nlriStr": ++ "[E][B][I0x0][N[c65001][q2.2.2.2]][R[c65001][q1.1.1.1]][L[l3/0][i172.16.1.2][n172.16.1.1]]", ++ "nlri": { ++ "nlriType": "link", ++ "protocolId": 7, ++ "identifier": 0, ++ "localNodeDescriptors": { ++ "asn": 65001, ++ "bgpRouterId": "2.2.2.2" ++ }, ++ "remoteNodeDescriptors": { ++ "asn": 65001, ++ "bgpRouterId": "1.1.1.1" ++ }, ++ "linkDescriptors": { ++ "linkLocalId": 3, ++ "linkRemoteId": 0, ++ "ipv4InterfaceAddress": "172.16.1.2", ++ "ipv4NeighborAddress": "172.16.1.1" ++ } ++ }, ++ "weight": 0, ++ "peerId": "10.255.2.1", ++ "path": "65001", ++ "origin": "incomplete" ++ } ++ ], ++ "[E][B][I0x0][N[c65002][q3.3.3.3]][R[c65000][q192.0.2.254]][L[l2/0][i2001:db9:3::1][n2001:db9:3::2]]": ++ [ ++ { ++ "valid": true, ++ "bestpath": true, ++ "selectionReason": "First path received", ++ "pathFrom": "external", ++ "nlriStr": ++ "[E][B][I0x0][N[c65002][q3.3.3.3]][R[c65000][q192.0.2.254]][L[l2/0][i2001:db9:3::1][n2001:db9:3::2]]", ++ "nlri": { ++ "nlriType": "link", ++ "protocolId": 7, ++ "identifier": 0, ++ "localNodeDescriptors": { ++ "asn": 65002, ++ "bgpRouterId": "3.3.3.3" ++ }, ++ "remoteNodeDescriptors": { ++ "asn": 65000, ++ "bgpRouterId": "192.0.2.254" ++ }, ++ "linkDescriptors": { ++ "linkLocalId": 2, ++ "linkRemoteId": 0, ++ "ipv6InterfaceAddress": "2001:db9:3::1", ++ "ipv6NeighborAddress": "2001:db9:3::2" ++ } ++ }, ++ "weight": 0, ++ "peerId": "2001:db9:3::1", ++ "path": "65002", ++ "origin": "incomplete", ++ "nexthops": [ ++ { ++ "ip": "2001:db9:3::1", ++ "hostname": "r3", ++ "afi": "ipv6", ++ "scope": "global", ++ "linkLocalOnly": false, ++ "length": 16, ++ "used": true ++ } ++ ] ++ } ++ ], ++ "[E][B][I0x0][N[c65002][q3.3.3.3]][R[c65003][q4.4.4.4]][L[l2/0][i2001:db8:1::1][n2001:db8:1::2]]": [ ++ { ++ "valid": true, ++ "bestpath": true, ++ "selectionReason": "First path received", ++ "pathFrom": "external", ++ "nlriStr": ++ "[E][B][I0x0][N[c65002][q3.3.3.3]][R[c65003][q4.4.4.4]][L[l2/0][i2001:db8:1::1][n2001:db8:1::2]]", ++ "nlri": { ++ "nlriType": "link", ++ "protocolId": 7, ++ "identifier": 0, ++ "localNodeDescriptors": { ++ "asn": 65002, ++ "bgpRouterId": "3.3.3.3" ++ }, ++ "remoteNodeDescriptors": { ++ "asn": 65003, ++ "bgpRouterId": "4.4.4.4" ++ }, ++ "linkDescriptors": { ++ "linkLocalId": 2, ++ "linkRemoteId": 0, ++ "ipv6InterfaceAddress": "2001:db8:1::1", ++ "ipv6NeighborAddress": "2001:db8:1::2" ++ } ++ }, ++ "weight": 0, ++ "peerId": "2001:db9:3::1", ++ "path": "65002", ++ "origin": "incomplete", ++ "nexthops": [ ++ { ++ "ip": "2001:db9:3::1", ++ "hostname": "r3", ++ "afi": "ipv6", ++ "scope": "global", ++ "linkLocalOnly": false, ++ "length": 16, ++ "used": true ++ } ++ ] ++ } ++ ], ++ "[E][B][I0x0][N[c65003][q4.4.4.4]][R[c65000][q192.0.2.254]][L[l2/0][i2001:db9:4::1][n2001:db9:4::2]]": ++ [ ++ { ++ "valid": true, ++ "bestpath": true, ++ "selectionReason": "First path received", ++ "pathFrom": "external", ++ "nlriStr": ++ "[E][B][I0x0][N[c65003][q4.4.4.4]][R[c65000][q192.0.2.254]][L[l2/0][i2001:db9:4::1][n2001:db9:4::2]]", ++ "nlri": { ++ "nlriType": "link", ++ "protocolId": 7, ++ "identifier": 0, ++ "localNodeDescriptors": { ++ "asn": 65003, ++ "bgpRouterId": "4.4.4.4" ++ }, ++ "remoteNodeDescriptors": { ++ "asn": 65000, ++ "bgpRouterId": "192.0.2.254" ++ }, ++ "linkDescriptors": { ++ "linkLocalId": 2, ++ "linkRemoteId": 0, ++ "ipv6InterfaceAddress": "2001:db9:4::1", ++ "ipv6NeighborAddress": "2001:db9:4::2" ++ } ++ }, ++ "weight": 0, ++ "peerId": "2001:db9:4::1", ++ "path": "65003", ++ "origin": "incomplete", ++ "nexthops": [ ++ { ++ "ip": "2001:db9:4::1", ++ "hostname": "r4", ++ "afi": "ipv6", ++ "scope": "global", ++ "linkLocalOnly": false, ++ "length": 16, ++ "used": true ++ } ++ ] ++ } ++ ], ++ "[E][B][I0x0][N[c65003][q4.4.4.4]][R[c65002][q3.3.3.3]][L[l2/0][i2001:db8:1::2][n2001:db8:1::1]]": [ ++ { ++ "valid": true, ++ "bestpath": true, ++ "selectionReason": "First path received", ++ "pathFrom": "external", ++ "nlriStr": ++ "[E][B][I0x0][N[c65003][q4.4.4.4]][R[c65002][q3.3.3.3]][L[l2/0][i2001:db8:1::2][n2001:db8:1::1]]", ++ "nlri": { ++ "nlriType": "link", ++ "protocolId": 7, ++ "identifier": 0, ++ "localNodeDescriptors": { ++ "asn": 65003, ++ "bgpRouterId": "4.4.4.4" ++ }, ++ "remoteNodeDescriptors": { ++ "asn": 65002, ++ "bgpRouterId": "3.3.3.3" ++ }, ++ "linkDescriptors": { ++ "linkLocalId": 2, ++ "linkRemoteId": 0, ++ "ipv6InterfaceAddress": "2001:db8:1::2", ++ "ipv6NeighborAddress": "2001:db8:1::1" ++ } ++ }, ++ "weight": 0, ++ "peerId": "2001:db9:4::1", ++ "path": "65003", ++ "origin": "incomplete", ++ "nexthops": [ ++ { ++ "ip": "2001:db9:4::1", ++ "hostname": "r4", ++ "afi": "ipv6", ++ "scope": "global", ++ "linkLocalOnly": false, ++ "length": 16, ++ "used": true ++ } ++ ] ++ } ++ ] ++ }, ++ "totalRoutes": 54, ++ "totalPaths": 54 ++} +diff --git a/tests/topotests/bgp_link_state_bgp_fabric/rr/frr.conf b/tests/topotests/bgp_link_state_bgp_fabric/rr/frr.conf +new file mode 100644 +index 0000000000..21754424a0 +--- /dev/null ++++ b/tests/topotests/bgp_link_state_bgp_fabric/rr/frr.conf +@@ -0,0 +1,53 @@ ++! ++hostname rr ++! ++! BGP-LS Speaker (Route Reflector) ++! Collects BGP-LS NLRIs from all routers ++! ++! debug bgp updates ++! debug bgp link-state ++! debug bgp zebra ++! debug bgp neighbor-events ++! ++interface rr-eth0 ++ description Point-to-point link to r1 ++ ip address 10.255.1.2/30 ++! ++interface rr-eth1 ++ description Point-to-point link to r2 ++ ip address 10.255.2.2/30 ++! ++interface rr-eth2 ++ description Point-to-point link to r3 ++ ipv6 address 2001:db9:3::2/126 ++! ++interface rr-eth3 ++ description Point-to-point link to r4 ++ ipv6 address 2001:db9:4::2/126 ++! ++route-map BLOCK-ALL deny 10 ++! ++router bgp 65000 ++ bgp router-id 192.0.2.254 ++ no bgp ebgp-requires-policy ++ no bgp default ipv4-unicast ++ ! ++ ! Neighbors for BGP-LS collection ++ neighbor 10.255.1.1 remote-as 65001 ++ neighbor 10.255.2.1 remote-as 65001 ++ neighbor 2001:db9:3::1 remote-as 65002 ++ neighbor 2001:db9:4::1 remote-as 65003 ++ ! ++ ! Enable Link-State address family ++ address-family link-state link-state ++ neighbor 10.255.1.1 activate ++ neighbor 10.255.1.1 route-map BLOCK-ALL out ++ neighbor 10.255.2.1 activate ++ neighbor 10.255.2.1 route-map BLOCK-ALL out ++ neighbor 2001:db9:3::1 activate ++ neighbor 2001:db9:3::1 route-map BLOCK-ALL out ++ neighbor 2001:db9:4::1 activate ++ neighbor 2001:db9:4::1 route-map BLOCK-ALL out ++ exit-address-family ++! ++end +diff --git a/tests/topotests/bgp_link_state_bgp_fabric/test_bgp_link_state_bgp_fabric.py b/tests/topotests/bgp_link_state_bgp_fabric/test_bgp_link_state_bgp_fabric.py +new file mode 100644 +index 0000000000..7cdb5852b5 +--- /dev/null ++++ b/tests/topotests/bgp_link_state_bgp_fabric/test_bgp_link_state_bgp_fabric.py +@@ -0,0 +1,1180 @@ ++#!/usr/bin/env python ++# SPDX-License-Identifier: ISC ++# ++# Copyright (c) 2026 by Carmine Scarpitta ++# ++ ++"""Topotest for BGP-LS export in a BGP-only fabric scenario. ++ ++Reference: draft-ietf-idr-bgp-ls-bgp-only-fabric-04. ++ ++Topology diagram: ++ ++ +--------+ ++ | rr | ++ | AS65000| ++ +---+----+ ++ | ++ +------------------+------------------+------------------+ ++ | | | | ++ +----+----+ +----+----+ +----+----+ +----+----+ ++ | r1 |========| r2 | | r3 |========| r4 | ++ | AS65001 | IPv4 | AS65001 | | AS65002 | IPv6 | AS65003 | ++ +---------+ iBGP +---------+ +---------+ eBGP +---------+ ++ ++Topology summary: ++- rr (AS65000) acts as the BGP-LS collector/speaker. ++- r1/r2 form an IPv4 iBGP session in AS65001. ++- r3/r4 form an IPv6 eBGP session (AS65002 <-> AS65003). ++- All routers peer with rr in address-family link-state link-state. ++""" ++ ++import os ++import sys ++import json ++import copy ++import re ++import pytest ++import functools ++ ++CWD = os.path.dirname(os.path.realpath(__file__)) ++sys.path.append(os.path.join(CWD, "../")) ++ ++# pylint: disable=C0413 ++from lib import topotest ++from lib.topogen import Topogen, TopoRouter, get_topogen ++from lib.topolog import logger ++ ++pytestmark = [pytest.mark.bgpd] ++ ++ ++def _normalize_bgp_ls_link_ifindex(data): ++ """ ++ Normalize dynamic link-local ifindex values in BGP-LS JSON output. ++ ++ Link NLRI keys and nlriStr contain local link-id derived from interface ifindex, ++ which can vary between test runs. Replace only that volatile value so JSON ++ comparisons remain deterministic while still validating all other fields. ++ """ ++ normalized = copy.deepcopy(data) ++ routes = normalized.get("routes") ++ if not isinstance(routes, dict): ++ return normalized ++ ++ new_routes = {} ++ for nlri_key, route_list in routes.items(): ++ normalized_key = re.sub(r"\[L\[l\d+/", "[L[l<ifindex>/", nlri_key) ++ new_route_list = [] ++ ++ for route in route_list: ++ route_copy = copy.deepcopy(route) ++ nlri = route_copy.get("nlri", {}) ++ ++ if nlri.get("nlriType") == "link": ++ if "nlriStr" in route_copy: ++ route_copy["nlriStr"] = re.sub( ++ r"\[L\[l\d+/", "[L[l<ifindex>/", route_copy["nlriStr"] ++ ) ++ ++ link_desc = nlri.get("linkDescriptors", {}) ++ if "linkLocalId" in link_desc: ++ link_desc["linkLocalId"] = "<ifindex>" ++ ++ new_route_list.append(route_copy) ++ ++ new_routes[normalized_key] = new_route_list ++ ++ normalized["routes"] = new_routes ++ return normalized ++ ++ ++def bgp_ls_router_json_cmp(router, expected): ++ """Compare BGP-LS JSON while ignoring dynamic link-local ifindex values.""" ++ output = router.vtysh_cmd("show bgp link-state link-state json") ++ actual = json.loads(output) ++ ++ actual_norm = _normalize_bgp_ls_link_ifindex(actual) ++ expected_norm = _normalize_bgp_ls_link_ifindex(expected) ++ return topotest.json_cmp(actual_norm, expected_norm) ++ ++ ++def count_node_nlris(data, bgp_router_id, asn=None): ++ """ ++ Count Node NLRIs for a specific BGP router ++ ++ Args: ++ data: BGP-LS JSON data (dict with 'routes' key) ++ bgp_router_id: BGP Router-ID to filter by ++ asn: Optional ASN to filter by ++ ++ Returns: ++ List of matching Node NLRIs ++ """ ++ # Extract all route objects from the routes dictionary ++ all_routes = [] ++ if "routes" in data: ++ for nlri_key, route_list in data["routes"].items(): ++ all_routes.extend(route_list) ++ nodes = [r for r in all_routes if r.get("nlri", {}).get("nlriType") == "node" and ++ r.get("nlri", {}).get("localNodeDescriptors", {}).get("bgpRouterId") == bgp_router_id] ++ if asn is not None: ++ nodes = [r for r in nodes if r.get("nlri", {}).get("localNodeDescriptors", {}).get("asn") == asn] ++ return nodes ++ ++ ++def count_link_nlris(data, local_bgp_router_id, remote_bgp_router_id=None): ++ """ ++ Count Link NLRIs for a specific local BGP router ++ ++ Args: ++ data: BGP-LS JSON data (dict with 'routes' key) ++ local_bgp_router_id: Local BGP Router-ID to filter by ++ remote_bgp_router_id: Optional remote BGP Router-ID to filter by ++ ++ Returns: ++ List of matching Link NLRIs ++ """ ++ # Extract all route objects from the routes dictionary ++ all_routes = [] ++ if "routes" in data: ++ for nlri_key, route_list in data["routes"].items(): ++ all_routes.extend(route_list) ++ ++ links = [r for r in all_routes if r.get("nlri", {}).get("nlriType") == "link" and ++ r.get("nlri", {}).get("localNodeDescriptors", {}).get("bgpRouterId") == local_bgp_router_id] ++ ++ if remote_bgp_router_id is not None: ++ links = [r for r in links if r.get("nlri", {}).get("remoteNodeDescriptors", {}).get("bgpRouterId") == remote_bgp_router_id] ++ ++ return links ++ ++ ++def count_prefix_nlris(data, bgp_router_id, prefix=None): ++ """ ++ Count Prefix NLRIs for a specific BGP router ++ ++ Args: ++ data: BGP-LS JSON data (dict with 'routes' key) ++ bgp_router_id: BGP Router-ID to filter by ++ prefix: Optional specific prefix to filter by (e.g., "10.1.1.1/32") ++ ++ Returns: ++ List of matching Prefix NLRIs ++ """ ++ # Extract all route objects from the routes dictionary ++ all_routes = [] ++ if "routes" in data: ++ for nlri_key, route_list in data["routes"].items(): ++ all_routes.extend(route_list) ++ ++ prefixes = [r for r in all_routes if r.get("nlri", {}).get("nlriType") in ["ipv4Prefix", "ipv6Prefix"] and ++ r.get("nlri", {}).get("localNodeDescriptors", {}).get("bgpRouterId") == bgp_router_id] ++ ++ if prefix is not None: ++ prefixes = [r for r in prefixes if r.get("nlri", {}).get("prefixDescriptors", {}).get("ipReachabilityInformation") == prefix] ++ ++ return prefixes ++ ++ ++def count_all_nlris_by_type(data): ++ """ ++ Count all NLRIs by type across all routers ++ ++ Args: ++ data: BGP-LS JSON data (dict with 'routes' key) ++ ++ Returns: ++ Dictionary with counts: { ++ 'nodes': int, ++ 'links': int, ++ 'prefixes': int, ++ 'total': int ++ } ++ """ ++ # Extract all route objects from the routes dictionary ++ all_routes = [] ++ if "routes" in data: ++ for nlri_key, route_list in data["routes"].items(): ++ all_routes.extend(route_list) ++ ++ nodes = [r for r in all_routes if r.get("nlri", {}).get("nlriType") == "node"] ++ links = [r for r in all_routes if r.get("nlri", {}).get("nlriType") == "link"] ++ prefixes = [r for r in all_routes if r.get("nlri", {}).get("nlriType") in ["ipv4Prefix", "ipv6Prefix"]] ++ ++ return { ++ 'nodes': len(nodes), ++ 'links': len(links), ++ 'prefixes': len(prefixes), ++ 'total': len(nodes) + len(links) + len(prefixes) ++ } ++ ++ ++def count_router_nlris(data, bgp_router_id, asn=None): ++ """ ++ Count all NLRIs for a specific router (nodes, links, prefixes) ++ ++ Args: ++ data: BGP-LS JSON data (dict with 'routes' key) ++ bgp_router_id: BGP Router-ID to filter by ++ asn: Optional ASN to filter by for node NLRIs ++ ++ Returns: ++ Dictionary with counts: { ++ 'nodes': int, ++ 'links': int, ++ 'prefixes': int, ++ 'total': int ++ } ++ """ ++ nodes = count_node_nlris(data, bgp_router_id, asn) ++ links = count_link_nlris(data, bgp_router_id) ++ prefixes = count_prefix_nlris(data, bgp_router_id) ++ ++ return { ++ 'nodes': len(nodes), ++ 'links': len(links), ++ 'prefixes': len(prefixes), ++ 'total': len(nodes) + len(links) + len(prefixes) ++ } ++ ++ ++def build_topo(tgen): ++ """Build test topology with rr collector and iBGP/eBGP peer pairs.""" ++ ++ # Create 5 routers ++ tgen.add_router("rr") # BGP-LS speaker (route reflector) ++ tgen.add_router("r1") # AS65001 ++ tgen.add_router("r2") # AS65001 ++ tgen.add_router("r3") # AS65002 ++ tgen.add_router("r4") # AS65003 ++ ++ # Create point-to-point switches for RR connections ++ # rr to r1 (10.255.1.0/30) ++ switch = tgen.add_switch("s-rr-r1") ++ switch.add_link(tgen.gears["rr"]) ++ switch.add_link(tgen.gears["r1"]) ++ ++ # rr to r2 (10.255.2.0/30) ++ switch = tgen.add_switch("s-rr-r2") ++ switch.add_link(tgen.gears["rr"]) ++ switch.add_link(tgen.gears["r2"]) ++ ++ # rr to r3 (10.255.3.0/30) ++ switch = tgen.add_switch("s-rr-r3") ++ switch.add_link(tgen.gears["rr"]) ++ switch.add_link(tgen.gears["r3"]) ++ ++ # rr to r4 (10.255.4.0/30) ++ switch = tgen.add_switch("s-rr-r4") ++ switch.add_link(tgen.gears["rr"]) ++ switch.add_link(tgen.gears["r4"]) ++ ++ # r1 to r2 (IPv4 iBGP session) ++ switch = tgen.add_switch("s-r1-r2") ++ switch.add_link(tgen.gears["r1"]) ++ switch.add_link(tgen.gears["r2"]) ++ ++ # r3 to r4 (IPv6 eBGP session) ++ switch = tgen.add_switch("s-r3-r4") ++ switch.add_link(tgen.gears["r3"]) ++ switch.add_link(tgen.gears["r4"]) ++ ++ ++def setup_module(mod): ++ """Set up test environment.""" ++ tgen = Topogen(build_topo, mod.__name__) ++ tgen.start_topology() ++ ++ router_list = tgen.routers() ++ ++ for rname, router in router_list.items(): ++ router.load_frr_config(os.path.join(CWD, "{}/frr.conf".format(rname))) ++ ++ tgen.start_router() ++ ++ ++def teardown_module(mod): ++ """Tear down test environment.""" ++ tgen = get_topogen() ++ tgen.stop_topology() ++ ++ ++def test_bgp_convergence(): ++ """Test BGP convergence""" ++ tgen = get_topogen() ++ ++ if tgen.routers_have_failure(): ++ pytest.skip(tgen.errors) ++ ++ logger.info("Waiting for BGP convergence") ++ ++ # Wait for BGP sessions to establish ++ for router in ["rr", "r1", "r2", "r3", "r4"]: ++ logger.info("Checking BGP convergence on {}".format(router)) ++ test_func = functools.partial( ++ topotest.router_json_cmp, ++ tgen.gears[router], ++ "show bgp summary json", ++ {}, ++ ) ++ _, result = topotest.run_and_expect(test_func, None, count=60, wait=0.5) ++ assertmsg = '"{}" BGP convergence failure'.format(router) ++ assert result is None, assertmsg ++ ++ ++def test_bgp_ls_nlris(): ++ """Test BGP-LS NLRIs on route reflector""" ++ tgen = get_topogen() ++ ++ if tgen.routers_have_failure(): ++ pytest.skip(tgen.errors) ++ ++ logger.info("Verifying BGP-LS NLRIs on route reflector") ++ ++ rr = tgen.gears["rr"] ++ ++ # Load expected JSON ++ expected_file = os.path.join(CWD, "rr/expected_bgp_ls.json") ++ expected = json.load(open(expected_file)) ++ ++ # Use router_json_cmp to compare with expected output ++ test_func = functools.partial( ++ bgp_ls_router_json_cmp, ++ rr, ++ expected, ++ ) ++ _, result = topotest.run_and_expect(test_func, None, count=60, wait=0.5) ++ assertmsg = 'BGP-LS NLRIs do not match expected output' ++ assert result is None, assertmsg ++ ++ logger.info("All NLRIs verified successfully") ++ ++ ++def test_bgp_router_id_unset_reset(): ++ """Verify router-id unset/reset triggers NLRI re-origination on r1.""" ++ tgen = get_topogen() ++ ++ if tgen.routers_have_failure(): ++ pytest.skip(tgen.errors) ++ ++ logger.info("Testing BGP router-id unset/reset on r1") ++ ++ rr = tgen.gears["rr"] ++ r1 = tgen.gears["r1"] ++ ++ # Step 1: Verify baseline - r1 NLRIs should be present ++ logger.info("Step 1: Verifying baseline BGP-LS NLRIs from r1") ++ ++ def check_r1_nlris_present(): ++ output = rr.vtysh_cmd("show bgp link-state link-state json") ++ data = json.loads(output) ++ ++ # Count NLRIs from r1 and all routers ++ r1_counts = count_router_nlris(data, "1.1.1.1") ++ total_counts = count_all_nlris_by_type(data) ++ ++ # Store counts for error reporting ++ check_r1_nlris_present.last_counts = { ++ "r1_nodes": r1_counts['nodes'], ++ "r1_links": r1_counts['links'], ++ "r1_prefixes": r1_counts['prefixes'], ++ "r1_total": r1_counts['total'], ++ "total_nodes": total_counts['nodes'], ++ "total_links": total_counts['links'], ++ "total_prefixes": total_counts['prefixes'], ++ "total_nlris": total_counts['total'] ++ } ++ ++ # r1 should have: 1 node, 2 links, 11 prefixes ++ # Total should have: 54 NLRIs ++ if r1_counts['nodes'] == 1 and r1_counts['links'] == 2 and r1_counts['prefixes'] == 11 and total_counts['total'] == 54: ++ return True ++ return False ++ ++ test_func = functools.partial(check_r1_nlris_present) ++ success, _ = topotest.run_and_expect(test_func, True, count=30, wait=1) ++ assert success, ( ++ f"r1 NLRIs not present in baseline check. " ++ f"Expected: r1 nodes=1, links=2, prefixes=11, total NLRIs=54. " ++ f"Found: r1 nodes={check_r1_nlris_present.last_counts['r1_nodes']}, " ++ f"links={check_r1_nlris_present.last_counts['r1_links']}, " ++ f"prefixes={check_r1_nlris_present.last_counts['r1_prefixes']}, " ++ f"total={check_r1_nlris_present.last_counts['total_nlris']} " ++ f"(nodes={check_r1_nlris_present.last_counts['total_nodes']}, " ++ f"links={check_r1_nlris_present.last_counts['total_links']}, " ++ f"prefixes={check_r1_nlris_present.last_counts['total_prefixes']})" ++ ) ++ ++ # Step 2: Unset BGP router-id on r1 ++ logger.info("Step 2: Unsetting BGP router-id on r1") ++ r1.vtysh_cmd(""" ++ configure terminal ++ router bgp 65001 ++ no bgp router-id ++ """) ++ ++ # Step 3: Verify NLRIs are re-advertised with zebra-provided router-id (10.1.1.1) ++ logger.info("Step 3: Verifying r1 NLRIs are re-advertised with zebra router-id 10.1.1.1") ++ ++ def check_r1_nlris_with_zebra_id(): ++ output = rr.vtysh_cmd("show bgp link-state link-state json") ++ data = json.loads(output) ++ ++ # Count NLRIs with old router-id (1.1.1.1) - should be 0 ++ old_counts = count_router_nlris(data, "1.1.1.1") ++ ++ # Count NLRIs with zebra router-id (10.1.1.1) - should have all r1's NLRIs ++ zebra_counts = count_router_nlris(data, "10.1.1.1") ++ ++ # Count total NLRIs (should remain the same) ++ total_counts = count_all_nlris_by_type(data) ++ ++ # Store counts for error reporting ++ check_r1_nlris_with_zebra_id.last_counts = { ++ "old_id_total": old_counts['total'], ++ "zebra_id_nodes": zebra_counts['nodes'], ++ "zebra_id_links": zebra_counts['links'], ++ "zebra_id_prefixes": zebra_counts['prefixes'], ++ "zebra_id_total": zebra_counts['total'], ++ "total_nlris": total_counts['total'], ++ "expected_total": check_r1_nlris_present.last_counts['total_nlris'] ++ } ++ ++ # Old router-id should have no NLRIs, zebra router-id should have all r1's NLRIs ++ if (old_counts['total'] == 0 and ++ zebra_counts['nodes'] == 1 and ++ zebra_counts['links'] == 2 and ++ zebra_counts['prefixes'] == 11 and ++ total_counts['total'] == check_r1_nlris_present.last_counts['total_nlris']): ++ return True ++ return False ++ ++ test_func = functools.partial(check_r1_nlris_with_zebra_id) ++ success, _ = topotest.run_and_expect(test_func, True, count=30, wait=1) ++ assert success, ( ++ f"r1 NLRIs not properly re-advertised with zebra router-id after unsetting configured router-id. " ++ f"Expected: old router-id 1.1.1.1 NLRIs=0, zebra router-id 10.1.1.1 nodes=1, links=2, prefixes=11, total NLRIs=54. " ++ f"Found: old router-id NLRIs={check_r1_nlris_with_zebra_id.last_counts['old_id_total']}, " ++ f"zebra router-id nodes={check_r1_nlris_with_zebra_id.last_counts['zebra_id_nodes']}, " ++ f"links={check_r1_nlris_with_zebra_id.last_counts['zebra_id_links']}, " ++ f"prefixes={check_r1_nlris_with_zebra_id.last_counts['zebra_id_prefixes']}, " ++ f"total NLRIs={check_r1_nlris_with_zebra_id.last_counts['total_nlris']} " ++ f"(expected {check_r1_nlris_with_zebra_id.last_counts['expected_total']})" ++ ) ++ ++ # Step 4: Reconfigure BGP router-id on r1 ++ logger.info("Step 4: Reconfiguring BGP router-id to 1.1.1.1 on r1") ++ r1.vtysh_cmd(""" ++ configure terminal ++ router bgp 65001 ++ bgp router-id 1.1.1.1 ++ """) ++ ++ # Step 5: Verify NLRIs are re-advertised with configured router-id (1.1.1.1) ++ logger.info("Step 5: Verifying r1 NLRIs are re-advertised with configured router-id 1.1.1.1") ++ ++ def check_r1_nlris_with_configured_id(): ++ output = rr.vtysh_cmd("show bgp link-state link-state json") ++ data = json.loads(output) ++ ++ # Count NLRIs with zebra router-id (10.1.1.1) - should be 0 ++ zebra_counts = count_router_nlris(data, "10.1.1.1") ++ ++ # Count NLRIs with configured router-id (1.1.1.1) - should have all r1's NLRIs ++ configured_counts = count_router_nlris(data, "1.1.1.1") ++ ++ # Count total NLRIs (should remain the same) ++ total_counts = count_all_nlris_by_type(data) ++ ++ # Store counts for error reporting ++ check_r1_nlris_with_configured_id.last_counts = { ++ "zebra_id_total": zebra_counts['total'], ++ "configured_id_nodes": configured_counts['nodes'], ++ "configured_id_links": configured_counts['links'], ++ "configured_id_prefixes": configured_counts['prefixes'], ++ "configured_id_total": configured_counts['total'], ++ "total_nlris": total_counts['total'] ++ } ++ ++ # Zebra router-id should have no NLRIs, configured router-id should have all r1's NLRIs ++ if (zebra_counts['total'] == 0 and ++ configured_counts['nodes'] == 1 and ++ configured_counts['links'] == 2 and ++ configured_counts['prefixes'] == 11 and ++ total_counts['total'] == 54): ++ return True ++ return False ++ ++ test_func = functools.partial(check_r1_nlris_with_configured_id) ++ success, _ = topotest.run_and_expect(test_func, True, count=30, wait=1) ++ assert success, ( ++ f"r1 NLRIs not re-advertised with configured router-id after reconfiguration. " ++ f"Expected: zebra router-id 10.1.1.1 NLRIs=0, configured router-id 1.1.1.1 nodes=1, links=2, prefixes=11, total NLRIs=54. " ++ f"Found: zebra router-id NLRIs={check_r1_nlris_with_configured_id.last_counts['zebra_id_total']}, " ++ f"configured router-id nodes={check_r1_nlris_with_configured_id.last_counts['configured_id_nodes']}, " ++ f"links={check_r1_nlris_with_configured_id.last_counts['configured_id_links']}, " ++ f"prefixes={check_r1_nlris_with_configured_id.last_counts['configured_id_prefixes']}, " ++ f"total NLRIs={check_r1_nlris_with_configured_id.last_counts['total_nlris']}" ++ ) ++ ++ # Final check: Verify output matches expected baseline ++ logger.info("Final verification: Comparing output with expected baseline") ++ expected_file = os.path.join(CWD, "rr/expected_bgp_ls.json") ++ expected = json.load(open(expected_file)) ++ ++ test_func = functools.partial( ++ bgp_ls_router_json_cmp, ++ rr, ++ expected, ++ ) ++ _, result = topotest.run_and_expect(test_func, None, count=60, wait=0.5) ++ assertmsg = 'BGP-LS NLRIs do not match expected baseline output after router-id reset' ++ assert result is None, assertmsg ++ ++ logger.info("BGP router-id unset/reset test completed successfully") ++ ++ ++def test_bgp_asn_unset_reset(): ++ """Verify ASN removal/recreation withdraws and re-originates r1 NLRIs.""" ++ tgen = get_topogen() ++ ++ if tgen.routers_have_failure(): ++ pytest.skip(tgen.errors) ++ ++ logger.info("Testing BGP ASN unset/reset on r1") ++ ++ rr = tgen.gears["rr"] ++ r1 = tgen.gears["r1"] ++ ++ # Step 1: Verify baseline - r1 NLRIs should be present ++ logger.info("Step 1: Verifying baseline BGP-LS NLRIs from r1") ++ ++ def check_r1_nlris_present(): ++ output = rr.vtysh_cmd("show bgp link-state link-state json") ++ data = json.loads(output) ++ ++ # Count NLRIs from r1 (BGP Router-ID 1.1.1.1, ASN 65001) ++ r1_nodes = count_node_nlris(data, "1.1.1.1", asn=65001) ++ r1_links = count_link_nlris(data, "1.1.1.1") ++ r1_prefixes = count_prefix_nlris(data, "1.1.1.1") ++ total_nlris = len(r1_nodes) + len(r1_links) + len(r1_prefixes) ++ ++ # Store count for error reporting ++ check_r1_nlris_present.last_count = total_nlris ++ ++ if total_nlris == 14: # 1 node + 2 links + 11 prefixes ++ return True ++ return False ++ ++ test_func = functools.partial(check_r1_nlris_present) ++ success, _ = topotest.run_and_expect(test_func, True, count=30, wait=1) ++ assert success, ( ++ f"r1 NLRIs not present in baseline check (ASN test). " ++ f"Expected: 14 NLRIs (1 node + 2 link + 11 prefixes). " ++ f"Found: {check_r1_nlris_present.last_count} NLRIs" ++ ) ++ ++ # Step 2: Remove BGP instance on r1 ++ logger.info("Step 2: Removing BGP instance (unsetting ASN) on r1") ++ r1.vtysh_cmd(""" ++ configure terminal ++ no router bgp 65001 ++ """) ++ ++ # Step 3: Verify all NLRIs from r1 are withdrawn ++ logger.info("Step 3: Verifying r1 NLRIs are withdrawn from rr") ++ ++ def check_r1_nlris_withdrawn(): ++ output = rr.vtysh_cmd("show bgp link-state link-state json") ++ data = json.loads(output) ++ ++ # Count all NLRIs from r1 ++ r1_nodes = count_node_nlris(data, "1.1.1.1") ++ r1_links = count_link_nlris(data, "1.1.1.1") ++ r1_prefixes = count_prefix_nlris(data, "1.1.1.1") ++ total_nlris = len(r1_nodes) + len(r1_links) + len(r1_prefixes) ++ ++ # Store count for error reporting ++ check_r1_nlris_withdrawn.last_count = total_nlris ++ ++ if total_nlris == 0: ++ return True ++ return False ++ ++ test_func = functools.partial(check_r1_nlris_withdrawn) ++ success, _ = topotest.run_and_expect(test_func, True, count=30, wait=1) ++ assert success, ( ++ f"r1 NLRIs not withdrawn after ASN removal. " ++ f"Expected: 0 NLRIs. Found: {check_r1_nlris_withdrawn.last_count} NLRIs still present" ++ ) ++ ++ # Step 4: Reconfigure BGP with full config on r1 ++ logger.info("Step 4: Reconfiguring BGP with ASN on r1") ++ r1.vtysh_cmd(""" ++ configure terminal ++ router bgp 65001 ++ bgp router-id 1.1.1.1 ++ no bgp ebgp-requires-policy ++ no bgp default ipv4-unicast ++ neighbor 172.16.1.2 remote-as 65001 ++ neighbor 172.16.1.2 update-source r1-eth1 ++ neighbor 10.255.1.2 remote-as 65000 ++ address-family ipv4 unicast ++ neighbor 172.16.1.2 activate ++ redistribute local ++ redistribute connected ++ redistribute static ++ exit-address-family ++ address-family link-state link-state ++ distribute bgp-fabric-link-state ++ neighbor 10.255.1.2 activate ++ exit-address-family ++ """) ++ ++ # Step 5: Verify all NLRIs from r1 are re-advertised ++ logger.info("Step 5: Verifying r1 NLRIs are re-advertised to rr") ++ ++ test_func = functools.partial(check_r1_nlris_present) ++ success, _ = topotest.run_and_expect(test_func, True, count=60, wait=1) ++ assert success, ( ++ f"r1 NLRIs not re-advertised after ASN reconfiguration. " ++ f"Expected: 14 NLRIs. Found: {check_r1_nlris_present.last_count} NLRIs" ++ ) ++ ++ # Final check: Verify output matches expected baseline ++ logger.info("Final verification: Comparing output with expected baseline") ++ expected_file = os.path.join(CWD, "rr/expected_bgp_ls.json") ++ expected = json.load(open(expected_file)) ++ ++ test_func = functools.partial( ++ bgp_ls_router_json_cmp, ++ rr, ++ expected, ++ ) ++ _, result = topotest.run_and_expect(test_func, None, count=60, wait=0.5) ++ assertmsg = 'BGP-LS NLRIs do not match expected baseline output after router-id reset' ++ assert result is None, assertmsg ++ ++ logger.info("BGP ASN unset/reset test completed successfully") ++ ++ ++def test_bgp_session_teardown_restore(): ++ """Verify r1-r2 session teardown/restore updates link NLRI as expected.""" ++ tgen = get_topogen() ++ ++ if tgen.routers_have_failure(): ++ pytest.skip(tgen.errors) ++ ++ logger.info("Testing BGP session teardown/restore between r1 and r2") ++ ++ rr = tgen.gears["rr"] ++ r1 = tgen.gears["r1"] ++ ++ # Step 1: Verify baseline - link from r1 to r2 should be present ++ logger.info("Step 1: Verifying baseline link NLRI from r1 to r2") ++ ++ def check_r1_r2_link_present(): ++ output = rr.vtysh_cmd("show bgp link-state link-state json") ++ data = json.loads(output) ++ ++ # Find link from r1 to r2 ++ r1_r2_link = count_link_nlris(data, "1.1.1.1", "2.2.2.2") ++ ++ # Store count for error reporting ++ check_r1_r2_link_present.last_count = len(r1_r2_link) ++ ++ if len(r1_r2_link) == 1: ++ return True ++ return False ++ ++ test_func = functools.partial(check_r1_r2_link_present) ++ success, _ = topotest.run_and_expect(test_func, True, count=30, wait=1) ++ assert success, ( ++ f"r1-r2 link NLRI not present in baseline check (BGP session test). " ++ f"Expected: 1 link. Found: {check_r1_r2_link_present.last_count} links" ++ ) ++ ++ # Step 2: Tear down BGP session with r2 ++ logger.info("Step 2: Tearing down BGP session on r1 with r2") ++ r1.vtysh_cmd(""" ++ configure terminal ++ router bgp 65001 ++ no neighbor 172.16.1.2 ++ """) ++ ++ # Step 3: Verify link NLRI is withdrawn ++ logger.info("Step 3: Verifying r1-r2 link NLRI is withdrawn from rr") ++ ++ def check_r1_r2_link_withdrawn(): ++ output = rr.vtysh_cmd("show bgp link-state link-state json") ++ data = json.loads(output) ++ ++ # Find link from r1 to r2 ++ r1_r2_link = count_link_nlris(data, "1.1.1.1", "2.2.2.2") ++ ++ # Store count for error reporting ++ check_r1_r2_link_withdrawn.last_count = len(r1_r2_link) ++ ++ if len(r1_r2_link) == 0: ++ return True ++ return False ++ ++ test_func = functools.partial(check_r1_r2_link_withdrawn) ++ success, _ = topotest.run_and_expect(test_func, True, count=30, wait=1) ++ assert success, ( ++ f"r1-r2 link NLRI not withdrawn after session teardown. " ++ f"Expected: 0 links. Found: {check_r1_r2_link_withdrawn.last_count} links still present" ++ ) ++ ++ # Step 4: Restore BGP session with r2 ++ logger.info("Step 4: Restoring BGP session on r1 with r2") ++ r1.vtysh_cmd(""" ++ configure terminal ++ router bgp 65001 ++ neighbor 172.16.1.2 remote-as 65001 ++ neighbor 172.16.1.2 update-source r1-eth1 ++ address-family ipv4 unicast ++ neighbor 172.16.1.2 activate ++ exit-address-family ++ """) ++ ++ # Step 5: Verify link NLRI is re-advertised ++ logger.info("Step 5: Verifying r1-r2 link NLRI is re-advertised to rr") ++ ++ test_func = functools.partial(check_r1_r2_link_present) ++ success, _ = topotest.run_and_expect(test_func, True, count=60, wait=1) ++ assert success, ( ++ f"r1-r2 link NLRI not re-advertised after session restoration. " ++ f"Expected: 1 links. Found: {check_r1_r2_link_present.last_count} links" ++ ) ++ ++ # Final check: Verify output matches expected baseline ++ logger.info("Final verification: Comparing output with expected baseline") ++ expected_file = os.path.join(CWD, "rr/expected_bgp_ls.json") ++ expected = json.load(open(expected_file)) ++ ++ test_func = functools.partial( ++ bgp_ls_router_json_cmp, ++ rr, ++ expected, ++ ) ++ _, result = topotest.run_and_expect(test_func, None, count=60, wait=0.5) ++ assertmsg = 'BGP-LS NLRIs do not match expected baseline output after router-id reset' ++ assert result is None, assertmsg ++ ++ logger.info("BGP session teardown/restore test completed successfully") ++ ++ ++def test_prefix_removal_restore(): ++ """Verify static prefix removal/restoration updates prefix NLRI on rr.""" ++ tgen = get_topogen() ++ ++ if tgen.routers_have_failure(): ++ pytest.skip(tgen.errors) ++ ++ logger.info("Testing prefix removal/restore on r1") ++ ++ rr = tgen.gears["rr"] ++ r1 = tgen.gears["r1"] ++ ++ # Step 1: Verify baseline - static prefix should be present ++ logger.info("Step 1: Verifying baseline static prefix 192.0.2.0/24 from r1") ++ ++ def check_static_prefix_present(): ++ output = rr.vtysh_cmd("show bgp link-state link-state json") ++ data = json.loads(output) ++ ++ # Find prefix 192.0.2.0/24 from r1 ++ static_prefix = count_prefix_nlris(data, "1.1.1.1", prefix="192.0.2.0/24") ++ ++ # Store count for error reporting ++ check_static_prefix_present.last_count = len(static_prefix) ++ ++ if len(static_prefix) == 1: ++ return True ++ return False ++ ++ test_func = functools.partial(check_static_prefix_present) ++ success, _ = topotest.run_and_expect(test_func, True, count=30, wait=1) ++ assert success, ( ++ f"Static prefix 192.0.2.0/24 not present in baseline check. " ++ f"Expected: 1 prefix. Found: {check_static_prefix_present.last_count} prefixes" ++ ) ++ ++ # Step 2: Remove static prefix ++ logger.info("Step 2: Removing static prefix 192.0.2.0/24 on r1") ++ r1.vtysh_cmd(""" ++ configure terminal ++ no ip route 192.0.2.0/24 Null0 ++ """) ++ ++ # Step 3: Verify prefix NLRI is withdrawn ++ logger.info("Step 3: Verifying prefix NLRI is withdrawn from rr") ++ ++ def check_static_prefix_withdrawn(): ++ output = rr.vtysh_cmd("show bgp link-state link-state json") ++ data = json.loads(output) ++ ++ # Find prefix 192.0.2.0/24 from r1 ++ static_prefix = count_prefix_nlris(data, "1.1.1.1", prefix="192.0.2.0/24") ++ ++ # Store count for error reporting ++ check_static_prefix_withdrawn.last_count = len(static_prefix) ++ ++ if len(static_prefix) == 0: ++ return True ++ return False ++ ++ test_func = functools.partial(check_static_prefix_withdrawn) ++ success, _ = topotest.run_and_expect(test_func, True, count=30, wait=1) ++ assert success, ( ++ f"Static prefix not withdrawn after removal. " ++ f"Expected: 0 prefixes. Found: {check_static_prefix_withdrawn.last_count} prefixes still present" ++ ) ++ ++ # Step 4: Restore static prefix ++ logger.info("Step 4: Restoring static prefix 192.0.2.0/24 on r1") ++ r1.vtysh_cmd(""" ++ configure terminal ++ ip route 192.0.2.0/24 Null0 ++ """) ++ ++ # Step 5: Verify prefix NLRI is re-advertised ++ logger.info("Step 5: Verifying prefix NLRI is re-advertised to rr") ++ ++ test_func = functools.partial(check_static_prefix_present) ++ success, _ = topotest.run_and_expect(test_func, True, count=30, wait=1) ++ assert success, ( ++ f"Static prefix not re-advertised after restoration. " ++ f"Expected: 1 prefix. Found: {check_static_prefix_present.last_count} prefixes" ++ ) ++ ++ # Final check: Verify output matches expected baseline ++ logger.info("Final verification: Comparing output with expected baseline") ++ expected_file = os.path.join(CWD, "rr/expected_bgp_ls.json") ++ expected = json.load(open(expected_file)) ++ ++ test_func = functools.partial( ++ bgp_ls_router_json_cmp, ++ rr, ++ expected, ++ ) ++ _, result = topotest.run_and_expect(test_func, None, count=60, wait=0.5) ++ assertmsg = 'BGP-LS NLRIs do not match expected baseline output after router-id reset' ++ assert result is None, assertmsg ++ ++ logger.info("Prefix removal/restore test completed successfully") ++ ++ ++def test_loopback_address_unset_restore(): ++ """Verify loopback address changes are reflected in prefix NLRI export.""" ++ tgen = get_topogen() ++ ++ if tgen.routers_have_failure(): ++ pytest.skip(tgen.errors) ++ ++ logger.info("Testing loopback address unset/restore on r1") ++ ++ rr = tgen.gears["rr"] ++ r1 = tgen.gears["r1"] ++ ++ # Step 1: Verify baseline - loopback prefix should be present ++ logger.info("Step 1: Verifying baseline loopback prefix 10.1.1.1/32 from r1") ++ ++ def check_loopback_prefix_present(): ++ output = rr.vtysh_cmd("show bgp link-state link-state json") ++ data = json.loads(output) ++ ++ # Find loopback prefix 10.1.1.1/32 from r1 ++ loopback_prefix = count_prefix_nlris(data, "1.1.1.1", prefix="10.1.1.1/32") ++ ++ # Store count for error reporting ++ check_loopback_prefix_present.last_count = len(loopback_prefix) ++ ++ if len(loopback_prefix) == 1: ++ return True ++ return False ++ ++ test_func = functools.partial(check_loopback_prefix_present) ++ success, _ = topotest.run_and_expect(test_func, True, count=30, wait=1) ++ assert success, ( ++ f"Loopback prefix 10.1.1.1/32 not present in baseline check. " ++ f"Expected: 1 prefix. Found: {check_loopback_prefix_present.last_count} prefixes" ++ ) ++ ++ # Step 2: Remove IP address from loopback ++ logger.info("Step 2: Removing IP address from loopback interface on r1") ++ r1.run("ip addr del 10.1.1.1/32 dev lo") ++ ++ # Step 3: Verify loopback prefix NLRI is withdrawn ++ logger.info("Step 3: Verifying loopback prefix NLRI is withdrawn from rr") ++ ++ def check_loopback_prefix_withdrawn(): ++ output = rr.vtysh_cmd("show bgp link-state link-state json") ++ data = json.loads(output) ++ ++ # Find loopback prefix 10.1.1.1/32 from r1 ++ loopback_prefix = count_prefix_nlris(data, "1.1.1.1", prefix="10.1.1.1/32") ++ ++ # Store count for error reporting ++ check_loopback_prefix_withdrawn.last_count = len(loopback_prefix) ++ ++ if len(loopback_prefix) == 0: ++ return True ++ return False ++ ++ test_func = functools.partial(check_loopback_prefix_withdrawn) ++ success, _ = topotest.run_and_expect(test_func, True, count=30, wait=1) ++ assert success, ( ++ f"Loopback prefix not withdrawn after address removal. " ++ f"Expected: 0 prefixes. Found: {check_loopback_prefix_withdrawn.last_count} prefixes still present" ++ ) ++ ++ # Step 4: Restore IP address on loopback ++ logger.info("Step 4: Restoring IP address on loopback interface on r1") ++ r1.run("ip addr add 10.1.1.1/32 dev lo") ++ ++ # Step 5: Verify loopback prefix NLRI is re-advertised ++ logger.info("Step 5: Verifying loopback prefix NLRI is re-advertised to rr") ++ ++ test_func = functools.partial(check_loopback_prefix_present) ++ success, _ = topotest.run_and_expect(test_func, True, count=30, wait=1) ++ assert success, ( ++ f"Loopback prefix not re-advertised after address restoration. " ++ f"Expected: 1 prefix. Found: {check_loopback_prefix_present.last_count} prefixes" ++ ) ++ ++ # Final check: Verify output matches expected baseline ++ logger.info("Final verification: Comparing output with expected baseline") ++ expected_file = os.path.join(CWD, "rr/expected_bgp_ls.json") ++ expected = json.load(open(expected_file)) ++ ++ test_func = functools.partial( ++ bgp_ls_router_json_cmp, ++ rr, ++ expected, ++ ) ++ _, result = topotest.run_and_expect(test_func, None, count=60, wait=0.5) ++ assertmsg = 'BGP-LS NLRIs do not match expected baseline output after router-id reset' ++ assert result is None, assertmsg ++ ++ logger.info("Loopback address unset/restore test completed successfully") ++ ++ ++def test_link_down_restore(): ++ """Verify interface shutdown/restore updates the r1-r2 link NLRI.""" ++ tgen = get_topogen() ++ ++ if tgen.routers_have_failure(): ++ pytest.skip(tgen.errors) ++ ++ logger.info("Testing link down/restore between r1 and r2") ++ ++ rr = tgen.gears["rr"] ++ r1 = tgen.gears["r1"] ++ ++ # Step 1: Verify baseline - link from r1 to r2 should be present ++ logger.info("Step 1: Verifying baseline link NLRI from r1 to r2") ++ ++ def check_r1_r2_link_present(): ++ output = rr.vtysh_cmd("show bgp link-state link-state json") ++ data = json.loads(output) ++ ++ # Find link from r1 to r2 ++ r1_r2_link = count_link_nlris(data, "1.1.1.1", "2.2.2.2") ++ ++ # Store count for error reporting ++ check_r1_r2_link_present.last_count = len(r1_r2_link) ++ ++ if len(r1_r2_link) == 1: ++ return True ++ return False ++ ++ test_func = functools.partial(check_r1_r2_link_present) ++ success, _ = topotest.run_and_expect(test_func, True, count=30, wait=1) ++ assert success, ( ++ f"r1-r2 link NLRI not present in baseline check (link down test). " ++ f"Expected: 1 link. Found: {check_r1_r2_link_present.last_count} links" ++ ) ++ ++ # Step 2: Shut down interface r1-eth1 ++ logger.info("Step 2: Shutting down interface r1-eth1 on r1") ++ r1.vtysh_cmd(""" ++ configure terminal ++ interface r1-eth1 ++ shutdown ++ """) ++ ++ # Step 3: Verify link NLRI is withdrawn ++ logger.info("Step 3: Verifying r1-r2 link NLRI is withdrawn from rr") ++ ++ def check_r1_r2_link_withdrawn(): ++ output = rr.vtysh_cmd("show bgp link-state link-state json") ++ data = json.loads(output) ++ ++ # Find link from r1 to r2 ++ r1_r2_link = count_link_nlris(data, "1.1.1.1", "2.2.2.2") ++ ++ # Store count for error reporting ++ check_r1_r2_link_withdrawn.last_count = len(r1_r2_link) ++ ++ if len(r1_r2_link) == 1: ++ return True ++ return False ++ ++ test_func = functools.partial(check_r1_r2_link_withdrawn) ++ success, _ = topotest.run_and_expect(test_func, True, count=30, wait=1) ++ assert success, ( ++ f"r1-r2 link NLRI not withdrawn after interface shutdown. " ++ f"Expected: 1 links. Found: {check_r1_r2_link_withdrawn.last_count} links still present" ++ ) ++ ++ # Step 4: Bring up interface r1-eth1 ++ logger.info("Step 4: Bringing up interface r1-eth1 on r1") ++ r1.vtysh_cmd(""" ++ configure terminal ++ interface r1-eth1 ++ no shutdown ++ """) ++ ++ # Step 5: Verify link NLRI is re-advertised ++ logger.info("Step 5: Verifying r1-r2 link NLRI is re-advertised to rr") ++ ++ test_func = functools.partial(check_r1_r2_link_present) ++ success, _ = topotest.run_and_expect(test_func, True, count=60, wait=1) ++ assert success, ( ++ f"r1-r2 link NLRI not re-advertised after interface restoration. " ++ f"Expected: 1 link. Found: {check_r1_r2_link_present.last_count} links" ++ ) ++ ++ # Final check: Verify output matches expected baseline ++ logger.info("Final verification: Comparing output with expected baseline") ++ expected_file = os.path.join(CWD, "rr/expected_bgp_ls.json") ++ expected = json.load(open(expected_file)) ++ ++ test_func = functools.partial( ++ bgp_ls_router_json_cmp, ++ rr, ++ expected, ++ ) ++ _, result = topotest.run_and_expect(test_func, None, count=60, wait=0.5) ++ assertmsg = 'BGP-LS NLRIs do not match expected baseline output after router-id reset' ++ assert result is None, assertmsg ++ ++ logger.info("Link down/restore test completed successfully") ++ ++ ++def test_static_route_removal_restore(): ++ """Verify IPv4 prefix NLRI count drops/restores on static route changes.""" ++ tgen = get_topogen() ++ ++ if tgen.routers_have_failure(): ++ pytest.skip(tgen.errors) ++ ++ logger.info("Testing static route removal/restore on r1") ++ ++ rr = tgen.gears["rr"] ++ r1 = tgen.gears["r1"] ++ ++ # Step 1: Verify baseline - count total ipv4Prefix NLRIs ++ logger.info("Step 1: Counting baseline ipv4Prefix NLRIs") ++ ++ def get_ipv4_prefix_count(): ++ """Get total count of ipv4Prefix NLRIs""" ++ output = rr.vtysh_cmd("show bgp link-state link-state json") ++ data = json.loads(output) ++ ++ # Extract all route objects from the routes dictionary ++ all_routes = [] ++ if "routes" in data: ++ for nlri_key, route_list in data["routes"].items(): ++ all_routes.extend(route_list) ++ ++ # Count all ipv4Prefix NLRIs ++ ipv4_prefixes = [r for r in all_routes if r.get("nlri", {}).get("nlriType") == "ipv4Prefix"] ++ return len(ipv4_prefixes) ++ ++ baseline_count = get_ipv4_prefix_count() ++ logger.info(f"Baseline ipv4Prefix count: {baseline_count}") ++ ++ # Step 2: Remove static route ++ logger.info("Step 2: Removing static route 192.0.2.0/24 on r1") ++ r1.vtysh_cmd(""" ++ configure terminal ++ no ip route 192.0.2.0/24 Null0 ++ """) ++ ++ # Step 3: Verify ipv4Prefix count drops by 2 ++ logger.info("Step 3: Verifying ipv4Prefix count drops by 2") ++ ++ def check_prefix_count_decreased(): ++ """Verify ipv4Prefix count decreased by 2""" ++ current_count = get_ipv4_prefix_count() ++ expected_count = baseline_count - 2 ++ ++ # Store counts for error reporting ++ check_prefix_count_decreased.current = current_count ++ check_prefix_count_decreased.expected = expected_count ++ ++ if current_count == expected_count: ++ return True ++ return False ++ ++ test_func = functools.partial(check_prefix_count_decreased) ++ success, _ = topotest.run_and_expect(test_func, True, count=30, wait=1) ++ assert success, ( ++ f"ipv4Prefix count did not decrease by 2 after route removal. " ++ f"Baseline: {baseline_count}, Expected: {check_prefix_count_decreased.expected}, " ++ f"Found: {check_prefix_count_decreased.current}" ++ ) ++ ++ # Step 4: Restore static route ++ logger.info("Step 4: Restoring static route 192.0.2.0/24 on r1") ++ r1.vtysh_cmd(""" ++ configure terminal ++ ip route 192.0.2.0/24 Null0 ++ """) ++ ++ # Step 5: Verify ipv4Prefix count is restored ++ logger.info("Step 5: Verifying ipv4Prefix count is restored") ++ ++ def check_prefix_count_restored(): ++ """Verify ipv4Prefix count is restored to baseline""" ++ current_count = get_ipv4_prefix_count() ++ ++ # Store counts for error reporting ++ check_prefix_count_restored.current = current_count ++ check_prefix_count_restored.expected = baseline_count ++ ++ if current_count == baseline_count: ++ return True ++ return False ++ ++ test_func = functools.partial(check_prefix_count_restored) ++ success, _ = topotest.run_and_expect(test_func, True, count=30, wait=1) ++ assert success, ( ++ f"ipv4Prefix count not restored after route re-addition. " ++ f"Expected: {check_prefix_count_restored.expected}, " ++ f"Found: {check_prefix_count_restored.current}" ++ ) ++ ++ # Final check: Verify output matches expected baseline ++ logger.info("Final verification: Comparing output with expected baseline") ++ expected_file = os.path.join(CWD, "rr/expected_bgp_ls.json") ++ expected = json.load(open(expected_file)) ++ ++ test_func = functools.partial( ++ bgp_ls_router_json_cmp, ++ rr, ++ expected, ++ ) ++ _, result = topotest.run_and_expect(test_func, None, count=60, wait=0.5) ++ assertmsg = 'BGP-LS NLRIs do not match expected baseline output after router-id reset' ++ assert result is None, assertmsg ++ ++ logger.info("Static route removal/restore test completed successfully") ++ ++ ++if __name__ == "__main__": ++ args = ["-s"] + sys.argv[1:] ++ sys.exit(pytest.main(args)) diff --git a/vtysh/vtysh.c b/vtysh/vtysh.c -index 6c0d9fefc6..dfbc492496 100644 +index 3123f39ba1..0d1728efe8 100644 --- a/vtysh/vtysh.c +++ b/vtysh/vtysh.c @@ -1517,6 +1517,13 @@ static struct cmd_node bgp_srv6_node = { @@ -17861,7 +20819,7 @@ index 6c0d9fefc6..dfbc492496 100644 vty->node = BGP_NODE; return CMD_SUCCESS; } -@@ -5090,6 +5108,7 @@ void vtysh_init_vty(void) +@@ -5167,6 +5185,7 @@ void vtysh_init_vty(void) install_node(&rpki_node); install_node(&bmp_node); install_node(&bgp_srv6_node); @@ -17869,7 +20827,7 @@ index 6c0d9fefc6..dfbc492496 100644 install_node(&rip_node); install_node(&ripng_node); install_node(&ospf_node); -@@ -5270,6 +5289,12 @@ void vtysh_init_vty(void) +@@ -5347,6 +5366,12 @@ void vtysh_init_vty(void) install_element(BGP_SRV6_NODE, &exit_bgp_srv6_cmd); install_element(BGP_SRV6_NODE, &quit_bgp_srv6_cmd); install_element(BGP_SRV6_NODE, &vtysh_end_all_cmd); @@ -17883,10 +20841,10 @@ index 6c0d9fefc6..dfbc492496 100644 /* ripd */ diff --git a/zebra/connected.c b/zebra/connected.c -index 0a4d9220b4..9a9ec96759 100644 +index 0a2744ef71..d30a07fb84 100644 --- a/zebra/connected.c +++ b/zebra/connected.c -@@ -283,6 +283,7 @@ void connected_up(struct interface *ifp, struct connected *ifc) +@@ -284,6 +284,7 @@ void connected_up(struct interface *ifp, struct connected *ifc) break; case AFI_UNSPEC: case AFI_L2VPN: @@ -17894,7 +20852,7 @@ index 0a4d9220b4..9a9ec96759 100644 case AFI_MAX: flog_warn(EC_ZEBRA_CONNECTED_AFI_UNKNOWN, "Received unknown AFI: %s", afi2str(afi)); -@@ -496,6 +497,7 @@ void connected_down(struct interface *ifp, struct connected *ifc) +@@ -494,6 +495,7 @@ void connected_down(struct interface *ifp, struct connected *ifc) break; case AFI_UNSPEC: case AFI_L2VPN: @@ -17923,10 +20881,10 @@ index 945f09cd86..bf91f5f7e2 100644 return -1; } diff --git a/zebra/zapi_msg.c b/zebra/zapi_msg.c -index bd8ee28b21..b35d5bda28 100644 +index 79b377754a..ad68dbe84d 100644 --- a/zebra/zapi_msg.c +++ b/zebra/zapi_msg.c -@@ -547,6 +547,7 @@ int zsend_redistribute_route(int cmd, struct zserv *client, const struct route_n +@@ -550,6 +550,7 @@ int zsend_redistribute_route(int cmd, struct zserv *client, const struct route_n break; case AFI_L2VPN: case AFI_MAX: @@ -17935,10 +20893,10 @@ index bd8ee28b21..b35d5bda28 100644 break; } diff --git a/zebra/zebra_nhg.c b/zebra/zebra_nhg.c -index ccb354cf86..5673d39e69 100644 +index c77b047661..5ef2fd610f 100644 --- a/zebra/zebra_nhg.c +++ b/zebra/zebra_nhg.c -@@ -2355,6 +2355,7 @@ static int nexthop_active(struct nexthop *nexthop, struct nhg_hash_entry *nhe, +@@ -2374,6 +2374,7 @@ static int nexthop_active(struct nexthop *nexthop, struct nhg_hash_entry *nhe, break; case AFI_UNSPEC: case AFI_L2VPN: @@ -17946,7 +20904,7 @@ index ccb354cf86..5673d39e69 100644 case AFI_MAX: flog_err(EC_LIB_DEVELOPMENT, "%s: unknown address-family: %u", __func__, -@@ -2398,6 +2399,7 @@ static int nexthop_active(struct nexthop *nexthop, struct nhg_hash_entry *nhe, +@@ -2416,6 +2417,7 @@ static int nexthop_active(struct nexthop *nexthop, struct nhg_hash_entry *nhe, break; case AFI_UNSPEC: case AFI_L2VPN: diff --git a/scripts/package-build/frr/patches/frr/0021-pathd-add-optional-protocol-to-no-mpls-te-import.patch b/scripts/package-build/frr/patches/frr/0021-pathd-add-optional-protocol-to-no-mpls-te-import.patch index ae24b7c7..aec83562 100644 --- a/scripts/package-build/frr/patches/frr/0021-pathd-add-optional-protocol-to-no-mpls-te-import.patch +++ b/scripts/package-build/frr/patches/frr/0021-pathd-add-optional-protocol-to-no-mpls-te-import.patch @@ -1,4 +1,4 @@ -From fb892c9871e984fed3e240f4b4da5089dd9cbd8f Mon Sep 17 00:00:00 2001 +From 6d9e1d3bdeb8dee3d482cd98bcb598730c2fca7a Mon Sep 17 00:00:00 2001 From: Kyrylo Yatsenko <hedrok@gmail.com> Date: Mon, 20 Apr 2026 23:02:27 +0300 Subject: [PATCH 1/2] pathd: add optional protocol to 'no mpls-te import' @@ -14,12 +14,13 @@ To fix: * Create helper function `path_ted_start_importing_igp`. Signed-off-by: Kyrylo Yatsenko <hedrok@gmail.com> +(cherry picked from commit d421a3360bdb6eba18777388325b003b8bc985bc) --- pathd/path_ted.c | 92 ++++++++++++++++++++++++++++-------------------- 1 file changed, 53 insertions(+), 39 deletions(-) diff --git a/pathd/path_ted.c b/pathd/path_ted.c -index a7442e7080..caa19178fd 100644 +index 05bd8cbf93..9dcd398d43 100644 --- a/pathd/path_ted.c +++ b/pathd/path_ted.c @@ -25,11 +25,12 @@ @@ -30,8 +31,8 @@ index a7442e7080..caa19178fd 100644 +static uint32_t path_ted_start_importing_igp(enum igp_import daemon); static uint32_t path_ted_stop_importing_igp(void); static enum zclient_send_status path_ted_link_state_sync(void); - static void path_ted_timer_handler_sync(struct event *thread); - static void path_ted_timer_handler_refresh(struct event *thread); + static void path_ted_timer_handler_sync(struct event *event); + static void path_ted_timer_handler_refresh(struct event *event); +static enum igp_import str2igp_import(const char *str); extern struct zclient *pathd_zclient; diff --git a/scripts/package-build/frr/patches/frr/0022-pathd-add-optional-parameters-to-no-index-cmd.patch b/scripts/package-build/frr/patches/frr/0022-pathd-add-optional-parameters-to-no-index-cmd.patch index 69841508..32193f88 100644 --- a/scripts/package-build/frr/patches/frr/0022-pathd-add-optional-parameters-to-no-index-cmd.patch +++ b/scripts/package-build/frr/patches/frr/0022-pathd-add-optional-parameters-to-no-index-cmd.patch @@ -1,4 +1,4 @@ -From f53e78b89a08d9dfaeb58789567429b714112470 Mon Sep 17 00:00:00 2001 +From c1a42d7b8774f2ec81a6946f96fa1238b6539115 Mon Sep 17 00:00:00 2001 From: Kyrylo Yatsenko <hedrok@gmail.com> Date: Wed, 22 Apr 2026 09:32:22 +0300 Subject: [PATCH 2/2] pathd: add optional parameters to `no index` cmd @@ -10,6 +10,7 @@ Merge `no index` and `index` commands to fix this. `no` version ignores all additional parameters, which are made optional in case of `no`. Signed-off-by: Kyrylo Yatsenko <hedrok@gmail.com> +(cherry picked from commit 7735ca88019a4fc6a9affee9df276b9c99773148) --- pathd/path_cli.c | 36 ++++++++++++------------------------ 1 file changed, 12 insertions(+), 24 deletions(-) |
