summaryrefslogtreecommitdiff
path: root/scripts/package-build
diff options
context:
space:
mode:
authorChristian Breunig <christian@breunig.cc>2026-05-11 21:08:49 +0200
committerGitHub <noreply@github.com>2026-05-11 21:08:49 +0200
commitb0810fb7930534e97dbd7a69417ec24ceed78163 (patch)
tree62f0915184ce9b2a4471f92e8bfdf7b3623f07d5 /scripts/package-build
parentc1d6991e8b8611ac89334e06e40981c8fbcffeab (diff)
parentda230dab2560a12aeb0758df2896ca8831a9a1ed (diff)
downloadvyos-build-b0810fb7930534e97dbd7a69417ec24ceed78163.tar.gz
vyos-build-b0810fb7930534e97dbd7a69417ec24ceed78163.zip
Merge pull request #1183 from hedrok/T8612-upgrate-to-frr-10.6
T8612: upgrade to frr 10.6.1
Diffstat (limited to 'scripts/package-build')
-rw-r--r--scripts/package-build/frr/package.toml2
-rw-r--r--scripts/package-build/frr/patches/frr/0008-isis-fix-advertise-passive-only-routes-install.patch133
-rw-r--r--scripts/package-build/frr/patches/frr/0009-T7909-eigrp-malformed-update-fix.patch101
-rw-r--r--scripts/package-build/frr/patches/frr/0010-bgp-Support-multiple-labels-in-BGP-LU.patch596
-rw-r--r--scripts/package-build/frr/patches/frr/0011-zebra-add-CLI-no-versions-for-max-bw-and-others.patch222
-rw-r--r--scripts/package-build/frr/patches/frr/0014-Simplify-and-cleanup-mgmtd.patch12048
-rw-r--r--scripts/package-build/frr/patches/frr/0019-ospf6d-Fix-router-LSA-when-no-new-BDR-elected.patch21
-rw-r--r--scripts/package-build/frr/patches/frr/0020-BGP-LS-support.patch8956
-rw-r--r--scripts/package-build/frr/patches/frr/0021-pathd-add-optional-protocol-to-no-mpls-te-import.patch9
-rw-r--r--scripts/package-build/frr/patches/frr/0022-pathd-add-optional-parameters-to-no-index-cmd.patch3
-rw-r--r--scripts/package-build/frr/patches/frr/0023-ospf6d-Fix-command-output-for-default-route.patch60
11 files changed, 6036 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(-)
diff --git a/scripts/package-build/frr/patches/frr/0023-ospf6d-Fix-command-output-for-default-route.patch b/scripts/package-build/frr/patches/frr/0023-ospf6d-Fix-command-output-for-default-route.patch
new file mode 100644
index 00000000..efd9ebec
--- /dev/null
+++ b/scripts/package-build/frr/patches/frr/0023-ospf6d-Fix-command-output-for-default-route.patch
@@ -0,0 +1,60 @@
+From c58e42c5c9bc2bcbeb8af5c8d259b4d0b500cbde Mon Sep 17 00:00:00 2001
+From: Kyrylo Yatsenko <hedrok@gmail.com>
+Date: Thu, 7 May 2026 15:42:51 +0300
+Subject: [PATCH] ospf6d: Fix command output for default route
+
+Commit 026f24989fd2451975bbf5edffd7d4a443d10a44 introduced a bug:
+
+When entering configuration
+
+----
+route-map foo-bar-3 permit 10
+exit
+router ospf6
+ default-information originate metric-type 1 route-map foo-bar-3
+exit
+----
+
+Then `write terminal` outputs:
+
+----
+router ospf6
+ redistribute unknown metric-type 1 route-map foo-bar-3
+ default-information originate metric-type 1 route-map foo-bar-3
+exit
+---
+
+`redistribute unknown metric-type 1 route-map foo-bar-3` is invalid
+command, after this `frr-reload.py` is totally ununsable even with
+`--test` option.
+
+Fix by reverting one line of that commit, in output loop it is needed to
+have `< ZEBRA_ROUTE_MAX`, not `<=`.
+
+Signed-off-by: Kyrylo Yatsenko <hedrok@gmail.com>
+---
+ ospf6d/ospf6_asbr.c | 8 +++++++-
+ 1 file changed, 7 insertions(+), 1 deletion(-)
+
+diff --git a/ospf6d/ospf6_asbr.c b/ospf6d/ospf6_asbr.c
+index d3806062a8..c0fbe308b7 100644
+--- a/ospf6d/ospf6_asbr.c
++++ b/ospf6d/ospf6_asbr.c
+@@ -1809,7 +1809,13 @@ int ospf6_redistribute_config_write(struct vty *vty, struct ospf6 *ospf6)
+ int type;
+ struct ospf6_redist *red;
+
+- for (type = 0; type <= ZEBRA_ROUTE_MAX; type++) {
++ /*
++ * type < ZEBRA_ROUTE_MAX is intentional here: otherwise
++ * for default routes invalid command
++ * no redistribute unknown metric-type 1 route-map foo-bar-baz-0815
++ * will be output
++ */
++ for (type = 0; type < ZEBRA_ROUTE_MAX; type++) {
+ red = ospf6_redist_lookup(ospf6, type, 0);
+ if (!red)
+ continue;
+--
+2.51.2
+