summaryrefslogtreecommitdiff
path: root/scripts/package-build
diff options
context:
space:
mode:
authorChristian Breunig <christian@breunig.cc>2026-10-10 21:59:03 +0200
committerChristian Breunig <christian@breunig.cc>2026-10-10 21:59:03 +0200
commit8ff706153243997fc33d2e7da735942c88596291 (patch)
treeaa23694dcca7431e19ed94e85e38362dc07ca46c /scripts/package-build
parentf5a1e8334f772225d9e3ccdab45f6884cec28e59 (diff)
downloadvyos-build-8ff706153243997fc33d2e7da735942c88596291.tar.gz
vyos-build-8ff706153243997fc33d2e7da735942c88596291.zip
T9454: Upgrade FRR to 10.7.1
Exactly it will be per Git describe: frr-10.7.1-60-g45b345f38f
Diffstat (limited to 'scripts/package-build')
-rw-r--r--scripts/package-build/frr/package.toml4
-rw-r--r--scripts/package-build/frr/patches/frr/0001-Enable-PCRE2-in-Debian-package-builds.patch8
-rw-r--r--scripts/package-build/frr/patches/frr/0002-ospf6d-Fix-router-LSA-when-no-new-BDR-elected.patch (renamed from scripts/package-build/frr/patches/frr/0019-ospf6d-Fix-router-LSA-when-no-new-BDR-elected.patch)6
-rw-r--r--scripts/package-build/frr/patches/frr/0003-zebra-enhance-MACVLAN-support-in-EVPN.patch (renamed from scripts/package-build/frr/patches/frr/0024-zebra-enhance-MACVLAN-support-in-EVPN.patch)44
-rw-r--r--scripts/package-build/frr/patches/frr/0004-tests-BGP-LS-Adjacency-SID-TLV-1099.patch803
-rw-r--r--scripts/package-build/frr/patches/frr/0007-zebra-remove-kernel-route-on-last-address-deletion.patch214
-rw-r--r--scripts/package-build/frr/patches/frr/0012-Revert-tools-Allow-deleting-of-interfaces.patch37
-rw-r--r--scripts/package-build/frr/patches/frr/0016-zebra-Add-no-encapsulation-under-segment-routing-srv.patch83
-rw-r--r--scripts/package-build/frr/patches/frr/0017-pathd-support-no-traffic-eng-command-fixes.patch464
-rw-r--r--scripts/package-build/frr/patches/frr/0020-BGP-LS-support.patch20917
-rw-r--r--scripts/package-build/frr/patches/frr/0021-pathd-add-optional-protocol-to-no-mpls-te-import.patch178
-rw-r--r--scripts/package-build/frr/patches/frr/0022-pathd-add-optional-parameters-to-no-index-cmd.patch94
-rw-r--r--scripts/package-build/frr/patches/frr/0023-ospf6d-Fix-command-output-for-default-route.patch60
-rw-r--r--scripts/package-build/frr/patches/frr/0025-bgpd-fix-bmp-connect-deletion-with-source-interface.patch31
14 files changed, 834 insertions, 22109 deletions
diff --git a/scripts/package-build/frr/package.toml b/scripts/package-build/frr/package.toml
index 183358e0..a614c41b 100644
--- a/scripts/package-build/frr/package.toml
+++ b/scripts/package-build/frr/package.toml
@@ -2,11 +2,11 @@
name = "libyang"
commit_id = "v3.13.6"
scm_url = "https://github.com/CESNET/libyang.git"
-build_cmd = "pipx run apkg build -i && find pkg/pkgs -type f -name *.deb -exec mv -t .. {} +"
+build_cmd = "pipx run apkg build -i && find pkg/pkgs -type f -name *.deb -exec mv -t .. {} + && sudo dpkg -i ../libyang*.deb"
[[packages]]
name = "frr"
-commit_id = "71da51baee6fb2a02b24262defc46591c86e8a81" # tag frr-10.6.1
+commit_id = "45b345f38f51f" # stable/10.7 branch
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/0001-Enable-PCRE2-in-Debian-package-builds.patch b/scripts/package-build/frr/patches/frr/0001-Enable-PCRE2-in-Debian-package-builds.patch
index 545e7d5e..fc837fb0 100644
--- a/scripts/package-build/frr/patches/frr/0001-Enable-PCRE2-in-Debian-package-builds.patch
+++ b/scripts/package-build/frr/patches/frr/0001-Enable-PCRE2-in-Debian-package-builds.patch
@@ -1,4 +1,4 @@
-From 21800432167ac022c01772df993efca8d4969b38 Mon Sep 17 00:00:00 2001
+From 20ff690dcf8ab7e800e1dc0c4fd83041d039fb3d Mon Sep 17 00:00:00 2001
From: Daniil Baturin <daniil@baturin.org>
Date: Wed, 6 Nov 2024 15:58:10 +0000
Subject: [PATCH] Enable PCRE2 in Debian package builds
@@ -8,10 +8,10 @@ Subject: [PATCH] Enable PCRE2 in Debian package builds
1 file changed, 1 insertion(+)
diff --git a/debian/rules b/debian/rules
-index 43e5d7e61..1f971ab22 100755
+index 21094c62d9..ea248ff528 100755
--- a/debian/rules
+++ b/debian/rules
-@@ -69,6 +69,7 @@ override_dh_auto_configure:
+@@ -89,6 +89,7 @@ override_dh_auto_configure:
--enable-vty-group=frrvty \
--enable-configfile-mask=0640 \
--enable-logfile-mask=0640 \
@@ -20,5 +20,5 @@ index 43e5d7e61..1f971ab22 100755
override_dh_auto_install:
--
-2.47.0
+2.39.5
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/0002-ospf6d-Fix-router-LSA-when-no-new-BDR-elected.patch
index 80539471..88da99af 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/0002-ospf6d-Fix-router-LSA-when-no-new-BDR-elected.patch
@@ -1,4 +1,4 @@
-From 0f1808a6a89c71bf7dea1d351c9cd81007638a1a Mon Sep 17 00:00:00 2001
+From 7a8296a9024fa007624b9adba5e29879f6d376de 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
@@ -28,7 +28,7 @@ Signed-off-by: Kyrylo Yatsenko <hedrok@gmail.com>
1 file changed, 10 insertions(+), 3 deletions(-)
diff --git a/ospf6d/ospf6_interface.c b/ospf6d/ospf6_interface.c
-index 7f16a96d4d..74aa013bfb 100644
+index e1ce158660..f74bf0725e 100644
--- a/ospf6d/ospf6_interface.c
+++ b/ospf6d/ospf6_interface.c
@@ -951,6 +951,7 @@ void backup_seen(struct event *event)
@@ -59,5 +59,5 @@ index 7f16a96d4d..74aa013bfb 100644
void interface_down(struct event *event)
--
-2.51.2
+2.39.5
diff --git a/scripts/package-build/frr/patches/frr/0024-zebra-enhance-MACVLAN-support-in-EVPN.patch b/scripts/package-build/frr/patches/frr/0003-zebra-enhance-MACVLAN-support-in-EVPN.patch
index 16fd469c..f63fcbcb 100644
--- a/scripts/package-build/frr/patches/frr/0024-zebra-enhance-MACVLAN-support-in-EVPN.patch
+++ b/scripts/package-build/frr/patches/frr/0003-zebra-enhance-MACVLAN-support-in-EVPN.patch
@@ -1,4 +1,4 @@
-From 0ed0db22759e229698ce266c6535be407c865737 Mon Sep 17 00:00:00 2001
+From 3ea79edc7460154c210f5df048917b735dd2f547 Mon Sep 17 00:00:00 2001
From: Kyrylo Yatsenko <hedrok@gmail.com>
Date: Wed, 27 May 2026 18:21:04 +0300
Subject: [PATCH] zebra: enhance MACVLAN support in EVPN
@@ -23,10 +23,10 @@ Signed-off-by: Kyrylo Yatsenko <hedrok@gmail.com>
3 files changed, 57 insertions(+), 20 deletions(-)
diff --git a/zebra/zebra_evpn.c b/zebra/zebra_evpn.c
-index 258e818ec5..7f8f77b2c2 100644
+index 945ec8541f..eb773460e1 100644
--- a/zebra/zebra_evpn.c
+++ b/zebra/zebra_evpn.c
-@@ -963,13 +963,13 @@ void zebra_evpn_read_mac_neigh(struct zebra_evpn *zevpn, struct interface *ifp)
+@@ -968,13 +968,13 @@ void zebra_evpn_read_mac_neigh(struct zebra_evpn *zevpn, struct interface *ifp)
zebra_evpn_add_macip_for_intf(vlan_if, zevpn);
/* Add VRR MAC-IP - if any*/
@@ -39,17 +39,17 @@ index 258e818ec5..7f8f77b2c2 100644
+ if (vrr_if && advertise_gw_macip_enabled(zevpn))
+ zebra_evpn_add_macip_for_intf(vrr_if, zevpn);
- neigh_read_for_vlan(zns, vlan_if);
+ dplane_neigh_read_for_vlan(zns, vlan_if);
+ if (vrr_if)
-+ neigh_read_for_vlan(zns, vrr_if);
++ dplane_neigh_read_for_vlan(zns, vrr_if);
}
}
diff --git a/zebra/zebra_evpn_neigh.c b/zebra/zebra_evpn_neigh.c
-index d66aad50d8..b21aae76aa 100644
+index 69870b88fe..5297eeaa78 100644
--- a/zebra/zebra_evpn_neigh.c
+++ b/zebra/zebra_evpn_neigh.c
-@@ -30,6 +30,7 @@
+@@ -31,6 +31,7 @@
#include "zebra/zebra_evpn_mh.h"
#include "zebra/zebra_evpn_neigh.h"
#include "zebra/zebra_evpn_mac.h"
@@ -57,7 +57,7 @@ index d66aad50d8..b21aae76aa 100644
DEFINE_MTYPE_STATIC(ZEBRA, NEIGH, "EVI Neighbor");
-@@ -149,6 +150,7 @@ int zebra_evpn_rem_neigh_install(struct zebra_evpn *zevpn,
+@@ -97,6 +98,7 @@ int zebra_evpn_rem_neigh_install(struct zebra_evpn *zevpn,
struct zebra_neigh *n, bool was_static)
{
struct interface *vlan_if;
@@ -65,7 +65,7 @@ index d66aad50d8..b21aae76aa 100644
int flags;
int ret = 0;
-@@ -166,6 +168,10 @@ int zebra_evpn_rem_neigh_install(struct zebra_evpn *zevpn,
+@@ -114,6 +116,10 @@ int zebra_evpn_rem_neigh_install(struct zebra_evpn *zevpn,
dplane_rem_neigh_add(vlan_if, &n->ip, &n->emac, flags, was_static);
@@ -76,7 +76,7 @@ index d66aad50d8..b21aae76aa 100644
return ret;
}
-@@ -858,6 +864,7 @@ static int zebra_evpn_neigh_uninstall(struct zebra_evpn *zevpn,
+@@ -802,6 +808,7 @@ static int zebra_evpn_neigh_uninstall(struct zebra_evpn *zevpn,
struct zebra_neigh *n)
{
struct interface *vlan_if;
@@ -84,7 +84,7 @@ index d66aad50d8..b21aae76aa 100644
if (!(n->flags & ZEBRA_NEIGH_REMOTE))
return 0;
-@@ -871,6 +878,10 @@ static int zebra_evpn_neigh_uninstall(struct zebra_evpn *zevpn,
+@@ -815,6 +822,10 @@ static int zebra_evpn_neigh_uninstall(struct zebra_evpn *zevpn,
dplane_rem_neigh_delete(vlan_if, &n->ip);
@@ -96,10 +96,10 @@ index d66aad50d8..b21aae76aa 100644
}
diff --git a/zebra/zebra_neigh.c b/zebra/zebra_neigh.c
-index 6ba4aed7f9..dfb951da8b 100644
+index bb268a1f85..1fb1fe6021 100644
--- a/zebra/zebra_neigh.c
+++ b/zebra/zebra_neigh.c
-@@ -370,12 +370,14 @@ static void zebra_neigh_ipaddr_update(struct zebra_dplane_ctx *ctx)
+@@ -448,12 +448,14 @@ static void zebra_neigh_ipaddr_update(struct zebra_dplane_ctx *ctx)
ns_id_t ns_id;
int32_t ndm_ifindex;
struct interface *ifp;
@@ -115,7 +115,7 @@ index 6ba4aed7f9..dfb951da8b 100644
struct ethaddr mac;
bool is_own;
bool is_router;
-@@ -432,6 +434,28 @@ static void zebra_neigh_ipaddr_update(struct zebra_dplane_ctx *ctx)
+@@ -510,6 +512,28 @@ static void zebra_neigh_ipaddr_update(struct zebra_dplane_ctx *ctx)
if (op == DPLANE_OP_NEIGH_DISCOVER)
return;
@@ -144,7 +144,7 @@ index 6ba4aed7f9..dfb951da8b 100644
/* The neighbor is present on an SVI. From this, we locate the
* underlying
* bridge because we're only interested in neighbors on a VxLAN bridge.
-@@ -443,17 +467,19 @@ static void zebra_neigh_ipaddr_update(struct zebra_dplane_ctx *ctx)
+@@ -521,17 +545,19 @@ static void zebra_neigh_ipaddr_update(struct zebra_dplane_ctx *ctx)
* interface
* itself
*/
@@ -175,8 +175,8 @@ index 6ba4aed7f9..dfb951da8b 100644
}
if (op == DPLANE_OP_NEIGH_IP_INSTALL) {
-@@ -477,7 +503,7 @@ static void zebra_neigh_ipaddr_update(struct zebra_dplane_ctx *ctx)
- zebra_neigh_add(ifp, &ip, &mac);
+@@ -555,7 +581,7 @@ static void zebra_neigh_ipaddr_update(struct zebra_dplane_ctx *ctx)
+ zebra_neigh_add(ns_id, ifp, &ip, &mac, ndm_state);
if (link_if)
- zebra_vxlan_handle_kernel_neigh_update(ifp, link_if, &ip, &mac,
@@ -184,18 +184,18 @@ index 6ba4aed7f9..dfb951da8b 100644
ndm_state, is_own, is_router,
local_inactive, dp_static);
return;
-@@ -485,7 +511,7 @@ static void zebra_neigh_ipaddr_update(struct zebra_dplane_ctx *ctx)
+@@ -563,7 +589,7 @@ static void zebra_neigh_ipaddr_update(struct zebra_dplane_ctx *ctx)
- zebra_neigh_del(ifp, &ip);
+ zebra_neigh_del(ns_id, ifp, &ip);
if (link_if)
- zebra_vxlan_handle_kernel_neigh_del(ifp, link_if, &ip);
+ zebra_vxlan_handle_kernel_neigh_del(svi_ifp, link_if, &ip);
return;
}
-@@ -499,7 +525,7 @@ static void zebra_neigh_ipaddr_update(struct zebra_dplane_ctx *ctx)
+@@ -577,7 +603,7 @@ static void zebra_neigh_ipaddr_update(struct zebra_dplane_ctx *ctx)
*/
- zebra_neigh_del(ifp, &ip);
+ zebra_neigh_del(ns_id, ifp, &ip);
if (link_if)
- zebra_vxlan_handle_kernel_neigh_del(ifp, link_if, &ip);
+ zebra_vxlan_handle_kernel_neigh_del(svi_ifp, link_if, &ip);
@@ -203,5 +203,5 @@ index 6ba4aed7f9..dfb951da8b 100644
static void zebra_neigh_macfdb_update(struct zebra_dplane_ctx *ctx)
--
-2.51.2
+2.39.5
diff --git a/scripts/package-build/frr/patches/frr/0004-tests-BGP-LS-Adjacency-SID-TLV-1099.patch b/scripts/package-build/frr/patches/frr/0004-tests-BGP-LS-Adjacency-SID-TLV-1099.patch
new file mode 100644
index 00000000..7edd21f9
--- /dev/null
+++ b/scripts/package-build/frr/patches/frr/0004-tests-BGP-LS-Adjacency-SID-TLV-1099.patch
@@ -0,0 +1,803 @@
+From 7b51fe0ff14f1a44520a82281518fe60cdf9c9b0 Mon Sep 17 00:00:00 2001
+From: Kyrylo Yatsenko <hedrok@gmail.com>
+Date: Thu, 30 Apr 2026 12:36:54 +0300
+Subject: [PATCH 1/3] tests: BGP-LS Adjacency SID (TLV 1099)
+
+Signed-off-by: Kyrylo Yatsenko <hedrok@gmail.com>
+---
+ .../bgp_link_state/rr/bgp_ls_attrs_link43.json | 14 ++++++++++++++
+ .../bgp_link_state/test_bgp_link_state.py | 15 +++++++++++++++
+ 2 files changed, 29 insertions(+)
+ create mode 100644 tests/topotests/bgp_link_state/rr/bgp_ls_attrs_link43.json
+
+diff --git a/tests/topotests/bgp_link_state/rr/bgp_ls_attrs_link43.json b/tests/topotests/bgp_link_state/rr/bgp_ls_attrs_link43.json
+new file mode 100644
+index 000000000000..4ce4cb631b4d
+--- /dev/null
++++ b/tests/topotests/bgp_link_state/rr/bgp_ls_attrs_link43.json
+@@ -0,0 +1,14 @@
++{
++ "paths": [
++ {
++ "linkStateAttrs": {
++ "adjSids": [
++ {
++ "flags": "0x30",
++ "weight": 0
++ }
++ ]
++ }
++ }
++ ]
++}
+diff --git a/tests/topotests/bgp_link_state/test_bgp_link_state.py b/tests/topotests/bgp_link_state/test_bgp_link_state.py
+index fba67d0f54aa..cfb0ab8bd9d0 100644
+--- a/tests/topotests/bgp_link_state/test_bgp_link_state.py
++++ b/tests/topotests/bgp_link_state/test_bgp_link_state.py
+@@ -473,6 +473,21 @@ def test_bgp_ls_attributes_consumer():
+ assertmsg = '"rr" BGP-LS node attributes not received correctly'
+ assert result is None, assertmsg
+
++ # Check BGP-LS attributes are received for link
++ reffile = os.path.join(CWD, "rr/bgp_ls_attrs_link43.json")
++ expected = json.loads(open(reffile).read())
++
++ test_func = functools.partial(
++ topotest.router_json_cmp,
++ router,
++ "show bgp link-state link-state [E][L2][I0x0][N[s0000.0000.0004.00]][R[s0000.0000.0003.00]][L[i10.0.5.4][n10.0.5.3]] json",
++ expected,
++ )
++ _, result = topotest.run_and_expect(test_func, None, count=60, wait=1)
++ assertmsg = '"rr" BGP-LS link attributes not received correctly'
++ assert result is None, assertmsg
++
++
+
+ def test_bgp_ls_static_route_add():
+ """Test adding a static route and verifying BGP-LS update"""
+
+From 369b7720f5f3c256855657db512ceb579ebe2b1d Mon Sep 17 00:00:00 2001
+From: Kyrylo Yatsenko <hedrok@gmail.com>
+Date: Tue, 12 May 2026 09:20:20 +0300
+Subject: [PATCH 2/3] bgpd: BGP-LS: add protocol id to attr processing
+
+Some TLVs need protocol id to be parsed/encoded properly.
+
+Add `protocol_id` argument to `bgp_ls_encode_attr`,
+`bgp_packet_ls_attribute` and `bgp_ls_parse_attr`.
+
+`bgp_ls_parse_attr` is run before NLRI decoding, so to get protocol id
+before that add `bgp_ls_decode_nlri_protocol_id` that decodes only
+protocol id from NLRI.
+
+Signed-off-by: Kyrylo Yatsenko <hedrok@gmail.com>
+---
+ bgpd/bgp_attr.c | 59 +++++++++++++++++++++---
+ bgpd/bgp_ls_nlri.c | 97 +++++++++++++++++++++++++++++++++++++++-
+ bgpd/bgp_ls_nlri.h | 14 +++++-
+ bgpd/bgp_ls_ted.c | 5 ++-
+ bgpd/bgp_ls_ted.h | 6 ++-
+ bgpd/bgp_updgrp_packet.c | 2 +-
+ 6 files changed, 169 insertions(+), 14 deletions(-)
+
+diff --git a/bgpd/bgp_attr.c b/bgpd/bgp_attr.c
+index 23bd6185f142..50ec97a0fd6c 100644
+--- a/bgpd/bgp_attr.c
++++ b/bgpd/bgp_attr.c
+@@ -4271,8 +4271,52 @@ static enum bgp_attr_parse_ret bgp_attr_otc(struct bgp_attr_parser_args *args)
+ return bgp_attr_ignore(peer, args->type);
+ }
+
++/* Get Protocol-ID from NLRIs, if both present they should have equal Protocol-ID */
++static enum bgp_ls_protocol_id get_nlri_protocol_id(struct bgp_nlri *mp_update, struct bgp_nlri *mp_withdraw)
++{
++ int ret;
++ enum bgp_ls_protocol_id protocol_id_update;
++ enum bgp_ls_protocol_id protocol_id_withdraw;
++
++ if (mp_update->nlri) {
++ ret = bgp_ls_decode_nlri_protocol_id(mp_update, &protocol_id_update);
++ if (ret == -1) {
++ zlog_warn("%s: couldn't decode protocol id of UPDATE, setting reserved",
++ __func__);
++ return BGP_LS_PROTO_RESERVED;
++ }
++ }
++
++ if (mp_withdraw->nlri) {
++ ret = bgp_ls_decode_nlri_protocol_id(mp_withdraw, &protocol_id_withdraw);
++ if (ret == -1) {
++ zlog_warn("%s: couldn't decode protocol id of WITHDRAW, setting reserved",
++ __func__);
++ return BGP_LS_PROTO_RESERVED;
++ }
++ }
++
++ if (mp_update->nlri && mp_withdraw->nlri) {
++ if (protocol_id_update != protocol_id_withdraw) {
++ zlog_warn("%s: different protocol ids in UPDATE and WITHDRAW, setting reserved",
++ __func__);
++ return BGP_LS_PROTO_RESERVED;
++ }
++ return protocol_id_update;
++ }
++
++ if (mp_update->nlri)
++ return protocol_id_update;
++
++ if (mp_withdraw->nlri)
++ return protocol_id_withdraw;
++
++ return BGP_LS_PROTO_RESERVED;
++}
++
+ /* BGP-LS attribute (rfc9552) */
+-static enum bgp_attr_parse_ret bgp_attr_ls(struct bgp_attr_parser_args *args)
++static enum bgp_attr_parse_ret bgp_attr_ls(struct bgp_attr_parser_args *args,
++ struct bgp_nlri *mp_update, struct bgp_nlri *mp_withdraw)
+ {
+ struct peer_connection *const connection = args->connection;
+ struct peer *const peer = connection->peer;
+@@ -4280,13 +4324,16 @@ static enum bgp_attr_parse_ret bgp_attr_ls(struct bgp_attr_parser_args *args)
+ const size_t attr_startp = stream_get_getp(connection->curr);
+ int ret;
+ struct bgp_ls_attr *ls_attr;
++ enum bgp_ls_protocol_id protocol_id;
+
+ if (peer->discard_attrs[args->type] || peer->withdraw_attrs[args->type])
+ goto ls_attr_ignore;
+
++ protocol_id = get_nlri_protocol_id(mp_update, mp_withdraw);
++
+ ls_attr = bgp_ls_attr_alloc();
+
+- ret = bgp_ls_parse_attr(connection->curr, args->length, ls_attr);
++ ret = bgp_ls_parse_attr(connection->curr, args->length, protocol_id, ls_attr);
+ if (ret != 0) {
+ bgp_ls_attr_free(ls_attr);
+ flog_warn(EC_BGP_LS_PACKET, "%s: malformed BGP-LS Attribute, discarding attribute",
+@@ -4670,7 +4717,7 @@ enum bgp_attr_parse_ret bgp_attr_parse(struct peer_connection *connection, struc
+ ret = bgp_attr_nhc(&attr_args);
+ break;
+ case BGP_ATTR_LINK_STATE:
+- ret = bgp_attr_ls(&attr_args);
++ ret = bgp_attr_ls(&attr_args, mp_update, mp_withdraw);
+ break;
+ default:
+ ret = bgp_attr_unknown(&attr_args);
+@@ -5169,7 +5216,7 @@ static void bgp_packet_nhc(struct stream *s, struct peer *peer, afi_t afi, safi_
+ }
+
+ static void bgp_packet_ls_attribute(struct stream *s, struct bgp *bgp, struct attr *attr,
+- struct bgp_path_info *bpi)
++ struct bgp_path_info *bpi, enum bgp_ls_protocol_id protocol_id)
+ {
+ struct bgp_ls_attr *ls_attr = bgp_attr_get_ls_attr(attr);
+ size_t attr_start, len_pos, attr_len;
+@@ -5182,7 +5229,7 @@ static void bgp_packet_ls_attribute(struct stream *s, struct bgp *bgp, struct at
+ len_pos = stream_get_endp(s);
+ stream_putw(s, 0); /* Placeholder for extended length */
+
+- ret = bgp_ls_encode_attr(s, ls_attr);
++ ret = bgp_ls_encode_attr(s, ls_attr, protocol_id);
+
+ if (ret < 0) {
+ /* No attributes or encoding failed - rollback the stream */
+@@ -6122,7 +6169,7 @@ bgp_size_t bgp_packet_attribute(struct bgp *bgp, struct peer *peer, struct strea
+
+ /* BGP-LS Attribute (Type 29) - RFC 9552 Section 4 */
+ if (afi == AFI_BGP_LS && safi == SAFI_BGP_LS && bgp_attr_get_ls_attr(attr))
+- bgp_packet_ls_attribute(s, bgp, attr, bpi);
++ bgp_packet_ls_attribute(s, bgp, attr, bpi, bgp_ls_nlri_protocol_id(ls_nlri));
+
+ /* draft-ietf-idr-entropy-label */
+ if (peergroup_flag_check(peer, PEER_FLAG_SEND_NHC_ATTRIBUTE))
+diff --git a/bgpd/bgp_ls_nlri.c b/bgpd/bgp_ls_nlri.c
+index 9996ab069505..ff37c451d3c9 100644
+--- a/bgpd/bgp_ls_nlri.c
++++ b/bgpd/bgp_ls_nlri.c
+@@ -1030,6 +1030,28 @@ size_t bgp_ls_nlri_size(const struct bgp_ls_nlri *nlri)
+ return size;
+ }
+
++/* NLRI get protocol_id helper */
++enum bgp_ls_protocol_id bgp_ls_nlri_protocol_id(const struct bgp_ls_nlri *nlri)
++{
++ if (!nlri)
++ return BGP_LS_PROTO_RESERVED;
++
++ switch (nlri->nlri_type) {
++ case BGP_LS_NLRI_TYPE_NODE:
++ return nlri->nlri_data.node.protocol_id;
++ case BGP_LS_NLRI_TYPE_LINK:
++ return nlri->nlri_data.link.protocol_id;
++ case BGP_LS_NLRI_TYPE_IPV4_PREFIX:
++ case BGP_LS_NLRI_TYPE_IPV6_PREFIX:
++ return nlri->nlri_data.prefix.protocol_id;
++ case BGP_LS_NLRI_TYPE_SRV6_SID:
++ return nlri->nlri_data.srv6_sid.protocol_id;
++ case BGP_LS_NLRI_TYPE_RESERVED:
++ return BGP_LS_PROTO_RESERVED;
++ }
++ return BGP_LS_PROTO_RESERVED;
++}
++
+ /*
+ * ===========================================================================
+ * String Conversion Functions
+@@ -2233,7 +2255,8 @@ int bgp_ls_encode_nlri(struct stream *s, const struct bgp_ls_nlri *nlri)
+ *
+ * Returns number of bytes written, or -1 on error
+ */
+-int bgp_ls_encode_attr(struct stream *s, const struct bgp_ls_attr *attr)
++int bgp_ls_encode_attr(struct stream *s, const struct bgp_ls_attr *attr,
++ enum bgp_ls_protocol_id protocol_id)
+ {
+ size_t start_pos;
+
+@@ -3869,6 +3892,75 @@ int bgp_ls_decode_srv6_sid_nlri(struct stream *s, struct bgp_ls_nlri *nlri, uint
+ return 0;
+ }
+
++/*
++ * Decode protocol id from BGP-LS NLRI UPDATE message
++ *
++ * Needed by BGP-LS attribute that is parsed before NLRI
++ *
++ * Returns: 0 on success, -1 on error
++ */
++int bgp_ls_decode_nlri_protocol_id(struct bgp_nlri *packet,
++ enum bgp_ls_protocol_id *protocol_id)
++{
++ uint16_t nlri_type, nlri_length;
++ uint8_t *data;
++
++ if (!packet || !protocol_id)
++ return -1;
++
++ /* Read NLRI Type + Length (4 bytes total) */
++ if (packet->length < BGP_LS_NLRI_TYPE_SIZE + BGP_LS_NLRI_LENGTH_SIZE) {
++ flog_warn(EC_BGP_LS_PACKET, "BGP-LS: %s: Not enough data for NLRI type and length",
++ __func__);
++ return -1;
++ }
++
++ /* Creating stream requires allocating/deallocating and copying memory, it
++ * seems too much for two words parsing
++ */
++ data = packet->nlri;
++ nlri_type = *data++ << 8;
++ nlri_type += *data++;
++ nlri_length = *data++ << 8;
++ nlri_length += *data++;
++
++ /* Check if stream has enough data for NLRI */
++ if (packet->length - (data - packet->nlri) < nlri_length) {
++ flog_warn(EC_BGP_LS_PACKET,
++ "BGP-LS: %s: NLRI type=%u length=%u exceeds available data", __func__,
++ nlri_type, nlri_length);
++ return -1;
++ }
++
++ /* Decode based on NLRI type */
++ switch ((enum bgp_ls_nlri_type)nlri_type) {
++ case BGP_LS_NLRI_TYPE_NODE:
++ case BGP_LS_NLRI_TYPE_LINK:
++ case BGP_LS_NLRI_TYPE_IPV4_PREFIX:
++ case BGP_LS_NLRI_TYPE_IPV6_PREFIX:
++ case BGP_LS_NLRI_TYPE_SRV6_SID:
++ /* Check minimum length */
++ if (nlri_length < BGP_LS_NLRI_MIN_LENGTH) {
++ flog_warn(EC_BGP_LS_PACKET,
++ "BGP-LS: %s: Node NLRI length %u too short (minimum %u)",
++ __func__, nlri_length, BGP_LS_NLRI_MIN_LENGTH);
++ return -1;
++ }
++
++ /* Read Protocol-ID (1 byte) */
++ *protocol_id = *data;
++ break;
++
++ case BGP_LS_NLRI_TYPE_RESERVED:
++ default:
++ /* Unknown NLRI type - preserve and propagate (RFC 9552 Section 5.2) */
++ zlog_warn("BGP-LS: %s: Unknown NLRI type %u", __func__, nlri_type);
++ return -1;
++ }
++
++ return 0;
++}
++
+ /*
+ * Decode complete BGP-LS NLRI from UPDATE message (RFC 9552 Section 5.2)
+ *
+@@ -5263,7 +5355,8 @@ static int parse_srv6_endpoint_behavior(struct stream *s, uint16_t length, struc
+ return 0;
+ }
+
+-int bgp_ls_parse_attr(struct stream *s, uint16_t total_length, struct bgp_ls_attr *attr)
++int bgp_ls_parse_attr(struct stream *s, uint16_t total_length, enum bgp_ls_protocol_id protocol_id,
++ struct bgp_ls_attr *attr)
+ {
+ uint16_t type, length;
+ size_t end_pos = stream_get_getp(s) + total_length;
+diff --git a/bgpd/bgp_ls_nlri.h b/bgpd/bgp_ls_nlri.h
+index feacfebdefb4..dc3b814d12f7 100644
+--- a/bgpd/bgp_ls_nlri.h
++++ b/bgpd/bgp_ls_nlri.h
+@@ -851,6 +851,9 @@ extern bool bgp_ls_nlri_validate(const struct bgp_ls_nlri *nlri);
+ /* NLRI size calculation helpers */
+ extern size_t bgp_ls_nlri_size(const struct bgp_ls_nlri *nlri);
+
++/* NLRI get protocol_id helper */
++extern enum bgp_ls_protocol_id bgp_ls_nlri_protocol_id(const struct bgp_ls_nlri *nlri);
++
+ /* String conversion helpers */
+ extern const char *bgp_ls_protocol_id_str(enum bgp_ls_protocol_id proto_id);
+ extern const char *bgp_ls_nlri_type_str(enum bgp_ls_nlri_type nlri_type);
+@@ -948,7 +951,8 @@ extern int bgp_ls_encode_prefix_nlri(struct stream *s, const struct bgp_ls_prefi
+ extern int bgp_ls_encode_nlri(struct stream *s, const struct bgp_ls_nlri *nlri);
+
+ /* Encode BGP-LS Attributes (Type 29 TLVs) */
+-extern int bgp_ls_encode_attr(struct stream *s, const struct bgp_ls_attr *attr);
++extern int bgp_ls_encode_attr(struct stream *s, const struct bgp_ls_attr *attr,
++ enum bgp_ls_protocol_id protocol_id);
+
+ /*
+ * Get Prefix-SID attribute SID length by flags
+@@ -997,15 +1001,21 @@ extern int bgp_ls_decode_srv6_sid_nlri(struct stream *s, struct bgp_ls_nlri *nlr
+ /* Decode complete NLRI */
+ extern int bgp_ls_decode_nlri(struct stream *s, struct bgp_ls_nlri *nlri);
+
++/* Decode enough NLRI to get protocol id */
++extern int bgp_ls_decode_nlri_protocol_id(struct bgp_nlri *packet,
++ enum bgp_ls_protocol_id *protocol_id);
++
+ /*
+ * Parse BGP-LS Attributes (Type 29 TLVs)
+ *
+ * @param s Stream containing the attribute TLVs
+ * @param total_length Total length of all TLVs in bytes
++ * @param protocol_id Protocol-ID from NLRI needed by some attributes
+ * @param attr Pointer to node attribute structure to populate
+ * @return 0 on success, -1 on error
+ */
+-extern int bgp_ls_parse_attr(struct stream *s, uint16_t total_length, struct bgp_ls_attr *attr);
++extern int bgp_ls_parse_attr(struct stream *s, uint16_t total_length,
++ enum bgp_ls_protocol_id protocol_id, struct bgp_ls_attr *attr);
+
+ /*
+ * Convert BGP-LS Attributes to JSON object
+diff --git a/bgpd/bgp_ls_ted.c b/bgpd/bgp_ls_ted.c
+index 993f5dcd70fe..076455bfe9b7 100644
+--- a/bgpd/bgp_ls_ted.c
++++ b/bgpd/bgp_ls_ted.c
+@@ -182,7 +182,8 @@ static void bgp_ls_populate_srv6_adj_sid(struct bgp_ls_attr *attr,
+ /*
+ * Populate BGP-LS Attributes from Link State Attributes
+ */
+-int bgp_ls_populate_link_attr(struct ls_attributes *ls_attr, struct bgp_ls_attr *attr)
++int bgp_ls_populate_link_attr(struct ls_attributes *ls_attr, struct bgp_ls_attr *attr,
++ enum bgp_ls_protocol_id protocol_id)
+ {
+ if (!ls_attr || !attr)
+ return -1;
+@@ -713,7 +714,7 @@ int bgp_ls_originate_link(struct bgp *bgp, uint8_t protocol_id, uint8_t *local_r
+
+ /* Populate BGP-LS attributes from Link State edge */
+ ls_attr = bgp_ls_attr_alloc();
+- if (bgp_ls_populate_link_attr(edge->attributes, ls_attr) < 0) {
++ if (bgp_ls_populate_link_attr(edge->attributes, ls_attr, protocol_id) < 0) {
+ zlog_warn("BGP-LS: Failed to populate Link attributes");
+ bgp_ls_attr_free(ls_attr);
+ return -1;
+diff --git a/bgpd/bgp_ls_ted.h b/bgpd/bgp_ls_ted.h
+index 6eb6579a49d6..f706322460ab 100644
+--- a/bgpd/bgp_ls_ted.h
++++ b/bgpd/bgp_ls_ted.h
+@@ -11,6 +11,8 @@
+ #ifndef _FRR_BGP_LS_TED_H
+ #define _FRR_BGP_LS_TED_H
+
++#include "bgpd/bgp_ls_nlri.h"
++
+ #define UNKNOWN LS_UNKNOWN
+ #include "lib/link_state.h"
+ #undef UNKNOWN
+@@ -33,9 +35,11 @@ extern int bgp_ls_populate_node_attr(struct ls_node *ls_node, struct bgp_ls_attr
+ *
+ * @param ls_attr Link State attributes from TED
+ * @param attr BGP-LS link attribute structure to populate
++ * @param protocol_id - IGP protocol (ISIS/OSPF)
+ * @return 0 on success, -1 on error
+ */
+-extern int bgp_ls_populate_link_attr(struct ls_attributes *ls_attr, struct bgp_ls_attr *attr);
++extern int bgp_ls_populate_link_attr(struct ls_attributes *ls_attr, struct bgp_ls_attr *attr,
++ enum bgp_ls_protocol_id protocol_id);
+
+ /*
+ * Populate BGP-LS Attributes from Link State Prefix
+diff --git a/bgpd/bgp_updgrp_packet.c b/bgpd/bgp_updgrp_packet.c
+index 6cd31f74b9f5..ea48fc92c80f 100644
+--- a/bgpd/bgp_updgrp_packet.c
++++ b/bgpd/bgp_updgrp_packet.c
+@@ -786,7 +786,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, dest->srv6_unicast, 0, 0,
+- path, NULL, false);
++ path, dest->ls_nlri, false);
+ space_remaining =
+ STREAM_CONCAT_REMAIN(s, snlri, STREAM_SIZE(s))
+ - BGP_MAX_PACKET_SIZE_OVERFLOW;
+
+From 0c1d6a72af4111b2eba4b70db6a019f760d3d603 Mon Sep 17 00:00:00 2001
+From: Kyrylo Yatsenko <hedrok@gmail.com>
+Date: Tue, 12 May 2026 09:36:43 +0300
+Subject: [PATCH 3/3] bgpd: BGP-LS: add Adjacency SID (TLV 1099)
+
+Signed-off-by: Kyrylo Yatsenko <hedrok@gmail.com>
+---
+ bgpd/bgp_ls_nlri.c | 179 +++++++++++++++++++++++++++++++++++++++++++--
+ bgpd/bgp_ls_nlri.h | 26 +++++++
+ bgpd/bgp_ls_ted.c | 35 +++++++++
+ 3 files changed, 233 insertions(+), 7 deletions(-)
+
+diff --git a/bgpd/bgp_ls_nlri.c b/bgpd/bgp_ls_nlri.c
+index ff37c451d3c9..02f706691030 100644
+--- a/bgpd/bgp_ls_nlri.c
++++ b/bgpd/bgp_ls_nlri.c
+@@ -449,6 +449,19 @@ int bgp_ls_attr_cmp(const struct bgp_ls_attr *attr1, const struct bgp_ls_attr *a
+ return ret;
+ }
+
++ 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++) {
++ if (attr1->adj_sid[i].sid != attr2->adj_sid[i].sid)
++ return numcmp(attr1->adj_sid[i].sid, attr2->adj_sid[i].sid);
++ if (attr1->adj_sid[i].flags != attr2->adj_sid[i].flags)
++ return numcmp(attr1->adj_sid[i].flags, attr2->adj_sid[i].flags);
++ if (attr1->adj_sid[i].weight != attr2->adj_sid[i].weight)
++ return numcmp(attr1->adj_sid[i].weight, attr2->adj_sid[i].weight);
++ }
++ }
++
+ 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);
+@@ -1460,6 +1473,15 @@ unsigned int bgp_ls_attr_hash_key(const struct bgp_ls_attr *attr)
+ if (CHECK_FLAG(attr->present_tlvs, BGP_LS_ATTR_LINK_NAME_BIT))
+ key = jhash(attr->link_name, strlen(attr->link_name), key);
+
++ 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);
++ key = jhash(&attr->adj_sid[i].flags, sizeof(attr->adj_sid[i].flags), key);
++ key = jhash(&attr->adj_sid[i].weight, sizeof(attr->adj_sid[i].weight), key);
++ }
++ }
++
+ if (CHECK_FLAG(attr->present_tlvs, BGP_LS_ATTR_IGP_FLAGS_BIT))
+ key = jhash_1word(attr->igp_flags, key);
+
+@@ -2485,6 +2507,33 @@ int bgp_ls_encode_attr(struct stream *s, const struct bgp_ls_attr *attr,
+ }
+ }
+
++ /* Adjacency SID (TLV 1099) */
++ 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);
++
++ if (sid_len == -1) {
++ /* Should be impossible here */
++ zlog_warn("BGP-LS: %s wrong combination of V-Flag and L-Flag for Adjacency SID",
++ __func__);
++ 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 (CHECK_FLAG(attr->present_tlvs, BGP_LS_ATTR_EXT_ADMIN_GROUP_BIT)) {
+ size_t nb_words = admin_group_nb_words(&attr->ext_admin_group);
+@@ -2820,12 +2869,7 @@ int bgp_ls_encode_attr(struct stream *s, const struct bgp_ls_attr *attr,
+ return stream_get_endp(s) - start_pos;
+ }
+
+-/*
+- * Get Prefix-SID attribute SID length by flags
+- *
+- * @return 3 or 4 in normal case, -1 in error case
+- */
+-int bgp_ls_attr_prefix_sid_len(uint8_t flags)
++static int bgp_ls_attr_sid_len_by_lv_flags(uint8_t flags, uint8_t v_flag_mask, uint8_t l_flag_mask)
+ {
+ /*
+ * IS-IS: RFC 8667, 2.1.1; OSPFv2: RFC8665, 5; OSPFv3: RFC8666, 6:
+@@ -2834,7 +2878,7 @@ int bgp_ls_attr_prefix_sid_len(uint8_t flags)
+ * are invalid and any SID Advertisement received with an invalid setting
+ * for V- and L-Flags MUST be ignored.
+ */
+- uint8_t vl_mask = BGP_LS_PREFIX_SID_FLAG_VALUE | BGP_LS_PREFIX_SID_FLAG_LOCAL;
++ uint8_t vl_mask = v_flag_mask | l_flag_mask;
+ uint8_t vl_val = CHECK_FLAG(flags, vl_mask);
+
+ if (vl_val != 0 && vl_val != vl_mask)
+@@ -2843,6 +2887,43 @@ int bgp_ls_attr_prefix_sid_len(uint8_t flags)
+ return vl_val ? 3 : 4;
+ }
+
++/*
++ * Get Prefix-SID attribute SID length by flags
++ *
++ * @return 3 or 4 in normal case, -1 in error case
++ */
++int bgp_ls_attr_prefix_sid_len(uint8_t flags)
++{
++ return bgp_ls_attr_sid_len_by_lv_flags(flags, BGP_LS_PREFIX_SID_FLAG_VALUE,
++ BGP_LS_PREFIX_SID_FLAG_LOCAL);
++}
++
++/*
++ * Get Adjacency SID attribute Label/Index SID length by flags
++ *
++ * @return 3 or 4 in normal case, -1 in error case
++ */
++int bgp_ls_attr_adjacency_sid_len(uint8_t flags, enum bgp_ls_protocol_id protocol_id)
++{
++ switch (protocol_id) {
++ case BGP_LS_PROTO_ISIS_L1:
++ case BGP_LS_PROTO_ISIS_L2:
++ return bgp_ls_attr_sid_len_by_lv_flags(flags, ISIS_EXT_SUBTLV_LINK_ADJ_SID_VFLG,
++ ISIS_EXT_SUBTLV_LINK_ADJ_SID_LFLG);
++ case BGP_LS_PROTO_OSPFV2:
++ case BGP_LS_PROTO_OSPFV3:
++ return bgp_ls_attr_sid_len_by_lv_flags(flags, OSPF_EXT_SUBTLV_LINK_ADJ_SID_VFLG,
++ OSPF_EXT_SUBTLV_LINK_ADJ_SID_LFLG);
++ default:
++ case BGP_LS_PROTO_RESERVED:
++ case BGP_LS_PROTO_DIRECT:
++ case BGP_LS_PROTO_STATIC:
++ case BGP_LS_PROTO_BGP:
++ zlog_warn("BGP-LS: %s unsupported protocol %d", __func__, protocol_id);
++ return -1;
++ }
++}
++
+ /*
+ * ===========================================================================
+ * NLRI Decoding Functions
+@@ -4481,6 +4562,59 @@ static int parse_link_name(struct stream *s, uint16_t length, struct bgp_ls_attr
+ return 0;
+ }
+
++/*
++ * Parse Adjacency SID (TLV 1099)
++ * RFC 9085 Section 2.2.1
++ */
++static int parse_adjacency_sid(struct stream *s, uint16_t length, enum bgp_ls_protocol_id protocol_id, struct bgp_ls_attr *attr)
++{
++ int flags;
++ int sid_len;
++
++ if (length != 7 && length != 8) {
++ flog_warn(EC_BGP_UPDATE_RCV, "BGP-LS: Invalid Adjacency SID length (%u bytes)",
++ length);
++ return -1;
++ }
++
++ if (attr->adj_sid_count == BGP_LS_ADJ_MAX) {
++ flog_warn(EC_BGP_UPDATE_RCV,
++ "BGP-LS: Ignoring Adjacency SID, maximum %d is implemented",
++ BGP_LS_ADJ_MAX);
++ stream_forward_getp(s, length);
++ return 0;
++ }
++
++ flags = stream_getc(s);
++ sid_len = bgp_ls_attr_adjacency_sid_len(flags, protocol_id);
++ if (sid_len != length - 4) {
++ stream_forward_getp(s, length - 1);
++ if (sid_len == -1) {
++ flog_warn(EC_BGP_LS_PACKET,
++ "BGP-LS: %s wrong combination of V-Flag and L-Flag for Adjacency SID, ignoring",
++ __func__);
++ } else {
++ flog_warn(EC_BGP_LS_PACKET,
++ "BGP-LS: %s L/V-Flag value contradicts length of Adjacent SID, ignoring",
++ __func__);
++ }
++ return 0;
++ }
++
++ attr->adj_sid[attr->adj_sid_count].flags = flags;
++ attr->adj_sid[attr->adj_sid_count].weight = stream_getc(s);
++ stream_getw(s); /* Reserved, ignored */
++ if (sid_len == 3)
++ attr->adj_sid[attr->adj_sid_count].sid = stream_get3(s);
++ else
++ attr->adj_sid[attr->adj_sid_count].sid = stream_getl(s);
++ attr->adj_sid_count++;
++
++ SET_FLAG(attr->present_tlvs, BGP_LS_ATTR_ADJ_SID_BIT);
++
++ return 0;
++}
++
+ /*
+ * Parse Extended Admin Group TLV (TLV 1173)
+ */
+@@ -5467,6 +5601,11 @@ int bgp_ls_parse_attr(struct stream *s, uint16_t total_length, enum bgp_ls_proto
+ return -1;
+ break;
+
++ case BGP_LS_ATTR_ADJ_SID:
++ if (parse_adjacency_sid(s, length, protocol_id, attr) < 0)
++ return -1;
++ break;
++
+ case BGP_LS_ATTR_UNIDIRECTIONAL_LINK_DELAY:
+ if (parse_link_delay(s, length, attr) < 0)
+ return -1;
+@@ -5722,6 +5861,22 @@ struct json_object *bgp_ls_attr_to_json(struct bgp_ls_attr *ls_attr)
+ 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 (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);
++ for (int i = 0; i < ls_attr->adj_sid_count; i++) {
++ json_object *jobj = json_object_new_object();
++
++ json_object_int_add(jobj, "sid", ls_attr->adj_sid[i].sid);
++ json_object_string_addf(jobj, "flags", "0x%x", ls_attr->adj_sid[i].flags);
++ json_object_int_add(jobj, "weight", ls_attr->adj_sid[i].weight);
++
++ json_object_array_add(jadj, jobj);
++ }
++ }
++
+ /* Performance Metrics - Link Delay */
+ if (CHECK_FLAG(ls_attr->present_tlvs, BGP_LS_ATTR_DELAY_BIT))
+ json_object_int_add(json_ls_attr, "delay", ls_attr->delay);
+@@ -6056,6 +6211,16 @@ void bgp_ls_attr_display(struct vty *vty, struct bgp_ls_attr *ls_attr)
+ col += vty_out(vty, "Link Name: %s", ls_attr->link_name);
+ }
+
++ /* Adjacency SID */
++ 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",
++ ls_attr->adj_sid[i].sid, ls_attr->adj_sid[i].flags,
++ ls_attr->adj_sid[i].weight);
++ }
++ }
++
+ /* Performance Metrics - Link Delay */
+ if (CHECK_FLAG(ls_attr->present_tlvs, BGP_LS_ATTR_DELAY_BIT)) {
+ CHECK_WRAP();
+diff --git a/bgpd/bgp_ls_nlri.h b/bgpd/bgp_ls_nlri.h
+index dc3b814d12f7..0cb5c4d0c2be 100644
+--- a/bgpd/bgp_ls_nlri.h
++++ b/bgpd/bgp_ls_nlri.h
+@@ -442,6 +442,16 @@ enum bgp_ls_attr_tlv {
+ #define BGP_LS_PREFIX_SID_FLAG_VALUE 0x08 /* Same for IS-IS, OSPFv2, OSPFv3 */
+ #define BGP_LS_PREFIX_SID_FLAG_LOCAL 0x04 /* Same for IS-IS, OSPFv2, OSPFv3 */
+
++/*
++ * IS-IS RFC 8667, 2.2.1
++ * OSPFv2 RFC 8665, 6.1
++ * OSPFv3 RFC 8666, 7.1
++ */
++#define ISIS_EXT_SUBTLV_LINK_ADJ_SID_VFLG 0x20
++#define ISIS_EXT_SUBTLV_LINK_ADJ_SID_LFLG 0x10
++#define OSPF_EXT_SUBTLV_LINK_ADJ_SID_VFLG 0x40
++#define OSPF_EXT_SUBTLV_LINK_ADJ_SID_LFLG 0x20
++
+ /*
+ * ===========================================================================
+ * Descriptor Structures (RFC 9552 Section 5.2)
+@@ -738,6 +748,15 @@ struct bgp_ls_attr {
+ /* Link Name (TLV 1098) */
+ char *link_name;
+
++ /* Adjacency SID (TLV 1099) */
++#define BGP_LS_ADJ_MAX 4
++ uint8_t adj_sid_count; /* number of adj_sid elements */
++ struct bgp_ls_adjacency {
++ uint32_t sid; /* SID as MPLS label or index */
++ uint8_t flags; /* Flags */
++ uint8_t weight; /* Administrative weight */
++ } adj_sid[BGP_LS_ADJ_MAX]; /* IPv4/IPv6 & Primary/Backup Adj. SID */
++
+ /* Extended Administrative Group (TLV 1173) */
+ struct admin_group ext_admin_group;
+
+@@ -961,6 +980,13 @@ extern int bgp_ls_encode_attr(struct stream *s, const struct bgp_ls_attr *attr,
+ */
+ extern int bgp_ls_attr_prefix_sid_len(uint8_t flags);
+
++/*
++ * Get Adjacency SID attribute Label/Index SID length by flags
++ *
++ * @return 3 or 4 in normal case, -1 in error case
++ */
++extern int bgp_ls_attr_adjacency_sid_len(uint8_t flags, enum bgp_ls_protocol_id protocol_id);
++
+ /*
+ * ===========================================================================
+ * NLRI Decoding Functions
+diff --git a/bgpd/bgp_ls_ted.c b/bgpd/bgp_ls_ted.c
+index 076455bfe9b7..eaa7ed3698e2 100644
+--- a/bgpd/bgp_ls_ted.c
++++ b/bgpd/bgp_ls_ted.c
+@@ -109,6 +109,35 @@ int bgp_ls_populate_node_attr(struct ls_node *ls_node, struct bgp_ls_attr *attr)
+ * ===========================================================================
+ */
+
++static void populate_adj_sid(struct ls_attributes *ls_attr, int index, int flag,
++ struct bgp_ls_attr *attr, enum bgp_ls_protocol_id protocol_id)
++{
++ int sid_len;
++
++ if (!CHECK_FLAG(ls_attr->flags, flag))
++ return;
++
++ if (attr->adj_sid_count >= BGP_LS_ADJ_MAX) {
++ zlog_warn("BGP-LS: %s maximum supported number of Adjacency SID is %d, ignoring others",
++ __func__, BGP_LS_ADJ_MAX);
++ return;
++ }
++
++ sid_len = bgp_ls_attr_adjacency_sid_len(ls_attr->adj_sid[index].flags, protocol_id);
++ if (sid_len == -1) {
++ zlog_warn("BGP-LS: %s TED contains wrong combination of V-Flag and L-Flag for Adjacent SID",
++ __func__);
++ return;
++ }
++
++ attr->adj_sid[attr->adj_sid_count].sid = ls_attr->adj_sid[index].sid;
++ attr->adj_sid[attr->adj_sid_count].flags = ls_attr->adj_sid[index].flags;
++ attr->adj_sid[attr->adj_sid_count].weight = ls_attr->adj_sid[index].weight;
++ attr->adj_sid_count++;
++
++ SET_FLAG(attr->present_tlvs, BGP_LS_ATTR_ADJ_SID_BIT);
++}
++
+ /*
+ * Fill a pre-allocated SRv6 End.X SID entry (TLV 1106) from a Link State adjacency SID
+ */
+@@ -255,6 +284,12 @@ int bgp_ls_populate_link_attr(struct ls_attributes *ls_attr, struct bgp_ls_attr
+ SET_FLAG(attr->present_tlvs, BGP_LS_ATTR_LINK_NAME_BIT);
+ }
+
++ /* Adjacency SID (TLV 1099) */
++ populate_adj_sid(ls_attr, ADJ_PRI_IPV4, LS_ATTR_ADJ_SID, attr, protocol_id);
++ populate_adj_sid(ls_attr, ADJ_BCK_IPV4, LS_ATTR_BCK_ADJ_SID, attr, protocol_id);
++ populate_adj_sid(ls_attr, ADJ_PRI_IPV6, LS_ATTR_ADJ_SID6, attr, protocol_id);
++ populate_adj_sid(ls_attr, ADJ_BCK_IPV6, LS_ATTR_BCK_ADJ_SID6, attr, protocol_id);
++
+ /* 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;
diff --git a/scripts/package-build/frr/patches/frr/0007-zebra-remove-kernel-route-on-last-address-deletion.patch b/scripts/package-build/frr/patches/frr/0007-zebra-remove-kernel-route-on-last-address-deletion.patch
deleted file mode 100644
index 55646666..00000000
--- a/scripts/package-build/frr/patches/frr/0007-zebra-remove-kernel-route-on-last-address-deletion.patch
+++ /dev/null
@@ -1,214 +0,0 @@
-From 0e94af0907446266b28d40b1115144aa12aeb7c8 Mon Sep 17 00:00:00 2001
-From: Kyrylo Yatsenko <hedrok@gmail.com>
-Date: Thu, 11 Sep 2025 17:43:08 +0300
-Subject: [PATCH] zebra: remove kernel route on last address deletion
-
-Fixes: #13561
-
-Linux kernel deletes IPv4 routes when last interface IPv4 address
-is deleted, but intentionally doesn't send RTM_DELROUTE in this case.
-
-FRR has function rib_update_handle_kernel_route_down_possibility
-that handles setting interface down, but not removal of last address.
-
-To fix the situation:
-
-* Add RIB_UPDATE_KERNEL_LAST_ADDRESS_DELETED to enum rib_update_event
-* In zebra_if_addr_update_ctx make more specific check that last
- address is deleted and trigger RIB_UPDATE_KERNEL_LAST_ADDRESS_DELETED
- instead of RIB_UPDATE_KERNEL in this case. If it was not last address,
- don't emit any RIB_UPDATE.
-* Call rib_update_handle_kernel_route_down_possibility not only
- for event RIB_UPDATE_INTERFACE_DOWN, but also for
- RIB_UPDATE_KERNEL_LAST_ADDRESS_DELETED.
-* Change rib_update_handle_kernel_route_down_possibility not to
- consider IPv4 route alive when interface is up, but there
- are no IPv4 addresses left.
-
-Relevant kernel code:
-
-* net/ipv4/fib_frontend.c fib_inetaddr_event
-* net/ipv4/fib_semantics.c fib_sync_down_dev
-
-Comment from kernel networking author that RTM_DELROUTE isn't sent
-intentionally:
-
-https://bugzilla.kernel.org/show_bug.cgi?id=207089
-Signed-off-by: Kyrylo Yatsenko <hedrok@gmail.com>
----
- lib/if.c | 12 +++++++++++
- lib/if.h | 1 +
- zebra/interface.c | 8 +++++---
- zebra/rib.h | 1 +
- zebra/zebra_rib.c | 51 +++++++++++++++++++++++++++++++++++++----------
- 5 files changed, 60 insertions(+), 13 deletions(-)
-
-diff --git a/lib/if.c b/lib/if.c
-index 798bcd3da1..0991b765bb 100644
---- a/lib/if.c
-+++ b/lib/if.c
-@@ -972,6 +972,18 @@ struct nbr_connected *nbr_connected_check(struct interface *ifp,
- return NULL;
- }
-
-+/* Return true if there is at least one connected address in the given family */
-+bool if_has_connected_with_family(struct interface *ifp, int family)
-+{
-+ struct connected *connected;
-+
-+ frr_each (if_connected, ifp->connected, connected)
-+ if (connected->address->family == family)
-+ return true;
-+
-+ return false;
-+}
-+
- /* count the number of connected addresses that are in the given family */
- unsigned int connected_count_by_family(struct interface *ifp, int family)
- {
-diff --git a/lib/if.h b/lib/if.h
-index fce6705c23..97f1f13a15 100644
---- a/lib/if.h
-+++ b/lib/if.h
-@@ -609,6 +609,7 @@ extern struct connected *connected_lookup_prefix(struct interface *ifp,
- const struct prefix *p);
- extern struct connected *connected_lookup_prefix_exact(struct interface *ifp,
- const struct prefix *p);
-+extern bool if_has_connected_with_family(struct interface *ifp, int family);
- extern unsigned int connected_count_by_family(struct interface *ifp, int family);
- extern struct nbr_connected *nbr_connected_new(void);
- extern void nbr_connected_free(struct nbr_connected *connected);
-diff --git a/zebra/interface.c b/zebra/interface.c
-index 47850c7f89..095ad355a2 100644
---- a/zebra/interface.c
-+++ b/zebra/interface.c
-@@ -1280,11 +1280,13 @@ static void zebra_if_addr_update_ctx(struct zebra_dplane_ctx *ctx,
- }
-
- /*
-- * Linux kernel does not send route delete on interface down/addr del
-+ * Linux kernel does not send route delete on interface down/last addr del
- * so we have to re-process routes it owns (i.e. kernel routes)
-+ * See rib_update_handle_kernel_route_down_possibility for more details
- */
-- if (op != DPLANE_OP_INTF_ADDR_ADD)
-- rib_update(RIB_UPDATE_KERNEL);
-+ if (op != DPLANE_OP_INTF_ADDR_ADD && addr->family == AF_INET &&
-+ !if_has_connected_with_family(ifp, AF_INET))
-+ rib_update(RIB_UPDATE_KERNEL_LAST_ADDRESS_DELETED);
- }
-
- static void zebra_if_update_ctx(struct zebra_dplane_ctx *ctx,
-diff --git a/zebra/rib.h b/zebra/rib.h
-index f46c372335..230d304ba2 100644
---- a/zebra/rib.h
-+++ b/zebra/rib.h
-@@ -350,6 +350,7 @@ enum rib_update_event {
- RIB_UPDATE_KERNEL,
- RIB_UPDATE_RMAP_CHANGE,
- RIB_UPDATE_OTHER,
-+ RIB_UPDATE_KERNEL_LAST_ADDRESS_DELETED,
- RIB_UPDATE_MAX
- };
- void rib_update_finish(void);
-diff --git a/zebra/zebra_rib.c b/zebra/zebra_rib.c
-index f1cc5ab085..b1eee9875f 100644
---- a/zebra/zebra_rib.c
-+++ b/zebra/zebra_rib.c
-@@ -4717,6 +4717,9 @@ static const char *rib_update_event2str(enum rib_update_event event)
- case RIB_UPDATE_OTHER:
- ret = "RIB_UPDATE_OTHER";
- break;
-+ case RIB_UPDATE_KERNEL_LAST_ADDRESS_DELETED:
-+ ret = "RIB_UPDATE_KERNEL_LAST_ADDRESS_DELETED";
-+ break;
- case RIB_UPDATE_MAX:
- break;
- }
-@@ -4739,7 +4742,7 @@ rib_update_handle_kernel_route_down_possibility(struct route_node *rn,
- bool alive = false;
-
- for (ALL_NEXTHOPS(re->nhe->nhg, nexthop)) {
-- if (!nexthop->ifindex) {
-+ if (!nexthop->ifindex || nexthop->type == NEXTHOP_TYPE_BLACKHOLE) {
- /* blackhole nexthops have no interfaces */
- alive = true;
- break;
-@@ -4748,10 +4751,36 @@ rib_update_handle_kernel_route_down_possibility(struct route_node *rn,
- struct interface *ifp = if_lookup_by_index(nexthop->ifindex,
- nexthop->vrf_id);
-
-- if ((ifp && if_is_up(ifp)) || nexthop->type == NEXTHOP_TYPE_BLACKHOLE) {
-+ if (!ifp || !if_is_up(ifp)) {
-+ /* interface is not up, not alive */
-+ continue;
-+ }
-+
-+ /*
-+ * Kernel deletes IPv4 routes from interface when last IPv4 address is deleted
-+ * It is not important whether nexthop is reachable after address
-+ * is deleted - route is deleted only after last address deletion.
-+ *
-+ * See net/ipv4/fib_frontend.c fib_inetaddr_event,
-+ * net/ipv4/fib_semantics.c fib_sync_down_dev
-+ */
-+
-+ /* If not IPv4, set alive
-+ * Check rn->p->family: for nexthop->type=NEXTHOP_TYPE_IFINDEX it
-+ * depends on destination only
-+ */
-+ if (rn->p.family != AF_INET) {
- alive = true;
- break;
- }
-+
-+ /* Check if there are any IPv4 addresses connected, if yes - set alive */
-+ if (if_has_connected_with_family(ifp, AF_INET)) {
-+ alive = true;
-+ break;
-+ }
-+
-+ /* Otherwise kernel deletes the route */
- }
-
- if (!alive) {
-@@ -4776,8 +4805,9 @@ static void rib_update_route_node(struct route_node *rn, int type,
- bool re_changed = false;
-
- RNODE_FOREACH_RE_SAFE (rn, re, next) {
-- if (event == RIB_UPDATE_INTERFACE_DOWN && type == re->type &&
-- type == ZEBRA_ROUTE_KERNEL)
-+ if ((event == RIB_UPDATE_INTERFACE_DOWN ||
-+ event == RIB_UPDATE_KERNEL_LAST_ADDRESS_DELETED) &&
-+ type == re->type && type == ZEBRA_ROUTE_KERNEL)
- rib_update_handle_kernel_route_down_possibility(rn, re);
- else if (type == ZEBRA_ROUTE_ALL || type == re->type) {
- SET_FLAG(re->status, ROUTE_ENTRY_CHANGED);
-@@ -4820,17 +4850,18 @@ void rib_update_table(struct route_table *table, enum rib_update_event event,
- * If we are looking at a route node and the node
- * has already been queued we don't
- * need to queue it up again, unless it is
-- * an interface down event as that we need
-- * to process this no matter what.
-+ * an interface down event or last address down event
-+ * as that we need
-+ * to process these no matter what.
- */
-- if (rn->info &&
-- CHECK_FLAG(rib_dest_from_rnode(rn)->flags,
-- RIB_ROUTE_ANY_QUEUED) &&
-- event != RIB_UPDATE_INTERFACE_DOWN)
-+ if (rn->info && CHECK_FLAG(rib_dest_from_rnode(rn)->flags, RIB_ROUTE_ANY_QUEUED) &&
-+ event != RIB_UPDATE_INTERFACE_DOWN &&
-+ event != RIB_UPDATE_KERNEL_LAST_ADDRESS_DELETED)
- continue;
-
- switch (event) {
- case RIB_UPDATE_INTERFACE_DOWN:
-+ case RIB_UPDATE_KERNEL_LAST_ADDRESS_DELETED:
- case RIB_UPDATE_KERNEL:
- rib_update_route_node(rn, ZEBRA_ROUTE_KERNEL, event);
- break;
---
-2.50.1
-
diff --git a/scripts/package-build/frr/patches/frr/0012-Revert-tools-Allow-deleting-of-interfaces.patch b/scripts/package-build/frr/patches/frr/0012-Revert-tools-Allow-deleting-of-interfaces.patch
deleted file mode 100644
index 4a61dad8..00000000
--- a/scripts/package-build/frr/patches/frr/0012-Revert-tools-Allow-deleting-of-interfaces.patch
+++ /dev/null
@@ -1,37 +0,0 @@
-From 3b720b6e9b4a6a6f53f00c01536efb7a4e013c94 Mon Sep 17 00:00:00 2001
-From: Kyrylo Yatsenko <hedrok@gmail.com>
-Date: Tue, 23 Dec 2025 09:35:14 +0200
-Subject: [PATCH] Revert "tools: Allow deleting of interfaces"
-
-This reverts commit f1a1703a0a2c2ecd81f50ccd384dcdb7e0c1a793.
----
- tools/frr-reload.py | 13 ++++++-------
- 1 file changed, 6 insertions(+), 7 deletions(-)
-
-diff --git a/tools/frr-reload.py b/tools/frr-reload.py
-index fcd64bae3e..2ac7c6cc17 100755
---- a/tools/frr-reload.py
-+++ b/tools/frr-reload.py
-@@ -1799,13 +1799,12 @@ def compare_context_objects(newconf, running):
- delete_bgpd = True
- lines_to_del.append((running_ctx_keys, None))
-
-- elif running_ctx_keys[0].startswith("interface"):
-- lines_to_del.append((running_ctx_keys, None))
--
-- # We cannot do 'no vrf' in FRR, and so deal with it
-- elif running_ctx_keys[0].startswith("vrf") or running_ctx_keys[
-- 0
-- ].startswith("router pim"):
-+ # We cannot do 'no interface' or 'no vrf' in FRR, and so deal with it
-+ elif (
-+ running_ctx_keys[0].startswith("interface")
-+ or running_ctx_keys[0].startswith("vrf")
-+ or running_ctx_keys[0].startswith("router pim")
-+ ):
- for line in running_ctx.lines:
- lines_to_del.append((running_ctx_keys, line))
-
---
-2.50.1
-
diff --git a/scripts/package-build/frr/patches/frr/0016-zebra-Add-no-encapsulation-under-segment-routing-srv.patch b/scripts/package-build/frr/patches/frr/0016-zebra-Add-no-encapsulation-under-segment-routing-srv.patch
deleted file mode 100644
index 66b6e416..00000000
--- a/scripts/package-build/frr/patches/frr/0016-zebra-Add-no-encapsulation-under-segment-routing-srv.patch
+++ /dev/null
@@ -1,83 +0,0 @@
-From 3372c944bc10646bec52ad730a76a4b60e833ce5 Mon Sep 17 00:00:00 2001
-From: Kyrylo Yatsenko <hedrok@gmail.com>
-Date: Fri, 6 Feb 2026 09:07:15 +0200
-Subject: [PATCH] zebra: Add 'no encapsulation' under segment-routing/srv6
-
-Add missing command 'no encapsulation'.
-
-Add small static helper function `unset_srv6_encapsulation` to be used
-in 'no srv6', 'no encapsulation' and 'no source-address'.
-
-Signed-off-by: Kyrylo Yatsenko <hedrok@gmail.com>
----
- zebra/zebra_srv6_vty.c | 26 +++++++++++++++++++++-----
- 1 file changed, 21 insertions(+), 5 deletions(-)
-
-diff --git a/zebra/zebra_srv6_vty.c b/zebra/zebra_srv6_vty.c
-index ba87928f78..ec71941539 100644
---- a/zebra/zebra_srv6_vty.c
-+++ b/zebra/zebra_srv6_vty.c
-@@ -738,6 +738,13 @@ DEFUN_NOSH (srv6,
- return CMD_SUCCESS;
- }
-
-+/* Unset SRv6 encapsulation source address */
-+static void unset_srv6_encap_source_address(void)
-+{
-+ zebra_srv6_encap_src_addr_unset();
-+ dplane_srv6_encap_srcaddr_set(&in6addr_any, NS_DEFAULT);
-+}
-+
- DEFUN (no_srv6,
- no_srv6_cmd,
- "no srv6",
-@@ -772,9 +779,7 @@ DEFUN (no_srv6,
- zebra_srv6_locator_delete(locator);
- }
-
-- /* Unset SRv6 encapsulation source address */
-- zebra_srv6_encap_src_addr_unset();
-- dplane_srv6_encap_srcaddr_set(&in6addr_any, NS_DEFAULT);
-+ unset_srv6_encap_source_address();
-
- return CMD_SUCCESS;
- }
-@@ -1131,6 +1136,17 @@ DEFUN_NOSH (srv6_encap,
- return CMD_SUCCESS;
- }
-
-+DEFUN (no_srv6_encap,
-+ no_srv6_encap_cmd,
-+ "no encapsulation",
-+ NO_STR
-+ "Segment Routing SRv6 encapsulation\n")
-+{
-+ unset_srv6_encap_source_address();
-+
-+ return CMD_SUCCESS;
-+}
-+
- DEFPY (srv6_src_addr,
- srv6_src_addr_cmd,
- "source-address X:X::X:X$encap_src_addr",
-@@ -1149,8 +1165,7 @@ DEFPY (no_srv6_src_addr,
- "Segment Routing SRv6 source address\n"
- "Specify source address for SRv6 encapsulation\n")
- {
-- zebra_srv6_encap_src_addr_unset();
-- dplane_srv6_encap_srcaddr_set(&in6addr_any, NS_DEFAULT);
-+ unset_srv6_encap_source_address();
- return CMD_SUCCESS;
- }
-
-@@ -1743,6 +1758,7 @@ void zebra_srv6_vty_init(void)
- install_element(SEGMENT_ROUTING_NODE, &no_srv6_cmd);
- install_element(SRV6_NODE, &srv6_locators_cmd);
- install_element(SRV6_NODE, &srv6_encap_cmd);
-+ install_element(SRV6_NODE, &no_srv6_encap_cmd);
- install_element(SRV6_NODE, &srv6_sid_formats_cmd);
- install_element(SRV6_LOCS_NODE, &srv6_locator_cmd);
- install_element(SRV6_LOCS_NODE, &no_srv6_locator_cmd);
---
-2.51.2
-
diff --git a/scripts/package-build/frr/patches/frr/0017-pathd-support-no-traffic-eng-command-fixes.patch b/scripts/package-build/frr/patches/frr/0017-pathd-support-no-traffic-eng-command-fixes.patch
deleted file mode 100644
index 4da50180..00000000
--- a/scripts/package-build/frr/patches/frr/0017-pathd-support-no-traffic-eng-command-fixes.patch
+++ /dev/null
@@ -1,464 +0,0 @@
-From 40c51e9fad7b698475cb8332d2a278d17cb4749c Mon Sep 17 00:00:00 2001
-From: Kyrylo Yatsenko <hedrok@gmail.com>
-Date: Thu, 29 Jan 2026 17:11:06 +0200
-Subject: [PATCH] pathd: support no traffic-eng command + fixes
-
-* Add 'segment-routing/no traffic-eng' command, add hook
- pathd_srte_no_srte to clear configuration in modules
-* Move logic of turning off mpls-te to separate function
- 'path_ted_mpls_te_off' to call it in 'no traffic-eng'
-* Don't output 'segment-routing/traffic-eng' if there is no
- configuration - add hook pathd_srte_check_config_not_empty
- to check whether there is configuration in modules
-* path_pcep: implement new hooks
-* Fix wrong output of `write terminal`:
-
-index 0 mpls label 1000 nai adjacency 192.168.0.1 192.168.0.2
-
-for configuration
-
-segment-routing
- traffic-eng
- segment-list testing-segment-list
- index 0 mpls label 1000
- index 0 nai adjacency 192.168.0.1 192.168.0.2
- exit
- exit
-exit
-
-* frr-reload: allow no traffic-eng and no pcep
-
-As 'no traffic-eng' is now supported, allow it.
-
-Fixes that when no 'segment-routing/traffic-eng' was present, command
-`no mpls-te` was not issued, so switch from
-
-segment-routing
- traffic-eng
- mpls-te on
- end
-end
-
-to configuration without this part didn't work.
-
-* fix memory leak on finalization
-
-pcep_g->config_group_opts[*] was not freed during finalization, added
-deallocation to pcep_module_finish
-
-* pathd: fix leak with timer data
-
-schedule_thread_timer_with_cb allocates data that is passed to timer.
-
-Timer callback frees the data. Function `pcep_thread_cancel_timer`
-cancels timer and frees this data.
-
-Replace simple `event_cancel` calls in `pcep_pcc_finalize` with calls
-of `pcep_thread_cancel_timer` to free the data on shutdown.
----
- pathd/path_cli.c | 38 ++++++++++++++++++--
- pathd/path_pcep.c | 4 +++
- pathd/path_pcep_cli.c | 80 +++++++++++++++++++++++++++++++------------
- pathd/path_pcep_pcc.c | 17 ++-------
- pathd/path_ted.c | 35 +++++++++++++------
- pathd/path_ted.h | 12 +++++++
- pathd/pathd.h | 9 +++++
- tools/frr-reload.py | 12 ++-----
- 8 files changed, 149 insertions(+), 58 deletions(-)
-
-diff --git a/pathd/path_cli.c b/pathd/path_cli.c
-index 254e4d7f03..7eeec7b40e 100644
---- a/pathd/path_cli.c
-+++ b/pathd/path_cli.c
-@@ -45,7 +45,9 @@ static int segment_list_has_prefix(
-
- DEFINE_MTYPE_STATIC(PATHD, PATH_CLI, "Client");
-
-+DEFINE_HOOK(pathd_srte_check_config_not_empty, (), ());
- DEFINE_HOOK(pathd_srte_config_write, (struct vty *vty), (vty));
-+DEFINE_HOOK(pathd_srte_no_srte, (), ());
-
- /* Vty node structures. */
- static struct cmd_node segment_routing_node = {
-@@ -231,6 +233,26 @@ DEFPY_NOSH(
- return CMD_SUCCESS;
- }
-
-+/*
-+ * XPath: /frr-pathd:pathd/srte
-+ */
-+DEFPY_YANG(
-+ sr_no_traffic_eng_list,
-+ sr_no_traffic_eng_cmd,
-+ "no traffic-eng",
-+ NO_STR
-+ "Configure SR traffic engineering\n")
-+{
-+ const char *xpath = "/frr-pathd:pathd/srte";
-+
-+ path_ted_mpls_te_off();
-+ hook_call(pathd_srte_no_srte);
-+
-+ nb_cli_enqueue_change(vty, xpath, NB_OP_DESTROY, NULL);
-+
-+ return nb_cli_apply_changes(vty, NULL);
-+}
-+
- /*
- * XPath: /frr-pathd:pathd/srte/segment-list
- */
-@@ -546,15 +568,17 @@ void cli_show_srte_segment_list_segment(struct vty *vty,
- const struct lyd_node *dnode,
- bool show_defaults)
- {
-- vty_out(vty, " index %s", yang_dnode_get_string(dnode, "index"));
- if (yang_dnode_exists(dnode, "sid-value")) {
-+ vty_out(vty, " index %s", yang_dnode_get_string(dnode, "index"));
- vty_out(vty, " mpls label %s",
- yang_dnode_get_string(dnode, "sid-value"));
-+ vty_out(vty, "\n");
- }
- if (yang_dnode_exists(dnode, "nai")) {
- struct ipaddr addr;
- struct ipaddr addr_rmt;
-
-+ vty_out(vty, " index %s", yang_dnode_get_string(dnode, "index"));
- switch (yang_dnode_get_enum(dnode, "nai/type")) {
- case SRTE_SEGMENT_NAI_TYPE_IPV4_NODE:
- case SRTE_SEGMENT_NAI_TYPE_IPV4_LOCAL_IFACE:
-@@ -600,8 +624,8 @@ void cli_show_srte_segment_list_segment(struct vty *vty,
- yang_dnode_get_string(dnode,
- "./nai/algorithm"));
- }
-+ vty_out(vty, "\n");
- }
-- vty_out(vty, "\n");
- }
-
- /*
-@@ -1286,6 +1310,15 @@ static int config_write_dnode(const struct lyd_node *dnode, void *arg)
-
- int config_write_segment_routing(struct vty *vty)
- {
-+ /**
-+ * If no mpls-te, no children of srte node (currently 'segment-list' and 'policy'),
-+ * no modules with configuration - don't output anything
-+ */
-+ if (!path_is_ted_enabled() &&
-+ !yang_dnode_exists(running_config->dnode, "/frr-pathd:pathd/srte/*") &&
-+ !hook_call(pathd_srte_check_config_not_empty))
-+ return 1;
-+
- vty_out(vty, "segment-routing\n");
- vty_out(vty, " traffic-eng\n");
-
-@@ -1328,6 +1361,7 @@ void path_cli_init(void)
-
- install_element(CONFIG_NODE, &segment_routing_cmd);
- install_element(SEGMENT_ROUTING_NODE, &sr_traffic_eng_cmd);
-+ install_element(SEGMENT_ROUTING_NODE, &sr_no_traffic_eng_cmd);
- install_element(SR_TRAFFIC_ENG_NODE, &srte_segment_list_cmd);
- install_element(SR_TRAFFIC_ENG_NODE, &srte_no_segment_list_cmd);
- install_element(SR_SEGMENT_LIST_NODE,
-diff --git a/pathd/path_pcep.c b/pathd/path_pcep.c
-index a0a53b0d84..fbceb411c1 100644
---- a/pathd/path_pcep.c
-+++ b/pathd/path_pcep.c
-@@ -357,6 +357,10 @@ int pcep_module_finish(void)
- if (pcep_g->pce_opts_cli[i] != NULL)
- XFREE(MTYPE_PCEP, pcep_g->pce_opts_cli[i]);
-
-+ for (int i = 0; i < MAX_PCE; i++)
-+ if (pcep_g->config_group_opts[i] != NULL)
-+ XFREE(MTYPE_PCEP, pcep_g->config_group_opts[i]);
-+
- return 0;
- }
-
-diff --git a/pathd/path_pcep_cli.c b/pathd/path_pcep_cli.c
-index 8ceadb3e60..61428d0dec 100644
---- a/pathd/path_pcep_cli.c
-+++ b/pathd/path_pcep_cli.c
-@@ -50,6 +50,8 @@ static int pcep_cli_pcep_config_write(struct vty *vty);
- static int pcep_cli_pcc_config_write(struct vty *vty);
- static int pcep_cli_pce_config_write(struct vty *vty);
- static int pcep_cli_pcep_pce_config_write(struct vty *vty);
-+static int pcep_cli_pcep_check_config_not_empty(void);
-+static int pcep_cli_pcep_no_srte(void);
-
- /* Internal Util Function declarations */
- static void reset_pcc_peer(const char *peer_name);
-@@ -997,7 +999,7 @@ static int path_pcep_cli_pcc(struct vty *vty)
- return CMD_SUCCESS;
- }
-
--static int path_pcep_cli_pcc_delete(struct vty *vty)
-+static int path_pcep_cli_pcc_delete(void)
- {
- /* Clear the pce_connections */
- memset(&pce_connections_g, 0, sizeof(pce_connections_g));
-@@ -1696,6 +1698,9 @@ static int path_pcep_cli_clear_srte_pcep_session(struct vty *vty,
-
- int pcep_cli_pcep_config_write(struct vty *vty)
- {
-+ if (!pcep_cli_pcep_check_config_not_empty())
-+ return 1;
-+
- vty_out(vty, " pcep\n");
- pcep_cli_pcep_pce_config_write(vty);
- pcep_cli_pce_config_write(vty);
-@@ -1874,6 +1879,55 @@ static int pcep_cli_print_pce_config(struct pcep_config_group_opts *group_opts,
- return lines;
- }
-
-+static int pcep_cli_pcep_check_config_not_empty(void)
-+{
-+ /* pcep_cli_pcep_pce_config_write */
-+ for (int i = 0; i < MAX_PCE; i++) {
-+ struct pcep_config_group_opts *group_opts = pcep_g->config_group_opts[i];
-+
-+ if (group_opts != NULL)
-+ return 1;
-+ }
-+
-+ /* pcep_cli_pce_config_write */
-+ for (int i = 0; i < MAX_PCE; i++) {
-+ struct pce_opts_cli *pce_opts_cli = pcep_g->pce_opts_cli[i];
-+
-+ if (pce_opts_cli != NULL)
-+ return 1;
-+ }
-+
-+ /* pcep_cli_pcc_config_write */
-+ if (!(!pcc_msd_configured_g && pce_connections_g.num_connections == 0))
-+ return 1;
-+
-+ /* Nothing to output */
-+ return 0;
-+}
-+
-+static int pcep_cli_pcep_no_srte(void)
-+{
-+ /* Delete PCCs */
-+ path_pcep_cli_pcc_delete();
-+
-+ for (int i = 0; i < MAX_PCE; i++) {
-+ /* Delete PCEs */
-+ if (pcep_g->pce_opts_cli[i] != NULL) {
-+ XFREE(MTYPE_PCEP, pcep_g->pce_opts_cli[i]);
-+ pcep_g->pce_opts_cli[i] = NULL;
-+ pcep_g->num_pce_opts_cli--;
-+ }
-+
-+ /* Delete PCE-CONFIGs */
-+ if (pcep_g->config_group_opts[i] != NULL) {
-+ XFREE(MTYPE_PCEP, pcep_g->config_group_opts[i]);
-+ pcep_g->config_group_opts[i] = NULL;
-+ pcep_g->num_config_group_opts--;
-+ }
-+ }
-+ return 0;
-+}
-+
- int pcep_cli_pce_config_write(struct vty *vty)
- {
- int lines = 0;
-@@ -1998,25 +2052,7 @@ DEFPY(
- NO_STR
- "PCEP configuration\n")
- {
-- /* Delete PCCs */
-- path_pcep_cli_pcc_delete(vty);
--
-- for (int i = 0; i < MAX_PCE; i++) {
-- /* Delete PCEs */
-- if (pcep_g->pce_opts_cli[i] != NULL) {
-- XFREE(MTYPE_PCEP, pcep_g->pce_opts_cli[i]);
-- pcep_g->pce_opts_cli[i] = NULL;
-- pcep_g->num_pce_opts_cli--;
-- }
--
-- /* Delete PCE-CONFIGs */
-- if (pcep_g->config_group_opts[i] != NULL) {
-- XFREE(MTYPE_PCEP, pcep_g->config_group_opts[i]);
-- pcep_g->config_group_opts[i] = NULL;
-- pcep_g->num_config_group_opts--;
-- }
-- }
--
-+ pcep_cli_pcep_no_srte();
- return CMD_SUCCESS;
- }
-
-@@ -2204,7 +2240,7 @@ DEFPY(pcep_cli_no_pcc,
- NO_STR
- "PCC configuration\n")
- {
-- return path_pcep_cli_pcc_delete(vty);
-+ return path_pcep_cli_pcc_delete();
- }
-
- DEFPY(pcep_cli_pcc_pcc_msd,
-@@ -2294,7 +2330,9 @@ DEFPY(pcep_cli_clear_srte_pcep_session,
-
- void pcep_cli_init(void)
- {
-+ hook_register(pathd_srte_check_config_not_empty, pcep_cli_pcep_check_config_not_empty);
- hook_register(pathd_srte_config_write, pcep_cli_pcep_config_write);
-+ hook_register(pathd_srte_no_srte, pcep_cli_pcep_no_srte);
-
- debug_install(&pcep_g->dbg_basic);
- debug_install(&pcep_g->dbg_path);
-diff --git a/pathd/path_pcep_pcc.c b/pathd/path_pcep_pcc.c
-index 82c39ca868..f77d773a58 100644
---- a/pathd/path_pcep_pcc.c
-+++ b/pathd/path_pcep_pcc.c
-@@ -193,20 +193,9 @@ void pcep_pcc_finalize(struct ctrl_state *ctrl_state,
- pcc_state->originator = NULL;
- }
-
-- if (pcc_state->t_reconnect != NULL) {
-- event_cancel(&pcc_state->t_reconnect);
-- pcc_state->t_reconnect = NULL;
-- }
--
-- if (pcc_state->t_update_best != NULL) {
-- event_cancel(&pcc_state->t_update_best);
-- pcc_state->t_update_best = NULL;
-- }
--
-- if (pcc_state->t_session_timeout != NULL) {
-- event_cancel(&pcc_state->t_session_timeout);
-- pcc_state->t_session_timeout = NULL;
-- }
-+ pcep_thread_cancel_timer(&pcc_state->t_reconnect);
-+ pcep_thread_cancel_timer(&pcc_state->t_update_best);
-+ pcep_thread_cancel_timer(&pcc_state->t_session_timeout);
-
- XFREE(MTYPE_PCEP, pcc_state);
- }
-diff --git a/pathd/path_ted.c b/pathd/path_ted.c
-index f9dff1f019..a7442e7080 100644
---- a/pathd/path_ted.c
-+++ b/pathd/path_ted.c
-@@ -325,6 +325,29 @@ uint32_t path_ted_query_type_e(struct prefix *prefix, uint32_t iface_id)
- return sid;
- }
-
-+bool path_is_ted_enabled(void)
-+{
-+ return ted_state_g.enabled;
-+}
-+
-+uint32_t path_ted_mpls_te_off(void)
-+{
-+ if (!ted_state_g.enabled) {
-+ PATH_TED_DEBUG("%s: PATHD-TED: OFF -> OFF", __func__);
-+ return 0;
-+ }
-+
-+ /* Remove TED */
-+ ls_ted_del_all(&ted_state_g.ted);
-+ ted_state_g.enabled = false;
-+ PATH_TED_DEBUG("%s: PATHD-TED: ON -> OFF", __func__);
-+ ted_state_g.import = IMPORT_UNKNOWN;
-+ if (ls_unregister(pathd_zclient, false /*client*/) != 0)
-+ return 1;
-+
-+ return 0;
-+}
-+
- DEFPY (debug_path_ted,
- debug_path_ted_cmd,
- "[no] debug pathd mpls-te",
-@@ -372,17 +395,7 @@ DEFUN (no_path_ted,
- "Disable the TE Database functionality\n")
- /* clang-format on */
- {
-- if (!ted_state_g.enabled) {
-- PATH_TED_DEBUG("%s: PATHD-TED: OFF -> OFF", __func__);
-- return CMD_SUCCESS;
-- }
--
-- /* Remove TED */
-- ls_ted_del_all(&ted_state_g.ted);
-- ted_state_g.enabled = false;
-- PATH_TED_DEBUG("%s: PATHD-TED: ON -> OFF", __func__);
-- ted_state_g.import = IMPORT_UNKNOWN;
-- if (ls_unregister(pathd_zclient, false /*client*/) != 0) {
-+ if (path_ted_mpls_te_off() == 1) {
- vty_out(vty, "Unable to unregister Link State\n");
- return CMD_WARNING;
- }
-diff --git a/pathd/path_ted.h b/pathd/path_ted.h
-index 7f3b3f590f..f51fdaa81b 100644
---- a/pathd/path_ted.h
-+++ b/pathd/path_ted.h
-@@ -151,6 +151,18 @@ uint32_t path_ted_query_type_f(struct ipaddr *local, struct ipaddr *remote);
- */
- uint32_t path_ted_rcvd_message(struct ls_message *msg);
-
-+/**
-+ * @return is ted enabled
-+ */
-+bool path_is_ted_enabled(void);
-+
-+/**
-+ * Turn mpls_te off
-+ *
-+ * @return 0 on success, 1 if could not unregister Link state
-+ */
-+uint32_t path_ted_mpls_te_off(void);
-+
- #ifdef __cplusplus
- }
- #endif
-diff --git a/pathd/pathd.h b/pathd/pathd.h
-index 75e7eff920..97070871e1 100644
---- a/pathd/pathd.h
-+++ b/pathd/pathd.h
-@@ -21,7 +21,16 @@
-
- DECLARE_MGROUP(PATHD);
-
-+/*
-+ * If config is not empty, pathd_srte_check_config_not_empty must
-+ * return 1, otherwise - 0. If no hook subscribers return 1 and there is no
-+ * configuration in pathd, headers `segment-routing/traffic-eng` won't be
-+ * output for `write` command and `pathd_srte_config_write` won't be called.
-+ */
-+DECLARE_HOOK(pathd_srte_check_config_not_empty, (), ());
- DECLARE_HOOK(pathd_srte_config_write, (struct vty *vty), (vty));
-+/* Sent when user requests 'no traffic-eng', please clean configuration */
-+DECLARE_HOOK(pathd_srte_no_srte, (), ());
-
- enum srte_protocol_origin {
- SRTE_ORIGIN_UNDEFINED = 0,
-diff --git a/tools/frr-reload.py b/tools/frr-reload.py
-index fcd64bae3e..4fa47d94e0 100755
---- a/tools/frr-reload.py
-+++ b/tools/frr-reload.py
-@@ -1882,18 +1882,10 @@ def compare_context_objects(newconf, running):
- ):
- continue
-
-- # Segment routing and traffic engineering never need to be deleted
-+ # Segment routing never needs to be deleted
- elif (
- running_ctx_keys[0].startswith("segment-routing")
-- and len(running_ctx_keys) < 3
-- ):
-- continue
--
-- # Neither the pcep command
-- elif (
-- len(running_ctx_keys) == 3
-- and running_ctx_keys[0].startswith("segment-routing")
-- and running_ctx_keys[2].startswith("pcep")
-+ and len(running_ctx_keys) == 1
- ):
- continue
-
---
-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
deleted file mode 100644
index bb399662..00000000
--- a/scripts/package-build/frr/patches/frr/0020-BGP-LS-support.patch
+++ /dev/null
@@ -1,20917 +0,0 @@
-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
-
-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/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/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
-* https://github.com/FRRouting/frr/pull/21825
----
- 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_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 | 234 +-
- bgpd/bgp_route.h | 10 +
- 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 | 319 +-
- bgpd/bgp_zebra.c | 34 +-
- 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 | 274 +
- doc/user/bgp.rst | 2 +
- isisd/isis_te.c | 10 +
- lib/command.h | 1 +
- lib/iana_afi.h | 10 +
- lib/link_state.c | 34 +
- lib/link_state.h | 4 +
- lib/prefix.c | 6 +
- lib/zebra.h | 6 +-
- pbrd/pbr_zebra.c | 8 +
- staticd/static_vty.c | 1 +
- tests/bgpd/test_peer_attr.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 +
- .../bgp_link_state/r2/bgp_capability.json | 11 +
- .../bgp_link_state/r2/bgp_ls_nlri.json | 1507 +++++
- .../bgp_link_state/r2/bgp_neighbor.json | 7 +
- tests/topotests/bgp_link_state/r2/frr.conf | 72 +
- .../topotests/bgp_link_state/r2/isis_adj.json | 18 +
- tests/topotests/bgp_link_state/r3/frr.conf | 56 +
- .../topotests/bgp_link_state/r3/isis_adj.json | 18 +
- tests/topotests/bgp_link_state/r4/frr.conf | 54 +
- .../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 | 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 +
- 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
- create mode 100644 bgpd/bgp_ls_nlri.h
- create mode 100644 bgpd/bgp_ls_ted.c
- create mode 100644 bgpd/bgp_ls_ted.h
- create mode 100644 doc/user/bgp-linkstate.rst
- create mode 100644 tests/topotests/bgp_link_state/__init__.py
- create mode 100644 tests/topotests/bgp_link_state/r1/frr.conf
- create mode 100644 tests/topotests/bgp_link_state/r1/isis_adj.json
- create mode 100644 tests/topotests/bgp_link_state/r2/bgp_capability.json
- create mode 100644 tests/topotests/bgp_link_state/r2/bgp_ls_nlri.json
- create mode 100644 tests/topotests/bgp_link_state/r2/bgp_neighbor.json
- create mode 100644 tests/topotests/bgp_link_state/r2/frr.conf
- create mode 100644 tests/topotests/bgp_link_state/r2/isis_adj.json
- create mode 100644 tests/topotests/bgp_link_state/r3/frr.conf
- create mode 100644 tests/topotests/bgp_link_state/r3/isis_adj.json
- create mode 100644 tests/topotests/bgp_link_state/r4/frr.conf
- create mode 100644 tests/topotests/bgp_link_state/r4/isis_adj.json
- create mode 100644 tests/topotests/bgp_link_state/rr/bgp_capability.json
- create mode 100644 tests/topotests/bgp_link_state/rr/bgp_ls_attrs_link43.json
- create mode 100644 tests/topotests/bgp_link_state/rr/bgp_ls_attrs_node4.json
- create mode 100644 tests/topotests/bgp_link_state/rr/bgp_ls_nlri.json
- create mode 100644 tests/topotests/bgp_link_state/rr/bgp_ls_prefix4.json
- 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 09d4948ab8..cfbde632bd 100644
---- a/bgpd/bgp_attr.c
-+++ b/bgpd/bgp_attr.c
-@@ -45,6 +45,7 @@
- #include "bgp_evpn.h"
- #include "bgp_flowspec_private.h"
- #include "bgp_mac.h"
-+#include "bgpd/bgp_ls_nlri.h"
-
- /* Attribute strings for logging. */
- static const struct message attr_str[] = {
-@@ -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)));
-+ if (attr->ls_attr)
-+ MIX(bgp_ls_attr_hash_key(attr->ls_attr));
-
- return key;
- }
-@@ -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)) &&
-- bgp_nhc_same(bgp_attr_get_nhc(attr1), bgp_attr_get_nhc(attr2)))
-+ bgp_nhc_same(bgp_attr_get_nhc(attr1), bgp_attr_get_nhc(attr2)) &&
-+ bgp_ls_attr_same(attr1->ls_attr, attr2->ls_attr))
- return true;
- }
-
-@@ -1349,6 +1353,13 @@ struct attr *bgp_attr_intern(struct attr *attr)
- nhc->refcnt++;
- }
-
-+ if (attr->ls_attr) {
-+ if (!attr->ls_attr->refcnt)
-+ attr->ls_attr = bgp_ls_attr_intern(attr->ls_attr);
-+ else
-+ attr->ls_attr->refcnt++;
-+ }
-+
- /* 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.
-@@ -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);
-+
-+ bgp_ls_attr_unintern(&attr->ls_attr);
- }
-
- /* 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_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;
-+
-+ if (peer->discard_attrs[args->type] || peer->withdraw_attrs[args->type])
-+ goto ls_attr_ignore;
-+
-+ ls_attr = bgp_ls_attr_alloc();
-+
-+ ret = bgp_ls_parse_attr(connection->curr, args->length, ls_attr);
-+ if (ret != 0) {
-+ bgp_ls_attr_free(ls_attr);
-+ /*
-+ * 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);
-+
-+ bgp_ls_attr_free(ls_attr);
-+
-+ return BGP_ATTR_PARSE_PROCEED;
-+
-+ls_attr_ignore:
-+ stream_forward_getp(connection->curr, args->length);
-+
-+ return bgp_attr_ignore(peer, args->type);
-+}
-+
- /* BGP unknown attribute treatment. */
- static enum bgp_attr_parse_ret
- bgp_attr_unknown(struct bgp_attr_parser_args *args)
-@@ -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;
-+ case BGP_ATTR_LINK_STATE:
-+ ret = bgp_attr_ls(&attr_args);
-+ break;
- default:
- ret = bgp_attr_unknown(&attr_args);
- break;
-@@ -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;
-+ else if (safi == SAFI_BGP_LS)
-+ nh_afi = CHECK_FLAG(peer->af_flags[AFI_BGP_LS][SAFI_BGP_LS], PEER_FLAG_BGP_LS_IPV6)
-+ ? AFI_IP6
-+ : AFI_IP;
- else
- nh_afi = BGP_NEXTHOP_AFI_FROM_NHLEN(attr->mp_nexthop_len);
-
-@@ -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;
-+ case SAFI_BGP_LS:
-+ stream_putc(s, IPV4_MAX_BYTELEN);
-+ stream_put_ipv4(s, attr->mp_nexthop_global_in.s_addr);
-+ break;
- case SAFI_UNSPEC:
- case SAFI_MAX:
- assert(!"SAFI's UNSPEC or MAX being specified are a DEV ESCAPE");
-@@ -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;
-+ case SAFI_BGP_LS:
-+ stream_putc(s, attr->mp_nexthop_len);
-+ stream_put(s, &attr->mp_nexthop_global, IPV6_MAX_BYTELEN);
-+ if (attr->mp_nexthop_len == BGP_ATTR_NHLEN_IPV6_GLOBAL_AND_LL)
-+ stream_put(s, &attr->mp_nexthop_local, IPV6_MAX_BYTELEN);
-+ break;
- case SAFI_UNSPEC:
- case SAFI_MAX:
- assert(!"SAFI's UNSPEC or MAX being specified are a DEV ESCAPE");
-@@ -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;
-+ case AFI_BGP_LS:
- case AFI_UNSPEC:
- case AFI_MAX:
-- assert(!"DEV ESCAPE: AFI_UNSPEC or AFI_MAX should not be used here");
-+ assert(!"DEV ESCAPE: AFI_BGP_LS, AFI_UNSPEC or AFI_MAX should not be used here");
- break;
- }
-
-@@ -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);
- }
-
--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)
-+static void bgp_packet_ls_attribute(struct stream *s, struct bgp *bgp, struct attr *attr,
-+ struct bgp_path_info *bpi, enum bgp_ls_protocol_id protocol_id)
-+{
-+ struct bgp_ls_attr *ls_attr = attr->ls_attr;
-+ size_t attr_start, len_pos, attr_len;
-+ int ret = -1;
-+
-+ /* Write BGP-LS attribute header (RFC 9552 Section 4) */
-+ attr_start = stream_get_endp(s);
-+ stream_putc(s, BGP_ATTR_FLAG_OPTIONAL | BGP_ATTR_FLAG_EXTLEN);
-+ stream_putc(s, BGP_ATTR_LINK_STATE);
-+ len_pos = stream_get_endp(s);
-+ stream_putw(s, 0); /* Placeholder for extended length */
-+
-+ ret = bgp_ls_encode_attr(s, ls_attr, protocol_id);
-+
-+ if (ret < 0) {
-+ /* No attributes or encoding failed - rollback the stream */
-+ stream_set_endp(s, attr_start);
-+ return;
-+ }
-+
-+ /* Update the length field */
-+ attr_len = stream_get_endp(s) - len_pos - 2;
-+ if (attr_len > UINT16_MAX) {
-+ stream_set_endp(s, attr_start);
-+ return;
-+ }
-+
-+ stream_putw_at(s, len_pos, attr_len);
-+}
-+
-+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,
-+ struct bgp_ls_nlri *ls_nlri)
- {
- 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;
-+ case SAFI_BGP_LS:
-+ bgp_ls_encode_nlri(s, ls_nlri);
-+ break;
- case SAFI_LABELED_UNICAST:
- /* 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;
-+ case SAFI_BGP_LS:
-+ /* TODO: add explaination */
-+ size = 0;
-+ break;
- }
-
- return size;
-@@ -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,
- 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;
-@@ -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);
-- bgp_packet_mpattr_prefix(s, afi, safi, p, prd, label,
-- num_labels, addpath_capable,
-- addpath_tx_id, attr);
-+ bgp_packet_mpattr_prefix(s, afi, safi, p, prd, label, num_labels, addpath_capable,
-+ addpath_tx_id, attr, ls_nlri);
- bgp_packet_mpattr_end(s, mpattrlen_pos);
- }
-
-@@ -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);
- }
-
-+ /* BGP-LS Attribute (Type 29) - RFC 9552 Section 4 */
-+ if (afi == AFI_BGP_LS && safi == SAFI_BGP_LS && attr->ls_attr && ls_nlri)
-+ bgp_packet_ls_attribute(s, bgp, attr, bpi, bgp_ls_nlri_protocol_id(ls_nlri));
-+
- /* draft-ietf-idr-entropy-label */
- if (peergroup_flag_check(peer, PEER_FLAG_SEND_NHC_ATTRIBUTE))
- bgp_packet_nhc(s, peer, afi, safi, attr, bpi);
-@@ -5661,12 +5771,10 @@ size_t bgp_packet_mpunreach_start(struct stream *s, afi_t afi, safi_t safi)
- return attrlen_pnt;
- }
-
--void bgp_packet_mpunreach_prefix(struct stream *s, const struct prefix *p,
-- 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 *attr)
-+void bgp_packet_mpunreach_prefix(struct stream *s, const struct prefix *p, 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 *attr, struct bgp_ls_nlri *ls_nlri)
- {
- uint8_t wlabel[4] = {0x80, 0x00, 0x00};
-
-@@ -5681,8 +5789,8 @@ void bgp_packet_mpunreach_prefix(struct stream *s, const struct prefix *p,
- num_labels = 1;
- }
-
-- bgp_packet_mpattr_prefix(s, afi, safi, p, prd, label, num_labels,
-- addpath_capable, addpath_tx_id, attr);
-+ bgp_packet_mpattr_prefix(s, afi, safi, p, prd, label, num_labels, addpath_capable,
-+ addpath_tx_id, attr, ls_nlri);
- }
-
- 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 b5a6ba317c..3fd848b889 100644
---- a/bgpd/bgp_attr.h
-+++ b/bgpd/bgp_attr.h
-@@ -315,6 +315,9 @@ struct attr {
-
- /* Next-hop characteristics */
- struct bgp_nhc *nhc;
-+
-+ /* For BGP-LS Attribute (RFC 9552) */
-+ struct bgp_ls_attr *ls_attr;
- };
-
- /* rmap_change_flags definition */
-@@ -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);
-@@ -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);
- extern void bgp_packet_mpattr_end(struct stream *s, size_t sizep);
-
- extern size_t bgp_packet_mpunreach_start(struct stream *s, afi_t afi,
- safi_t safi);
--extern void bgp_packet_mpunreach_prefix(
-- struct stream *s, const struct prefix *p, 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 *attr);
-+extern void bgp_packet_mpunreach_prefix(struct stream *s, const struct prefix *p, 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 *attr, struct bgp_ls_nlri *ls_nlri);
- extern void bgp_packet_mpunreach_end(struct stream *s, size_t attrlen_pnt);
-
- 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 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, 0, NULL);
-+ NULL, NULL, 0, 0, 0, 0, NULL, NULL);
-
- /* space check? */
-
-@@ -1132,8 +1132,7 @@ static struct stream *bmp_update(const struct prefix *p, struct prefix_rd *prd,
-
- mpattrlen_pos = bgp_packet_mpattr_start(s, peer, afi, safi,
- &vecarr, attr);
-- bgp_packet_mpattr_prefix(s, afi, safi, p, prd, label,
-- num_labels, 0, 0, attr);
-+ bgp_packet_mpattr_prefix(s, afi, safi, p, prd, label, num_labels, 0, 0, attr, NULL);
- bgp_packet_mpattr_end(s, mpattrlen_pos);
- total_attr_len += stream_get_endp(s) - p1;
- }
-@@ -1171,8 +1170,7 @@ static struct stream *bmp_withdraw(const struct prefix *p,
- mp_start = stream_get_endp(s);
- mplen_pos = bgp_packet_mpunreach_start(s, afi, safi);
-
-- bgp_packet_mpunreach_prefix(s, p, afi, safi, prd, NULL, 0, 0, 0,
-- NULL);
-+ bgp_packet_mpunreach_prefix(s, p, afi, safi, prd, NULL, 0, 0, 0, NULL, NULL);
- /* Set the mp_unreach attr's length */
- bgp_packet_mpunreach_end(s, mplen_pos);
-
-diff --git a/bgpd/bgp_debug.c b/bgpd/bgp_debug.c
-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;
- unsigned long conf_bgp_debug_bfd;
- unsigned long conf_bgp_debug_cond_adv;
- unsigned long conf_bgp_debug_aggregate;
-+unsigned long conf_bgp_debug_linkstate;
-
- unsigned long term_bgp_debug_as4;
- unsigned long term_bgp_debug_neighbor_events;
-@@ -83,6 +84,7 @@ unsigned long term_bgp_debug_evpn_mh;
- unsigned long term_bgp_debug_bfd;
- unsigned long term_bgp_debug_cond_adv;
- unsigned long term_bgp_debug_aggregate;
-+unsigned long term_bgp_debug_linkstate;
-
- struct list *bgp_debug_neighbor_events_peers = NULL;
- struct list *bgp_debug_keepalive_peers = NULL;
-@@ -2284,6 +2286,41 @@ DEFPY (debug_bgp_cond_adv,
- return CMD_SUCCESS;
- }
-
-+DEFPY (debug_bgp_linkstate,
-+ debug_bgp_linkstate_cmd,
-+ "debug bgp link-state",
-+ DEBUG_STR
-+ BGP_STR
-+ "BGP Link-State\n")
-+{
-+ if (vty->node == CONFIG_NODE)
-+ DEBUG_ON(linkstate, LINKSTATE);
-+ else {
-+ TERM_DEBUG_ON(linkstate, LINKSTATE);
-+ vty_out(vty, "BGP Link-State debugging is on\n");
-+ }
-+
-+ return CMD_SUCCESS;
-+}
-+
-+DEFPY (no_debug_bgp_linkstate,
-+ no_debug_bgp_linkstate_cmd,
-+ "no debug bgp link-state",
-+ NO_STR
-+ DEBUG_STR
-+ BGP_STR
-+ "BGP Link-State\n")
-+{
-+ if (vty->node == CONFIG_NODE)
-+ DEBUG_OFF(linkstate, LINKSTATE);
-+ else {
-+ TERM_DEBUG_OFF(linkstate, LINKSTATE);
-+ vty_out(vty, "BGP Link-State debugging is off\n");
-+ }
-+
-+ return CMD_SUCCESS;
-+}
-+
- DEFUN (no_debug_bgp,
- no_debug_bgp_cmd,
- "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);
-+ TERM_DEBUG_OFF(linkstate, LINKSTATE);
-
- vty_out(vty, "All possible debugging has been turned off\n");
-
-@@ -2429,6 +2467,9 @@ DEFUN_NOSH (show_debugging_bgp,
- vty_out(vty,
- " BGP conditional advertisement debugging is on\n");
-
-+ if (BGP_DEBUG(linkstate, LINKSTATE))
-+ vty_out(vty, " BGP Link-State debugging is on\n");
-+
- cmd_show_lib_debugs(vty);
-
- hook_call(bgp_hook_config_write_debug, vty, false);
-@@ -2572,6 +2613,11 @@ static int bgp_config_write_debug(struct vty *vty)
- write++;
- }
-
-+ if (CONF_BGP_DEBUG(linkstate, LINKSTATE)) {
-+ vty_out(vty, "debug bgp link-state\n");
-+ write++;
-+ }
-+
- if (CONF_BGP_DEBUG(aggregate, AGGREGATE)) {
- if (!bgp_debug_aggregate_prefixes || list_isempty(bgp_debug_aggregate_prefixes)) {
- vty_out(vty, "debug bgp aggregate\n");
-@@ -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);
-+
-+ /* debug bgp link-state */
-+ install_element(ENABLE_NODE, &debug_bgp_linkstate_cmd);
-+ install_element(CONFIG_NODE, &debug_bgp_linkstate_cmd);
-+ install_element(ENABLE_NODE, &no_debug_bgp_linkstate_cmd);
-+ install_element(CONFIG_NODE, &no_debug_bgp_linkstate_cmd);
- }
-
- /* 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 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;
- extern unsigned long conf_bgp_debug_bfd;
- extern unsigned long conf_bgp_debug_cond_adv;
- extern unsigned long conf_bgp_debug_aggregate;
-+extern unsigned long conf_bgp_debug_linkstate;
-
- extern unsigned long term_bgp_debug_as4;
- extern unsigned long term_bgp_debug_neighbor_events;
-@@ -91,6 +92,7 @@ extern unsigned long term_bgp_debug_evpn_mh;
- extern unsigned long term_bgp_debug_bfd;
- extern unsigned long term_bgp_debug_cond_adv;
- extern unsigned long term_bgp_debug_aggregate;
-+extern unsigned long term_bgp_debug_linkstate;
-
- extern struct list *bgp_debug_neighbor_events_peers;
- extern struct list *bgp_debug_keepalive_peers;
-@@ -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
-+#define BGP_DEBUG_LINKSTATE 0x01
-
- #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 fcfd26645f..d92cbcc674 100644
---- a/bgpd/bgp_errors.c
-+++ b/bgpd/bgp_errors.c
-@@ -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,
-+ .title = "BGP-LS packet error",
-+ .description = "BGP-LS NLRI or attribute packet parsing/encoding error",
-+ .suggestion = "Check that BGP-LS peer is sending valid packets per RFC 9552. May indicate interoperability issue or malformed data.",
-+ },
- {
- .code = END_FERR,
- }
-diff --git a/bgpd/bgp_errors.h b/bgpd/bgp_errors.h
-index e71be1882e..0d12fc0c5f 100644
---- a/bgpd/bgp_errors.h
-+++ b/bgpd/bgp_errors.h
-@@ -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..97c82b3654
---- /dev/null
-+++ b/bgpd/bgp_ls.c
-@@ -0,0 +1,1515 @@
-+// SPDX-License-Identifier: GPL-2.0-or-later
-+/*
-+ * BGP Link-State (RFC 9552) - Core Implementation
-+ * Copyright (C) 2025 Carmine Scarpitta
-+ */
-+
-+#include <zebra.h>
-+
-+#include "lib/json.h"
-+
-+#include "stream.h"
-+#include "bgpd/bgpd.h"
-+#include "bgpd/bgp_aspath.h"
-+#include "bgpd/bgp_ls.h"
-+#include "bgpd/bgp_ls_nlri.h"
-+#include "bgpd/bgp_ls_ted.h"
-+#include "bgpd/bgp_debug.h"
-+#include "bgpd/bgp_attr.h"
-+#include "bgpd/bgp_route.h"
-+#include "bgpd/bgp_errors.h"
-+#include "bgpd/bgp_zebra.h"
-+#define UNKNOWN LS_UNKNOWN
-+#include "lib/link_state.h"
-+#undef UNKNOWN
-+
-+DEFINE_MTYPE_STATIC(BGPD, BGP_LS, "BGP-LS instance");
-+
-+/*
-+ * Helper Functions for NLRI Formatting
-+ */
-+
-+/* Convert node descriptor to JSON */
-+static json_object *node_desc_to_json(struct bgp_ls_node_descriptor *node)
-+{
-+ json_object *json_node = json_object_new_object();
-+
-+ if (CHECK_FLAG(node->present_tlvs, BGP_LS_NODE_DESC_AS_BIT))
-+ json_object_int_add(json_node, "asn", node->asn);
-+
-+ 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 (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 (CHECK_FLAG(node->present_tlvs, BGP_LS_NODE_DESC_IGP_ROUTER_BIT)) {
-+ char igp_router_id[256];
-+ char *p = igp_router_id;
-+
-+ for (int i = 0; i < node->igp_router_id_len; i++) {
-+ p += snprintfrr(p, sizeof(igp_router_id) - (p - igp_router_id), "%02x",
-+ node->igp_router_id[i]);
-+ if (i < node->igp_router_id_len - 1 && (i + 1) % 2 == 0) {
-+ p += snprintfrr(p, sizeof(igp_router_id) - (p - igp_router_id),
-+ ".");
-+ }
-+ }
-+ 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;
-+}
-+
-+/* Convert link descriptor to JSON */
-+static json_object *link_desc_to_json(struct bgp_ls_link_descriptor *link_desc)
-+{
-+ json_object *json_link = json_object_new_object();
-+
-+ 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 (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 (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 (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 (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 (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]);
-+
-+ return json_link;
-+}
-+
-+/* Convert prefix descriptor to JSON */
-+static json_object *prefix_desc_to_json(struct bgp_ls_prefix_descriptor *prefix_desc,
-+ enum bgp_ls_nlri_type nlri_type)
-+{
-+ json_object *json_prefix = json_object_new_object();
-+
-+ /* IP Reachability Information */
-+ json_object_string_addf(json_prefix, "ipReachabilityInformation", "%pFX",
-+ &prefix_desc->prefix);
-+
-+ /* OSPF Route Type */
-+ 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;
-+}
-+
-+/* Convert NLRI to JSON */
-+json_object *bgp_ls_nlri_to_json(struct bgp_ls_nlri *nlri)
-+{
-+ json_object *json_nlri = json_object_new_object();
-+ const char *nlri_type_str = NULL;
-+ enum bgp_ls_protocol_id protocol_id = 0;
-+ uint64_t identifier = 0;
-+
-+ /* NLRI Type */
-+ switch (nlri->nlri_type) {
-+ case BGP_LS_NLRI_TYPE_NODE:
-+ nlri_type_str = "node";
-+ protocol_id = nlri->nlri_data.node.protocol_id;
-+ identifier = nlri->nlri_data.node.identifier;
-+ break;
-+ case BGP_LS_NLRI_TYPE_LINK:
-+ nlri_type_str = "link";
-+ protocol_id = nlri->nlri_data.link.protocol_id;
-+ identifier = nlri->nlri_data.link.identifier;
-+ break;
-+ case BGP_LS_NLRI_TYPE_IPV4_PREFIX:
-+ nlri_type_str = "ipv4Prefix";
-+ protocol_id = nlri->nlri_data.prefix.protocol_id;
-+ identifier = nlri->nlri_data.prefix.identifier;
-+ break;
-+ case BGP_LS_NLRI_TYPE_IPV6_PREFIX:
-+ nlri_type_str = "ipv6Prefix";
-+ protocol_id = nlri->nlri_data.prefix.protocol_id;
-+ identifier = nlri->nlri_data.prefix.identifier;
-+ break;
-+ case BGP_LS_NLRI_TYPE_RESERVED:
-+ nlri_type_str = "unknown";
-+ break;
-+ }
-+ json_object_string_add(json_nlri, "nlriType", nlri_type_str);
-+
-+ /* Protocol ID */
-+ json_object_int_add(json_nlri, "protocolId", protocol_id);
-+
-+ /* Identifier */
-+ json_object_int_add(json_nlri, "identifier", identifier);
-+
-+ /* Type-specific descriptors */
-+ if (nlri->nlri_type == BGP_LS_NLRI_TYPE_NODE) {
-+ json_object *json_local = node_desc_to_json(&nlri->nlri_data.node.local_node);
-+
-+ json_object_object_add(json_nlri, "localNodeDescriptors", json_local);
-+ } else if (nlri->nlri_type == BGP_LS_NLRI_TYPE_LINK) {
-+ json_object *json_local = node_desc_to_json(&nlri->nlri_data.link.local_node);
-+ json_object *json_remote = node_desc_to_json(&nlri->nlri_data.link.remote_node);
-+ json_object *json_link = link_desc_to_json(&nlri->nlri_data.link.link_desc);
-+
-+ json_object_object_add(json_nlri, "localNodeDescriptors", json_local);
-+ json_object_object_add(json_nlri, "remoteNodeDescriptors", json_remote);
-+ json_object_object_add(json_nlri, "linkDescriptors", json_link);
-+ } else if (nlri->nlri_type == BGP_LS_NLRI_TYPE_IPV4_PREFIX ||
-+ nlri->nlri_type == BGP_LS_NLRI_TYPE_IPV6_PREFIX) {
-+ json_object *json_local = node_desc_to_json(&nlri->nlri_data.prefix.local_node);
-+ json_object *json_prefix = prefix_desc_to_json(&nlri->nlri_data.prefix.prefix_desc,
-+ nlri->nlri_type);
-+ json_object_object_add(json_nlri, "localNodeDescriptors", json_local);
-+ json_object_object_add(json_nlri, "prefixDescriptors", json_prefix);
-+ }
-+
-+ return json_nlri;
-+}
-+
-+/* Format node descriptor to string */
-+static void format_node_desc(char **p, size_t *remain, struct bgp_ls_node_descriptor *node,
-+ const char *prefix_str)
-+{
-+ int len;
-+
-+ len = snprintfrr(*p, *remain, "[%s", prefix_str);
-+ *p += len;
-+ *remain -= len;
-+
-+ /* AS Number */
-+ 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 (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);
-+ *p += len;
-+ *remain -= len;
-+ }
-+
-+ /* IGP Router ID */
-+ if (CHECK_FLAG(node->present_tlvs, BGP_LS_NODE_DESC_IGP_ROUTER_BIT)) {
-+ len = snprintfrr(*p, *remain, "[s");
-+ *p += len;
-+ *remain -= len;
-+ for (int i = 0; i < node->igp_router_id_len; i++) {
-+ len = snprintfrr(*p, *remain, "%02x", node->igp_router_id[i]);
-+ *p += len;
-+ *remain -= len;
-+ if (i < node->igp_router_id_len - 1 && (i + 1) % 2 == 0) {
-+ len = snprintfrr(*p, *remain, ".");
-+ *p += len;
-+ *remain -= len;
-+ }
-+ }
-+ len = snprintfrr(*p, *remain, "]");
-+ *p += len;
-+ *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;
-+}
-+
-+/* Format link descriptor to string */
-+static void format_link_desc(char **p, size_t *remain, struct bgp_ls_link_descriptor *link_desc)
-+{
-+ int len;
-+
-+ len = snprintfrr(*p, *remain, "[L");
-+ *p += len;
-+ *remain -= len;
-+
-+ /* Link Local/Remote Identifiers (TLV 258) */
-+ 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;
-+ *remain -= len;
-+ }
-+
-+ /* IPv4 Interface Address (TLV 259) */
-+ 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 (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 (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 (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;
-+ }
-+
-+ len = snprintfrr(*p, *remain, "]");
-+ *p += len;
-+ *remain -= len;
-+}
-+
-+void bgp_ls_nlri_format(struct bgp_ls_nlri *nlri, char *buf, size_t buf_len)
-+{
-+ char tmp[512];
-+ char *p = buf;
-+ size_t remain = buf_len;
-+ int len;
-+
-+ if (!nlri || !buf || buf_len == 0)
-+ return;
-+
-+ /* NLRI Type prefix */
-+ switch (nlri->nlri_type) {
-+ case BGP_LS_NLRI_TYPE_RESERVED:
-+ len = snprintfrr(p, remain, "[R]");
-+ break;
-+ case BGP_LS_NLRI_TYPE_NODE:
-+ len = snprintfrr(p, remain, "[V]");
-+ break;
-+ case BGP_LS_NLRI_TYPE_LINK:
-+ len = snprintfrr(p, remain, "[E]");
-+ break;
-+ case BGP_LS_NLRI_TYPE_IPV4_PREFIX:
-+ case BGP_LS_NLRI_TYPE_IPV6_PREFIX:
-+ len = snprintfrr(p, remain, "[T]");
-+ break;
-+ default:
-+ len = snprintfrr(p, remain, "[U]");
-+ break;
-+ }
-+ p += len;
-+ remain -= len;
-+
-+ /* Protocol ID and Instance ID - directly from NLRI structures */
-+ const char *proto_str = NULL;
-+ uint64_t instance_id = 0;
-+ enum bgp_ls_protocol_id protocol_id = 0;
-+
-+ if (nlri->nlri_type == BGP_LS_NLRI_TYPE_NODE) {
-+ protocol_id = nlri->nlri_data.node.protocol_id;
-+ instance_id = nlri->nlri_data.node.identifier;
-+ } else if (nlri->nlri_type == BGP_LS_NLRI_TYPE_LINK) {
-+ protocol_id = nlri->nlri_data.link.protocol_id;
-+ instance_id = nlri->nlri_data.link.identifier;
-+ } else if (nlri->nlri_type == BGP_LS_NLRI_TYPE_IPV4_PREFIX ||
-+ nlri->nlri_type == BGP_LS_NLRI_TYPE_IPV6_PREFIX) {
-+ protocol_id = nlri->nlri_data.prefix.protocol_id;
-+ instance_id = nlri->nlri_data.prefix.identifier;
-+ }
-+
-+ switch (protocol_id) {
-+ case BGP_LS_PROTO_ISIS_L1:
-+ proto_str = "L1";
-+ break;
-+ case BGP_LS_PROTO_ISIS_L2:
-+ proto_str = "L2";
-+ break;
-+ case BGP_LS_PROTO_OSPFV2:
-+ proto_str = "O";
-+ break;
-+ case BGP_LS_PROTO_OSPFV3:
-+ proto_str = "O3";
-+ break;
-+ case BGP_LS_PROTO_DIRECT:
-+ proto_str = "D";
-+ break;
-+ case BGP_LS_PROTO_STATIC:
-+ proto_str = "S";
-+ break;
-+ case BGP_LS_PROTO_BGP:
-+ proto_str = "B";
-+ break;
-+ case BGP_LS_PROTO_RESERVED:
-+ proto_str = "U";
-+ break;
-+ }
-+ len = snprintfrr(p, remain, "[%s][I0x%llx]", proto_str, (unsigned long long)instance_id);
-+ p += len;
-+ remain -= len;
-+
-+ /* Add NLRI type-specific descriptors */
-+ if (nlri->nlri_type == BGP_LS_NLRI_TYPE_NODE) {
-+ format_node_desc(&p, &remain, &nlri->nlri_data.node.local_node, "N");
-+ } else if (nlri->nlri_type == BGP_LS_NLRI_TYPE_LINK) {
-+ format_node_desc(&p, &remain, &nlri->nlri_data.link.local_node, "N");
-+ format_node_desc(&p, &remain, &nlri->nlri_data.link.remote_node, "R");
-+ format_link_desc(&p, &remain, &nlri->nlri_data.link.link_desc);
-+ } else if (nlri->nlri_type == BGP_LS_NLRI_TYPE_IPV4_PREFIX ||
-+ nlri->nlri_type == BGP_LS_NLRI_TYPE_IPV6_PREFIX) {
-+ format_node_desc(&p, &remain, &nlri->nlri_data.prefix.local_node, "N");
-+
-+ /* Format prefix */
-+ len = snprintfrr(p, remain, "[P[p");
-+ p += len;
-+ remain -= len;
-+
-+ if (nlri->nlri_type == BGP_LS_NLRI_TYPE_IPV4_PREFIX) {
-+ inet_ntop(AF_INET, &nlri->nlri_data.prefix.prefix_desc.prefix.u.prefix4,
-+ tmp, sizeof(tmp));
-+ } else {
-+ inet_ntop(AF_INET6, &nlri->nlri_data.prefix.prefix_desc.prefix.u.prefix6,
-+ tmp, sizeof(tmp));
-+ }
-+ len = snprintfrr(p, remain, "%s/%u", tmp,
-+ nlri->nlri_data.prefix.prefix_desc.prefix.prefixlen);
-+ p += len;
-+ remain -= len;
-+
-+ 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;
-+ }
-+ }
-+}
-+
-+/*
-+ * Helper function to lookup BGP-LS NLRI by string representation
-+ */
-+struct bgp_dest *bgp_ls_lookup_nlri_by_str(struct bgp *bgp, const char *nlri_str)
-+{
-+ struct bgp_table *table;
-+ struct bgp_dest *dest;
-+ char formatted_nlri[1024];
-+ struct bgp_ls_nlri *entry;
-+
-+ if (!bgp || !bgp->ls_info)
-+ return NULL;
-+
-+ table = bgp->rib[AFI_BGP_LS][SAFI_BGP_LS];
-+ if (!table)
-+ return NULL;
-+
-+ /* Iterate through the table and match formatted NLRI string */
-+ for (dest = bgp_table_top(table); dest; dest = bgp_route_next(dest)) {
-+ entry = dest->ls_nlri;
-+ if (!entry)
-+ continue;
-+ bgp_ls_nlri_format(entry, formatted_nlri, sizeof(formatted_nlri));
-+ if (strcmp(formatted_nlri, nlri_str) == 0)
-+ return dest;
-+ }
-+
-+ return NULL;
-+}
-+
-+/*
-+ * ===========================================================================
-+ * RIB Operations
-+ * ===========================================================================
-+ */
-+
-+/*
-+ * Install or update BGP-LS route in RIB (RFC 9552 Section 6)
-+ *
-+ * This function handles locally originated BGP-LS routes from IGP.
-+ * It creates a synthetic prefix in the standard BGP RIB using AF_UNSPEC
-+ * and stores the full NLRI in bgp_path_info_extra->ls_nlri.
-+ *
-+ * The function uses a dual-storage approach:
-+ * 1. BGP-LS hash table - for fast NLRI lookups and ID allocation
-+ * 2. Standard BGP RIB - for integration with existing BGP processing
-+ *
-+ * @param bgp - BGP instance
-+ * @param nlri - BGP-LS NLRI to install
-+ * @return 0 on success, -1 on error
-+ */
-+int bgp_ls_update(struct bgp *bgp, struct bgp_ls_nlri *nlri, struct bgp_ls_attr *ls_attr)
-+{
-+ struct bgp_path_info *bpi;
-+ struct bgp_path_info *new;
-+ struct attr attr;
-+ struct attr *attr_new;
-+ struct bgp_dest *dest;
-+ struct bgp_ls_nlri *ls_nlri;
-+ struct prefix p;
-+
-+ if (!bgp || !nlri) {
-+ 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) {
-+ flog_err(EC_BGP_LS_PACKET, "BGP-LS: ls_info not initialized");
-+ return -1;
-+ }
-+
-+ /* Lookup or insert NLRI in hash table */
-+ ls_nlri = bgp_ls_nlri_intern(nlri);
-+
-+ /* Make BGP-LS NLRI prefix */
-+ memset(&p, 0, sizeof(p));
-+ p.family = AF_UNSPEC;
-+ p.prefixlen = 32;
-+ p.u.val32[0] = ls_nlri->id;
-+
-+ dest = bgp_afi_node_get(bgp->rib[AFI_BGP_LS][SAFI_BGP_LS], AFI_BGP_LS, SAFI_BGP_LS, &p,
-+ NULL);
-+
-+ /*
-+ * Unintern any existing NLRI reference before installing the new one
-+ * to avoid leaking the previous interned pointer.
-+ */
-+ if (dest->ls_nlri)
-+ bgp_ls_nlri_unintern(&dest->ls_nlri);
-+
-+ dest->ls_nlri = ls_nlri;
-+
-+ /* Make default attribute. */
-+ bgp_attr_default_set(&attr, bgp, BGP_ORIGIN_INCOMPLETE);
-+
-+ attr.ls_attr = ls_attr;
-+
-+ attr_new = bgp_attr_intern(&attr);
-+
-+ for (bpi = bgp_dest_get_bgp_path_info(dest); bpi; bpi = bpi->next)
-+ if (bpi->peer == bgp->peer_self)
-+ break;
-+
-+ if (bpi) {
-+ if (!CHECK_FLAG(bpi->flags, BGP_PATH_REMOVED) && attrhash_cmp(bpi->attr, attr_new)) {
-+ /* The attribute is not changed. */
-+ bgp_attr_unintern(&attr_new);
-+ aspath_unintern(&attr.aspath);
-+ bgp_dest_unlock_node(dest);
-+ } else {
-+ /* The attribute is changed. */
-+ bgp_path_info_set_flag(dest, bpi, BGP_PATH_ATTR_CHANGED);
-+
-+ UNSET_FLAG(bpi->flags, BGP_PATH_REMOVED);
-+ /* unset of previous already took care of pcount */
-+ SET_FLAG(bpi->flags, BGP_PATH_VALID);
-+
-+ bgp_attr_unintern(&bpi->attr);
-+ bpi->attr = attr_new;
-+ bpi->uptime = monotime(NULL);
-+
-+ /* Process change. */
-+ bgp_process(bgp, dest, bpi, AFI_BGP_LS, SAFI_BGP_LS);
-+ bgp_dest_unlock_node(dest);
-+ aspath_unintern(&attr.aspath);
-+ }
-+
-+ return 0;
-+ }
-+
-+ /* Make new BGP info. */
-+ new = info_make(ZEBRA_ROUTE_BGP, BGP_ROUTE_REDISTRIBUTE, 0, bgp->peer_self, attr_new, dest);
-+ SET_FLAG(new->flags, BGP_PATH_VALID);
-+
-+ /* Register new BGP information. */
-+ bgp_path_info_add(dest, new);
-+
-+ /* Process change */
-+ bgp_process(bgp, dest, new, AFI_BGP_LS, SAFI_BGP_LS);
-+
-+ /* Unlock node from bgp_afi_node_get */
-+ bgp_dest_unlock_node(dest);
-+
-+ /* Unintern original */
-+ aspath_unintern(&attr.aspath);
-+
-+ return 0;
-+}
-+
-+
-+/*
-+ * Remove BGP-LS route from RIB (RFC 9552 Section 6)
-+ *
-+ * This function handles withdrawal of locally originated BGP-LS routes.
-+ * It marks the route as removed and triggers BGP processing for
-+ * withdrawal advertisement to peers.
-+ *
-+ * @param bgp - BGP instance
-+ * @param nlri - BGP-LS NLRI to withdraw
-+ * @return 0 on success, -1 on error
-+ */
-+int bgp_ls_withdraw(struct bgp *bgp, struct bgp_ls_nlri *nlri)
-+{
-+ struct bgp_path_info *bpi;
-+ struct bgp_dest *dest;
-+ struct prefix p;
-+ struct bgp_ls_nlri *ls_nlri;
-+
-+ if (!bgp || !nlri) {
-+ 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;
-+ }
-+
-+ /* Lookup NLRI in hash table */
-+ ls_nlri = bgp_ls_nlri_lookup(&bgp->ls_info->nlri_hash, nlri);
-+ if (!ls_nlri) {
-+ if (BGP_DEBUG(linkstate, LINKSTATE)) {
-+ zlog_debug("%s: BGP-LS WITHDRAW for non-existent NLRI type=%u", __func__,
-+ nlri->nlri_type);
-+ }
-+ return 0; /* Not an error - may have been withdrawn already */
-+ }
-+
-+ /* Make synthetic prefix using hash table ID */
-+ memset(&p, 0, sizeof(p));
-+ p.family = AF_UNSPEC;
-+ p.prefixlen = 32;
-+ p.u.val32[0] = ls_nlri->id;
-+
-+ dest = bgp_node_lookup(bgp->rib[AFI_BGP_LS][SAFI_BGP_LS], &p);
-+ if (!dest) {
-+ if (BGP_DEBUG(linkstate, LINKSTATE))
-+ zlog_debug("%s: No RIB entry found for NLRI type=%u", __func__,
-+ nlri->nlri_type);
-+ return 0;
-+ }
-+
-+ /* Find path from local peer */
-+ for (bpi = bgp_dest_get_bgp_path_info(dest); bpi; bpi = bpi->next)
-+ if (bpi->peer == bgp->peer_self)
-+ break;
-+
-+ if (bpi) {
-+ if (BGP_DEBUG(linkstate, LINKSTATE)) {
-+ zlog_debug("%s: Withdrawing BGP-LS route type=%u", __func__,
-+ nlri->nlri_type);
-+ }
-+
-+ /* Mark for deletion */
-+ SET_FLAG(bpi->flags, BGP_PATH_REMOVED);
-+ UNSET_FLAG(bpi->flags, BGP_PATH_VALID);
-+
-+ /* Process change - triggers withdrawal to peers */
-+ bgp_process(bgp, dest, bpi, AFI_BGP_LS, SAFI_BGP_LS);
-+ } else {
-+ if (BGP_DEBUG(linkstate, LINKSTATE))
-+ zlog_debug("%s: No path found for NLRI type=%u", __func__, nlri->nlri_type);
-+ }
-+
-+ /* Unlock node from bgp_node_lookup */
-+ bgp_dest_unlock_node(dest);
-+
-+ return 0;
-+}
-+
-+/*
-+ * ===========================================================================
-+ * BGP-LS NLRI Parsing
-+ * ===========================================================================
-+ */
-+
-+/*
-+ * Parse BGP-LS NLRI from UPDATE/WITHDRAW messages
-+ *
-+ * Called from bgp_nlri_parse() for SAFI_BGP_LS packets.
-+ * Decodes NLRIs from MP_REACH_NLRI or MP_UNREACH_NLRI and processes them.
-+ *
-+ * @param peer - BGP peer sending the NLRI
-+ * @param attr - BGP path attributes (NULL for withdrawals)
-+ * @param packet - NLRI packet containing AFI/SAFI and NLRI data
-+ * @return BGP_NLRI_PARSE_OK on success, error code otherwise
-+ */
-+int bgp_nlri_parse_ls(struct peer *peer, struct attr *attr, struct bgp_nlri *packet)
-+{
-+ struct stream *s;
-+ struct bgp_ls_nlri *nlri;
-+ struct prefix p;
-+ struct bgp_ls_nlri *ls_entry;
-+ struct bgp_dest *dest;
-+ int ret = BGP_NLRI_PARSE_OK;
-+
-+ if (!peer || !peer->bgp || !peer->bgp->ls_info || !packet)
-+ return BGP_NLRI_PARSE_ERROR;
-+
-+ s = stream_new(packet->length);
-+ stream_put(s, packet->nlri, packet->length);
-+ stream_set_getp(s, 0);
-+
-+ while (STREAM_READABLE(s) > 0) {
-+ nlri = bgp_ls_nlri_alloc();
-+ if (!nlri) {
-+ ret = BGP_NLRI_PARSE_ERROR;
-+ goto done;
-+ }
-+
-+ ret = bgp_ls_decode_nlri(s, nlri);
-+ if (ret < 0) {
-+ bgp_ls_nlri_free(nlri);
-+ flog_warn(EC_BGP_LS_PACKET, "%s [Error] Failed to decode BGP-LS NLRI",
-+ peer->host);
-+ ret = BGP_NLRI_PARSE_ERROR;
-+ goto done;
-+ }
-+
-+ ls_entry = bgp_ls_nlri_get(&peer->bgp->ls_info->nlri_hash, peer->bgp, nlri);
-+
-+ memset(&p, 0, sizeof(p));
-+ p.family = AF_UNSPEC;
-+ p.prefixlen = 32;
-+ p.u.val32[0] = ls_entry->id;
-+
-+ if (attr) {
-+ if (attr->ls_attr &&
-+ bgp_ls_attr_process_with_protocol_id(attr->ls_attr,
-+ bgp_ls_nlri_protocol_id(nlri)) <
-+ 0) {
-+ flog_warn(EC_BGP_LS_PACKET,
-+ "%s [Error] Processing of BGP-LS attributes failed",
-+ peer->host);
-+ ret = BGP_NLRI_PARSE_ERROR;
-+ goto done;
-+ }
-+
-+ dest = bgp_afi_node_get(bgp_get_default()->rib[AFI_BGP_LS][SAFI_BGP_LS],
-+ AFI_BGP_LS, SAFI_BGP_LS, &p, NULL);
-+ dest->ls_nlri = ls_entry;
-+
-+ bgp_update(peer, &p, 0, attr, packet->afi, packet->safi, ZEBRA_ROUTE_BGP,
-+ BGP_ROUTE_NORMAL, NULL, NULL, 0, 0, NULL);
-+
-+ bgp_dest_unlock_node(dest);
-+ } else
-+ bgp_withdraw(peer, &p, 0, packet->afi, packet->safi, ZEBRA_ROUTE_BGP,
-+ BGP_ROUTE_NORMAL, NULL, NULL, 0);
-+
-+ if (BGP_DEBUG(linkstate, LINKSTATE))
-+ zlog_debug("%s processed BGP-LS %s NLRI type=%u", peer->host,
-+ attr ? "UPDATE" : "WITHDRAW", nlri->nlri_type);
-+
-+ bgp_ls_nlri_free(nlri);
-+ }
-+
-+done:
-+ stream_free(s);
-+ return ret;
-+}
-+
-+/*
-+ * ===========================================================================
-+ * BGP-LS Link State Database Registration
-+ * ===========================================================================
-+ */
-+
-+/*
-+ * Register BGP with zebra link-state database to receive updates from IGPs
-+ *
-+ * @return true on success, false on failure
-+ */
-+bool bgp_ls_register(struct bgp *bgp)
-+{
-+ if (!bgp->ls_info)
-+ return false;
-+
-+ /* Already registered */
-+ if (bgp_ls_is_registered(bgp))
-+ return true;
-+
-+ if (ls_register(bgp_zclient, false) != 0) {
-+ zlog_err("BGP-LS: Failed to register with Link State database");
-+ return false;
-+ }
-+
-+ if (ls_request_sync(bgp_zclient) == -1)
-+ zlog_warn("BGP-LS: Failed to request initial Link State database sync");
-+ else
-+ zlog_info("BGP-LS: Requested initial Link State database sync");
-+
-+ bgp->ls_info->registered_ls_db = true;
-+
-+ zlog_info("BGP-LS: Registered with Link State database for BGP instance %s",
-+ bgp->name_pretty);
-+ return true;
-+}
-+
-+/*
-+ * Unregister BGP from zebra link-state database
-+ *
-+ * @return true on success, false on failure
-+ */
-+bool bgp_ls_unregister(struct bgp *bgp)
-+{
-+ if (!bgp->ls_info)
-+ return false;
-+
-+ /* Not registered */
-+ if (!bgp_ls_is_registered(bgp))
-+ return true;
-+
-+ /*
-+ * Clear the local registration flag *before* the zebra call.
-+ *
-+ * If ls_unregister() fails, BGP has lost sync with zebra. Leaving
-+ * registered_ls_db set to true in that case would make
-+ * bgp_ls_is_registered() report "still registered", preventing any
-+ * subsequent bgp_ls_register() call from attempting re-registration
-+ * and leaving BGP permanently unable to receive link-state updates.
-+ *
-+ * By clearing the flag eagerly, bgp_ls_register() will see
-+ * registered_ls_db=false and attempt re-registration, giving the
-+ * system a chance to recover.
-+ */
-+ bgp->ls_info->registered_ls_db = false;
-+
-+ if (ls_unregister(bgp_zclient, false) != 0) {
-+ zlog_err("BGP-LS: Failed to unregister from Link State database");
-+ return false;
-+ }
-+
-+ zlog_info("BGP-LS: Unregistered from Link State database for BGP instance %s",
-+ bgp->name_pretty);
-+ return true;
-+}
-+
-+/*
-+ * Check if BGP is registered with zebra link-state database
-+ * Returns true if registered, false otherwise
-+ */
-+bool bgp_ls_is_registered(struct bgp *bgp)
-+{
-+ if (!bgp || !bgp->ls_info)
-+ return false;
-+
-+ return bgp->ls_info->registered_ls_db;
-+}
-+
-+/*
-+ * ===========================================================================
-+ * Module Initialization and Cleanup
-+ * ===========================================================================
-+ */
-+
-+/*
-+ * Initialize BGP-LS module for a BGP instance
-+ * Called from bgp_create() for the default BGP instance only
-+ */
-+void bgp_ls_init(struct bgp *bgp)
-+{
-+ if (bgp->inst_type != BGP_INSTANCE_TYPE_DEFAULT)
-+ return;
-+
-+ bgp->ls_info = XCALLOC(MTYPE_BGP_LS, sizeof(struct bgp_ls));
-+ bgp->ls_info->bgp = bgp;
-+ bgp->ls_info->allocator = idalloc_new("BGP-LS NLRI ID Allocator");
-+ bgp_ls_nlri_hash_init(&bgp->ls_info->nlri_hash);
-+ bgp_ls_attr_hash_init(&bgp->ls_info->ls_attr_hash);
-+
-+ bgp->ls_info->ted = ls_ted_new(bgp->as, "BGP-LS TED", bgp->as);
-+
-+ zlog_info("BGP-LS: Module initialized for instance %s", bgp->name_pretty);
-+}
-+
-+/*
-+ * Cleanup BGP-LS module for a BGP instance
-+ * Called from bgp_free() for the default BGP instance only
-+ */
-+void bgp_ls_cleanup(struct bgp *bgp)
-+{
-+ struct bgp_ls_nlri *entry;
-+ struct bgp_ls_attr *ls_attr;
-+
-+ if (bgp->inst_type != BGP_INSTANCE_TYPE_DEFAULT)
-+ return;
-+
-+ if (!bgp->ls_info)
-+ return;
-+
-+ bgp_ls_unregister(bgp);
-+
-+ frr_each_safe (bgp_ls_nlri_hash, &bgp->ls_info->nlri_hash, entry) {
-+ bgp_ls_nlri_hash_del(&bgp->ls_info->nlri_hash, entry);
-+ bgp_ls_nlri_free(entry);
-+ }
-+ bgp_ls_nlri_hash_fini(&bgp->ls_info->nlri_hash);
-+
-+ frr_each_safe (bgp_ls_attr_hash, &bgp->ls_info->ls_attr_hash, ls_attr) {
-+ bgp_ls_attr_hash_del(&bgp->ls_info->ls_attr_hash, ls_attr);
-+ bgp_ls_attr_free(ls_attr);
-+ }
-+ bgp_ls_attr_hash_fini(&bgp->ls_info->ls_attr_hash);
-+
-+ ls_ted_del_all(&bgp->ls_info->ted);
-+
-+ idalloc_destroy(bgp->ls_info->allocator);
-+
-+ XFREE(MTYPE_BGP_LS, bgp->ls_info);
-+
-+ 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..e833be08a2
---- /dev/null
-+++ b/bgpd/bgp_ls.h
-@@ -0,0 +1,124 @@
-+// SPDX-License-Identifier: GPL-2.0-or-later
-+/*
-+ * BGP Link-State (RFC 9552) - Core Constants and Structures
-+ * Copyright (C) 2025 Carmine Scarpitta
-+ */
-+
-+#ifndef _FRR_BGP_LS_H
-+#define _FRR_BGP_LS_H
-+
-+#include "bgpd/bgpd.h"
-+#include "bgpd/bgp_ls_nlri.h"
-+
-+struct bgp_ls {
-+ /* Back-pointer to parent BGP instance */
-+ struct bgp *bgp;
-+
-+ /* Hash table for BGP-LS NLRIs (nodes, links, prefixes) */
-+ struct bgp_ls_nlri_hash_head nlri_hash;
-+
-+ /* Hash table for BGP-LS Attributes */
-+ struct bgp_ls_attr_hash_head ls_attr_hash;
-+
-+ /* Traffic Engineering Database */
-+ struct ls_ted *ted;
-+
-+ /* NLRI ID allocator */
-+ struct id_alloc *allocator;
-+
-+ /* Link-state database registration status */
-+ bool registered_ls_db;
-+
-+ bool enable_distribution;
-+
-+ /* BGP-fabric link-state instance ID */
-+ uint64_t instance_id;
-+};
-+
-+/* Function prototypes */
-+
-+/*
-+ * ===========================================================================
-+ * RIB Operations
-+ * ===========================================================================
-+ */
-+
-+/*
-+ * Install or update BGP-LS route in RIB
-+ *
-+ * This function handles locally originated BGP-LS routes from IGP.
-+ * It creates a synthetic prefix in the standard BGP RIB and stores
-+ * the full NLRI in bgp_path_info_extra->ls_nlri.
-+ *
-+ * @param bgp - BGP instance
-+ * @param nlri - Decoded BGP-LS NLRI
-+ * @return 0 on success, -1 on error
-+ */
-+extern int bgp_ls_update(struct bgp *bgp, struct bgp_ls_nlri *nlri, struct bgp_ls_attr *ls_attr);
-+
-+/*
-+ * Remove BGP-LS route from RIB
-+ *
-+ * This function handles withdrawal of locally originated BGP-LS routes.
-+ * It marks the route as removed and triggers BGP processing for
-+ * withdrawal advertisement to peers.
-+ *
-+ * @param bgp - BGP instance
-+ * @param nlri - Decoded BGP-LS NLRI
-+ * @return 0 on success, -1 on error
-+ */
-+extern int bgp_ls_withdraw(struct bgp *bgp, struct bgp_ls_nlri *nlri);
-+
-+/* BGP-LS NLRI parsing */
-+extern int bgp_nlri_parse_ls(struct peer *peer, struct attr *attr, struct bgp_nlri *packet);
-+
-+/* BGP-LS registration with link-state database */
-+extern bool bgp_ls_register(struct bgp *bgp);
-+extern bool bgp_ls_unregister(struct bgp *bgp);
-+extern bool bgp_ls_is_registered(struct bgp *bgp);
-+
-+/* BGP-LS NLRI lookup helpers */
-+extern struct bgp_dest *bgp_ls_lookup_nlri_by_str(struct bgp *bgp, const char *nlri_str);
-+
-+/* BGP-LS NLRI helpers */
-+extern struct json_object *bgp_ls_nlri_to_json(struct bgp_ls_nlri *nlri);
-+extern void bgp_ls_nlri_format(struct bgp_ls_nlri *nlri, char *buf, size_t buf_len);
-+
-+/* Module initialization and cleanup */
-+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..c34f8ca296
---- /dev/null
-+++ b/bgpd/bgp_ls_nlri.c
-@@ -0,0 +1,5537 @@
-+// SPDX-License-Identifier: GPL-2.0-or-later
-+/*
-+ * BGP Link-State NLRI (RFC 9552)
-+ * Copyright (C) 2025 Carmine Scarpitta
-+ */
-+
-+#include <zebra.h>
-+
-+#include "bgpd/bgp_ls.h"
-+#include "bgpd/bgp_ls_nlri.h"
-+#include "bgpd/bgp_errors.h"
-+#include "bgpd/bgp_debug.h"
-+#include "lib/iso.h"
-+
-+DEFINE_MTYPE_STATIC(BGPD, BGP_LS_NLRI, "BGP-LS NLRI");
-+DEFINE_MTYPE(BGPD, BGP_LS_ATTR, "BGP-LS Attribute");
-+
-+/*
-+ * ===========================================================================
-+ * Node Descriptor Functions
-+ * ===========================================================================
-+ */
-+
-+/*
-+ * Compare two node descriptors for equality
-+ * Returns 0 if equal, non-zero otherwise
-+ */
-+int bgp_ls_node_descriptor_cmp(const struct bgp_ls_node_descriptor *d1,
-+ const struct bgp_ls_node_descriptor *d2)
-+{
-+ int ret;
-+
-+ /* Must have same TLVs present */
-+ if (d1->present_tlvs != d2->present_tlvs)
-+ return numcmp(d1->present_tlvs, d2->present_tlvs);
-+
-+ /* Compare AS Number if present */
-+ 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 (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 (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 (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);
-+ if (ret != 0)
-+ 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;
-+}
-+
-+/*
-+ * ===========================================================================
-+ * Link Descriptor Functions
-+ * ===========================================================================
-+ */
-+
-+/*
-+ * Compare two link descriptors for equality
-+ * Returns 0 if equal, non-zero otherwise
-+ */
-+int bgp_ls_link_descriptor_cmp(const struct bgp_ls_link_descriptor *d1,
-+ const struct bgp_ls_link_descriptor *d2)
-+{
-+ int ret;
-+
-+ /* Must have same TLVs present */
-+ if (d1->present_tlvs != d2->present_tlvs)
-+ return numcmp(d1->present_tlvs, d2->present_tlvs);
-+
-+ /* Compare Link IDs if present */
-+ 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)
-+ return numcmp(d1->link_remote_id, d2->link_remote_id);
-+ }
-+
-+ /* Compare IPv4 Interface Address if present */
-+ 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 (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 (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 (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 (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++) {
-+ if (d1->mt_id[i] != d2->mt_id[i])
-+ return numcmp(d1->mt_id[i], d2->mt_id[i]);
-+ }
-+ }
-+
-+ return 0;
-+}
-+
-+/*
-+ * ===========================================================================
-+ * Prefix Descriptor Functions
-+ * ===========================================================================
-+ */
-+
-+/*
-+ * Compare two prefix descriptors for equality
-+ * Returns 0 if equal, non-zero otherwise
-+ */
-+int bgp_ls_prefix_descriptor_cmp(const struct bgp_ls_prefix_descriptor *d1,
-+ const struct bgp_ls_prefix_descriptor *d2)
-+{
-+ int ret;
-+
-+ /* Must have same TLVs present */
-+ if (d1->present_tlvs != d2->present_tlvs)
-+ return numcmp(d1->present_tlvs, d2->present_tlvs);
-+
-+ /* Compare prefix */
-+ ret = prefix_cmp(&d1->prefix, &d2->prefix);
-+ if (ret != 0)
-+ return ret;
-+
-+ /* Compare OSPF Route Type if present */
-+ 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 (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++) {
-+ if (d1->mt_id[i] != d2->mt_id[i])
-+ return numcmp(d1->mt_id[i], d2->mt_id[i]);
-+ }
-+ }
-+
-+ /* 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;
-+}
-+
-+/*
-+ * ===========================================================================
-+ * NLRI Comparison Functions
-+ * ===========================================================================
-+ */
-+
-+/*
-+ * Compare two NLRIs for equality
-+ * Returns 0 if equal, non-zero otherwise
-+ */
-+int bgp_ls_nlri_cmp(const struct bgp_ls_nlri *nlri1, const struct bgp_ls_nlri *nlri2)
-+{
-+ int ret;
-+
-+ /* Different types are never equal */
-+ if (nlri1->nlri_type != nlri2->nlri_type)
-+ return numcmp(nlri1->nlri_type, nlri2->nlri_type);
-+
-+ /* Type-specific comparison */
-+ switch (nlri1->nlri_type) {
-+ case BGP_LS_NLRI_TYPE_NODE: {
-+ const struct bgp_ls_node_nlri *n1 = &nlri1->nlri_data.node;
-+ const struct bgp_ls_node_nlri *n2 = &nlri2->nlri_data.node;
-+
-+ /* Compare protocol ID */
-+ if (n1->protocol_id != n2->protocol_id)
-+ return numcmp(n1->protocol_id, n2->protocol_id);
-+
-+ /* Compare identifier */
-+ if (n1->identifier != n2->identifier)
-+ return numcmp(n1->identifier, n2->identifier);
-+
-+ /* Compare local node descriptor */
-+ return bgp_ls_node_descriptor_cmp(&n1->local_node, &n2->local_node);
-+ }
-+
-+ case BGP_LS_NLRI_TYPE_LINK: {
-+ const struct bgp_ls_link_nlri *l1 = &nlri1->nlri_data.link;
-+ const struct bgp_ls_link_nlri *l2 = &nlri2->nlri_data.link;
-+
-+ /* Compare protocol ID */
-+ if (l1->protocol_id != l2->protocol_id)
-+ return numcmp(l1->protocol_id, l2->protocol_id);
-+
-+ /* Compare identifier */
-+ if (l1->identifier != l2->identifier)
-+ return numcmp(l1->identifier, l2->identifier);
-+
-+ /* Compare local node descriptor */
-+ ret = bgp_ls_node_descriptor_cmp(&l1->local_node, &l2->local_node);
-+ if (ret != 0)
-+ return ret;
-+
-+ /* Compare remote node descriptor */
-+ ret = bgp_ls_node_descriptor_cmp(&l1->remote_node, &l2->remote_node);
-+ if (ret != 0)
-+ return ret;
-+
-+ /* Compare link descriptor */
-+ return bgp_ls_link_descriptor_cmp(&l1->link_desc, &l2->link_desc);
-+ }
-+
-+ case BGP_LS_NLRI_TYPE_IPV4_PREFIX:
-+ case BGP_LS_NLRI_TYPE_IPV6_PREFIX: {
-+ const struct bgp_ls_prefix_nlri *p1 = &nlri1->nlri_data.prefix;
-+ const struct bgp_ls_prefix_nlri *p2 = &nlri2->nlri_data.prefix;
-+
-+ /* Compare protocol ID */
-+ if (p1->protocol_id != p2->protocol_id)
-+ return numcmp(p1->protocol_id, p2->protocol_id);
-+
-+ /* Compare identifier */
-+ if (p1->identifier != p2->identifier)
-+ return numcmp(p1->identifier, p2->identifier);
-+
-+ /* Compare local node descriptor */
-+ ret = bgp_ls_node_descriptor_cmp(&p1->local_node, &p2->local_node);
-+ if (ret != 0)
-+ return ret;
-+
-+ /* Compare prefix descriptor */
-+ return bgp_ls_prefix_descriptor_cmp(&p1->prefix_desc, &p2->prefix_desc);
-+ }
-+
-+ case BGP_LS_NLRI_TYPE_RESERVED:
-+ /* Reserved type should never be compared */
-+ return 0;
-+ }
-+
-+ return 0;
-+}
-+
-+/*
-+ * Compare two BGP-LS attributes for equality
-+ * Returns 0 if equal, non-zero otherwise
-+ */
-+int bgp_ls_attr_cmp(const struct bgp_ls_attr *attr1, const struct bgp_ls_attr *attr2)
-+{
-+ int ret;
-+
-+ if (attr1->present_tlvs != attr2->present_tlvs)
-+ return numcmp(attr1->present_tlvs, attr2->present_tlvs);
-+
-+ 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 (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 (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);
-+ if (ret != 0)
-+ return ret;
-+ }
-+
-+ 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)
-+ return numcmp(attr1->srgb.lower_bound, attr2->srgb.lower_bound);
-+ if (attr1->srgb.range_size != attr2->srgb.range_size)
-+ return numcmp(attr1->srgb.range_size, attr2->srgb.range_size);
-+ }
-+
-+ if (CHECK_FLAG(attr1->present_tlvs, BGP_LS_ATTR_NODE_MSD_BIT)) {
-+ if (attr1->msd != attr2->msd)
-+ return numcmp(attr1->msd, attr2->msd);
-+ }
-+
-+ 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 (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 (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 (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 (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 (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 (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 (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;
-+ if (attr1->unreserved_bw[i] > attr2->unreserved_bw[i])
-+ return 1;
-+ }
-+ }
-+
-+ 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 (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 (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 (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 (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++) {
-+ if (attr1->srlg_values[i] != attr2->srlg_values[i])
-+ return numcmp(attr1->srlg_values[i], attr2->srlg_values[i]);
-+ }
-+ }
-+
-+ 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 (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++) {
-+ if (attr1->adj_sid[i].sid != attr2->adj_sid[i].sid)
-+ return numcmp(attr1->adj_sid[i].sid, attr2->adj_sid[i].sid);
-+ if (attr1->adj_sid[i].flags != attr2->adj_sid[i].flags)
-+ return numcmp(attr1->adj_sid[i].flags, attr2->adj_sid[i].flags);
-+ if (attr1->adj_sid[i].weight != attr2->adj_sid[i].weight)
-+ return numcmp(attr1->adj_sid[i].weight, attr2->adj_sid[i].weight);
-+ }
-+ }
-+
-+ 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 (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++) {
-+ if (attr1->route_tags[i] != attr2->route_tags[i])
-+ return numcmp(attr1->route_tags[i], attr2->route_tags[i]);
-+ }
-+ }
-+
-+ 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++) {
-+ if (attr1->extended_tags[i] != attr2->extended_tags[i])
-+ return numcmp(attr1->extended_tags[i], attr2->extended_tags[i]);
-+ }
-+ }
-+
-+ 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 (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;
-+ ret = IPV6_ADDR_CMP(&attr1->ospf_fwd_addr6, &attr2->ospf_fwd_addr6);
-+ if (ret != 0)
-+ return ret;
-+ }
-+
-+ 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)
-+ return numcmp(attr1->prefix_sid.sid_flag, attr2->prefix_sid.sid_flag);
-+ if (attr1->prefix_sid.algo != attr2->prefix_sid.algo)
-+ return numcmp(attr1->prefix_sid.algo, attr2->prefix_sid.algo);
-+ }
-+
-+ if (attr1->opaque_len != attr2->opaque_len)
-+ return numcmp(attr1->opaque_len, attr2->opaque_len);
-+ if (attr1->opaque_len > 0) {
-+ ret = memcmp(attr1->opaque_data, attr2->opaque_data, attr1->opaque_len);
-+ if (ret != 0)
-+ return ret;
-+ }
-+
-+ if (attr1->mt_id_count != attr2->mt_id_count)
-+ return numcmp(attr1->mt_id_count, attr2->mt_id_count);
-+ for (int i = 0; i < attr1->mt_id_count; i++) {
-+ if (attr1->mt_id[i] != attr2->mt_id[i])
-+ return numcmp(attr1->mt_id[i], attr2->mt_id[i]);
-+ }
-+
-+ return 0;
-+}
-+
-+bool bgp_ls_attr_same(const struct bgp_ls_attr *attr1, const struct bgp_ls_attr *attr2)
-+{
-+ if (attr1 == attr2)
-+ return true;
-+
-+ if (!attr1 || !attr2)
-+ return false;
-+
-+ return bgp_ls_attr_cmp(attr1, attr2) == 0;
-+}
-+
-+/*
-+ * ===========================================================================
-+ * NLRI Memory Management Functions
-+ * ===========================================================================
-+ */
-+
-+struct bgp_ls_nlri *bgp_ls_nlri_alloc(void)
-+{
-+ struct bgp_ls_nlri *nlri;
-+
-+ nlri = XCALLOC(MTYPE_BGP_LS_NLRI, sizeof(struct bgp_ls_nlri));
-+ return nlri;
-+}
-+
-+void bgp_ls_nlri_free(struct bgp_ls_nlri *nlri)
-+{
-+ if (!nlri)
-+ return;
-+
-+ switch (nlri->nlri_type) {
-+ case BGP_LS_NLRI_TYPE_NODE:
-+ break;
-+
-+ case BGP_LS_NLRI_TYPE_LINK:
-+ XFREE(MTYPE_BGP_LS_NLRI, nlri->nlri_data.link.link_desc.mt_id);
-+ break;
-+
-+ case BGP_LS_NLRI_TYPE_IPV4_PREFIX:
-+ case BGP_LS_NLRI_TYPE_IPV6_PREFIX:
-+ XFREE(MTYPE_BGP_LS_NLRI, nlri->nlri_data.prefix.prefix_desc.mt_id);
-+ break;
-+
-+ case BGP_LS_NLRI_TYPE_RESERVED:
-+ break;
-+ }
-+
-+ XFREE(MTYPE_BGP_LS_NLRI, nlri);
-+}
-+
-+struct bgp_ls_attr *bgp_ls_attr_alloc(void)
-+{
-+ struct bgp_ls_attr *attr;
-+
-+ attr = XCALLOC(MTYPE_BGP_LS_ATTR, sizeof(struct bgp_ls_attr));
-+ return attr;
-+}
-+
-+void bgp_ls_attr_free(struct bgp_ls_attr *attr)
-+{
-+ if (!attr)
-+ return;
-+
-+ XFREE(MTYPE_BGP_LS_ATTR, attr->node_name);
-+ XFREE(MTYPE_BGP_LS_ATTR, attr->isis_area_id);
-+ XFREE(MTYPE_BGP_LS_ATTR, attr->mt_id);
-+ XFREE(MTYPE_BGP_LS_ATTR, attr->srlg_values);
-+ XFREE(MTYPE_BGP_LS_ATTR, attr->link_name);
-+ XFREE(MTYPE_BGP_LS_ATTR, attr->route_tags);
-+ XFREE(MTYPE_BGP_LS_ATTR, attr->extended_tags);
-+ XFREE(MTYPE_BGP_LS_ATTR, attr->opaque_data);
-+
-+ XFREE(MTYPE_BGP_LS_ATTR, attr);
-+}
-+
-+/* Deep copy BGP-LS NLRI (including dynamic fields) */
-+struct bgp_ls_nlri *bgp_ls_nlri_copy(const struct bgp_ls_nlri *nlri)
-+{
-+ struct bgp_ls_nlri *nlri_copy;
-+
-+ if (!nlri)
-+ return NULL;
-+
-+ /* Allocate and copy NLRI */
-+ nlri_copy = XCALLOC(MTYPE_BGP_LS_NLRI, sizeof(*nlri_copy));
-+ memcpy(nlri_copy, nlri, sizeof(*nlri_copy));
-+ nlri_copy->refcnt = 0;
-+
-+ /* Copy type-specific dynamically allocated fields */
-+ switch (nlri->nlri_type) {
-+ case BGP_LS_NLRI_TYPE_NODE:
-+ break;
-+
-+ case BGP_LS_NLRI_TYPE_LINK:
-+ /* Copy link descriptor mt_id */
-+ if (nlri->nlri_data.link.link_desc.mt_id) {
-+ size_t mt_size = nlri->nlri_data.link.link_desc.mt_id_count *
-+ sizeof(uint16_t);
-+ nlri_copy->nlri_data.link.link_desc.mt_id = XCALLOC(MTYPE_BGP_LS_NLRI,
-+ mt_size);
-+ memcpy(nlri_copy->nlri_data.link.link_desc.mt_id,
-+ nlri->nlri_data.link.link_desc.mt_id, mt_size);
-+ }
-+ break;
-+
-+ case BGP_LS_NLRI_TYPE_IPV4_PREFIX:
-+ case BGP_LS_NLRI_TYPE_IPV6_PREFIX:
-+ /* Copy prefix descriptor mt_id */
-+ if (nlri->nlri_data.prefix.prefix_desc.mt_id) {
-+ size_t mt_size = nlri->nlri_data.prefix.prefix_desc.mt_id_count *
-+ sizeof(uint16_t);
-+ nlri_copy->nlri_data.prefix.prefix_desc.mt_id = XCALLOC(MTYPE_BGP_LS_NLRI,
-+ mt_size);
-+ memcpy(nlri_copy->nlri_data.prefix.prefix_desc.mt_id,
-+ nlri->nlri_data.prefix.prefix_desc.mt_id, mt_size);
-+ }
-+ break;
-+
-+ case BGP_LS_NLRI_TYPE_RESERVED:
-+ break;
-+ }
-+
-+ return nlri_copy;
-+}
-+
-+struct bgp_ls_attr *bgp_ls_attr_copy(const struct bgp_ls_attr *src)
-+{
-+ struct bgp_ls_attr *dst;
-+ size_t mt_size;
-+ size_t srlg_size;
-+ size_t tag_size;
-+
-+ if (!src)
-+ return NULL;
-+
-+ dst = XCALLOC(MTYPE_BGP_LS_ATTR, sizeof(*dst));
-+ memcpy(dst, src, sizeof(*dst));
-+
-+ if (src->node_name)
-+ dst->node_name = XSTRDUP(MTYPE_BGP_LS_ATTR, src->node_name);
-+
-+ if (src->isis_area_id) {
-+ dst->isis_area_id = XCALLOC(MTYPE_BGP_LS_ATTR, src->isis_area_id_len);
-+ memcpy(dst->isis_area_id, src->isis_area_id, src->isis_area_id_len);
-+ }
-+
-+ if (src->mt_id) {
-+ mt_size = src->mt_id_count * sizeof(uint16_t);
-+ dst->mt_id = XCALLOC(MTYPE_BGP_LS_ATTR, mt_size);
-+ memcpy(dst->mt_id, src->mt_id, mt_size);
-+ }
-+
-+ if (src->srlg_values) {
-+ srlg_size = src->srlg_count * sizeof(uint32_t);
-+ dst->srlg_values = XCALLOC(MTYPE_BGP_LS_ATTR, srlg_size);
-+ memcpy(dst->srlg_values, src->srlg_values, srlg_size);
-+ }
-+
-+ if (src->link_name)
-+ dst->link_name = XSTRDUP(MTYPE_BGP_LS_ATTR, src->link_name);
-+
-+ if (src->route_tags) {
-+ tag_size = src->route_tag_count * sizeof(uint32_t);
-+ dst->route_tags = XCALLOC(MTYPE_BGP_LS_ATTR, tag_size);
-+ memcpy(dst->route_tags, src->route_tags, tag_size);
-+ }
-+
-+ if (src->extended_tags) {
-+ tag_size = src->extended_tag_count * sizeof(uint64_t);
-+ dst->extended_tags = XCALLOC(MTYPE_BGP_LS_ATTR, tag_size);
-+ memcpy(dst->extended_tags, src->extended_tags, tag_size);
-+ }
-+
-+ if (src->opaque_data) {
-+ dst->opaque_data = XCALLOC(MTYPE_BGP_LS_ATTR, src->opaque_len);
-+ memcpy(dst->opaque_data, src->opaque_data, src->opaque_len);
-+ }
-+
-+ return dst;
-+}
-+
-+/*
-+ * ===========================================================================
-+ * NLRI Validation Functions
-+ * ===========================================================================
-+ */
-+
-+/*
-+ * Validate IGP Router-ID length based on protocol (RFC 9552 Section 5.2.1.4)
-+ *
-+ * Valid lengths:
-+ * 4 octets - OSPFv2/v3 non-pseudonode, Direct/Static IPv4
-+ * 6 octets - IS-IS non-pseudonode
-+ * 7 octets - IS-IS pseudonode
-+ * 8 octets - OSPFv2/v3 pseudonode
-+ * 16 octets - Direct/Static IPv6, BGP IPv6
-+ */
-+static bool bgp_ls_igp_router_id_len_valid(enum bgp_ls_protocol_id proto_id, uint8_t len)
-+{
-+ switch (proto_id) {
-+ case BGP_LS_PROTO_ISIS_L1:
-+ case BGP_LS_PROTO_ISIS_L2:
-+ /* IS-IS: 6 octets (non-pseudonode) or 7 octets (pseudonode) */
-+ return (len == BGP_LS_IGP_ROUTER_ID_ISIS_LEN ||
-+ len == BGP_LS_IGP_ROUTER_ID_ISIS_PSEUDO_LEN);
-+
-+ case BGP_LS_PROTO_OSPFV2:
-+ case BGP_LS_PROTO_OSPFV3:
-+ /* OSPF: 4 octets (non-pseudonode) or 8 octets (pseudonode) */
-+ return (len == BGP_LS_IGP_ROUTER_ID_OSPF_LEN ||
-+ len == BGP_LS_IGP_ROUTER_ID_OSPF_PSEUDO_LEN);
-+
-+ case BGP_LS_PROTO_DIRECT:
-+ case BGP_LS_PROTO_STATIC:
-+ /* Direct/Static: Accept IPv4 (4) or IPv6 (16), or IGP formats if IGP is running */
-+ return (len == BGP_LS_IGP_ROUTER_ID_DIRECT_IPV4_LEN ||
-+ len == BGP_LS_IGP_ROUTER_ID_DIRECT_IPV6_LEN ||
-+ len == BGP_LS_IGP_ROUTER_ID_OSPF_LEN ||
-+ len == BGP_LS_IGP_ROUTER_ID_ISIS_LEN ||
-+ len == BGP_LS_IGP_ROUTER_ID_ISIS_PSEUDO_LEN ||
-+ len == BGP_LS_IGP_ROUTER_ID_OSPF_PSEUDO_LEN);
-+
-+ case BGP_LS_PROTO_BGP:
-+ /* BGP: Typically uses IPv4 (4) or IPv6 (16) Router-ID */
-+ return (len == BGP_LS_IGP_ROUTER_ID_DIRECT_IPV4_LEN ||
-+ len == BGP_LS_IGP_ROUTER_ID_DIRECT_IPV6_LEN);
-+
-+ case BGP_LS_PROTO_RESERVED:
-+ return false;
-+ }
-+
-+ return false;
-+}
-+
-+bool bgp_ls_nlri_validate(const struct bgp_ls_nlri *nlri)
-+{
-+ if (!nlri)
-+ return false;
-+
-+ switch (nlri->nlri_type) {
-+ case BGP_LS_NLRI_TYPE_NODE: {
-+ const struct bgp_ls_node_nlri *n = &nlri->nlri_data.node;
-+
-+ if (n->protocol_id == BGP_LS_PROTO_RESERVED)
-+ return false;
-+
-+ /* IGP Router ID is mandatory (RFC 9552 Section 5.2.1.4) */
-+ 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 (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;
-+
-+ return true;
-+ }
-+
-+ case BGP_LS_NLRI_TYPE_LINK: {
-+ const struct bgp_ls_link_nlri *l = &nlri->nlri_data.link;
-+
-+ if (l->protocol_id == BGP_LS_PROTO_RESERVED)
-+ return false;
-+
-+ /* Local node IGP Router ID is mandatory */
-+ 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 (!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 ((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;
-+ }
-+
-+ case BGP_LS_NLRI_TYPE_IPV4_PREFIX: {
-+ const struct bgp_ls_prefix_nlri *p = &nlri->nlri_data.prefix;
-+
-+ if (p->protocol_id == BGP_LS_PROTO_RESERVED)
-+ return false;
-+
-+ /* Local node IGP Router ID is mandatory */
-+ 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 (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;
-+
-+ /* Prefix family must be IPv4 */
-+ if (p->prefix_desc.prefix.family != AF_INET)
-+ return false;
-+
-+ return true;
-+ }
-+
-+ case BGP_LS_NLRI_TYPE_IPV6_PREFIX: {
-+ const struct bgp_ls_prefix_nlri *p = &nlri->nlri_data.prefix;
-+
-+ if (p->protocol_id == BGP_LS_PROTO_RESERVED)
-+ return false;
-+
-+ /* Local node IGP Router ID is mandatory */
-+ 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 (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;
-+
-+ /* Prefix family must be IPv6 */
-+ if (p->prefix_desc.prefix.family != AF_INET6)
-+ return false;
-+
-+ return true;
-+ }
-+
-+ case BGP_LS_NLRI_TYPE_RESERVED:
-+ /* Reserved type is invalid */
-+ return false;
-+ }
-+
-+ return false;
-+}
-+
-+/*
-+ * ===========================================================================
-+ * NLRI Size Calculation Functions
-+ * ===========================================================================
-+ */
-+
-+/* Calculate size of node descriptor TLV */
-+static size_t bgp_ls_node_descriptor_size(const struct bgp_ls_node_descriptor *desc)
-+{
-+ size_t size = BGP_LS_TLV_HDR_SIZE; /* Descriptor TLV header */
-+
-+ /* AS Number TLV */
-+ 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 (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 (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 (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;
-+}
-+
-+/* Calculate wire format size of NLRI */
-+size_t bgp_ls_nlri_size(const struct bgp_ls_nlri *nlri)
-+{
-+ size_t size = 0;
-+
-+ if (!nlri)
-+ return 0;
-+
-+ /* Common NLRI header: Protocol-ID + Identifier */
-+ size += BGP_LS_NLRI_HDR_SIZE;
-+
-+ switch (nlri->nlri_type) {
-+ case BGP_LS_NLRI_TYPE_NODE: {
-+ const struct bgp_ls_node_nlri *n = &nlri->nlri_data.node;
-+
-+ /* Local Node Descriptor */
-+ size += bgp_ls_node_descriptor_size(&n->local_node);
-+
-+ break;
-+ }
-+
-+ case BGP_LS_NLRI_TYPE_LINK: {
-+ const struct bgp_ls_link_nlri *l = &nlri->nlri_data.link;
-+
-+ /* Local Node Descriptor */
-+ size += bgp_ls_node_descriptor_size(&l->local_node);
-+
-+ /* Remote Node Descriptor */
-+ size += bgp_ls_node_descriptor_size(&l->remote_node);
-+
-+ /* Link Descriptor */
-+ 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 (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 (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 (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 (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 (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);
-+
-+ break;
-+ }
-+
-+ case BGP_LS_NLRI_TYPE_IPV4_PREFIX:
-+ case BGP_LS_NLRI_TYPE_IPV6_PREFIX: {
-+ const struct bgp_ls_prefix_nlri *p = &nlri->nlri_data.prefix;
-+
-+ /* Local Node Descriptor */
-+ size += bgp_ls_node_descriptor_size(&p->local_node);
-+
-+ /* Prefix Descriptor */
-+ 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 (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 */
-+ if (nlri->nlri_type == BGP_LS_NLRI_TYPE_IPV4_PREFIX)
-+ size += BGP_LS_TLV_HDR_SIZE + BGP_LS_PREFIX_LEN_SIZE +
-+ BGP_LS_IPV4_ADDR_SIZE;
-+ else
-+ 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;
-+ }
-+
-+ case BGP_LS_NLRI_TYPE_RESERVED:
-+ return 0;
-+ }
-+
-+ return size;
-+}
-+
-+/* NLRI get protocol_id helper */
-+enum bgp_ls_protocol_id bgp_ls_nlri_protocol_id(const struct bgp_ls_nlri *nlri)
-+{
-+ switch (nlri->nlri_type) {
-+ case BGP_LS_NLRI_TYPE_NODE:
-+ return nlri->nlri_data.node.protocol_id;
-+ case BGP_LS_NLRI_TYPE_LINK:
-+ return nlri->nlri_data.link.protocol_id;
-+ case BGP_LS_NLRI_TYPE_IPV4_PREFIX:
-+ case BGP_LS_NLRI_TYPE_IPV6_PREFIX:
-+ return nlri->nlri_data.prefix.protocol_id;
-+ case BGP_LS_NLRI_TYPE_RESERVED:
-+ return BGP_LS_PROTO_RESERVED;
-+ }
-+ return BGP_LS_PROTO_RESERVED;
-+}
-+
-+/*
-+ * ===========================================================================
-+ * String Conversion Functions
-+ * ===========================================================================
-+ */
-+
-+const char *bgp_ls_protocol_id_str(enum bgp_ls_protocol_id proto_id)
-+{
-+ switch (proto_id) {
-+ case BGP_LS_PROTO_RESERVED:
-+ return "Reserved";
-+ case BGP_LS_PROTO_ISIS_L1:
-+ return "IS-IS Level 1";
-+ case BGP_LS_PROTO_ISIS_L2:
-+ return "IS-IS Level 2";
-+ case BGP_LS_PROTO_OSPFV2:
-+ return "OSPFv2";
-+ case BGP_LS_PROTO_DIRECT:
-+ return "Direct";
-+ case BGP_LS_PROTO_STATIC:
-+ return "Static";
-+ case BGP_LS_PROTO_OSPFV3:
-+ return "OSPFv3";
-+ case BGP_LS_PROTO_BGP:
-+ return "BGP";
-+ }
-+
-+ return "Unknown";
-+}
-+
-+const char *bgp_ls_nlri_type_str(enum bgp_ls_nlri_type nlri_type)
-+{
-+ switch (nlri_type) {
-+ case BGP_LS_NLRI_TYPE_RESERVED:
-+ return "Reserved";
-+ case BGP_LS_NLRI_TYPE_NODE:
-+ return "Node";
-+ case BGP_LS_NLRI_TYPE_LINK:
-+ return "Link";
-+ case BGP_LS_NLRI_TYPE_IPV4_PREFIX:
-+ return "IPv4 Prefix";
-+ case BGP_LS_NLRI_TYPE_IPV6_PREFIX:
-+ return "IPv6 Prefix";
-+ }
-+
-+ return "Unknown";
-+}
-+
-+const char *bgp_ls_node_descriptor_tlv_str(enum bgp_ls_node_descriptor_tlv tlv_type)
-+{
-+ switch (tlv_type) {
-+ case BGP_LS_TLV_LOCAL_NODE_DESC:
-+ return "Local Node Descriptors";
-+ case BGP_LS_TLV_REMOTE_NODE_DESC:
-+ return "Remote Node Descriptors";
-+ case BGP_LS_TLV_AS_NUMBER:
-+ return "Autonomous System";
-+ case BGP_LS_TLV_BGP_LS_ID:
-+ return "BGP-LS Identifier";
-+ case BGP_LS_TLV_OSPF_AREA_ID:
-+ 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";
-+}
-+
-+const char *bgp_ls_link_descriptor_tlv_str(enum bgp_ls_link_descriptor_tlv tlv_type)
-+{
-+ switch (tlv_type) {
-+ case BGP_LS_TLV_LINK_ID:
-+ return "Link Local/Remote Identifiers";
-+ case BGP_LS_TLV_IPV4_INTF_ADDR:
-+ return "IPv4 Interface Address";
-+ case BGP_LS_TLV_IPV4_NEIGH_ADDR:
-+ return "IPv4 Neighbor Address";
-+ case BGP_LS_TLV_IPV6_INTF_ADDR:
-+ return "IPv6 Interface Address";
-+ case BGP_LS_TLV_IPV6_NEIGH_ADDR:
-+ return "IPv6 Neighbor Address";
-+ case BGP_LS_TLV_MT_ID:
-+ return "Multi-Topology Identifier";
-+ case BGP_LS_TLV_REMOTE_AS_NUMBER:
-+ return "Remote AS Number";
-+ }
-+
-+ return "Unknown";
-+}
-+
-+const char *bgp_ls_prefix_descriptor_tlv_str(enum bgp_ls_prefix_descriptor_tlv tlv_type)
-+{
-+ switch (tlv_type) {
-+ case BGP_LS_TLV_OSPF_ROUTE_TYPE:
-+ 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";
-+}
-+
-+const char *bgp_ls_ospf_route_type_str(enum bgp_ls_ospf_route_type route_type)
-+{
-+ switch (route_type) {
-+ case BGP_LS_OSPF_RT_INTRA_AREA:
-+ return "Intra-Area";
-+ case BGP_LS_OSPF_RT_INTER_AREA:
-+ return "Inter-Area";
-+ case BGP_LS_OSPF_RT_EXTERNAL_1:
-+ return "External Type 1";
-+ case BGP_LS_OSPF_RT_EXTERNAL_2:
-+ return "External Type 2";
-+ case BGP_LS_OSPF_RT_NSSA_1:
-+ return "NSSA Type 1";
-+ case BGP_LS_OSPF_RT_NSSA_2:
-+ return "NSSA Type 2";
-+ }
-+
-+ 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) {
-+ case BGP_LS_OSPF_RT_INTRA_AREA:
-+ return "intraArea";
-+ case BGP_LS_OSPF_RT_INTER_AREA:
-+ return "interArea";
-+ case BGP_LS_OSPF_RT_EXTERNAL_1:
-+ return "externalType1";
-+ case BGP_LS_OSPF_RT_EXTERNAL_2:
-+ return "externalType2";
-+ case BGP_LS_OSPF_RT_NSSA_1:
-+ return "nssaType1";
-+ case BGP_LS_OSPF_RT_NSSA_2:
-+ return "nssaType2";
-+ }
-+
-+ 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
-+ * ===========================================================================
-+ */
-+
-+/* Hash a node descriptor */
-+static unsigned int bgp_ls_node_descriptor_hash(const struct bgp_ls_node_descriptor *desc,
-+ uint32_t key)
-+{
-+ if (CHECK_FLAG(desc->present_tlvs, BGP_LS_NODE_DESC_AS_BIT))
-+ key = jhash_1word(desc->asn, key);
-+
-+ if (CHECK_FLAG(desc->present_tlvs, BGP_LS_NODE_DESC_BGP_LS_ID_BIT))
-+ key = jhash_1word(desc->bgp_ls_id, key);
-+
-+ if (CHECK_FLAG(desc->present_tlvs, BGP_LS_NODE_DESC_OSPF_AREA_BIT))
-+ key = jhash_1word(desc->ospf_area_id, key);
-+
-+ 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;
-+}
-+
-+/* Hash key generation for Node NLRI */
-+static unsigned int bgp_ls_node_hash_key_internal(const struct bgp_ls_nlri *nlri)
-+{
-+ const struct bgp_ls_node_nlri *node = &nlri->nlri_data.node;
-+ uint32_t key = 0;
-+
-+ /* Hash protocol_id and identifier */
-+ key = jhash_2words(node->protocol_id, (uint32_t)(node->identifier >> 32), key);
-+ key = jhash_1word((uint32_t)node->identifier, key);
-+
-+ /* Hash node descriptor */
-+ key = bgp_ls_node_descriptor_hash(&node->local_node, key);
-+
-+ return key;
-+}
-+
-+/* Hash key generation for Link NLRI */
-+static unsigned int bgp_ls_link_hash_key_internal(const struct bgp_ls_nlri *nlri)
-+{
-+ const struct bgp_ls_link_nlri *link = &nlri->nlri_data.link;
-+ uint32_t key = 0;
-+
-+ /* Hash protocol_id and identifier */
-+ key = jhash_2words(link->protocol_id, (uint32_t)(link->identifier >> 32), key);
-+ key = jhash_1word((uint32_t)link->identifier, key);
-+
-+ /* Hash local and remote node descriptors */
-+ key = bgp_ls_node_descriptor_hash(&link->local_node, key);
-+ key = bgp_ls_node_descriptor_hash(&link->remote_node, key);
-+
-+ /* Hash link descriptor */
-+ 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 (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 (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 (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 (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;
-+}
-+
-+/* Hash key generation for Prefix NLRI (IPv4 and IPv6) */
-+static unsigned int bgp_ls_prefix_hash_key_internal(const struct bgp_ls_nlri *nlri)
-+{
-+ const struct bgp_ls_prefix_nlri *prefix = &nlri->nlri_data.prefix;
-+ uint32_t key = 0;
-+
-+ /* Hash protocol_id and identifier */
-+ key = jhash_2words(prefix->protocol_id, (uint32_t)(prefix->identifier >> 32), key);
-+ key = jhash_1word((uint32_t)prefix->identifier, key);
-+
-+ /* Hash local node descriptor */
-+ key = bgp_ls_node_descriptor_hash(&prefix->local_node, key);
-+
-+ /* Hash prefix descriptor */
-+ key = jhash_1word(prefix->prefix_desc.prefix.prefixlen, key);
-+
-+ if (prefix->prefix_desc.prefix.family == AF_INET)
-+ key = jhash_1word(prefix->prefix_desc.prefix.u.prefix4.s_addr, key);
-+ else if (prefix->prefix_desc.prefix.family == AF_INET6)
-+ key = jhash(&prefix->prefix_desc.prefix.u.prefix6, sizeof(struct in6_addr), key);
-+
-+ /* Hash OSPF route type if present */
-+ 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;
-+}
-+
-+/* Hash key generation for BGP-LS NLRI */
-+unsigned int bgp_ls_nlri_hash_key(const struct bgp_ls_nlri *nlri)
-+{
-+ /* Include NLRI type in hash to ensure different types don't collide */
-+ uint32_t key = jhash_1word(nlri->nlri_type, 0);
-+
-+ switch (nlri->nlri_type) {
-+ case BGP_LS_NLRI_TYPE_NODE:
-+ return key ^ bgp_ls_node_hash_key_internal(nlri);
-+ case BGP_LS_NLRI_TYPE_LINK:
-+ return key ^ bgp_ls_link_hash_key_internal(nlri);
-+ case BGP_LS_NLRI_TYPE_IPV4_PREFIX:
-+ case BGP_LS_NLRI_TYPE_IPV6_PREFIX:
-+ return key ^ bgp_ls_prefix_hash_key_internal(nlri);
-+ case BGP_LS_NLRI_TYPE_RESERVED:
-+ return key;
-+ }
-+
-+ return key;
-+}
-+
-+/*
-+ * Generate hash key for BGP-LS attribute
-+ * This hashes all fields present in the attribute based on present_tlvs bitmask
-+ */
-+unsigned int bgp_ls_attr_hash_key(const struct bgp_ls_attr *attr)
-+{
-+ uint32_t key = 0;
-+
-+ if (!attr)
-+ return 0;
-+
-+ /* Hash the present_tlvs bitmask first */
-+ key = jhash_1word((uint32_t)(attr->present_tlvs >> 32), key);
-+ key = jhash_1word((uint32_t)attr->present_tlvs, key);
-+
-+ if (CHECK_FLAG(attr->present_tlvs, BGP_LS_ATTR_NODE_FLAGS_BIT))
-+ key = jhash_1word(attr->node_flags, key);
-+
-+ if (CHECK_FLAG(attr->present_tlvs, BGP_LS_ATTR_NODE_NAME_BIT))
-+ key = jhash(attr->node_name, strlen(attr->node_name), key);
-+
-+ 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 (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 (CHECK_FLAG(attr->present_tlvs, BGP_LS_ATTR_NODE_MSD_BIT))
-+ key = jhash(&attr->msd, sizeof(attr->msd), key);
-+
-+ 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 (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 (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 (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 (CHECK_FLAG(attr->present_tlvs, BGP_LS_ATTR_ADMIN_GROUP_BIT))
-+ key = jhash_1word(attr->admin_group, key);
-+
-+ 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 (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 (CHECK_FLAG(attr->present_tlvs, BGP_LS_ATTR_UNRESV_BW_BIT))
-+ key = jhash(attr->unreserved_bw, sizeof(attr->unreserved_bw), key);
-+
-+ if (CHECK_FLAG(attr->present_tlvs, BGP_LS_ATTR_TE_METRIC_BIT))
-+ key = jhash_1word(attr->te_metric, key);
-+
-+ if (CHECK_FLAG(attr->present_tlvs, BGP_LS_ATTR_LINK_PROTECTION_BIT))
-+ key = jhash_1word(attr->link_protection, key);
-+
-+ if (CHECK_FLAG(attr->present_tlvs, BGP_LS_ATTR_MPLS_PROTOCOL_BIT))
-+ key = jhash_1word(attr->mpls_protocol_mask, key);
-+
-+ if (CHECK_FLAG(attr->present_tlvs, BGP_LS_ATTR_IGP_METRIC_BIT))
-+ key = jhash_1word(attr->igp_metric, key);
-+
-+ 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 (CHECK_FLAG(attr->present_tlvs, BGP_LS_ATTR_LINK_NAME_BIT))
-+ key = jhash(attr->link_name, strlen(attr->link_name), key);
-+
-+ 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);
-+ key = jhash(&attr->adj_sid[i].flags, sizeof(attr->adj_sid[i].flags), key);
-+ key = jhash(&attr->adj_sid[i].weight, sizeof(attr->adj_sid[i].weight), key);
-+ }
-+ }
-+
-+ if (CHECK_FLAG(attr->present_tlvs, BGP_LS_ATTR_IGP_FLAGS_BIT))
-+ key = jhash_1word(attr->igp_flags, key);
-+
-+ 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 (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 (CHECK_FLAG(attr->present_tlvs, BGP_LS_ATTR_PREFIX_METRIC_BIT))
-+ key = jhash_1word(attr->prefix_metric, key);
-+
-+ 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 (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)
-+ key = jhash(attr->opaque_data, attr->opaque_len, key);
-+
-+ for (int i = 0; i < attr->mt_id_count; i++)
-+ key = jhash_1word(attr->mt_id[i], key);
-+
-+ return key;
-+}
-+
-+/*
-+ * ===========================================================================
-+ * Hash Comparison Functions
-+ * ===========================================================================
-+ */
-+
-+/*
-+ * Hash table comparison function for BGP-LS NLRI
-+ * Returns 0 if equal, non-zero if not equal
-+ */
-+int bgp_ls_nlri_hash_cmp(const struct bgp_ls_nlri *n1, const struct bgp_ls_nlri *n2)
-+{
-+ return bgp_ls_nlri_cmp(n1, n2);
-+}
-+
-+/*
-+ * Hash table comparison function for BGP-LS attributes
-+ * Returns 0 if equal, non-zero if not equal
-+ */
-+int bgp_ls_attr_hash_cmp(const struct bgp_ls_attr *a1, const struct bgp_ls_attr *a2)
-+{
-+ return bgp_ls_attr_cmp(a1, a2);
-+}
-+
-+/*
-+ * ===========================================================================
-+ * BGP-LS NLRI Hash Table Management Functions
-+ * ===========================================================================
-+ */
-+
-+/* Insert or lookup NLRI in hash table */
-+struct bgp_ls_nlri *bgp_ls_nlri_get(struct bgp_ls_nlri_hash_head *hash, struct bgp *bgp,
-+ struct bgp_ls_nlri *nlri)
-+{
-+ struct bgp_ls_nlri *ls_nlri;
-+
-+ ls_nlri = bgp_ls_nlri_lookup(hash, nlri);
-+ if (!ls_nlri) {
-+ ls_nlri = bgp_ls_nlri_copy(nlri);
-+ ls_nlri->id = idalloc_allocate(bgp->ls_info->allocator);
-+
-+ bgp_ls_nlri_hash_add(hash, ls_nlri);
-+ }
-+
-+ ls_nlri->refcnt++;
-+ return ls_nlri;
-+}
-+
-+/* Lookup NLRI in BGP-LS NLRI hash table */
-+struct bgp_ls_nlri *bgp_ls_nlri_lookup(struct bgp_ls_nlri_hash_head *hash, struct bgp_ls_nlri *nlri)
-+{
-+ struct bgp_ls_nlri lookup;
-+
-+ if (!hash)
-+ return NULL;
-+
-+ memset(&lookup, 0, sizeof(lookup));
-+ lookup.nlri_type = nlri->nlri_type;
-+ lookup.nlri_data = nlri->nlri_data;
-+
-+ return bgp_ls_nlri_hash_find(hash, &lookup);
-+}
-+
-+/* Insert or lookup BGP-LS attribute in hash table */
-+struct bgp_ls_attr *bgp_ls_attr_get(struct bgp_ls_attr_hash_head *hash, struct bgp *bgp,
-+ struct bgp_ls_attr *attr)
-+{
-+ struct bgp_ls_attr *ls_attr;
-+
-+ ls_attr = bgp_ls_attr_lookup(hash, attr);
-+ if (!ls_attr) {
-+ ls_attr = bgp_ls_attr_copy(attr);
-+
-+ bgp_ls_attr_hash_add(hash, ls_attr);
-+ }
-+
-+ ls_attr->refcnt++;
-+ return ls_attr;
-+}
-+
-+/*
-+ * Lookup BGP-LS attribute in hash table
-+ *
-+ * Parameters:
-+ * hash - Hash table head
-+ * attr - Attribute to look up
-+ *
-+ * Returns:
-+ * Pointer to matching attribute if found, NULL otherwise
-+ */
-+struct bgp_ls_attr *bgp_ls_attr_lookup(struct bgp_ls_attr_hash_head *hash,
-+ const struct bgp_ls_attr *attr)
-+{
-+ struct bgp_ls_attr lookup;
-+
-+ if (!hash || !attr)
-+ return NULL;
-+
-+ /* Set up lookup key - copy the attribute for comparison */
-+ memcpy(&lookup, attr, sizeof(lookup));
-+
-+ return bgp_ls_attr_hash_find(hash, &lookup);
-+}
-+
-+/*
-+ * ===========================================================================
-+ * NLRI Interning (Lookup + Lock/Unlock)
-+ * ===========================================================================
-+ */
-+
-+/*
-+ * Intern BGP-LS NLRI (lookup and lock)
-+ *
-+ * Looks up an existing NLRI in the hash table and increments its reference count.
-+ * Returns the NLRI if found, NULL otherwise.
-+ */
-+struct bgp_ls_nlri *bgp_ls_nlri_intern(struct bgp_ls_nlri *ls_nlri)
-+{
-+ struct bgp *bgp = bgp_get_default();
-+
-+ if (!bgp || !bgp->ls_info || !ls_nlri)
-+ return NULL;
-+
-+ return bgp_ls_nlri_get(&bgp->ls_info->nlri_hash, bgp, ls_nlri);
-+}
-+
-+/*
-+ * Unintern BGP-LS NLRI (unlock and potentially free)
-+ *
-+ * Decrements the reference count and frees the NLRI if it reaches zero.
-+ */
-+void bgp_ls_nlri_unintern(struct bgp_ls_nlri **pls_nlri)
-+{
-+ struct bgp_ls_nlri *ls_nlri = *pls_nlri;
-+ struct bgp *bgp = bgp_get_default();
-+
-+ if (!bgp || !bgp->ls_info || !ls_nlri)
-+ return;
-+
-+ ls_nlri->refcnt--;
-+
-+ if (ls_nlri->refcnt == 0) {
-+ bgp_ls_nlri_hash_del(&bgp->ls_info->nlri_hash, ls_nlri);
-+ bgp_ls_nlri_free(ls_nlri);
-+ *pls_nlri = NULL;
-+ }
-+}
-+
-+/*
-+ * Intern BGP-LS attribute (lookup and lock)
-+ *
-+ * Looks up an existing BGP-LS attribute in the hash table and increments its reference count.
-+ * Returns the BGP-LS attribute if found, NULL otherwise.
-+ */
-+struct bgp_ls_attr *bgp_ls_attr_intern(struct bgp_ls_attr *ls_attr)
-+{
-+ struct bgp *bgp = bgp_get_default();
-+
-+ if (!bgp || !bgp->ls_info || !ls_attr)
-+ return NULL;
-+
-+ return bgp_ls_attr_get(&bgp->ls_info->ls_attr_hash, bgp, ls_attr);
-+}
-+
-+/*
-+ * Unintern BGP-LS attribute (unlock and potentially free)
-+ *
-+ * Decrements the reference count and frees the BGP-LS attribute if it reaches zero.
-+ */
-+void bgp_ls_attr_unintern(struct bgp_ls_attr **pls_attr)
-+{
-+ struct bgp_ls_attr *ls_attr = *pls_attr;
-+ struct bgp *bgp = bgp_get_default();
-+
-+ if (!bgp || !bgp->ls_info || !ls_attr)
-+ return;
-+
-+ ls_attr->refcnt--;
-+
-+ if (ls_attr->refcnt == 0) {
-+ bgp_ls_attr_hash_del(&bgp->ls_info->ls_attr_hash, ls_attr);
-+ bgp_ls_attr_free(ls_attr);
-+ *pls_attr = NULL;
-+ }
-+}
-+
-+/*
-+ * ===========================================================================
-+ * NLRI Encoding Functions
-+ * ===========================================================================
-+ */
-+
-+/*
-+ * Encode Node Descriptor to wire format (RFC 9552 Section 5.2.1)
-+ *
-+ * Wire format:
-+ * 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
-+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
-+ * | Descriptor Type | Descriptor Length |
-+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
-+ * // Node Descriptor Sub-TLVs (variable) //
-+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
-+ *
-+ * Node Descriptor Sub-TLVs (RFC 9552 Section 5.2.1.4):
-+ * - TLV 512: Autonomous System (4 bytes)
-+ * - TLV 513: BGP-LS Identifier (4 bytes, deprecated)
-+ * - TLV 514: OSPF Area-ID (4 bytes)
-+ * - TLV 515: IGP Router-ID (4-16 bytes, MANDATORY)
-+ *
-+ * tlv_type: BGP_LS_TLV_LOCAL_NODE_DESC or BGP_LS_TLV_REMOTE_NODE_DESC
-+ *
-+ * Returns: Number of bytes written, or -1 on error
-+ */
-+int bgp_ls_encode_node_descriptor(struct stream *s, const struct bgp_ls_node_descriptor *desc,
-+ uint16_t tlv_type)
-+{
-+ 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;
-+
-+ /* Write TLV type and reserve space for length */
-+ stream_putw(s, tlv_type);
-+ len_pos = stream_get_endp(s);
-+ stream_putw(s, 0); /* Placeholder for length */
-+
-+ sub_tlv_start = stream_get_endp(s);
-+
-+ /* AS Number (TLV 512) */
-+ 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);
-+ }
-+
-+ /* BGP-LS Identifier (TLV 513) - deprecated but may be present */
-+ 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);
-+ }
-+
-+ /* OSPF Area ID (TLV 514) */
-+ 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);
-+ }
-+
-+ /* IGP Router ID (TLV 515) - MANDATORY */
-+ 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;
-+ 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);
-+ }
-+
-+ /* 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_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 */
-+ stream_putw_at(s, len_pos, stream_get_endp(s) - sub_tlv_start);
-+
-+ return stream_get_endp(s) - start;
-+}
-+
-+/*
-+ * Encode Link Descriptor to wire format (RFC 9552 Section 5.2.2)
-+ *
-+ * Wire format:
-+ * 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
-+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
-+ * // Link Descriptor TLVs (variable) //
-+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
-+ *
-+ * Link Descriptor TLVs (RFC 9552 Section 5.2.2):
-+ * - TLV 258: Link Local/Remote Identifiers (8 bytes)
-+ * - TLV 259: IPv4 Interface Address (4 bytes)
-+ * - TLV 260: IPv4 Neighbor Address (4 bytes)
-+ * - TLV 261: IPv6 Interface Address (16 bytes)
-+ * - TLV 262: IPv6 Neighbor Address (16 bytes)
-+ * - TLV 263: Multi-Topology ID (2 bytes per MT-ID)
-+ *
-+ * Returns: Number of bytes written, or -1 on error
-+ */
-+int bgp_ls_encode_link_descriptor(struct stream *s, const struct bgp_ls_link_descriptor *desc)
-+{
-+ size_t start;
-+ uint16_t i;
-+
-+ if (!s || !desc)
-+ return -1;
-+
-+ start = stream_get_endp(s);
-+
-+ /* 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);
-+ }
-+
-+ /* IPv4 Interface Address (TLV 259) */
-+ 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);
-+ }
-+
-+ /* IPv4 Neighbor Address (TLV 260) */
-+ 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);
-+ }
-+
-+ /* IPv6 Interface Address (TLV 261) */
-+ 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);
-+ }
-+
-+ /* IPv6 Neighbor Address (TLV 262) */
-+ 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);
-+ }
-+
-+ /* Multi-Topology ID (TLV 263) */
-+ 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]);
-+ }
-+
-+ /* Remote AS Number (TLV 264) */
-+ 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);
-+ }
-+
-+ return stream_get_endp(s) - start;
-+}
-+
-+/*
-+ * Encode Prefix Descriptor to wire format (RFC 9552 Section 5.2.3)
-+ *
-+ * Wire format:
-+ * 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
-+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
-+ * // Prefix Descriptor TLVs (variable) //
-+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
-+ *
-+ * Prefix Descriptor TLVs (RFC 9552 Section 5.2.3):
-+ * - TLV 263: Multi-Topology ID (2 bytes per MT-ID)
-+ * - TLV 264: OSPF Route Type (1 byte)
-+ * - TLV 265: IP Reachability Information (1 + prefix bytes, MANDATORY)
-+ *
-+ * Returns: Number of bytes written, or -1 on error
-+ */
-+int bgp_ls_encode_prefix_descriptor(struct stream *s, const struct bgp_ls_prefix_descriptor *desc)
-+{
-+ size_t start;
-+ uint16_t i;
-+ uint8_t prefix_len_bytes;
-+
-+ if (!s || !desc)
-+ return -1;
-+
-+ start = stream_get_endp(s);
-+
-+ /* 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]);
-+ }
-+
-+ /* OSPF Route Type (TLV 264) */
-+ 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);
-+ }
-+
-+ /* IP Reachability Information (TLV 265) - MANDATORY */
-+ if (desc->prefix.family == AF_INET) {
-+ prefix_len_bytes = (desc->prefix.prefixlen + 7) / 8;
-+ 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);
-+ } else if (desc->prefix.family == AF_INET6) {
-+ prefix_len_bytes = (desc->prefix.prefixlen + 7) / 8;
-+ 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);
-+ }
-+
-+ /* 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;
-+}
-+
-+/*
-+ * Encode Node NLRI to wire format (RFC 9552 Section 5.2)
-+ *
-+ * Wire format:
-+ * 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
-+ * +-+-+-+-+-+-+-+-+
-+ * | Protocol-ID |
-+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
-+ * | Identifier |
-+ * | (64 bits) |
-+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
-+ * // Local Node Descriptors (variable) //
-+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
-+ *
-+ * Returns: Number of bytes written, or -1 on error
-+ */
-+int bgp_ls_encode_node_nlri(struct stream *s, const struct bgp_ls_node_nlri *nlri)
-+{
-+ 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);
-+
-+ /* Identifier (8 bytes) */
-+ if (STREAM_WRITEABLE(s) < BGP_LS_IDENTIFIER_SIZE)
-+ return -1;
-+ stream_putq(s, nlri->identifier);
-+
-+ /* Local Node Descriptors */
-+ if (bgp_ls_encode_node_descriptor(s, &nlri->local_node, BGP_LS_TLV_LOCAL_NODE_DESC) < 0)
-+ return -1;
-+
-+ return stream_get_endp(s) - start;
-+}
-+
-+/*
-+ * Encode Link NLRI to wire format (RFC 9552 Section 5.2)
-+ *
-+ * Wire format:
-+ * 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
-+ * +-+-+-+-+-+-+-+-+
-+ * | Protocol-ID |
-+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
-+ * | Identifier |
-+ * | (64 bits) |
-+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
-+ * // Local Node Descriptors (variable) //
-+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
-+ * // Remote Node Descriptors (variable) //
-+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
-+ * // Link Descriptors (variable) //
-+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
-+ *
-+ * Returns: Number of bytes written, or -1 on error
-+ */
-+int bgp_ls_encode_link_nlri(struct stream *s, const struct bgp_ls_link_nlri *nlri)
-+{
-+ 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);
-+
-+ /* Identifier (8 bytes) */
-+ if (STREAM_WRITEABLE(s) < BGP_LS_IDENTIFIER_SIZE)
-+ return -1;
-+ stream_putq(s, nlri->identifier);
-+
-+ /* Local Node Descriptors */
-+ if (bgp_ls_encode_node_descriptor(s, &nlri->local_node, BGP_LS_TLV_LOCAL_NODE_DESC) < 0)
-+ return -1;
-+
-+ /* Remote Node Descriptors */
-+ if (bgp_ls_encode_node_descriptor(s, &nlri->remote_node, BGP_LS_TLV_REMOTE_NODE_DESC) < 0)
-+ return -1;
-+
-+ /* Link Descriptors */
-+ if (bgp_ls_encode_link_descriptor(s, &nlri->link_desc) < 0)
-+ return -1;
-+
-+ return stream_get_endp(s) - start;
-+}
-+
-+/*
-+ * Encode Prefix NLRI to wire format (RFC 9552 Section 5.2)
-+ *
-+ * Wire format:
-+ * 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
-+ * +-+-+-+-+-+-+-+-+
-+ * | Protocol-ID |
-+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
-+ * | Identifier |
-+ * | (64 bits) |
-+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
-+ * // Local Node Descriptors (variable) //
-+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
-+ * // Prefix Descriptors (variable) //
-+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
-+ *
-+ * Note: Same structure for both IPv4 (Type 3) and IPv6 (Type 4)
-+ *
-+ * nlri_type: BGP_LS_NLRI_TYPE_IPV4_PREFIX or BGP_LS_NLRI_TYPE_IPV6_PREFIX
-+ *
-+ * Returns: Number of bytes written, or -1 on error
-+ */
-+int bgp_ls_encode_prefix_nlri(struct stream *s, const struct bgp_ls_prefix_nlri *nlri,
-+ enum bgp_ls_nlri_type nlri_type)
-+{
-+ size_t start;
-+
-+ if (!s || !nlri)
-+ return -1;
-+
-+ /* Validate prefix family matches NLRI type */
-+ if ((nlri_type == BGP_LS_NLRI_TYPE_IPV4_PREFIX &&
-+ nlri->prefix_desc.prefix.family != AF_INET) ||
-+ (nlri_type == BGP_LS_NLRI_TYPE_IPV6_PREFIX &&
-+ 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);
-+
-+ /* Identifier (8 bytes) */
-+ if (STREAM_WRITEABLE(s) < BGP_LS_IDENTIFIER_SIZE)
-+ return -1;
-+ stream_putq(s, nlri->identifier);
-+
-+ /* Local Node Descriptors */
-+ if (bgp_ls_encode_node_descriptor(s, &nlri->local_node, BGP_LS_TLV_LOCAL_NODE_DESC) < 0)
-+ return -1;
-+
-+ /* Prefix Descriptors */
-+ if (bgp_ls_encode_prefix_descriptor(s, &nlri->prefix_desc) < 0)
-+ return -1;
-+
-+ return stream_get_endp(s) - start;
-+}
-+
-+/*
-+ * Encode complete NLRI with Type-Length-Value header
-+ *
-+ * Wire format (RFC 9552 Section 5.2):
-+ * 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 |
-+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
-+ * | |
-+ * // NLRI Value (variable) //
-+ * | |
-+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
-+ *
-+ * Where:
-+ * Type = NLRI Type (1=Node, 2=Link, 3=IPv4 Prefix, 4=IPv6 Prefix)
-+ * Length = Length of NLRI Value in octets
-+ * Value = Type-specific NLRI data (Protocol-ID + Identifier + Descriptors)
-+ *
-+ * This is the top-level encoding used in MP_REACH/MP_UNREACH attributes.
-+ *
-+ * Returns: Number of bytes written, or -1 on error
-+ */
-+int bgp_ls_encode_nlri(struct stream *s, const struct bgp_ls_nlri *nlri)
-+{
-+ size_t start, len_pos, value_start;
-+ int ret = 0;
-+
-+ if (!s || !nlri)
-+ return -1;
-+
-+ /* Validate NLRI */
-+ 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);
-+
-+ /* 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 */
-+
-+ value_start = stream_get_endp(s);
-+
-+ /* Encode NLRI Value based on type */
-+ switch (nlri->nlri_type) {
-+ case BGP_LS_NLRI_TYPE_NODE:
-+ ret = bgp_ls_encode_node_nlri(s, &nlri->nlri_data.node);
-+ break;
-+
-+ case BGP_LS_NLRI_TYPE_LINK:
-+ ret = bgp_ls_encode_link_nlri(s, &nlri->nlri_data.link);
-+ break;
-+
-+ case BGP_LS_NLRI_TYPE_IPV4_PREFIX:
-+ case BGP_LS_NLRI_TYPE_IPV6_PREFIX:
-+ ret = bgp_ls_encode_prefix_nlri(s, &nlri->nlri_data.prefix, nlri->nlri_type);
-+ break;
-+
-+ case BGP_LS_NLRI_TYPE_RESERVED:
-+ return -1;
-+ }
-+
-+ if (ret < 0)
-+ return -1;
-+
-+ /* Update NLRI Length field */
-+ stream_putw_at(s, len_pos, stream_get_endp(s) - value_start);
-+
-+ return stream_get_endp(s) - start;
-+}
-+
-+/*
-+ * Encode BGP-LS Attributes (BGP-LS Attribute Type 29)
-+ * RFC 9552 Section 4.3.1
-+ *
-+ * Returns number of bytes written, or -1 on error
-+ */
-+int bgp_ls_encode_attr(struct stream *s, const struct bgp_ls_attr *attr,
-+ enum bgp_ls_protocol_id protocol_id)
-+{
-+ size_t start_pos;
-+
-+ if (!s || !attr)
-+ return -1;
-+
-+ start_pos = stream_get_endp(s);
-+
-+ /* Node Flag Bits (TLV 1024) */
-+ 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 (CHECK_FLAG(attr->present_tlvs, BGP_LS_ATTR_NODE_NAME_BIT)) {
-+ if (attr->node_name) {
-+ uint16_t len = strlen(attr->node_name);
-+
-+ 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 (CHECK_FLAG(attr->present_tlvs, BGP_LS_ATTR_ISIS_AREA_BIT)) {
-+ if (attr->isis_area_id && attr->isis_area_id_len > 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 (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);
-+ /* 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 (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 (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 (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 (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 (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 (CHECK_FLAG(attr->present_tlvs, BGP_LS_ATTR_ADMIN_GROUP_BIT)) {
-+ uint32_t admin_group_be = htonl(attr->admin_group);
-+
-+ 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 (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 (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 (CHECK_FLAG(attr->present_tlvs, BGP_LS_ATTR_UNRESV_BW_BIT)) {
-+ if (STREAM_WRITEABLE(s) < BGP_LS_TLV_HDR_SIZE + 32)
-+ 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 (CHECK_FLAG(attr->present_tlvs, BGP_LS_ATTR_TE_METRIC_BIT)) {
-+ uint32_t te_metric_be = htonl(attr->te_metric);
-+
-+ 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 (CHECK_FLAG(attr->present_tlvs, BGP_LS_ATTR_LINK_PROTECTION_BIT)) {
-+ uint16_t protection_be = htons(attr->link_protection);
-+
-+ 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 (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 (CHECK_FLAG(attr->present_tlvs, BGP_LS_ATTR_IGP_METRIC_BIT)) {
-+ uint8_t metric_buf[3];
-+ uint8_t len = attr->igp_metric_len;
-+
-+ if (len > 0 && len <= BGP_LS_IGP_METRIC_MAX_LEN) {
-+ /* Encode metric in big-endian */
-+ if (len == 1) {
-+ metric_buf[0] = attr->igp_metric & 0xFF;
-+ } else if (len == 2) {
-+ metric_buf[0] = (attr->igp_metric >> 8) & 0xFF;
-+ metric_buf[1] = attr->igp_metric & 0xFF;
-+ } else { /* len == 3 */
-+ metric_buf[0] = (attr->igp_metric >> 16) & 0xFF;
-+ metric_buf[1] = (attr->igp_metric >> 8) & 0xFF;
-+ metric_buf[2] = attr->igp_metric & 0xFF;
-+ }
-+ 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 (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_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]);
-+
-+ stream_put(s, &srlg_be, 4);
-+ }
-+ }
-+ }
-+
-+ /* Link Name (TLV 1098) */
-+ 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_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 (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);
-+
-+ if (sid_len == -1) {
-+ /* Should be impossible here */
-+ zlog_warn("BGP-LS: %s wrong combination of V-Flag and L-Flag for Adjacency SID",
-+ __func__);
-+ 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 (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_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);
-+
-+ stream_put(s, &word_be, 4);
-+ }
-+ }
-+ }
-+
-+ /* Unidirectional Link Delay (TLV 1114) */
-+ if (CHECK_FLAG(attr->present_tlvs, BGP_LS_ATTR_DELAY_BIT)) {
-+ uint32_t delay_be = htonl(attr->delay);
-+
-+ 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 (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_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 (CHECK_FLAG(attr->present_tlvs, BGP_LS_ATTR_JITTER_BIT)) {
-+ uint32_t jitter_be = htonl(attr->jitter);
-+
-+ 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 (CHECK_FLAG(attr->present_tlvs, BGP_LS_ATTR_PKT_LOSS_BIT)) {
-+ uint32_t pkt_loss_be = htonl(attr->pkt_loss);
-+
-+ 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 (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 (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 (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 (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 (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_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]);
-+
-+ stream_put(s, &tag_be, 4);
-+ }
-+ }
-+ }
-+
-+ /* Extended Tags (TLV 1154) */
-+ 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_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]);
-+
-+ stream_put(s, &tag_be, 8);
-+ }
-+ }
-+ }
-+
-+ /* Prefix Metric (TLV 1155) */
-+ if (CHECK_FLAG(attr->present_tlvs, BGP_LS_ATTR_PREFIX_METRIC_BIT)) {
-+ uint32_t metric_be = htonl(attr->prefix_metric);
-+
-+ 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 (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_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_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 (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) {
-+ /* Should be impossible here */
-+ zlog_warn("BGP-LS: %s wrong combination of V-Flag and L-Flag for Prefix SID",
-+ __func__);
-+ } else {
-+ 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 */
-+ /* SID Label/Index - three or four bytes */
-+ if (sid_len == 3)
-+ stream_put3(s, attr->prefix_sid.sid);
-+ else if (sid_len == 4)
-+ stream_putl(s, attr->prefix_sid.sid);
-+ }
-+ }
-+
-+ return stream_get_endp(s) - start_pos;
-+}
-+
-+static int bgp_ls_attr_sid_len_by_lv_flags(uint8_t flags, uint8_t v_flag_mask, uint8_t l_flag_mask)
-+{
-+ /*
-+ * IS-IS: RFC 8667, 2.1.1; OSPFv2: RFC8665, 5; OSPFv3: RFC8666, 6:
-+ *
-+ * All other combinations of V-Flag and L-Flag [all other = V-Flag!=L-Flag]
-+ * are invalid and any SID Advertisement received with an invalid setting
-+ * for V- and L-Flags MUST be ignored.
-+ */
-+ uint8_t vl_mask = v_flag_mask | l_flag_mask;
-+ uint8_t vl_val = CHECK_FLAG(flags, vl_mask);
-+
-+ if (vl_val != 0 && vl_val != vl_mask)
-+ return -1;
-+
-+ return vl_val ? 3 : 4;
-+}
-+
-+/*
-+ * Get Prefix-SID attribute SID length by flags
-+ *
-+ * @return 3 or 4 in normal case, -1 in error case
-+ */
-+int bgp_ls_attr_prefix_sid_len(uint8_t flags)
-+{
-+ return bgp_ls_attr_sid_len_by_lv_flags(flags, BGP_LS_PREFIX_SID_FLAG_VALUE,
-+ BGP_LS_PREFIX_SID_FLAG_LOCAL);
-+}
-+
-+/*
-+ * Get Adjacency SID attribute Label/Index SID length by flags
-+ *
-+ * @return 3 or 4 in normal case, -1 in error case
-+ */
-+int bgp_ls_attr_adjacency_sid_len(uint8_t flags, enum bgp_ls_protocol_id protocol_id)
-+{
-+ switch (protocol_id) {
-+ case BGP_LS_PROTO_ISIS_L1:
-+ case BGP_LS_PROTO_ISIS_L2:
-+ return bgp_ls_attr_sid_len_by_lv_flags(flags, ISIS_EXT_SUBTLV_LINK_ADJ_SID_VFLG,
-+ ISIS_EXT_SUBTLV_LINK_ADJ_SID_LFLG);
-+ case BGP_LS_PROTO_OSPFV2:
-+ case BGP_LS_PROTO_OSPFV3:
-+ return bgp_ls_attr_sid_len_by_lv_flags(flags, OSPF_EXT_SUBTLV_LINK_ADJ_SID_VFLG,
-+ OSPF_EXT_SUBTLV_LINK_ADJ_SID_LFLG);
-+ default:
-+ case BGP_LS_PROTO_RESERVED:
-+ case BGP_LS_PROTO_DIRECT:
-+ case BGP_LS_PROTO_STATIC:
-+ case BGP_LS_PROTO_BGP:
-+ zlog_warn("BGP-LS: %s unsupported protocol %d", __func__, protocol_id);
-+ return -1;
-+ }
-+}
-+
-+/*
-+ * ===========================================================================
-+ * NLRI Decoding Functions
-+ * ===========================================================================
-+ */
-+
-+/*
-+ * Read TLV header (Type + Length) from stream
-+ *
-+ * Wire format (RFC 9552 Section 5.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 0 on success, -1 on error
-+ */
-+static inline int stream_get_tlv_hdr(struct stream *s, uint16_t *type, uint16_t *length)
-+{
-+ if (STREAM_READABLE(s) < BGP_LS_TLV_HDR_SIZE) {
-+ flog_warn(EC_BGP_LS_PACKET, "BGP-LS: Not enough data for TLV header");
-+ return -1;
-+ }
-+
-+ *type = stream_getw(s);
-+ *length = stream_getw(s);
-+
-+ /* Check if stream has enough data for TLV value */
-+ if (STREAM_READABLE(s) < *length) {
-+ flog_warn(EC_BGP_LS_PACKET, "BGP-LS: TLV type=%u length=%u exceeds available data",
-+ *type, *length);
-+ return -1;
-+ }
-+
-+ return 0;
-+}
-+
-+/*
-+ * Skip unknown TLV in stream (RFC 9552 Section 5.1)
-+ * Unknown TLVs must be preserved and propagated
-+ */
-+static inline void stream_skip_tlv(struct stream *s, uint16_t length)
-+{
-+ stream_forward_getp(s, length);
-+}
-+
-+/*
-+ * Decode Node Descriptor from wire format (RFC 9552 Section 5.2.1)
-+ *
-+ * Wire format:
-+ * 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
-+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
-+ * | Descriptor Type | Descriptor Length |
-+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
-+ * // Node Descriptor Sub-TLVs (variable) //
-+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
-+ *
-+ * Node Descriptor Sub-TLVs (RFC 9552 Section 5.2.1.4):
-+ * - TLV 512: Autonomous System (4 bytes, OPTIONAL)
-+ * - TLV 513: BGP-LS Identifier (4 bytes, deprecated, OPTIONAL)
-+ * - TLV 514: OSPF Area-ID (4 bytes, CONDITIONAL)
-+ * - TLV 515: IGP Router-ID (variable, MANDATORY)
-+ *
-+ * Returns: 0 on success, -1 on error
-+ */
-+int bgp_ls_decode_node_descriptor(struct stream *s, struct bgp_ls_node_descriptor *desc,
-+ uint16_t desc_length)
-+{
-+ 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;
-+
-+ /* Clear descriptor */
-+ memset(desc, 0, sizeof(*desc));
-+
-+ /* Save end position for bounds checking */
-+ end_pos = stream_get_getp(s) + desc_length;
-+
-+ /* Parse sub-TLVs */
-+ while (stream_get_getp(s) < end_pos) {
-+ if (stream_get_tlv_hdr(s, &sub_type, &sub_len) < 0)
-+ return -1;
-+
-+ switch (sub_type) {
-+ case BGP_LS_TLV_AS_NUMBER:
-+ 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;
-+ }
-+ if (sub_len != BGP_LS_AS_NUMBER_SIZE) {
-+ flog_warn(EC_BGP_LS_PACKET,
-+ "BGP-LS: Invalid AS Number TLV length %u (expected %u)",
-+ sub_len, BGP_LS_AS_NUMBER_SIZE);
-+ return -1;
-+ }
-+ desc->asn = stream_getl(s);
-+ SET_FLAG(desc->present_tlvs, BGP_LS_NODE_DESC_AS_BIT);
-+ break;
-+
-+ case BGP_LS_TLV_BGP_LS_ID:
-+ 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;
-+ }
-+ if (sub_len != BGP_LS_BGP_LS_ID_SIZE) {
-+ flog_warn(EC_BGP_LS_PACKET,
-+ "BGP-LS: Invalid BGP-LS ID TLV length %u (expected %u)",
-+ sub_len, BGP_LS_BGP_LS_ID_SIZE);
-+ return -1;
-+ }
-+ desc->bgp_ls_id = stream_getl(s);
-+ SET_FLAG(desc->present_tlvs, BGP_LS_NODE_DESC_BGP_LS_ID_BIT);
-+ break;
-+
-+ case BGP_LS_TLV_OSPF_AREA_ID:
-+ 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;
-+ }
-+ if (sub_len != BGP_LS_OSPF_AREA_ID_SIZE) {
-+ flog_warn(EC_BGP_LS_PACKET,
-+ "BGP-LS: Invalid OSPF Area ID TLV length %u (expected %u)",
-+ sub_len, BGP_LS_OSPF_AREA_ID_SIZE);
-+ return -1;
-+ }
-+ desc->ospf_area_id = stream_getl(s);
-+ SET_FLAG(desc->present_tlvs, BGP_LS_NODE_DESC_OSPF_AREA_BIT);
-+ break;
-+
-+ case BGP_LS_TLV_IGP_ROUTER_ID:
-+ 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;
-+ }
-+ /* Variable length: 4 to 16 bytes */
-+ if (sub_len < BGP_LS_IGP_ROUTER_ID_MIN_SIZE ||
-+ sub_len > BGP_LS_IGP_ROUTER_ID_MAX_SIZE) {
-+ flog_warn(EC_BGP_LS_PACKET,
-+ "BGP-LS: Invalid IGP Router-ID TLV length %u", sub_len);
-+ return -1;
-+ }
-+ desc->igp_router_id_len = sub_len;
-+ stream_get(desc->igp_router_id, s, sub_len);
-+ 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,
-+ "BGP-LS: Unknown Node Descriptor sub-TLV %u, length %u (skipping)",
-+ sub_type, sub_len);
-+ stream_skip_tlv(s, sub_len);
-+ break;
-+ }
-+ }
-+
-+ /* Verify mandatory TLV present */
-+ if (!has_igp_router_id && !has_bgp_router_id) {
-+ flog_warn(EC_BGP_LS_PACKET,
-+ "BGP-LS: Mandatory IGP Router-ID TLV or BGP Router-ID TLV missing from Node Descriptor");
-+ return -1;
-+ }
-+
-+ /* Check we consumed exactly desc_length bytes */
-+ if (stream_get_getp(s) != end_pos) {
-+ flog_warn(EC_BGP_LS_PACKET,
-+ "BGP-LS: Node Descriptor length mismatch (consumed %zu, expected %u)",
-+ stream_get_getp(s) - (end_pos - desc_length), desc_length);
-+ return -1;
-+ }
-+
-+ return 0;
-+}
-+
-+/*
-+ * Decode Link Descriptor from wire format (RFC 9552 Section 5.2.2)
-+ *
-+ * Wire format:
-+ * 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
-+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
-+ * // Link Descriptor TLVs (variable) //
-+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
-+ *
-+ * Link Descriptor TLVs (RFC 9552 Section 5.2.2):
-+ * - TLV 258: Link Local/Remote Identifiers (8 bytes)
-+ * - TLV 259: IPv4 Interface Address (4 bytes)
-+ * - TLV 260: IPv4 Neighbor Address (4 bytes)
-+ * - TLV 261: IPv6 Interface Address (16 bytes)
-+ * - TLV 262: IPv6 Neighbor Address (16 bytes)
-+ * - TLV 263: Multi-Topology ID (variable, 2 bytes per MT-ID)
-+ *
-+ * Returns: 0 on success, -1 on error
-+ */
-+int bgp_ls_decode_link_descriptor(struct stream *s, struct bgp_ls_link_descriptor *desc,
-+ size_t total_length)
-+{
-+ size_t end_pos;
-+ uint16_t tlv_type, tlv_len;
-+
-+ if (!s || !desc)
-+ return -1;
-+
-+ /* Clear descriptor */
-+ memset(desc, 0, sizeof(*desc));
-+
-+ /* Save end position for bounds checking */
-+ end_pos = stream_get_getp(s) + total_length;
-+
-+ /* Parse TLVs */
-+ while (stream_get_getp(s) < end_pos) {
-+ if (stream_get_tlv_hdr(s, &tlv_type, &tlv_len) < 0)
-+ goto error;
-+
-+ switch (tlv_type) {
-+ case BGP_LS_TLV_LINK_ID:
-+ 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;
-+ }
-+ if (tlv_len != BGP_LS_LINK_ID_SIZE) {
-+ flog_warn(EC_BGP_LS_PACKET,
-+ "BGP-LS: Invalid Link ID TLV length %u (expected %u)",
-+ tlv_len, BGP_LS_LINK_ID_SIZE);
-+ goto error;
-+ }
-+ desc->link_local_id = stream_getl(s);
-+ desc->link_remote_id = stream_getl(s);
-+ SET_FLAG(desc->present_tlvs, BGP_LS_LINK_DESC_LINK_ID_BIT);
-+ break;
-+
-+ case BGP_LS_TLV_IPV4_INTF_ADDR:
-+ 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;
-+ }
-+ if (tlv_len != BGP_LS_IPV4_ADDR_SIZE) {
-+ flog_warn(EC_BGP_LS_PACKET,
-+ "BGP-LS: Invalid IPv4 Interface Address TLV length %u",
-+ tlv_len);
-+ goto error;
-+ }
-+ stream_get(&desc->ipv4_intf_addr.s_addr, s, BGP_LS_IPV4_ADDR_SIZE);
-+ SET_FLAG(desc->present_tlvs, BGP_LS_LINK_DESC_IPV4_INTF_BIT);
-+ break;
-+
-+ case BGP_LS_TLV_IPV4_NEIGH_ADDR:
-+ 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;
-+ }
-+ if (tlv_len != BGP_LS_IPV4_ADDR_SIZE) {
-+ flog_warn(EC_BGP_LS_PACKET,
-+ "BGP-LS: Invalid IPv4 Neighbor Address TLV length %u",
-+ tlv_len);
-+ goto error;
-+ }
-+ stream_get(&desc->ipv4_neigh_addr.s_addr, s, BGP_LS_IPV4_ADDR_SIZE);
-+ SET_FLAG(desc->present_tlvs, BGP_LS_LINK_DESC_IPV4_NEIGH_BIT);
-+ break;
-+
-+ case BGP_LS_TLV_IPV6_INTF_ADDR:
-+ 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;
-+ }
-+ if (tlv_len != BGP_LS_IPV6_ADDR_SIZE) {
-+ flog_warn(EC_BGP_LS_PACKET,
-+ "BGP-LS: Invalid IPv6 Interface Address TLV length %u",
-+ tlv_len);
-+ goto error;
-+ }
-+ stream_get(&desc->ipv6_intf_addr.s6_addr, s, BGP_LS_IPV6_ADDR_SIZE);
-+ SET_FLAG(desc->present_tlvs, BGP_LS_LINK_DESC_IPV6_INTF_BIT);
-+ break;
-+
-+ case BGP_LS_TLV_IPV6_NEIGH_ADDR:
-+ 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;
-+ }
-+ if (tlv_len != BGP_LS_IPV6_ADDR_SIZE) {
-+ flog_warn(EC_BGP_LS_PACKET,
-+ "BGP-LS: Invalid IPv6 Neighbor Address TLV length %u",
-+ tlv_len);
-+ goto error;
-+ }
-+ stream_get(&desc->ipv6_neigh_addr.s6_addr, s, BGP_LS_IPV6_ADDR_SIZE);
-+ SET_FLAG(desc->present_tlvs, BGP_LS_LINK_DESC_IPV6_NEIGH_BIT);
-+ break;
-+
-+ case BGP_LS_TLV_MT_ID:
-+ 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 == 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;
-+ }
-+ desc->mt_id_count = tlv_len / 2;
-+ 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);
-+ SET_FLAG(desc->present_tlvs, BGP_LS_LINK_DESC_MT_ID_BIT);
-+ break;
-+
-+ case BGP_LS_TLV_REMOTE_AS_NUMBER:
-+ 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;
-+ }
-+ if (tlv_len != 4) {
-+ flog_warn(EC_BGP_LS_PACKET,
-+ "BGP-LS: Invalid Remote AS Number TLV length %u (expected 4)",
-+ tlv_len);
-+ goto error;
-+ }
-+ desc->remote_asn = stream_getl(s);
-+ SET_FLAG(desc->present_tlvs, BGP_LS_LINK_DESC_REMOTE_AS_BIT);
-+ break;
-+
-+ default:
-+ /* Unknown TLV - skip but preserve (RFC 9552 Section 5.1) */
-+ flog_warn(EC_BGP_LS_PACKET,
-+ "BGP-LS: Unknown Link Descriptor TLV %u, length %u (skipping)",
-+ tlv_type, tlv_len);
-+ stream_skip_tlv(s, tlv_len);
-+ break;
-+ }
-+ }
-+
-+ /* Check we consumed exactly total_length bytes */
-+ if (stream_get_getp(s) != end_pos) {
-+ flog_warn(EC_BGP_LS_PACKET, "BGP-LS: Link Descriptor length mismatch");
-+ goto error;
-+ }
-+
-+ return 0;
-+
-+error:
-+ XFREE(MTYPE_BGP_LS_NLRI, desc->mt_id);
-+ desc->mt_id = NULL;
-+ desc->mt_id_count = 0;
-+ return -1;
-+}
-+
-+/*
-+ * Decode Prefix Descriptor from wire format (RFC 9552 Section 5.2.3)
-+ *
-+ * Wire format:
-+ * 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
-+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
-+ * // Prefix Descriptor TLVs (variable) //
-+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
-+ *
-+ * Prefix Descriptor TLVs (RFC 9552 Section 5.2.3):
-+ * - TLV 263: Multi-Topology ID (2 bytes, CONDITIONAL)
-+ * - TLV 264: OSPF Route Type (1 byte, CONDITIONAL)
-+ * - TLV 265: IP Reachability Information (variable, MANDATORY)
-+ *
-+ * Returns: 0 on success, -1 on error
-+ */
-+int bgp_ls_decode_prefix_descriptor(struct stream *s, struct bgp_ls_prefix_descriptor *desc,
-+ size_t total_length, bool is_ipv6)
-+{
-+ size_t end_pos;
-+ uint16_t tlv_type, tlv_len;
-+ bool has_ip_reach = false;
-+
-+ if (!s || !desc)
-+ return -1;
-+
-+ /* Clear descriptor */
-+ memset(desc, 0, sizeof(*desc));
-+
-+ /* Save end position for bounds checking */
-+ end_pos = stream_get_getp(s) + total_length;
-+
-+ /* Parse TLVs */
-+ while (stream_get_getp(s) < end_pos) {
-+ if (stream_get_tlv_hdr(s, &tlv_type, &tlv_len) < 0)
-+ goto error;
-+
-+ switch (tlv_type) {
-+ case BGP_LS_TLV_MT_ID:
-+ 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 == 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;
-+ }
-+ desc->mt_id_count = tlv_len / 2;
-+ 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);
-+ SET_FLAG(desc->present_tlvs, BGP_LS_PREFIX_DESC_MT_ID_BIT);
-+ break;
-+
-+ case BGP_LS_TLV_OSPF_ROUTE_TYPE:
-+ 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;
-+ }
-+ if (tlv_len != BGP_LS_OSPF_ROUTE_TYPE_SIZE) {
-+ flog_warn(EC_BGP_LS_PACKET,
-+ "BGP-LS: Invalid OSPF Route Type TLV length %u", tlv_len);
-+ goto error;
-+ }
-+ desc->ospf_route_type = stream_getc(s);
-+ SET_FLAG(desc->present_tlvs, BGP_LS_PREFIX_DESC_OSPF_ROUTE_BIT);
-+ break;
-+
-+ case BGP_LS_TLV_IP_REACH_INFO:
-+ 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;
-+ }
-+ /* Variable length: prefix_len + ceil(prefix_len/8) */
-+ if (tlv_len < 1) {
-+ flog_warn(EC_BGP_LS_PACKET,
-+ "BGP-LS: Invalid IP Reach Info TLV length %u", tlv_len);
-+ goto error;
-+ }
-+ /* First byte is prefix length */
-+ desc->prefix.prefixlen = stream_getc(s);
-+
-+ /* Calculate expected bytes for this prefix length */
-+ uint16_t expected_bytes = (desc->prefix.prefixlen + 7) / 8;
-+
-+ if (tlv_len != 1 + expected_bytes) {
-+ flog_warn(EC_BGP_LS_PACKET,
-+ "BGP-LS: IP Reach Info TLV length %u mismatch (prefix len %u requires %u bytes)",
-+ tlv_len, desc->prefix.prefixlen, 1 + expected_bytes);
-+ goto error;
-+ }
-+
-+ /*
-+ * Read prefix bytes
-+ */
-+ if (is_ipv6) {
-+ desc->prefix.family = AF_INET6;
-+ /* Validate prefix length is valid for IPv6 */
-+ if (desc->prefix.prefixlen > IPV6_MAX_BITLEN) {
-+ flog_warn(EC_BGP_LS_PACKET,
-+ "BGP-LS: Invalid IPv6 prefix length %u (max 128)",
-+ desc->prefix.prefixlen);
-+ goto error;
-+ }
-+ if (expected_bytes > 0)
-+ stream_get(&desc->prefix.u.prefix6.s6_addr, s,
-+ expected_bytes);
-+ } else {
-+ desc->prefix.family = AF_INET;
-+ /* Validate prefix length is valid for IPv4 */
-+ if (desc->prefix.prefixlen > IPV4_MAX_BITLEN) {
-+ flog_warn(EC_BGP_LS_PACKET,
-+ "BGP-LS: Invalid IPv4 prefix length %u (max 32)",
-+ desc->prefix.prefixlen);
-+ goto error;
-+ }
-+ if (expected_bytes > 0)
-+ stream_get(&desc->prefix.u.prefix4.s_addr, s,
-+ expected_bytes);
-+ }
-+
-+ 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,
-+ "BGP-LS: Unknown Prefix Descriptor TLV %u, length %u (skipping)",
-+ tlv_type, tlv_len);
-+ stream_skip_tlv(s, tlv_len);
-+ break;
-+ }
-+ }
-+
-+ /* Verify mandatory TLV present */
-+ if (!has_ip_reach) {
-+ flog_warn(EC_BGP_LS_PACKET,
-+ "BGP-LS: Mandatory IP Reachability Info TLV missing from Prefix Descriptor");
-+ goto error;
-+ }
-+
-+ /* Check we consumed exactly total_length bytes */
-+ if (stream_get_getp(s) != end_pos) {
-+ flog_warn(EC_BGP_LS_PACKET, "BGP-LS: Prefix Descriptor length mismatch");
-+ goto error;
-+ }
-+
-+ return 0;
-+
-+error:
-+ XFREE(MTYPE_BGP_LS_NLRI, desc->mt_id);
-+ desc->mt_id = NULL;
-+ desc->mt_id_count = 0;
-+ return -1;
-+}
-+
-+/*
-+ * Decode Node NLRI from wire format (RFC 9552 Section 5.2)
-+ *
-+ * Wire format:
-+ * 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
-+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
-+ * | NLRI Type = 1 | Total NLRI Length |
-+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
-+ * | Protocol-ID |
-+ * +-+-+-+-+-+-+-+-+
-+ *
-+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
-+ * | Identifier |
-+ * | (8 octets) |
-+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
-+ * | Local Node Descriptors TLV Type | Length |
-+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
-+ * // Local Node Descriptor Sub-TLVs //
-+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
-+ *
-+ * Returns: 0 on success, -1 on error
-+ */
-+int bgp_ls_decode_node_nlri(struct stream *s, struct bgp_ls_nlri *nlri, uint16_t nlri_length)
-+{
-+ uint16_t desc_type, desc_len;
-+ size_t start_pos;
-+
-+ if (!s || !nlri)
-+ return -1;
-+
-+ /* Save start position for length validation */
-+ start_pos = stream_get_getp(s);
-+
-+ /* Check minimum length */
-+ if (nlri_length < BGP_LS_NLRI_MIN_LENGTH) {
-+ flog_warn(EC_BGP_LS_PACKET, "BGP-LS: Node NLRI length %u too short (minimum %u)",
-+ nlri_length, BGP_LS_NLRI_MIN_LENGTH);
-+ return -1;
-+ }
-+
-+ /* Allocate NLRI structure */
-+ nlri->nlri_type = BGP_LS_NLRI_TYPE_NODE;
-+
-+ /* Read Protocol-ID (1 byte) */
-+ nlri->nlri_data.node.protocol_id = stream_getc(s);
-+
-+ /* Validate Protocol-ID */
-+ if (nlri->nlri_data.node.protocol_id == BGP_LS_PROTO_RESERVED ||
-+ nlri->nlri_data.node.protocol_id > BGP_LS_PROTO_BGP) {
-+ flog_warn(EC_BGP_LS_PACKET, "BGP-LS: Invalid Protocol-ID %u",
-+ nlri->nlri_data.node.protocol_id);
-+ return -1;
-+ }
-+
-+ /* Read Identifier (8 bytes) */
-+ nlri->nlri_data.node.identifier = stream_getq(s);
-+
-+ /* Read Local Node Descriptor TLV */
-+ if (stream_get_tlv_hdr(s, &desc_type, &desc_len) < 0)
-+ return -1;
-+
-+ if (desc_type != BGP_LS_TLV_LOCAL_NODE_DESC) {
-+ flog_warn(EC_BGP_LS_PACKET, "BGP-LS: Expected Local Node Descriptor TLV %u, got %u",
-+ BGP_LS_TLV_LOCAL_NODE_DESC, desc_type);
-+ return -1;
-+ }
-+
-+ if (bgp_ls_decode_node_descriptor(s, &nlri->nlri_data.node.local_node, desc_len) < 0)
-+ return -1;
-+
-+ /* Verify we consumed exactly nlri_length bytes */
-+ if (stream_get_getp(s) - start_pos != nlri_length) {
-+ flog_warn(EC_BGP_LS_PACKET,
-+ "BGP-LS: Node NLRI length mismatch (consumed %zu, expected %u)",
-+ stream_get_getp(s) - start_pos, nlri_length);
-+ return -1;
-+ }
-+
-+ return 0;
-+}
-+
-+/*
-+ * Decode Link NLRI from wire format (RFC 9552 Section 5.2)
-+ *
-+ * Wire format:
-+ * 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
-+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
-+ * | NLRI Type = 2 | Total NLRI Length |
-+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
-+ * | Protocol-ID |
-+ * +-+-+-+-+-+-+-+-+
-+ *
-+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
-+ * | Identifier |
-+ * | (8 octets) |
-+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
-+ * | Local Node Descriptors TLV Type | Length |
-+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
-+ * // Local Node Descriptor Sub-TLVs //
-+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
-+ * | Remote Node Descriptors TLV Type | Length |
-+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
-+ * // Remote Node Descriptor Sub-TLVs //
-+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
-+ * // Link Descriptor TLVs //
-+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
-+ *
-+ * Returns: 0 on success, -1 on error
-+ */
-+int bgp_ls_decode_link_nlri(struct stream *s, struct bgp_ls_nlri *nlri, uint16_t nlri_length)
-+{
-+ uint16_t desc_type, desc_len;
-+ size_t start_pos, link_desc_start;
-+
-+ if (!s || !nlri)
-+ return -1;
-+
-+ /* Save start position for length validation */
-+ start_pos = stream_get_getp(s);
-+
-+ /* Check minimum length */
-+ if (nlri_length < BGP_LS_NLRI_MIN_LENGTH) {
-+ flog_warn(EC_BGP_LS_PACKET, "BGP-LS: Link NLRI length %u too short (minimum %u)",
-+ nlri_length, BGP_LS_NLRI_MIN_LENGTH);
-+ return -1;
-+ }
-+
-+ /* Allocate NLRI structure */
-+ nlri->nlri_type = BGP_LS_NLRI_TYPE_LINK;
-+
-+ /* Read Protocol-ID (1 byte) */
-+ nlri->nlri_data.link.protocol_id = stream_getc(s);
-+
-+ /* Validate Protocol-ID */
-+ if (nlri->nlri_data.link.protocol_id == BGP_LS_PROTO_RESERVED ||
-+ nlri->nlri_data.link.protocol_id > BGP_LS_PROTO_BGP) {
-+ flog_warn(EC_BGP_LS_PACKET, "BGP-LS: Invalid Protocol-ID %u",
-+ nlri->nlri_data.link.protocol_id);
-+ return -1;
-+ }
-+
-+ /* Read Identifier (8 bytes) */
-+ nlri->nlri_data.link.identifier = stream_getq(s);
-+
-+ /* Read Local Node Descriptor TLV */
-+ if (stream_get_tlv_hdr(s, &desc_type, &desc_len) < 0)
-+ return -1;
-+
-+ if (desc_type != BGP_LS_TLV_LOCAL_NODE_DESC) {
-+ flog_warn(EC_BGP_LS_PACKET, "BGP-LS: Expected Local Node Descriptor TLV %u, got %u",
-+ BGP_LS_TLV_LOCAL_NODE_DESC, desc_type);
-+ return -1;
-+ }
-+
-+ if (bgp_ls_decode_node_descriptor(s, &nlri->nlri_data.link.local_node, desc_len) < 0)
-+ return -1;
-+
-+ /* Read Remote Node Descriptor TLV */
-+ if (stream_get_tlv_hdr(s, &desc_type, &desc_len) < 0)
-+ return -1;
-+
-+ if (desc_type != BGP_LS_TLV_REMOTE_NODE_DESC) {
-+ flog_warn(EC_BGP_LS_PACKET,
-+ "BGP-LS: Expected Remote Node Descriptor TLV %u, got %u",
-+ BGP_LS_TLV_REMOTE_NODE_DESC, desc_type);
-+ return -1;
-+ }
-+
-+ if (bgp_ls_decode_node_descriptor(s, &nlri->nlri_data.link.remote_node, desc_len) < 0)
-+ return -1;
-+
-+ /* Link Descriptors are remaining bytes */
-+ link_desc_start = stream_get_getp(s);
-+ size_t link_desc_len = nlri_length - (link_desc_start - start_pos);
-+
-+ if (link_desc_len > 0) {
-+ 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 */
-+ if (stream_get_getp(s) - start_pos != nlri_length) {
-+ flog_warn(EC_BGP_LS_PACKET,
-+ "BGP-LS: Link NLRI length mismatch (consumed %zu, expected %u)",
-+ stream_get_getp(s) - start_pos, nlri_length);
-+ return -1;
-+ }
-+
-+ return 0;
-+}
-+
-+/*
-+ * Decode Prefix NLRI from wire format (RFC 9552 Section 5.2)
-+ *
-+ * Wire format:
-+ * 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
-+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
-+ * | NLRI Type = 3/4 | Total NLRI Length |
-+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
-+ * | Protocol-ID |
-+ * +-+-+-+-+-+-+-+-+
-+ *
-+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
-+ * | Identifier |
-+ * | (8 octets) |
-+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
-+ * | Local Node Descriptors TLV Type | Length |
-+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
-+ * // Local Node Descriptor Sub-TLVs //
-+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
-+ * // Prefix Descriptor TLVs //
-+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
-+ *
-+ * Returns: 0 on success, -1 on error
-+ */
-+int bgp_ls_decode_prefix_nlri(struct stream *s, struct bgp_ls_nlri *nlri, uint16_t nlri_type,
-+ uint16_t nlri_length)
-+{
-+ uint16_t desc_type, desc_len;
-+ size_t start_pos, prefix_desc_start;
-+
-+ if (!s || !nlri)
-+ return -1;
-+
-+ /* Validate NLRI type */
-+ if (nlri_type != BGP_LS_NLRI_TYPE_IPV4_PREFIX && nlri_type != BGP_LS_NLRI_TYPE_IPV6_PREFIX) {
-+ flog_warn(EC_BGP_LS_PACKET, "BGP-LS: Invalid Prefix NLRI type %u", nlri_type);
-+ return -1;
-+ }
-+
-+ /* Save start position for length validation */
-+ start_pos = stream_get_getp(s);
-+
-+ /* Check minimum length */
-+ if (nlri_length < BGP_LS_NLRI_MIN_LENGTH) {
-+ flog_warn(EC_BGP_LS_PACKET, "BGP-LS: Prefix NLRI length %u too short (minimum %u)",
-+ nlri_length, BGP_LS_NLRI_MIN_LENGTH);
-+ return -1;
-+ }
-+
-+ /* Allocate NLRI structure */
-+ nlri->nlri_type = nlri_type;
-+
-+ /* Read Protocol-ID (1 byte) */
-+ nlri->nlri_data.prefix.protocol_id = stream_getc(s);
-+
-+ /* Validate Protocol-ID */
-+ if (nlri->nlri_data.prefix.protocol_id == BGP_LS_PROTO_RESERVED ||
-+ nlri->nlri_data.prefix.protocol_id > BGP_LS_PROTO_BGP) {
-+ flog_warn(EC_BGP_LS_PACKET, "BGP-LS: Invalid Protocol-ID %u",
-+ nlri->nlri_data.prefix.protocol_id);
-+ return -1;
-+ }
-+
-+ /* Read Identifier (8 bytes) */
-+ nlri->nlri_data.prefix.identifier = stream_getq(s);
-+
-+ /* Read Local Node Descriptor TLV */
-+ if (stream_get_tlv_hdr(s, &desc_type, &desc_len) < 0)
-+ return -1;
-+
-+ if (desc_type != BGP_LS_TLV_LOCAL_NODE_DESC) {
-+ flog_warn(EC_BGP_LS_PACKET, "BGP-LS: Expected Local Node Descriptor TLV %u, got %u",
-+ BGP_LS_TLV_LOCAL_NODE_DESC, desc_type);
-+ return -1;
-+ }
-+
-+ if (bgp_ls_decode_node_descriptor(s, &nlri->nlri_data.prefix.local_node, desc_len) < 0)
-+ return -1;
-+
-+ /* Prefix Descriptors are remaining bytes */
-+ prefix_desc_start = stream_get_getp(s);
-+ size_t prefix_desc_len = nlri_length - (prefix_desc_start - start_pos);
-+
-+ if (prefix_desc_len > 0) {
-+ /* Determine if IPv6 based on NLRI type (RFC 9552 Section 3.2) */
-+ bool is_ipv6 = (nlri_type == BGP_LS_NLRI_TYPE_IPV6_PREFIX);
-+
-+ if (bgp_ls_decode_prefix_descriptor(s, &nlri->nlri_data.prefix.prefix_desc,
-+ prefix_desc_len, is_ipv6) < 0)
-+ return -1;
-+ } else {
-+ flog_warn(EC_BGP_LS_PACKET, "BGP-LS: Prefix NLRI has no Prefix Descriptor TLVs");
-+ return -1;
-+ }
-+
-+ /* Verify we consumed exactly nlri_length bytes */
-+ if (stream_get_getp(s) - start_pos != nlri_length) {
-+ flog_warn(EC_BGP_LS_PACKET,
-+ "BGP-LS: Prefix NLRI length mismatch (consumed %zu, expected %u)",
-+ stream_get_getp(s) - start_pos, nlri_length);
-+ return -1;
-+ }
-+
-+ return 0;
-+}
-+
-+/*
-+ * Decode complete BGP-LS NLRI from UPDATE message (RFC 9552 Section 5.2)
-+ *
-+ * This is the main entry point for decoding NLRIs from MP_REACH_NLRI
-+ * or MP_UNREACH_NLRI attributes.
-+ *
-+ * Returns: 0 on success, -1 on error
-+ */
-+int bgp_ls_decode_nlri(struct stream *s, struct bgp_ls_nlri *nlri)
-+{
-+ uint16_t nlri_type, nlri_length;
-+
-+ if (!s || !nlri)
-+ return -1;
-+
-+ /* 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);
-+ nlri_length = stream_getw(s);
-+
-+ /* Check if stream has enough data for NLRI */
-+ if (STREAM_READABLE(s) < nlri_length) {
-+ flog_warn(EC_BGP_LS_PACKET, "BGP-LS: NLRI type=%u length=%u exceeds available data",
-+ nlri_type, nlri_length);
-+ return -1;
-+ }
-+
-+ /* Decode based on NLRI type */
-+ switch (nlri_type) {
-+ case BGP_LS_NLRI_TYPE_NODE:
-+ return bgp_ls_decode_node_nlri(s, nlri, nlri_length);
-+
-+ case BGP_LS_NLRI_TYPE_LINK:
-+ return bgp_ls_decode_link_nlri(s, nlri, nlri_length);
-+
-+ case BGP_LS_NLRI_TYPE_IPV4_PREFIX:
-+ case BGP_LS_NLRI_TYPE_IPV6_PREFIX:
-+ return bgp_ls_decode_prefix_nlri(s, nlri, nlri_type, nlri_length);
-+
-+ default:
-+ /* Unknown NLRI type - preserve and propagate (RFC 9552 Section 5.2) */
-+ flog_warn(EC_BGP_LS_PACKET, "BGP-LS: Unknown NLRI type %u, length %u (skipping)",
-+ nlri_type, nlri_length);
-+ stream_forward_getp(s, nlri_length);
-+ return -1;
-+ }
-+}
-+
-+/*
-+ * Parse Node Flag Bits TLV (TLV 1024)
-+ * RFC 9552 Section 5.3.1.1
-+ */
-+static int parse_node_flags(struct stream *s, uint16_t length, struct bgp_ls_attr *attr)
-+{
-+ if (length < 1) {
-+ flog_warn(EC_BGP_UPDATE_RCV, "BGP-LS: Node Flags TLV too short (%u bytes)", length);
-+ return -1;
-+ }
-+
-+ 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);
-+ SET_FLAG(attr->present_tlvs, BGP_LS_ATTR_NODE_FLAGS_BIT);
-+
-+ /* Skip any extra bytes */
-+ if (length > 1)
-+ stream_forward_getp(s, length - 1);
-+
-+ return 0;
-+}
-+
-+/*
-+ * Parse Node Name TLV (TLV 1026)
-+ * RFC 9552 Section 5.3.1.3
-+ */
-+static int parse_node_name(struct stream *s, uint16_t length, struct bgp_ls_attr *attr)
-+{
-+ if (length == 0)
-+ return 0;
-+
-+ 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;
-+ }
-+
-+ /* Allocate space for node name (null-terminated) */
-+ attr->node_name = XCALLOC(MTYPE_BGP_LS_ATTR, length + 1);
-+ stream_get(attr->node_name, s, length);
-+ attr->node_name[length] = '\0';
-+
-+ /*
-+ * 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;
-+}
-+
-+/*
-+ * Parse IS-IS Area ID TLV (TLV 1027)
-+ * RFC 9552 Section 5.3.1.4
-+ */
-+static int parse_isis_area_id(struct stream *s, uint16_t length, struct bgp_ls_attr *attr)
-+{
-+ if (length == 0 || length > 13) {
-+ flog_warn(EC_BGP_UPDATE_RCV, "BGP-LS: Invalid ISIS Area ID length (%u bytes)",
-+ length);
-+ return -1;
-+ }
-+
-+ 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
-+ * only one IS-IS Area-ID TLV; skip any additional ones.
-+ */
-+ if (BGP_DEBUG(linkstate, LINKSTATE))
-+ flog_warn(EC_BGP_UPDATE_RCV,
-+ "BGP-LS: multiple IS-IS Area-ID TLVs not supported, skipping duplicate");
-+ stream_forward_getp(s, length);
-+ return 0;
-+ }
-+
-+ attr->isis_area_id = XCALLOC(MTYPE_BGP_LS_ATTR, length);
-+ stream_get(attr->isis_area_id, s, length);
-+ attr->isis_area_id_len = length;
-+ SET_FLAG(attr->present_tlvs, BGP_LS_ATTR_ISIS_AREA_BIT);
-+
-+ return 0;
-+}
-+
-+/*
-+ * Parse SR Capabilities TLV (TLV 1034)
-+ * RFC 9085 Section 2.1.2
-+ */
-+static int parse_sr_capabilities(struct stream *s, uint16_t length, struct bgp_ls_attr *attr)
-+{
-+ uint32_t t;
-+
-+ /*
-+ * RFC8667 (IS-IS) 3.1:
-+ * The SR-Capabilities sub-TLV MAY be advertised in an LSP of any
-+ * number, but a router MUST NOT advertise more than one
-+ * SR-Capabilities sub-TLV. A router receiving multiple
-+ * SR-Capabilities sub-TLVs from the same originator SHOULD select the
-+ * first advertisement in the lowest-numbered LSP.
-+ *
-+ * RFC8665 (OSPF) 3.2:
-+ * Multiple occurrences of the SID/Label Range TLV MAY be advertised
-+ * in order to advertise multiple ranges. In such a case:
-+ *
-+ * So multiple TLVs are possible, just taking first one.
-+ * XXX Selection of lowest-numbered LSP for IS-IS is not implemented
-+ * (it is SHOULD)
-+ */
-+ 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");
-+
-+ stream_forward_getp(s, length);
-+ return 0;
-+ }
-+
-+ if (length < 12 || (length - 2) % 10 != 0) {
-+ flog_warn(EC_BGP_UPDATE_RCV, "BGP-LS: Invalid SR Capabilities length (%u bytes)",
-+ length);
-+ return -1;
-+ }
-+
-+ attr->srgb.flag = stream_getc(s);
-+ stream_getc(s); /* Reserved, ignore */
-+ attr->srgb.range_size = stream_get3(s);
-+ t = stream_getw(s);
-+ if (t != BGP_LS_ATTR_SID_LABEL) {
-+ flog_warn(EC_BGP_UPDATE_RCV,
-+ "BGP-LS: Failed parsing SR Capabilities, SubTLV Type is not %d",
-+ BGP_LS_ATTR_SID_LABEL);
-+ return -1;
-+ }
-+ t = stream_getw(s);
-+ if (t != 3) {
-+ flog_warn(EC_BGP_UPDATE_RCV,
-+ "BGP-LS: Failed parsing SR Capabilities, SubTLV Length is not 3");
-+ return -1;
-+ }
-+ attr->srgb.lower_bound = stream_get3(s);
-+ SET_FLAG(attr->present_tlvs, BGP_LS_ATTR_SR_CAPABILITIES_BIT);
-+
-+ if (length > 12) {
-+ flog_warn(EC_BGP_UPDATE_RCV,
-+ "BGP-LS: Multiple ranges in SR Capabilities not supported, skipping duplicate");
-+ stream_forward_getp(s, length - 12);
-+ }
-+
-+ return 0;
-+}
-+
-+/*
-+ * Parse Node MSD TLV (TLV 266)
-+ * RFC 8814 Section 3
-+ */
-+static int parse_node_msd(struct stream *s, uint16_t length, struct bgp_ls_attr *attr)
-+{
-+ uint8_t t;
-+ uint8_t v;
-+ uint8_t msd = 255;
-+ bool had_msd = false;
-+
-+ /*
-+ * RFC8476 (OSPFv2/OSPFv3)
-+ * When multiple Node MSD TLVs are received from a given router, the
-+ * receiver MUST use the first occurrence of the TLV in the Router
-+ * Information (RI) LSA.
-+ *
-+ * RFC8491 (IS-IS) 2: (not implemented, just taking first TLV here)
-+ * If there exist multiple Node MSD advertisements for the same MSD-Type
-+ * originated by the same router, the procedures defined in [RFC7981]
-+ * apply.
-+ */
-+ 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);
-+ return 0;
-+ }
-+
-+ if (length < 2 || length % 2 != 0) {
-+ flog_warn(EC_BGP_UPDATE_RCV, "BGP-LS: Invalid Node MSD length (%u bytes)", length);
-+ return -1;
-+ }
-+
-+ for (int i = 0; i < length; i += 2) {
-+ t = stream_getc(s);
-+ v = stream_getc(s);
-+ if (t == BGP_LS_IGP_MSD_TYPE_BASE_MPLS) {
-+ if (had_msd) {
-+ flog_warn(EC_BGP_UPDATE_RCV,
-+ "BGP-LS: Several MSD Values of Base MPLS Imposition MSD type, choosing minimum");
-+ }
-+ had_msd = true;
-+ msd = MIN(msd, v);
-+ }
-+ }
-+
-+ if (!had_msd) {
-+ flog_warn(EC_BGP_UPDATE_RCV,
-+ "BGP-LS: Received Node MSD without value for Base MPLS Imposition MSD type, skipping");
-+ return 0;
-+ }
-+
-+ attr->msd = msd;
-+ SET_FLAG(attr->present_tlvs, BGP_LS_ATTR_NODE_MSD_BIT);
-+
-+ return 0;
-+}
-+
-+/*
-+ * Parse Administrative Group TLV (TLV 1088)
-+ * RFC 5305 Section 3.1
-+ */
-+static int parse_admin_group(struct stream *s, uint16_t length, struct bgp_ls_attr *attr)
-+{
-+ if (length != 4) {
-+ flog_warn(EC_BGP_UPDATE_RCV, "BGP-LS: Invalid Admin Group length (%u bytes)",
-+ length);
-+ return -1;
-+ }
-+
-+ 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);
-+ SET_FLAG(attr->present_tlvs, BGP_LS_ATTR_ADMIN_GROUP_BIT);
-+
-+ return 0;
-+}
-+
-+/*
-+ * Parse Maximum Link Bandwidth TLV (TLV 1089)
-+ * RFC 5305 Section 3.4
-+ */
-+static int parse_max_link_bw(struct stream *s, uint16_t length, struct bgp_ls_attr *attr)
-+{
-+ uint32_t bw_bits;
-+
-+ if (length != 4) {
-+ flog_warn(EC_BGP_UPDATE_RCV, "BGP-LS: Invalid Max Link BW length (%u bytes)",
-+ length);
-+ return -1;
-+ }
-+
-+ 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;
-+ }
-+
-+ /* Read as 32-bit IEEE floating point */
-+ bw_bits = stream_getl(s);
-+ memcpy(&attr->max_link_bw, &bw_bits, 4);
-+ SET_FLAG(attr->present_tlvs, BGP_LS_ATTR_MAX_LINK_BW_BIT);
-+
-+ return 0;
-+}
-+
-+/*
-+ * Parse Maximum Reservable Bandwidth TLV (TLV 1090)
-+ * RFC 5305 Section 3.5
-+ */
-+static int parse_max_resv_bw(struct stream *s, uint16_t length, struct bgp_ls_attr *attr)
-+{
-+ uint32_t bw_bits;
-+
-+ if (length != 4) {
-+ flog_warn(EC_BGP_UPDATE_RCV, "BGP-LS: Invalid Max Resv BW length (%u bytes)",
-+ length);
-+ return -1;
-+ }
-+
-+ 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);
-+ SET_FLAG(attr->present_tlvs, BGP_LS_ATTR_MAX_RESV_BW_BIT);
-+
-+ return 0;
-+}
-+
-+/*
-+ * Parse Unreserved Bandwidth TLV (TLV 1091)
-+ * RFC 5305 Section 3.6
-+ */
-+static int parse_unresv_bw(struct stream *s, uint16_t length, struct bgp_ls_attr *attr)
-+{
-+ uint32_t bw_bits;
-+ int i;
-+
-+ if (length != 32) { /* 8 priorities * 4 bytes each */
-+ flog_warn(EC_BGP_UPDATE_RCV, "BGP-LS: Invalid Unreserved BW length (%u bytes)",
-+ length);
-+ return -1;
-+ }
-+
-+ 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;
-+ }
-+
-+ for (i = 0; i < BGP_LS_MAX_UNRESV_BW; i++) {
-+ bw_bits = stream_getl(s);
-+ memcpy(&attr->unreserved_bw[i], &bw_bits, 4);
-+ }
-+ SET_FLAG(attr->present_tlvs, BGP_LS_ATTR_UNRESV_BW_BIT);
-+
-+ return 0;
-+}
-+
-+/*
-+ * Parse TE Default Metric TLV (TLV 1092)
-+ * RFC 9552 Section 5.3.2.3
-+ */
-+static int parse_te_metric(struct stream *s, uint16_t length, struct bgp_ls_attr *attr)
-+{
-+ if (length != 4) {
-+ flog_warn(EC_BGP_UPDATE_RCV, "BGP-LS: Invalid TE Metric length (%u bytes)", length);
-+ return -1;
-+ }
-+
-+ 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);
-+ SET_FLAG(attr->present_tlvs, BGP_LS_ATTR_TE_METRIC_BIT);
-+
-+ return 0;
-+}
-+
-+/*
-+ * Parse IGP Metric TLV (TLV 1095)
-+ * RFC 9552 Section 5.3.2.4
-+ */
-+static int parse_igp_metric(struct stream *s, uint16_t length, struct bgp_ls_attr *attr)
-+{
-+ if (length < 1 || length > BGP_LS_IGP_METRIC_MAX_LEN) {
-+ flog_warn(EC_BGP_UPDATE_RCV, "BGP-LS: Invalid IGP Metric length (%u bytes)",
-+ length);
-+ return -1;
-+ }
-+
-+ 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;
-+ }
-+
-+ attr->igp_metric = 0;
-+ for (int i = 0; i < length; i++)
-+ attr->igp_metric = (attr->igp_metric << 8) | stream_getc(s);
-+ attr->igp_metric_len = length;
-+ SET_FLAG(attr->present_tlvs, BGP_LS_ATTR_IGP_METRIC_BIT);
-+
-+ return 0;
-+}
-+
-+/*
-+ * Parse SRLG TLV (TLV 1096)
-+ * RFC 9552 Section 5.3.2.5
-+ */
-+static int parse_srlg(struct stream *s, uint16_t length, struct bgp_ls_attr *attr)
-+{
-+ uint16_t count;
-+ int i;
-+
-+ if (length % 4 != 0) {
-+ flog_warn(EC_BGP_UPDATE_RCV, "BGP-LS: Invalid SRLG length (%u bytes)", length);
-+ return -1;
-+ }
-+
-+ count = length / 4;
-+ if (count > BGP_LS_MAX_SRLG) {
-+ flog_warn(EC_BGP_UPDATE_RCV, "BGP-LS: Too many SRLGs (%u, max %u)", count,
-+ BGP_LS_MAX_SRLG);
-+ return -1;
-+ }
-+
-+ if (CHECK_FLAG(attr->present_tlvs, BGP_LS_ATTR_SRLG_BIT)) {
-+ flog_warn(EC_BGP_UPDATE_RCV, "BGP-LS: duplicate SRLG TLV");
-+ return -1;
-+ }
-+
-+ attr->srlg_values = XMALLOC(MTYPE_BGP_LS_ATTR, count * sizeof(uint32_t));
-+ for (i = 0; i < count; i++)
-+ attr->srlg_values[i] = stream_getl(s);
-+ attr->srlg_count = count;
-+ SET_FLAG(attr->present_tlvs, BGP_LS_ATTR_SRLG_BIT);
-+
-+ return 0;
-+}
-+
-+/*
-+ * Parse Link Name TLV (TLV 1098)
-+ * RFC 9552 Section 5.3.2.7
-+ */
-+static int parse_link_name(struct stream *s, uint16_t length, struct bgp_ls_attr *attr)
-+{
-+ if (length == 0)
-+ return 0;
-+
-+ 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;
-+ }
-+
-+ attr->link_name = XCALLOC(MTYPE_BGP_LS_ATTR, length + 1);
-+ stream_get(attr->link_name, s, length);
-+ attr->link_name[length] = '\0';
-+
-+ /* 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;
-+}
-+
-+/*
-+ * Parse Adjacency SID (TLV 1099)
-+ * RFC 9085 Section 2.2.1
-+ */
-+static int parse_adjacency_sid(struct stream *s, uint16_t length, struct bgp_ls_attr *attr)
-+{
-+ int flags;
-+ int sid_len;
-+
-+ if (length != 7 && length != 8) {
-+ flog_warn(EC_BGP_UPDATE_RCV, "BGP-LS: Invalid Adjacency SID length (%u bytes)",
-+ length);
-+ return -1;
-+ }
-+
-+ if (attr->adj_sid_count == BGP_LS_ADJ_MAX) {
-+ flog_warn(EC_BGP_UPDATE_RCV,
-+ "BGP-LS: Ignoring Adjacency SID, maximum %d is implemented",
-+ BGP_LS_ADJ_MAX);
-+ stream_forward_getp(s, length);
-+ return 0;
-+ }
-+
-+ /*
-+ * We don't know protocol id yet here, but V-Flag must be equal to L-Flag
-+ * in all protocols. V-Flag position for IS-IS is same as L-Flag position for OSPF.
-+ * So here we check length based on this bit and in
-+ * bgp_ls_attr_process_with_protocol_id we check that V-Flag equals L-Flag.
-+ */
-+ flags = stream_getc(s);
-+ sid_len = bgp_ls_attr_sid_len_by_lv_flags(flags, ISIS_EXT_SUBTLV_LINK_ADJ_SID_VFLG,
-+ ISIS_EXT_SUBTLV_LINK_ADJ_SID_VFLG);
-+
-+ if (sid_len != length - 4) {
-+ stream_forward_getp(s, length - 1);
-+ flog_warn(EC_BGP_LS_PACKET,
-+ "BGP-LS: %s L/V-Flag value contradicts length of Adjacent SID, ignoring",
-+ __func__);
-+ return 0;
-+ }
-+
-+ attr->adj_sid[attr->adj_sid_count].flags = flags;
-+ attr->adj_sid[attr->adj_sid_count].weight = stream_getc(s);
-+ stream_getw(s); /* Reserved, ignored */
-+ if (sid_len == 3)
-+ attr->adj_sid[attr->adj_sid_count].sid = stream_get3(s);
-+ else
-+ attr->adj_sid[attr->adj_sid_count].sid = stream_getl(s);
-+ attr->adj_sid_count++;
-+
-+ SET_FLAG(attr->present_tlvs, BGP_LS_ATTR_ADJ_SID_BIT);
-+
-+ return 0;
-+}
-+
-+/*
-+ * Parse Extended Admin Group TLV (TLV 1173)
-+ */
-+static int parse_ext_admin_group(struct stream *s, uint16_t length, struct bgp_ls_attr *attr)
-+{
-+ size_t nb_words;
-+
-+ /* Length must be multiple of 4 (each word is 32 bits) */
-+ if (length % 4 != 0) {
-+ flog_warn(EC_BGP_UPDATE_RCV,
-+ "BGP-LS: Invalid Extended Admin Group TLV length %u (must be multiple of 4)",
-+ length);
-+ return -1;
-+ }
-+
-+ 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;
-+ }
-+
-+ nb_words = length / 4;
-+ if (nb_words > BGP_LS_MAX_EXT_ADMIN_GROUPS) {
-+ flog_warn(EC_BGP_UPDATE_RCV,
-+ "BGP-LS: Extended Admin Group TLV too large (%zu words, max %zu)",
-+ nb_words, (size_t)BGP_LS_MAX_EXT_ADMIN_GROUPS);
-+ return -1;
-+ }
-+
-+ /* Decode each 32-bit word */
-+ for (size_t i = 0; i < nb_words; i++)
-+ admin_group_bulk_set(&attr->ext_admin_group, stream_getl(s), i);
-+
-+ SET_FLAG(attr->present_tlvs, BGP_LS_ATTR_EXT_ADMIN_GROUP_BIT);
-+
-+ return 0;
-+}
-+
-+/*
-+ * Parse Unidirectional Link Delay TLV (TLV 1114)
-+ */
-+static int parse_link_delay(struct stream *s, uint16_t length, struct bgp_ls_attr *attr)
-+{
-+ if (length != 4) {
-+ flog_warn(EC_BGP_UPDATE_RCV, "BGP-LS: Invalid Link Delay length (%u bytes)",
-+ length);
-+ return -1;
-+ }
-+
-+ 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);
-+ SET_FLAG(attr->present_tlvs, BGP_LS_ATTR_DELAY_BIT);
-+
-+ return 0;
-+}
-+
-+/*
-+ * Parse Min/Max Unidirectional Link Delay TLV (TLV 1115)
-+ */
-+static int parse_min_max_delay(struct stream *s, uint16_t length, struct bgp_ls_attr *attr)
-+{
-+ if (length != 8) {
-+ flog_warn(EC_BGP_UPDATE_RCV, "BGP-LS: Invalid Min/Max Delay length (%u bytes)",
-+ length);
-+ return -1;
-+ }
-+
-+ 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;
-+ }
-+
-+ attr->min_delay = stream_getl(s);
-+ attr->max_delay = stream_getl(s);
-+ SET_FLAG(attr->present_tlvs, BGP_LS_ATTR_MIN_MAX_DELAY_BIT);
-+
-+ return 0;
-+}
-+
-+/*
-+ * Parse Unidirectional Delay Variation TLV (TLV 1116)
-+ */
-+static int parse_link_jitter(struct stream *s, uint16_t length, struct bgp_ls_attr *attr)
-+{
-+ if (length != 4) {
-+ flog_warn(EC_BGP_UPDATE_RCV, "BGP-LS: Invalid Link Jitter length (%u bytes)",
-+ length);
-+ return -1;
-+ }
-+
-+ 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);
-+ SET_FLAG(attr->present_tlvs, BGP_LS_ATTR_JITTER_BIT);
-+
-+ return 0;
-+}
-+
-+/*
-+ * Parse Unidirectional Packet Loss TLV (TLV 1117)
-+ */
-+static int parse_packet_loss(struct stream *s, uint16_t length, struct bgp_ls_attr *attr)
-+{
-+ if (length != 4) {
-+ flog_warn(EC_BGP_UPDATE_RCV, "BGP-LS: Invalid Packet Loss length (%u bytes)",
-+ length);
-+ return -1;
-+ }
-+
-+ 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);
-+ SET_FLAG(attr->present_tlvs, BGP_LS_ATTR_PKT_LOSS_BIT);
-+
-+ return 0;
-+}
-+
-+/*
-+ * Parse Unidirectional Residual Bandwidth TLV (TLV 1118)
-+ */
-+static int parse_residual_bw(struct stream *s, uint16_t length, struct bgp_ls_attr *attr)
-+{
-+ if (length != 4) {
-+ flog_warn(EC_BGP_UPDATE_RCV, "BGP-LS: Invalid Residual BW length (%u bytes)",
-+ length);
-+ return -1;
-+ }
-+
-+ 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);
-+ SET_FLAG(attr->present_tlvs, BGP_LS_ATTR_RESIDUAL_BW_BIT);
-+
-+ return 0;
-+}
-+
-+/*
-+ * Parse Unidirectional Available Bandwidth TLV (TLV 1119)
-+ */
-+static int parse_available_bw(struct stream *s, uint16_t length, struct bgp_ls_attr *attr)
-+{
-+ if (length != 4) {
-+ flog_warn(EC_BGP_UPDATE_RCV, "BGP-LS: Invalid Available BW length (%u bytes)",
-+ length);
-+ return -1;
-+ }
-+
-+ 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);
-+ SET_FLAG(attr->present_tlvs, BGP_LS_ATTR_AVAILABLE_BW_BIT);
-+
-+ return 0;
-+}
-+
-+/*
-+ * Parse Unidirectional Utilized Bandwidth TLV (TLV 1120)
-+ */
-+static int parse_utilized_bw(struct stream *s, uint16_t length, struct bgp_ls_attr *attr)
-+{
-+ if (length != 4) {
-+ flog_warn(EC_BGP_UPDATE_RCV, "BGP-LS: Invalid Utilized BW length (%u bytes)",
-+ length);
-+ return -1;
-+ }
-+
-+ 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);
-+ SET_FLAG(attr->present_tlvs, BGP_LS_ATTR_UTILIZED_BW_BIT);
-+
-+ return 0;
-+}
-+
-+/*
-+ * Parse IGP Flags TLV (TLV 1152)
-+ * RFC 9552 Section 5.3.3.1
-+ */
-+static int parse_igp_flags(struct stream *s, uint16_t length, struct bgp_ls_attr *attr)
-+{
-+ if (length < 1) {
-+ flog_warn(EC_BGP_UPDATE_RCV, "BGP-LS: IGP Flags TLV too short (%u bytes)", length);
-+ return -1;
-+ }
-+
-+ 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);
-+ SET_FLAG(attr->present_tlvs, BGP_LS_ATTR_IGP_FLAGS_BIT);
-+
-+ if (length > 1)
-+ stream_forward_getp(s, length - 1);
-+
-+ return 0;
-+}
-+
-+/*
-+ * Parse Route Tag TLV (TLV 1153)
-+ * RFC 9552 Section 5.3.3.2
-+ */
-+static int parse_route_tag(struct stream *s, uint16_t length, struct bgp_ls_attr *attr)
-+{
-+ uint16_t count;
-+ int i;
-+
-+ if (length % 4 != 0) {
-+ flog_warn(EC_BGP_UPDATE_RCV, "BGP-LS: Invalid Route Tag length (%u bytes)", length);
-+ return -1;
-+ }
-+
-+ count = length / 4;
-+ if (count > BGP_LS_MAX_ROUTE_TAGS) {
-+ flog_warn(EC_BGP_UPDATE_RCV, "BGP-LS: Too many Route Tags (%u, max %u)", count,
-+ BGP_LS_MAX_ROUTE_TAGS);
-+ return -1;
-+ }
-+
-+ 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;
-+ }
-+
-+ attr->route_tags = XMALLOC(MTYPE_BGP_LS_ATTR, count * sizeof(uint32_t));
-+ for (i = 0; i < count; i++)
-+ attr->route_tags[i] = stream_getl(s);
-+ attr->route_tag_count = count;
-+ SET_FLAG(attr->present_tlvs, BGP_LS_ATTR_ROUTE_TAG_BIT);
-+
-+ return 0;
-+}
-+
-+/*
-+ * Parse Prefix Metric TLV (TLV 1155)
-+ * RFC 9552 Section 5.3.3.4
-+ */
-+static int parse_prefix_metric(struct stream *s, uint16_t length, struct bgp_ls_attr *attr)
-+{
-+ if (length != 4) {
-+ flog_warn(EC_BGP_UPDATE_RCV, "BGP-LS: Invalid Prefix Metric length (%u bytes)",
-+ length);
-+ return -1;
-+ }
-+
-+ 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);
-+ SET_FLAG(attr->present_tlvs, BGP_LS_ATTR_PREFIX_METRIC_BIT);
-+
-+ return 0;
-+}
-+
-+/*
-+ * Parse OSPF Forwarding Address TLV (TLV 1156)
-+ * RFC 9552 Section 5.3.3.5
-+ */
-+static int parse_ospf_fwd_addr(struct stream *s, uint16_t length, struct bgp_ls_attr *attr)
-+{
-+ if (length != 4 && length != 16) {
-+ flog_warn(EC_BGP_UPDATE_RCV, "BGP-LS: Invalid OSPF Fwd Addr length (%u bytes)",
-+ length);
-+ return -1;
-+ }
-+
-+ 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;
-+ }
-+ if (length == 4) {
-+ /* IPv4 */
-+ stream_get(&attr->ospf_fwd_addr, s, 4);
-+ } else {
-+ /* IPv6 */
-+ stream_get(&attr->ospf_fwd_addr6, s, 16);
-+ }
-+
-+ SET_FLAG(attr->present_tlvs, BGP_LS_ATTR_OSPF_FWD_ADDR_BIT);
-+ return 0;
-+}
-+
-+/* Parse IPv4 Router-ID of Local Node (TLV 1028) */
-+static int parse_ipv4_router_id_local(struct stream *s, uint16_t length, struct bgp_ls_attr *attr)
-+{
-+ if (length != 4) {
-+ flog_warn(EC_BGP_UPDATE_RCV,
-+ "BGP-LS: Invalid IPv4 Router-ID Local length (%u bytes)", length);
-+ return -1;
-+ }
-+
-+ 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
-+ * support only one; skip any additional ones.
-+ */
-+ if (BGP_DEBUG(linkstate, LINKSTATE))
-+ flog_warn(EC_BGP_UPDATE_RCV,
-+ "BGP-LS: multiple IPv4 Local Router-ID TLVs not supported, skipping duplicate");
-+ stream_forward_getp(s, length);
-+ return 0;
-+ }
-+
-+ stream_get(&attr->ipv4_router_id_local, s, 4);
-+ SET_FLAG(attr->present_tlvs, BGP_LS_ATTR_IPV4_ROUTER_ID_LOCAL_BIT);
-+ return 0;
-+}
-+
-+/* Parse IPv6 Router-ID of Local Node (TLV 1029) */
-+static int parse_ipv6_router_id_local(struct stream *s, uint16_t length, struct bgp_ls_attr *attr)
-+{
-+ if (length != 16) {
-+ flog_warn(EC_BGP_UPDATE_RCV,
-+ "BGP-LS: Invalid IPv6 Router-ID Local length (%u bytes)", length);
-+ return -1;
-+ }
-+
-+ 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
-+ * support only one; skip any additional ones.
-+ */
-+ if (BGP_DEBUG(linkstate, LINKSTATE))
-+ flog_warn(EC_BGP_UPDATE_RCV,
-+ "BGP-LS: multiple IPv6 Local Router-ID TLVs not supported, skipping duplicate");
-+ stream_forward_getp(s, length);
-+ return 0;
-+ }
-+
-+ stream_get(&attr->ipv6_router_id_local, s, 16);
-+ SET_FLAG(attr->present_tlvs, BGP_LS_ATTR_IPV6_ROUTER_ID_LOCAL_BIT);
-+ return 0;
-+}
-+
-+/* Parse IPv4 Router-ID of Remote Node (TLV 1030) */
-+static int parse_ipv4_router_id_remote(struct stream *s, uint16_t length, struct bgp_ls_attr *attr)
-+{
-+ if (length != 4) {
-+ flog_warn(EC_BGP_UPDATE_RCV,
-+ "BGP-LS: Invalid IPv4 Router-ID Remote length (%u bytes)", length);
-+ return -1;
-+ }
-+
-+ 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
-+ * support only one; skip any additional ones.
-+ */
-+ if (BGP_DEBUG(linkstate, LINKSTATE))
-+ flog_warn(EC_BGP_UPDATE_RCV,
-+ "BGP-LS: multiple IPv4 Remote Router-ID TLVs not supported, skipping duplicate");
-+ stream_forward_getp(s, length);
-+ return 0;
-+ }
-+
-+ stream_get(&attr->ipv4_router_id_remote, s, 4);
-+ SET_FLAG(attr->present_tlvs, BGP_LS_ATTR_IPV4_ROUTER_ID_REMOTE_BIT);
-+ return 0;
-+}
-+
-+/* Parse IPv6 Router-ID of Remote Node (TLV 1031) */
-+static int parse_ipv6_router_id_remote(struct stream *s, uint16_t length, struct bgp_ls_attr *attr)
-+{
-+ if (length != 16) {
-+ flog_warn(EC_BGP_UPDATE_RCV,
-+ "BGP-LS: Invalid IPv6 Router-ID Remote length (%u bytes)", length);
-+ return -1;
-+ }
-+
-+ 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
-+ * support only one; skip any additional ones.
-+ */
-+ if (BGP_DEBUG(linkstate, LINKSTATE))
-+ flog_warn(EC_BGP_UPDATE_RCV,
-+ "BGP-LS: multiple IPv6 Remote Router-ID TLVs not supported, skipping duplicate");
-+ stream_forward_getp(s, length);
-+ return 0;
-+ }
-+
-+ stream_get(&attr->ipv6_router_id_remote, s, 16);
-+ SET_FLAG(attr->present_tlvs, BGP_LS_ATTR_IPV6_ROUTER_ID_REMOTE_BIT);
-+ return 0;
-+}
-+
-+/* Parse Link Protection Type (TLV 1093) */
-+static int parse_link_protection(struct stream *s, uint16_t length, struct bgp_ls_attr *attr)
-+{
-+ if (length != 2) {
-+ flog_warn(EC_BGP_UPDATE_RCV,
-+ "BGP-LS: Invalid Link Protection Type length (%u bytes)", length);
-+ return -1;
-+ }
-+
-+ 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);
-+ SET_FLAG(attr->present_tlvs, BGP_LS_ATTR_LINK_PROTECTION_BIT);
-+ return 0;
-+}
-+
-+/* Parse MPLS Protocol Mask (TLV 1094) */
-+static int parse_mpls_protocol_mask(struct stream *s, uint16_t length, struct bgp_ls_attr *attr)
-+{
-+ if (length != 1) {
-+ flog_warn(EC_BGP_UPDATE_RCV,
-+ "BGP-LS: Invalid MPLS Protocol Mask length (%u bytes)", length);
-+ return -1;
-+ }
-+
-+ 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);
-+ SET_FLAG(attr->present_tlvs, BGP_LS_ATTR_MPLS_PROTOCOL_BIT);
-+ return 0;
-+}
-+
-+/* Parse Extended Tags (TLV 1154) */
-+static int parse_extended_tag(struct stream *s, uint16_t length, struct bgp_ls_attr *attr)
-+{
-+ uint16_t count;
-+
-+ if (length % 8 != 0) {
-+ flog_warn(EC_BGP_UPDATE_RCV,
-+ "BGP-LS: Invalid Extended Tag length (%u bytes, must be multiple of 8)",
-+ length);
-+ return -1;
-+ }
-+
-+ count = length / 8;
-+ if (count > BGP_LS_MAX_ROUTE_TAGS) {
-+ flog_warn(EC_BGP_UPDATE_RCV, "BGP-LS: Too many Extended Tags (%u, max %u)", count,
-+ BGP_LS_MAX_ROUTE_TAGS);
-+ return -1;
-+ }
-+
-+ 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;
-+ }
-+
-+ attr->extended_tags = XCALLOC(MTYPE_BGP_LS_ATTR, count * sizeof(uint64_t));
-+ attr->extended_tag_count = count;
-+
-+ for (uint16_t i = 0; i < count; i++)
-+ attr->extended_tags[i] = stream_getq(s);
-+
-+ SET_FLAG(attr->present_tlvs, BGP_LS_ATTR_EXTENDED_TAG_BIT);
-+ return 0;
-+}
-+
-+/*
-+ * Parse Prefix SID (TLV 1158)
-+ * RFC 9085 Section 2.3.1
-+ */
-+static int parse_prefix_sid(struct stream *s, uint16_t length, struct bgp_ls_attr *attr)
-+{
-+ int flags;
-+ int sid_len;
-+
-+ if (length != 7 && length != 8) {
-+ flog_warn(EC_BGP_UPDATE_RCV,
-+ "BGP-LS: Invalid Prefix SID length (%u bytes, expected 7 or 8)", length);
-+ return -1;
-+ }
-+
-+ /*
-+ * IS-IS in RFC8667 2.1 references RFC8402, in section 3.1:
-+ * Multiple SIDs MAY be allocated to the same prefix so long
-+ * as the tuple <prefix, topology, algorithm> is unique.
-+ *
-+ * RFC8665 (OSPFv2), 5; RFC8666 (OSPFv3), 6:
-+ * It MAY appear more than once in the parent TLV
-+ */
-+ 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);
-+ return 0;
-+ }
-+
-+ flags = stream_getc(s);
-+ sid_len = bgp_ls_attr_prefix_sid_len(flags);
-+
-+ if (sid_len == -1) {
-+ stream_forward_getp(s, length - 1);
-+ flog_warn(EC_BGP_LS_PACKET,
-+ "BGP-LS: %s wrong combination of V-Flag and L-Flag for Prefix SID, ignoring",
-+ __func__);
-+ return 0;
-+ }
-+
-+ if (sid_len + 4 != length) {
-+ stream_forward_getp(s, length - 1);
-+ flog_warn(EC_BGP_LS_PACKET,
-+ "BGP-LS: %s V-Flag value contradicts length of Prefix SID, ignoring",
-+ __func__);
-+ return 0;
-+ }
-+
-+ attr->prefix_sid.sid_flag = flags;
-+ attr->prefix_sid.algo = stream_getc(s);
-+ stream_getw(s); /* Reserved, ignore two octets */
-+ 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);
-+ SET_FLAG(attr->present_tlvs, BGP_LS_ATTR_PREFIX_SID_BIT);
-+
-+ return 0;
-+}
-+
-+/*
-+ * Parse BGP-LS Attribute TLVs
-+ * RFC 9552 Section 5.3.1
-+ */
-+int bgp_ls_parse_attr(struct stream *s, uint16_t total_length, struct bgp_ls_attr *attr)
-+{
-+ uint16_t type, length;
-+ size_t end_pos = stream_get_getp(s) + total_length;
-+
-+ if (!attr)
-+ return -1;
-+
-+ while (stream_get_getp(s) < end_pos) {
-+ if (stream_get_tlv_hdr(s, &type, &length) < 0) {
-+ flog_warn(EC_BGP_UPDATE_RCV,
-+ "BGP-LS: Error parsing Node Attribute TLV header");
-+ return -1;
-+ }
-+
-+ switch (type) {
-+ case BGP_LS_ATTR_NODE_FLAG_BITS:
-+ if (parse_node_flags(s, length, attr) < 0)
-+ return -1;
-+ break;
-+
-+ case BGP_LS_ATTR_NODE_NAME:
-+ if (parse_node_name(s, length, attr) < 0)
-+ return -1;
-+ break;
-+
-+ case BGP_LS_ATTR_ISIS_AREA_ID:
-+ if (parse_isis_area_id(s, length, attr) < 0)
-+ return -1;
-+ break;
-+
-+ case BGP_LS_ATTR_SR_CAPABILITIES:
-+ if (parse_sr_capabilities(s, length, attr) < 0)
-+ return -1;
-+ break;
-+
-+ case BGP_LS_ATTR_NODE_MSD:
-+ if (parse_node_msd(s, length, attr) < 0)
-+ return -1;
-+ break;
-+
-+ case BGP_LS_ATTR_IPV4_ROUTER_ID_LOCAL:
-+ if (parse_ipv4_router_id_local(s, length, attr) < 0)
-+ return -1;
-+ break;
-+
-+ case BGP_LS_ATTR_IPV6_ROUTER_ID_LOCAL:
-+ if (parse_ipv6_router_id_local(s, length, attr) < 0)
-+ return -1;
-+ break;
-+
-+ case BGP_LS_ATTR_IPV4_ROUTER_ID_REMOTE:
-+ if (parse_ipv4_router_id_remote(s, length, attr) < 0)
-+ return -1;
-+ break;
-+
-+ case BGP_LS_ATTR_IPV6_ROUTER_ID_REMOTE:
-+ if (parse_ipv6_router_id_remote(s, length, attr) < 0)
-+ return -1;
-+ break;
-+
-+ case BGP_LS_ATTR_ADMIN_GROUP:
-+ if (parse_admin_group(s, length, attr) < 0)
-+ return -1;
-+ break;
-+
-+ case BGP_LS_ATTR_MAX_LINK_BW:
-+ if (parse_max_link_bw(s, length, attr) < 0)
-+ return -1;
-+ break;
-+
-+ case BGP_LS_ATTR_MAX_RESV_BW:
-+ if (parse_max_resv_bw(s, length, attr) < 0)
-+ return -1;
-+ break;
-+
-+ case BGP_LS_ATTR_UNRESV_BW:
-+ if (parse_unresv_bw(s, length, attr) < 0)
-+ return -1;
-+ break;
-+
-+ case BGP_LS_ATTR_TE_DEFAULT_METRIC:
-+ if (parse_te_metric(s, length, attr) < 0)
-+ return -1;
-+ break;
-+
-+ case BGP_LS_ATTR_LINK_PROTECTION_TYPE:
-+ if (parse_link_protection(s, length, attr) < 0)
-+ return -1;
-+ break;
-+
-+ case BGP_LS_ATTR_MPLS_PROTOCOL_MASK:
-+ if (parse_mpls_protocol_mask(s, length, attr) < 0)
-+ return -1;
-+ break;
-+
-+ case BGP_LS_ATTR_IGP_METRIC:
-+ if (parse_igp_metric(s, length, attr) < 0)
-+ return -1;
-+ break;
-+
-+ case BGP_LS_ATTR_SRLG:
-+ if (parse_srlg(s, length, attr) < 0)
-+ return -1;
-+ break;
-+
-+ case BGP_LS_ATTR_LINK_NAME:
-+ if (parse_link_name(s, length, attr) < 0)
-+ return -1;
-+ break;
-+
-+ case BGP_LS_ATTR_ADJ_SID:
-+ if (parse_adjacency_sid(s, length, attr) < 0)
-+ return -1;
-+ break;
-+
-+ case BGP_LS_ATTR_UNIDIRECTIONAL_LINK_DELAY:
-+ if (parse_link_delay(s, length, attr) < 0)
-+ return -1;
-+ break;
-+
-+ case BGP_LS_ATTR_MIN_MAX_UNIDIRECTIONAL_LINK_DELAY:
-+ if (parse_min_max_delay(s, length, attr) < 0)
-+ return -1;
-+ break;
-+
-+ case BGP_LS_ATTR_UNIDIRECTIONAL_DELAY_VARIATION:
-+ if (parse_link_jitter(s, length, attr) < 0)
-+ return -1;
-+ break;
-+
-+ case BGP_LS_ATTR_UNIDIRECTIONAL_LINK_LOSS:
-+ if (parse_packet_loss(s, length, attr) < 0)
-+ return -1;
-+ break;
-+
-+ case BGP_LS_ATTR_UNIDIRECTIONAL_RESIDUAL_BANDWIDTH:
-+ if (parse_residual_bw(s, length, attr) < 0)
-+ return -1;
-+ break;
-+
-+ case BGP_LS_ATTR_UNIDIRECTIONAL_AVAILABLE_BANDWIDTH:
-+ if (parse_available_bw(s, length, attr) < 0)
-+ return -1;
-+ break;
-+
-+ case BGP_LS_ATTR_UNIDIRECTIONAL_UTILIZED_BANDWIDTH:
-+ if (parse_utilized_bw(s, length, attr) < 0)
-+ return -1;
-+ break;
-+
-+ case BGP_LS_ATTR_EXTENDED_ADMIN_GROUP:
-+ if (parse_ext_admin_group(s, length, attr) < 0)
-+ return -1;
-+ break;
-+
-+ case BGP_LS_ATTR_IGP_FLAGS:
-+ if (parse_igp_flags(s, length, attr) < 0)
-+ return -1;
-+ break;
-+
-+ case BGP_LS_ATTR_ROUTE_TAG:
-+ if (parse_route_tag(s, length, attr) < 0)
-+ return -1;
-+ break;
-+
-+ case BGP_LS_ATTR_EXTENDED_TAG:
-+ if (parse_extended_tag(s, length, attr) < 0)
-+ return -1;
-+ break;
-+
-+ case BGP_LS_ATTR_PREFIX_METRIC:
-+ if (parse_prefix_metric(s, length, attr) < 0)
-+ return -1;
-+ break;
-+
-+ case BGP_LS_ATTR_OSPF_FWD_ADDR:
-+ if (parse_ospf_fwd_addr(s, length, attr) < 0)
-+ return -1;
-+ break;
-+
-+ case BGP_LS_ATTR_PREFIX_SID:
-+ if (parse_prefix_sid(s, length, attr) < 0)
-+ return -1;
-+ break;
-+
-+ default:
-+ if (BGP_DEBUG(update, UPDATE_IN))
-+ zlog_debug("BGP-LS: Skipping unrecognized BGP-LS Attribute TLV %u",
-+ type);
-+ stream_skip_tlv(s, length);
-+ break;
-+ }
-+ }
-+
-+ return 0;
-+}
-+
-+/*
-+ * Process BGP-LS Attributes when protocol id is known
-+ */
-+int bgp_ls_attr_process_with_protocol_id(struct bgp_ls_attr *attr,
-+ enum bgp_ls_protocol_id protocol_id)
-+{
-+ 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);
-+
-+ if (sid_len == -1) {
-+ flog_warn(EC_BGP_LS_PACKET,
-+ "BGP-LS: %s wrong combination of V-Flag and L-Flag for Adjacency SID, ignoring",
-+ __func__);
-+
-+ 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--;
-+ }
-+ }
-+ }
-+ return 0;
-+}
-+
-+/*
-+ * Convert BGP-LS Attributes to JSON object
-+ * Used for "show bgp" commands to display link-state topology information in json
-+ */
-+struct json_object *bgp_ls_attr_to_json(struct bgp_ls_attr *ls_attr)
-+{
-+ json_object *json_ls_attr = json_object_new_object();
-+
-+ /* Node Name */
-+ 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 (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;
-+ memcpy(addr.area_addr, ls_attr->isis_area_id, ls_attr->isis_area_id_len);
-+ json_object_string_addf(json_ls_attr, "isisAreaId", "%pIS", &addr);
-+ }
-+
-+ /* Local TE Router-ID (IPv4) */
-+ 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 (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 (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 (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 (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);
-+ for (int i = 0; i < MAX_CLASS_TYPE; i++) {
-+ char buf[INET6_BUFSIZ];
-+ json_object *jobj = json_object_new_object();
-+
-+ snprintfrr(buf, sizeof(buf), "classType%d", i);
-+ json_object_double_add(jobj, buf, ls_attr->unreserved_bw[i]);
-+ json_object_array_add(jbw, jobj);
-+ }
-+ }
-+
-+ 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 (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 (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);
-+ json_object_string_addf(jsrcap, "flags", "0x%x", ls_attr->srgb.flag);
-+ json_object_int_add(jsrcap, "lowerBound", ls_attr->srgb.lower_bound);
-+ json_object_int_add(jsrcap, "rangeSize", ls_attr->srgb.range_size);
-+ }
-+
-+ /* Node 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 (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 (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 (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 (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);
-+ for (int i = 0; i < ls_attr->srlg_count; i++) {
-+ json_object *jobj = json_object_new_object();
-+
-+ json_object_int_add(jobj, "srlg", ls_attr->srlg_values[i]);
-+ json_object_array_add(jsrlg, jobj);
-+ }
-+ }
-+
-+ /* Link Name */
-+ 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 (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);
-+ for (int i = 0; i < ls_attr->adj_sid_count; i++) {
-+ json_object *jobj = json_object_new_object();
-+
-+ json_object_int_add(jobj, "sid", ls_attr->adj_sid[i].sid);
-+ json_object_string_addf(jobj, "flags", "0x%x", ls_attr->adj_sid[i].flags);
-+ json_object_int_add(jobj, "weight", ls_attr->adj_sid[i].weight);
-+
-+ json_object_array_add(jadj, jobj);
-+ }
-+ }
-+
-+ /* Performance Metrics - Link Delay */
-+ 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 (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 (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 (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 (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 (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 (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 (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 (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);
-+ for (int i = 0; i < ls_attr->route_tag_count; i++) {
-+ json_object *jobj = json_object_new_object();
-+
-+ json_object_int_add(jobj, "tag", ls_attr->route_tags[i]);
-+ json_object_array_add(jtags, jobj);
-+ }
-+ }
-+
-+ /* Extended Tags */
-+ 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);
-+ for (int i = 0; i < ls_attr->extended_tag_count; i++) {
-+ json_object *jobj = json_object_new_object();
-+
-+ json_object_int_add(jobj, "tag", ls_attr->extended_tags[i]);
-+ json_object_array_add(jtags, jobj);
-+ }
-+ }
-+
-+ /* Prefix Metric */
-+ 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 (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);
-+ }
-+
-+ /* Prefix SID */
-+ 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);
-+ json_object_int_add(jpref, "sid", ls_attr->prefix_sid.sid);
-+ json_object_string_addf(jpref, "flags", "0x%x", ls_attr->prefix_sid.sid_flag);
-+ json_object_int_add(jpref, "algo", ls_attr->prefix_sid.algo);
-+ }
-+
-+ return json_ls_attr;
-+}
-+
-+/*
-+ * Display BGP-LS Attributes to VTY output
-+ * Used for "show bgp" commands to display link-state topology information
-+ */
-+void bgp_ls_attr_display(struct vty *vty, struct bgp_ls_attr *ls_attr)
-+{
-+ int col = 0;
-+ bool first_attr = true;
-+
-+#define COL_WIDTH 50
-+#define INIT_INDENT " Link-state: "
-+#define CONT_INDENT " "
-+#define CHECK_WRAP() \
-+ do { \
-+ if (col > COL_WIDTH) { \
-+ vty_out(vty, "\n" CONT_INDENT); \
-+ col = strlen(CONT_INDENT); \
-+ } else if (!first_attr) { \
-+ vty_out(vty, ", "); \
-+ col += 2; \
-+ } \
-+ first_attr = false; \
-+ } while (0)
-+
-+ vty_out(vty, INIT_INDENT);
-+ col = strlen(INIT_INDENT);
-+
-+ /* Node Name */
-+ 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 (CHECK_FLAG(ls_attr->present_tlvs, BGP_LS_ATTR_ISIS_AREA_BIT)) {
-+ struct iso_address addr;
-+
-+ CHECK_WRAP();
-+ addr.addr_len = ls_attr->isis_area_id_len;
-+ memcpy(addr.area_addr, ls_attr->isis_area_id, ls_attr->isis_area_id_len);
-+ col += vty_out(vty, "IS-IS Area ID: %pIS", &addr);
-+ }
-+
-+ /* Local TE Router-ID (IPv4) */
-+ 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 (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 (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 (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 (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 (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 (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 (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 (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 (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 (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 - 1);
-+ }
-+
-+ /* Node MSD */
-+ if (CHECK_FLAG(ls_attr->present_tlvs, BGP_LS_ATTR_NODE_MSD_BIT)) {
-+ CHECK_WRAP();
-+ col += vty_out(vty, "MSD: Type %d Value %d", BGP_LS_IGP_MSD_TYPE_BASE_MPLS,
-+ ls_attr->msd);
-+ }
-+
-+ /* SRLG */
-+ if (CHECK_FLAG(ls_attr->present_tlvs, BGP_LS_ATTR_SRLG_BIT)) {
-+ CHECK_WRAP();
-+ col += vty_out(vty, "SRLG:");
-+ col += 5;
-+ for (int j = 0; j < ls_attr->srlg_count; j++)
-+ col += vty_out(vty, " %u", ls_attr->srlg_values[j]);
-+ }
-+
-+ /* Link Name */
-+ 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 (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",
-+ ls_attr->adj_sid[i].sid, ls_attr->adj_sid[i].flags,
-+ ls_attr->adj_sid[i].weight);
-+ }
-+ }
-+
-+ /* Performance Metrics - Link Delay */
-+ 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 (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 (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 (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 (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 (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 (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 (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 (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++)
-+ col += vty_out(vty, " %u", ls_attr->route_tags[j]);
-+ }
-+
-+ /* Extended Tags */
-+ 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++)
-+ col += vty_out(vty, " %" PRIu64, ls_attr->extended_tags[j]);
-+ }
-+
-+ /* Prefix Metric */
-+ 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 (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);
-+ } else if (!IN6_IS_ADDR_UNSPECIFIED(&ls_attr->ospf_fwd_addr6)) {
-+ CHECK_WRAP();
-+ col += vty_out(vty, "Forwarding addr: %pI6", &ls_attr->ospf_fwd_addr6);
-+ }
-+ }
-+
-+ /* Prefix SID */
-+ 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);
-+ }
-+
-+ (void)col; /* Don't complain about last 'col +=' */
-+#undef CHECK_WRAP
-+#undef COL_WIDTH
-+#undef INIT_INDENT
-+#undef CONT_INDENT
-+
-+ vty_out(vty, "\n");
-+}
-+
-+/*
-+ * Display BGP-LS NLRI to VTY output
-+ * Used for "show bgp" commands to display link-state NLRI details
-+ */
-+void bgp_ls_nlri_display(struct vty *vty, struct bgp_ls_nlri *nlri)
-+{
-+ 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:
-+ nlri_type_str = "Node";
-+ break;
-+ case BGP_LS_NLRI_TYPE_LINK:
-+ nlri_type_str = "Link";
-+ break;
-+ case BGP_LS_NLRI_TYPE_IPV4_PREFIX:
-+ nlri_type_str = "Prefix";
-+ break;
-+ case BGP_LS_NLRI_TYPE_IPV6_PREFIX:
-+ nlri_type_str = "Prefix";
-+ break;
-+ case BGP_LS_NLRI_TYPE_RESERVED:
-+ nlri_type_str = "Unknown";
-+ break;
-+ }
-+
-+ /* Determine protocol string */
-+ switch (protocol_id) {
-+ case BGP_LS_PROTO_ISIS_L1:
-+ protocol_str = "ISIS L1";
-+ break;
-+ case BGP_LS_PROTO_ISIS_L2:
-+ protocol_str = "ISIS L2";
-+ break;
-+ case BGP_LS_PROTO_OSPFV2:
-+ protocol_str = "OSPFv2";
-+ break;
-+ case BGP_LS_PROTO_OSPFV3:
-+ protocol_str = "OSPFv3";
-+ break;
-+ case BGP_LS_PROTO_BGP:
-+ protocol_str = "BGP";
-+ break;
-+ case BGP_LS_PROTO_DIRECT:
-+ protocol_str = "Direct";
-+ break;
-+ case BGP_LS_PROTO_STATIC:
-+ protocol_str = "Static";
-+ break;
-+ case BGP_LS_PROTO_RESERVED:
-+ protocol_str = "Unknown";
-+ break;
-+ }
-+
-+ 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", identifier);
-+
-+ /* Display Local Node Descriptor */
-+ vty_out(vty, "Local Node Descriptor:\n");
-+ struct bgp_ls_node_descriptor *local_node = NULL;
-+
-+ if (nlri->nlri_type == BGP_LS_NLRI_TYPE_NODE) {
-+ local_node = &nlri->nlri_data.node.local_node;
-+ } else if (nlri->nlri_type == BGP_LS_NLRI_TYPE_LINK) {
-+ local_node = &nlri->nlri_data.link.local_node;
-+ } else if (nlri->nlri_type == BGP_LS_NLRI_TYPE_IPV4_PREFIX ||
-+ nlri->nlri_type == BGP_LS_NLRI_TYPE_IPV6_PREFIX) {
-+ local_node = &nlri->nlri_data.prefix.local_node;
-+ }
-+
-+ if (local_node) {
-+ if (CHECK_FLAG(local_node->present_tlvs, BGP_LS_NODE_DESC_AS_BIT))
-+ vty_out(vty, "\tAS Number: %u\n", local_node->asn);
-+ 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 (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 (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);
-+ } else if (local_node->igp_router_id_len == 6 ||
-+ local_node->igp_router_id_len == 7) {
-+ vty_out(vty, "\tISO Node ID: %02x%02x.%02x%02x.%02x%02x.%02x\n",
-+ local_node->igp_router_id[0], local_node->igp_router_id[1],
-+ 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)
-+ 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 */
-+ if (nlri->nlri_type == BGP_LS_NLRI_TYPE_LINK) {
-+ struct bgp_ls_node_descriptor *remote_node = &nlri->nlri_data.link.remote_node;
-+
-+ vty_out(vty, "Remote Node Descriptor:\n");
-+
-+ if (CHECK_FLAG(remote_node->present_tlvs, BGP_LS_NODE_DESC_AS_BIT))
-+ vty_out(vty, "\tAS Number: %u\n", remote_node->asn);
-+ 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 (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 (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);
-+ } else if (remote_node->igp_router_id_len == 6 ||
-+ remote_node->igp_router_id_len == 7) {
-+ vty_out(vty, "\tISO Node ID: %02x%02x.%02x%02x.%02x%02x.%02x\n",
-+ remote_node->igp_router_id[0],
-+ remote_node->igp_router_id[1],
-+ remote_node->igp_router_id[2],
-+ remote_node->igp_router_id[3],
-+ 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)
-+ 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 (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 (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 (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]);
-+ }
-+ }
-+
-+ /* Display Prefix Descriptor for Prefix NLRI */
-+ if (nlri->nlri_type == BGP_LS_NLRI_TYPE_IPV4_PREFIX ||
-+ nlri->nlri_type == BGP_LS_NLRI_TYPE_IPV6_PREFIX) {
-+ struct bgp_ls_prefix_descriptor *prefix_desc = &nlri->nlri_data.prefix.prefix_desc;
-+
-+ vty_out(vty, "Prefix Descriptor:\n");
-+
-+ char prefix_str[BUFSIZ];
-+
-+ prefix2str(&prefix_desc->prefix, prefix_str, sizeof(prefix_str));
-+ vty_out(vty, "\tPrefix: %s\n", prefix_str);
-+
-+ /* OSPF Route Type */
-+ 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) {
-+ case BGP_LS_OSPF_RT_INTRA_AREA:
-+ ospf_rt_str = "Intra-Area";
-+ break;
-+ case BGP_LS_OSPF_RT_INTER_AREA:
-+ ospf_rt_str = "Inter-Area";
-+ break;
-+ case BGP_LS_OSPF_RT_EXTERNAL_1:
-+ ospf_rt_str = "External Type 1";
-+ break;
-+ case BGP_LS_OSPF_RT_EXTERNAL_2:
-+ ospf_rt_str = "External Type 2";
-+ break;
-+ case BGP_LS_OSPF_RT_NSSA_1:
-+ ospf_rt_str = "NSSA Type 1";
-+ break;
-+ case BGP_LS_OSPF_RT_NSSA_2:
-+ ospf_rt_str = "NSSA Type 2";
-+ break;
-+ default:
-+ ospf_rt_str = "Unknown";
-+ break;
-+ }
-+ 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 (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]);
-+ }
-+ }
-+}
-diff --git a/bgpd/bgp_ls_nlri.h b/bgpd/bgp_ls_nlri.h
-new file mode 100644
-index 0000000000..01c34e7ea4
---- /dev/null
-+++ b/bgpd/bgp_ls_nlri.h
-@@ -0,0 +1,910 @@
-+// SPDX-License-Identifier: GPL-2.0-or-later
-+/*
-+ * BGP Link-State NLRI (RFC 9552)
-+ * Copyright (C) 2025 Carmine Scarpitta
-+ */
-+
-+#ifndef _FRR_BGP_LS_NLRI_H
-+#define _FRR_BGP_LS_NLRI_H
-+
-+#include "prefix.h"
-+#include "bgpd/bgpd.h"
-+
-+/* Memory types */
-+DECLARE_MTYPE(BGP_LS_ATTR);
-+
-+/*
-+ * ===========================================================================
-+ * Protocol and NLRI Type Definitions
-+ * ===========================================================================
-+ */
-+
-+/*
-+ * BGP-LS Protocol-ID values
-+ * IANA: https://www.iana.org/assignments/bgp-ls-parameters/bgp-ls-parameters.xhtml#protocol-ids
-+ */
-+enum bgp_ls_protocol_id {
-+ BGP_LS_PROTO_RESERVED = 0, /* Reserved - RFC 9552 */
-+ BGP_LS_PROTO_ISIS_L1 = 1, /* IS-IS Level 1 - RFC 9552 */
-+ BGP_LS_PROTO_ISIS_L2 = 2, /* IS-IS Level 2 - RFC 9552 */
-+ BGP_LS_PROTO_OSPFV2 = 3, /* OSPFv2 - RFC 9552 */
-+ BGP_LS_PROTO_DIRECT = 4, /* Direct - RFC 9552 */
-+ BGP_LS_PROTO_STATIC = 5, /* Static configuration - RFC 9552 */
-+ BGP_LS_PROTO_OSPFV3 = 6, /* OSPFv3 - RFC 9552 */
-+ BGP_LS_PROTO_BGP = 7, /* BGP - RFC 9086 */
-+};
-+
-+/*
-+ * BGP-LS NLRI Types
-+ * IANA: https://www.iana.org/assignments/bgp-ls-parameters/bgp-ls-parameters.xhtml#nlri-types
-+ */
-+enum bgp_ls_nlri_type {
-+ BGP_LS_NLRI_TYPE_RESERVED = 0, /* Reserved - RFC 9552 */
-+ BGP_LS_NLRI_TYPE_NODE = 1, /* Node NLRI - RFC 9552 */
-+ BGP_LS_NLRI_TYPE_LINK = 2, /* Link NLRI - RFC 9552 */
-+ BGP_LS_NLRI_TYPE_IPV4_PREFIX = 3, /* IPv4 Topology Prefix NLRI - RFC 9552 */
-+ BGP_LS_NLRI_TYPE_IPV6_PREFIX = 4, /* IPv6 Topology Prefix NLRI - RFC 9552 */
-+};
-+
-+/*
-+ * ===========================================================================
-+ * TLV Type Definitions
-+ * ===========================================================================
-+ */
-+
-+/*
-+ * Node Descriptor TLV Types
-+ * IANA: https://www.iana.org/assignments/bgp-ls-parameters/bgp-ls-parameters.xhtml#node-descriptor-link-descriptor-prefix-descriptor-attribute-tlv
-+ */
-+enum bgp_ls_node_descriptor_tlv {
-+ BGP_LS_TLV_LOCAL_NODE_DESC = 256, /* Local Node Descriptors - RFC 9552, Section 5.2.1.2 */
-+ BGP_LS_TLV_REMOTE_NODE_DESC = 257, /* Remote Node Descriptors - RFC 9552, Section 5.2.1.3 */
-+ BGP_LS_TLV_AS_NUMBER = 512, /* Autonomous System - RFC 9552, Section 5.2.1.4 */
-+ 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 */
-+};
-+
-+/*
-+ * Link Descriptor TLV Types
-+ * IANA: https://www.iana.org/assignments/bgp-ls-parameters/bgp-ls-parameters.xhtml#node-descriptor-link-descriptor-prefix-descriptor-attribute-tlv
-+ */
-+enum bgp_ls_link_descriptor_tlv {
-+ BGP_LS_TLV_LINK_ID = 258, /* Link Local/Remote Identifiers - RFC 9552, Section 5.2.2 */
-+ BGP_LS_TLV_IPV4_INTF_ADDR = 259, /* IPv4 interface address - RFC 9552, Section 5.2.2 */
-+ BGP_LS_TLV_IPV4_NEIGH_ADDR = 260, /* IPv4 neighbor address - RFC 9552, Section 5.2.2 */
-+ BGP_LS_TLV_IPV6_INTF_ADDR = 261, /* IPv6 interface address - RFC 9552, Section 5.2.2 */
-+ BGP_LS_TLV_IPV6_NEIGH_ADDR = 262, /* IPv6 neighbor address - RFC 9552, Section 5.2.2 */
-+ BGP_LS_TLV_MT_ID = 263, /* Multi-Topology Identifier - RFC 9552, Section 5.2.2.1 */
-+ BGP_LS_TLV_REMOTE_AS_NUMBER = 270, /* Remote AS Number - draft-ietf-idr-bgpls-inter-as-topology-ext */
-+};
-+
-+/*
-+ * Prefix Descriptor TLV Types
-+ * IANA: https://www.iana.org/assignments/bgp-ls-parameters/bgp-ls-parameters.xhtml#node-descriptor-link-descriptor-prefix-descriptor-attribute-tlv
-+ */
-+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 */
-+};
-+
-+/*
-+ * OSPF Route Type Values (for TLV 264)
-+ * RFC 9552, Section 5.2.3.1
-+ */
-+enum bgp_ls_ospf_route_type {
-+ BGP_LS_OSPF_RT_INTRA_AREA = 1, /* Intra-Area */
-+ BGP_LS_OSPF_RT_INTER_AREA = 2, /* Inter-Area */
-+ BGP_LS_OSPF_RT_EXTERNAL_1 = 3, /* External Type 1 */
-+ BGP_LS_OSPF_RT_EXTERNAL_2 = 4, /* External Type 2 */
-+ BGP_LS_OSPF_RT_NSSA_1 = 5, /* NSSA Type 1 */
-+ BGP_LS_OSPF_RT_NSSA_2 = 6, /* NSSA Type 2 */
-+};
-+
-+/*
-+ * 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
-+ */
-+
-+enum bgp_ls_attr_tlv {
-+ /* Node Attribute TLVs (RFC 9552 Section 5.3.1) */
-+ BGP_LS_ATTR_NODE_FLAG_BITS = 1024, /* Node Flag Bits */
-+ BGP_LS_ATTR_NODE_NAME = 1026, /* Node Name */
-+ BGP_LS_ATTR_ISIS_AREA_ID = 1027, /* IS-IS Area Identifier */
-+
-+ /* Node Attribute TLVs (RFC 9085 Section 2.1) */
-+ BGP_LS_ATTR_SR_CAPABILITIES = 1034, /* SR Capabilities */
-+ BGP_LS_ATTR_SR_ALGORITHM = 1035, /* SR Algorithm */
-+ BGP_LS_ATTR_SR_LOCAL_BLOCK = 1036, /* SR Local Block */
-+ BGP_LS_ATTR_SRMS_PREFERENCE = 1037, /* SRMS Preference */
-+
-+ /* Node Attribute TLVs (RFC 8814) */
-+ BGP_LS_ATTR_NODE_MSD = 266, /* Node MSD */
-+
-+ /* Link Attribute TLVs (RFC 9552 Section 4.3) */
-+ BGP_LS_ATTR_IPV4_ROUTER_ID_LOCAL = 1028, /* IPv4 Router-ID of Local Node */
-+ BGP_LS_ATTR_IPV6_ROUTER_ID_LOCAL = 1029, /* IPv6 Router-ID of Local Node */
-+ BGP_LS_ATTR_IPV4_ROUTER_ID_REMOTE = 1030, /* IPv4 Router-ID of Remote Node */
-+ BGP_LS_ATTR_IPV6_ROUTER_ID_REMOTE = 1031, /* IPv6 Router-ID of Remote Node */
-+ BGP_LS_ATTR_ADMIN_GROUP = 1088, /* Administrative Group (Color) */
-+ BGP_LS_ATTR_MAX_LINK_BW = 1089, /* Maximum Link Bandwidth */
-+ BGP_LS_ATTR_MAX_RESV_BW = 1090, /* Maximum Reservable Link Bandwidth */
-+ BGP_LS_ATTR_UNRESV_BW = 1091, /* Unreserved Bandwidth */
-+ BGP_LS_ATTR_TE_DEFAULT_METRIC = 1092, /* TE Default Metric */
-+ BGP_LS_ATTR_LINK_PROTECTION_TYPE = 1093, /* Link Protection Type */
-+ BGP_LS_ATTR_MPLS_PROTOCOL_MASK = 1094, /* MPLS Protocol Mask */
-+ BGP_LS_ATTR_IGP_METRIC = 1095, /* IGP Metric */
-+ BGP_LS_ATTR_SRLG = 1096, /* Shared Risk Link Group */
-+ BGP_LS_ATTR_OPAQUE_LINK_ATTR = 1097, /* Opaque Link Attribute */
-+ BGP_LS_ATTR_LINK_NAME = 1098, /* Link Name */
-+ BGP_LS_ATTR_ADJ_SID = 1099, /* Adjacency SID */
-+ BGP_LS_ATTR_LAN_ADJ_SID = 1100, /* LAN Adjacency SID */
-+ BGP_LS_ATTR_PEER_NODE_SID = 1101, /* PeerNode SID */
-+ BGP_LS_ATTR_PEER_ADJ_SID = 1102, /* PeerAdj SID */
-+ BGP_LS_ATTR_PEER_SET_SID = 1103, /* PeerSet SID */
-+ BGP_LS_ATTR_LINK_MSD = 1104, /* Link MSD */
-+ BGP_LS_ATTR_UNIDIRECTIONAL_LINK_DELAY = 1114, /* Unidirectional Link Delay */
-+ BGP_LS_ATTR_MIN_MAX_UNIDIRECTIONAL_LINK_DELAY = 1115, /* Min/Max Unidirectional Link Delay */
-+ BGP_LS_ATTR_UNIDIRECTIONAL_DELAY_VARIATION = 1116, /* Unidirectional Delay Variation */
-+ BGP_LS_ATTR_UNIDIRECTIONAL_LINK_LOSS = 1117, /* Unidirectional Link Loss */
-+ BGP_LS_ATTR_UNIDIRECTIONAL_RESIDUAL_BANDWIDTH = 1118, /* Unidirectional Residual Bandwidth */
-+ BGP_LS_ATTR_UNIDIRECTIONAL_AVAILABLE_BANDWIDTH = 1119, /* Unidirectional Available Bandwidth */
-+ BGP_LS_ATTR_UNIDIRECTIONAL_UTILIZED_BANDWIDTH = 1120, /* Unidirectional Utilized Bandwidth */
-+ BGP_LS_ATTR_EXTENDED_ADMIN_GROUP = 1173, /* Extended Administrative Group */
-+
-+ /* Prefix Attribute TLVs (RFC 9552 Section 4.3) */
-+ BGP_LS_ATTR_IGP_FLAGS = 1152, /* IGP Flags */
-+ BGP_LS_ATTR_ROUTE_TAG = 1153, /* Route Tag */
-+ BGP_LS_ATTR_EXTENDED_TAG = 1154, /* Extended Tag */
-+ BGP_LS_ATTR_PREFIX_METRIC = 1155, /* Prefix Metric */
-+ BGP_LS_ATTR_OSPF_FWD_ADDR = 1156, /* OSPF Forwarding Address */
-+ BGP_LS_ATTR_OPAQUE_PREFIX_ATTR = 1157, /* Opaque Prefix Attribute */
-+ BGP_LS_ATTR_PREFIX_SID = 1158, /* Prefix SID */
-+ BGP_LS_ATTR_RANGE = 1159, /* Range */
-+ BGP_LS_ATTR_SID_LABEL = 1161, /* SID/Label */
-+ BGP_LS_ATTR_PREFIX_ATTR_FLAGS = 1170, /* Prefix Attribute Flags */
-+ BGP_LS_ATTR_SRV6_LOCATOR = 1162, /* SRv6 Locator */
-+};
-+
-+/*
-+ * ===========================================================================
-+ * Constants and Macros
-+ * ===========================================================================
-+ */
-+
-+/* Bit positions for Node Descriptor TLVs */
-+#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 (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 (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
-+
-+/*
-+ * IGP Router-ID Length Constants (RFC 9552 Section 5.2.1.4)
-+ */
-+#define BGP_LS_IGP_ROUTER_ID_OSPF_LEN 4 /* OSPFv2/v3 non-pseudonode: Router-ID */
-+#define BGP_LS_IGP_ROUTER_ID_ISIS_LEN 6 /* IS-IS non-pseudonode: ISO System-ID */
-+#define BGP_LS_IGP_ROUTER_ID_ISIS_PSEUDO_LEN 7 /* IS-IS pseudonode: System-ID + PSN */
-+#define BGP_LS_IGP_ROUTER_ID_OSPF_PSEUDO_LEN 8 /* OSPFv2/v3 pseudonode: Router-ID + Interface ID */
-+#define BGP_LS_IGP_ROUTER_ID_DIRECT_IPV4_LEN IPV4_MAX_BYTELEN /* Direct/Static: IPv4 address */
-+#define BGP_LS_IGP_ROUTER_ID_DIRECT_IPV6_LEN IPV6_MAX_BYTELEN /* Direct/Static: IPv6 address */
-+#define BGP_LS_IGP_ROUTER_ID_MIN_SIZE BGP_LS_IGP_ROUTER_ID_OSPF_LEN /* Minimum size (4 bytes) */
-+#define BGP_LS_IGP_ROUTER_ID_MAX_SIZE \
-+ BGP_LS_IGP_ROUTER_ID_DIRECT_IPV6_LEN /* Maximum size (16 bytes) */
-+
-+/*
-+ * TLV Size Constants (for wire format size calculation)
-+ */
-+#define BGP_LS_TLV_HDR_SIZE 4 /* TLV Type (2) + Length (2) */
-+#define BGP_LS_PROTOCOL_ID_SIZE 1 /* Protocol-ID field */
-+#define BGP_LS_IDENTIFIER_SIZE 8 /* Identifier field (Instance ID) */
-+#define BGP_LS_NLRI_HDR_SIZE \
-+ (BGP_LS_PROTOCOL_ID_SIZE + BGP_LS_IDENTIFIER_SIZE) /* Protocol-ID + Identifier */
-+#define BGP_LS_NLRI_TYPE_SIZE 2 /* NLRI Type field */
-+#define BGP_LS_NLRI_LENGTH_SIZE 2 /* NLRI Length field */
-+/* Minimum NLRI length: Protocol-ID + Identifier + Node Descriptor TLV header + min descriptor */
-+#define BGP_LS_NLRI_MIN_LENGTH \
-+ (BGP_LS_PROTOCOL_ID_SIZE + BGP_LS_IDENTIFIER_SIZE + BGP_LS_TLV_HDR_SIZE)
-+#define BGP_LS_AS_NUMBER_SIZE 4 /* AS Number value */
-+#define BGP_LS_BGP_LS_ID_SIZE 4 /* BGP-LS Identifier value */
-+#define BGP_LS_OSPF_AREA_ID_SIZE 4 /* OSPF Area-ID value */
-+#define BGP_LS_LINK_ID_SIZE 8 /* Link Local ID (4) + Remote ID (4) */
-+#define BGP_LS_IPV4_ADDR_SIZE IPV4_MAX_BYTELEN /* IPv4 address */
-+#define BGP_LS_IPV6_ADDR_SIZE IPV6_MAX_BYTELEN /* IPv6 address */
-+#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
-+ */
-+#define BGP_LS_IGP_METRIC_MAX_LEN 3
-+
-+/*
-+ * IGP MSD Type
-+ * RFC 8491 Section 6, Figure 6
-+ */
-+#define BGP_LS_IGP_MSD_TYPE_BASE_MPLS 1
-+
-+/*
-+ * Maximum values for arrays
-+ */
-+#define BGP_LS_MAX_SRLG 64 /* Maximum SRLGs per link */
-+#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 (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)
-+ * RFC 9552 Section 4.3.1
-+ */
-+#define BGP_LS_NODE_FLAG_OVERLOAD 0x80 /* Overload Bit */
-+#define BGP_LS_NODE_FLAG_ATTACHED 0x40 /* Attached Bit */
-+#define BGP_LS_NODE_FLAG_EXTERNAL 0x20 /* External Bit */
-+#define BGP_LS_NODE_FLAG_ABR 0x10 /* ABR Bit */
-+#define BGP_LS_NODE_FLAG_ROUTER 0x08 /* Router Bit */
-+#define BGP_LS_NODE_FLAG_V6 0x04 /* V6 Bit */
-+
-+/*
-+ * IGP Prefix Flags (TLV 1152)
-+ * RFC 9552 Section 4.3.4.1
-+ */
-+#define BGP_LS_PREFIX_FLAG_DOWN 0x80 /* IS-IS Down Bit */
-+#define BGP_LS_PREFIX_FLAG_NO_UNICAST 0x40 /* OSPF No-Unicast Bit */
-+#define BGP_LS_PREFIX_FLAG_LOCAL 0x20 /* OSPF Local Address Bit */
-+#define BGP_LS_PREFIX_FLAG_PROPAGATE 0x10 /* OSPF Propagate NSSA Bit */
-+#define BGP_LS_PREFIX_FLAG_NODE 0x08 /* Node Prefix Attached Flag */
-+
-+/*
-+ * IGP Prefix SID Flags (TLV 1158)
-+ * RFC 9085 Section 2.3.1
-+ */
-+#define BGP_LS_PREFIX_SID_FLAG_VALUE 0x08 /* Same for IS-IS, OSPFv2, OSPFv3 */
-+#define BGP_LS_PREFIX_SID_FLAG_LOCAL 0x04 /* Same for IS-IS, OSPFv2, OSPFv3 */
-+
-+/*
-+ * IS-IS RFC 8667, 2.2.1
-+ * OSPFv2 RFC 8665, 6.1
-+ * OSPFv3 RFC 8666, 7.1
-+ */
-+#define ISIS_EXT_SUBTLV_LINK_ADJ_SID_VFLG 0x20
-+#define ISIS_EXT_SUBTLV_LINK_ADJ_SID_LFLG 0x10
-+#define OSPF_EXT_SUBTLV_LINK_ADJ_SID_VFLG 0x40
-+#define OSPF_EXT_SUBTLV_LINK_ADJ_SID_LFLG 0x20
-+
-+
-+/*
-+ * ===========================================================================
-+ * Descriptor Structures (RFC 9552 Section 5.2)
-+ * ===========================================================================
-+ *
-+ * These structures identify topology elements (nodes, links, prefixes)
-+ * per RFC 9552 Section 5.2
-+ */
-+
-+/*
-+ * Node Descriptor - Identifies a router/node in the topology
-+ * RFC 9552, Section 5.2.1
-+ */
-+struct bgp_ls_node_descriptor {
-+ uint16_t present_tlvs; /* Bitmask of present TLVs */
-+ as_t asn; /* Autonomous System Number */
-+ uint32_t bgp_ls_id; /* BGP-LS Identifier (deprecated) */
-+ 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) */
-+};
-+
-+/*
-+ * Link Descriptor - Identifies a link between two nodes
-+ * RFC 9552, Section 5.2.2
-+ */
-+struct bgp_ls_link_descriptor {
-+ uint32_t present_tlvs; /* Bitmask of present TLVs */
-+ uint32_t link_local_id; /* Link Local Identifier */
-+ uint32_t link_remote_id; /* Link Remote Identifier */
-+ struct in_addr ipv4_intf_addr; /* IPv4 Interface Address */
-+ struct in_addr ipv4_neigh_addr; /* IPv4 Neighbor Address */
-+ struct in6_addr ipv6_intf_addr; /* IPv6 Interface Address */
-+ struct in6_addr ipv6_neigh_addr; /* IPv6 Neighbor Address */
-+ as_t remote_asn; /* Remote AS Number */
-+ uint8_t mt_id_count; /* Number of Multi-Topology IDs */
-+ uint16_t *mt_id; /* Multi-Topology IDs */
-+};
-+
-+/*
-+ * Prefix Descriptor - Identifies an IP prefix advertised by a node
-+ * RFC 9552, Section 5.2.3
-+ */
-+struct bgp_ls_prefix_descriptor {
-+ uint16_t present_tlvs; /* Bitmask of present TLVs */
-+ 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) */
-+};
-+
-+/*
-+ * ===========================================================================
-+ * BGP-LS NLRI Structures (RFC 9552 Section 5.2)
-+ * ===========================================================================
-+ *
-+ * These structures represent complete BGP-LS NLRIs as exchanged in UPDATE
-+ * messages. Each NLRI consists of:
-+ * 1. Protocol-ID - IGP protocol that originated the information
-+ * 2. Identifier - 64-bit instance identifier for disambiguation
-+ * 3. NLRI-specific descriptors that identify the topology element
-+ */
-+
-+/*
-+ * Node NLRI (Type 1) - Identifies a router/node in the topology
-+ * RFC 9552, Section 5.2
-+ *
-+ * 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
-+ * +-+-+-+-+-+-+-+-+
-+ * | Protocol-ID |
-+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
-+ * | Identifier |
-+ * | (64 bits) |
-+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
-+ * // Local Node Descriptors (variable) //
-+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
-+ */
-+struct bgp_ls_node_nlri {
-+ enum bgp_ls_protocol_id protocol_id; /* IGP protocol */
-+ uint64_t identifier; /* Instance identifier */
-+ struct bgp_ls_node_descriptor local_node; /* Node identity */
-+};
-+
-+/*
-+ * Link NLRI (Type 2) - Identifies a link between two nodes
-+ * RFC 9552, Section 5.2
-+ *
-+ * 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
-+ * +-+-+-+-+-+-+-+-+
-+ * | Protocol-ID |
-+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
-+ * | Identifier |
-+ * | (64 bits) |
-+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
-+ * // Local Node Descriptors (variable) //
-+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
-+ * // Remote Node Descriptors (variable) //
-+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
-+ * // Link Descriptors (variable) //
-+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
-+ */
-+struct bgp_ls_link_nlri {
-+ enum bgp_ls_protocol_id protocol_id; /* IGP protocol */
-+ uint64_t identifier; /* Instance identifier */
-+ struct bgp_ls_node_descriptor local_node; /* Source node */
-+ struct bgp_ls_node_descriptor remote_node; /* Destination node */
-+ struct bgp_ls_link_descriptor link_desc; /* Link identity */
-+};
-+
-+/*
-+ * Prefix NLRI (Type 3/4) - Identifies an IP prefix advertised by a node
-+ * RFC 9552, Section 5.2
-+ *
-+ * 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
-+ * +-+-+-+-+-+-+-+-+
-+ * | Protocol-ID |
-+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
-+ * | Identifier |
-+ * | (64 bits) |
-+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
-+ * // Local Node Descriptors (variable) //
-+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
-+ * // Prefix Descriptors (variable) //
-+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
-+ *
-+ * Note: Same structure used for both IPv4 (Type 3) and IPv6 (Type 4)
-+ * The prefix_desc.prefix.family field distinguishes between them
-+ */
-+struct bgp_ls_prefix_nlri {
-+ enum bgp_ls_protocol_id protocol_id; /* IGP protocol */
-+ uint64_t identifier; /* Instance identifier */
-+ struct bgp_ls_node_descriptor local_node; /* Advertising node */
-+ struct bgp_ls_prefix_descriptor prefix_desc; /* Prefix identity */
-+};
-+
-+/* Forward declare the hash table */
-+PREDECL_HASH(bgp_ls_nlri_hash);
-+
-+/*
-+ * Top-level BGP-LS NLRI structure (RFC 9552 Section 5.2)
-+ *
-+ * BGP-LS NLRI encoding in UPDATE message:
-+ *
-+ * 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 |
-+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
-+ * | |
-+ * // NLRI Value (variable) //
-+ * | |
-+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
-+ *
-+ * Where:
-+ * Type = NLRI Type (1=Node, 2=Link, 3=IPv4 Prefix, 4=IPv6 Prefix)
-+ * Length = Length of NLRI Value in octets
-+ * Value = Type-specific NLRI data (see struct bgp_ls_*_nlri)
-+ *
-+ * This structure represents the decoded NLRI after parsing the wire format.
-+ */
-+struct bgp_ls_nlri {
-+ enum bgp_ls_nlri_type nlri_type; /* Discriminator */
-+ union {
-+ struct bgp_ls_node_nlri node; /* Node NLRI (Type 1) */
-+ struct bgp_ls_link_nlri link; /* Link NLRI (Type 2) */
-+ struct bgp_ls_prefix_nlri prefix; /* Prefix NLRI (Type 3/4) */
-+ } nlri_data;
-+
-+ unsigned long refcnt; /* Reference count */
-+ uint32_t id;
-+
-+ /* Hash table linkage */
-+ struct bgp_ls_nlri_hash_item hash_item;
-+};
-+
-+/* Forward declare the hash table */
-+PREDECL_HASH(bgp_ls_attr_hash);
-+
-+/* BGP-LS Attribute (Type 29) */
-+struct bgp_ls_attr {
-+ uint64_t present_tlvs; /* Bitmask of present TLVs */
-+
-+ /* Node Flag Bits (TLV 1024) */
-+ uint8_t node_flags;
-+
-+ /* Node Name (TLV 1026) */
-+ char *node_name;
-+
-+ /* IS-IS Area Identifier (TLV 1027) */
-+ uint8_t isis_area_id_len;
-+ uint8_t *isis_area_id;
-+
-+ /* SR Capabilities (TLV 1034), only one range supported */
-+ struct bgp_ls_srgb {
-+ uint32_t lower_bound; /* MPLS label lower bound */
-+ uint32_t range_size; /* MPLS label range size */
-+ uint8_t flag; /* IS-IS SRGB flags */
-+ } srgb;
-+
-+ /* Node MSD (TLV 266) */
-+ uint8_t msd;
-+
-+ /* Multi-Topology IDs (multiple TLVs, same as descriptor) */
-+ uint8_t mt_id_count;
-+ uint16_t *mt_id;
-+
-+ /* IPv4/IPv6 Router-IDs (TLVs 1028-1031) */
-+ struct in_addr ipv4_router_id_local;
-+ struct in6_addr ipv6_router_id_local;
-+ struct in_addr ipv4_router_id_remote;
-+ struct in6_addr ipv6_router_id_remote;
-+
-+ /* Administrative Group (TLV 1088) */
-+ uint32_t admin_group;
-+
-+ /* Maximum Link Bandwidth (TLV 1089) - IEEE floating point */
-+ float max_link_bw;
-+
-+ /* Maximum Reservable Bandwidth (TLV 1090) */
-+ float max_resv_bw;
-+
-+ /* Unreserved Bandwidth (TLV 1091) - 8 priority levels */
-+ float unreserved_bw[BGP_LS_MAX_UNRESV_BW];
-+
-+ /* TE Default Metric (TLV 1092) */
-+ uint32_t te_metric;
-+
-+ /* Link Protection Type (TLV 1093) */
-+ uint16_t link_protection;
-+
-+ /* MPLS Protocol Mask (TLV 1094) */
-+ uint8_t mpls_protocol_mask;
-+
-+ /* IGP Metric (TLV 1095) - Variable length 1-3 bytes */
-+ uint8_t igp_metric_len;
-+ uint32_t igp_metric;
-+
-+ /* Shared Risk Link Group (TLV 1096) */
-+ uint16_t srlg_count;
-+ uint32_t *srlg_values;
-+
-+ /* Link Name (TLV 1098) */
-+ char *link_name;
-+
-+ /* Adjacency SID (TLV 1099) */
-+#define BGP_LS_ADJ_MAX 4
-+ uint8_t adj_sid_count; /* number of adj_sid elements */
-+ struct bgp_ls_adjacency {
-+ uint32_t sid; /* SID as MPLS label or index */
-+ uint8_t flags; /* Flags */
-+ uint8_t weight; /* Administrative weight */
-+ } adj_sid[BGP_LS_ADJ_MAX]; /* IPv4/IPv6 & Primary/Backup Adj. SID */
-+
-+ /* Extended Administrative Group (TLV 1173) */
-+ struct admin_group ext_admin_group;
-+
-+ /* Performance Metrics - Unidirectional Link Delay (TLV 1114) */
-+ uint32_t delay;
-+
-+ /* Min/Max Unidirectional Link Delay (TLV 1115) */
-+ uint32_t min_delay;
-+ uint32_t max_delay;
-+
-+ /* Unidirectional Delay Variation (TLV 1116) */
-+ uint32_t jitter;
-+
-+ /* Unidirectional Packet Loss (TLV 1117) */
-+ uint32_t pkt_loss;
-+
-+ /* Unidirectional Residual Bandwidth (TLV 1118) */
-+ float residual_bw;
-+
-+ /* Unidirectional Available Bandwidth (TLV 1119) */
-+ float available_bw;
-+
-+ /* Unidirectional Utilized Bandwidth (TLV 1120) */
-+ float utilized_bw;
-+
-+ /* IGP Flags (TLV 1152) */
-+ uint8_t igp_flags;
-+
-+ /* Route Tags (TLV 1153) */
-+ uint16_t route_tag_count;
-+ uint32_t *route_tags;
-+
-+ /* Extended Tags (TLV 1154) */
-+ uint8_t extended_tag_count;
-+ uint64_t *extended_tags;
-+
-+ /* Prefix Metric (TLV 1155) */
-+ uint32_t prefix_metric;
-+
-+ /* OSPF Forwarding Address (TLV 1156) */
-+ struct in_addr ospf_fwd_addr; /* IPv4 */
-+ struct in6_addr ospf_fwd_addr6; /* IPv6 */
-+
-+ /* Prefix-SID (TLV 1158) */
-+ struct prefix_sid {
-+ uint8_t sid_flag; /* Segment Routing Flags */
-+ uint8_t algo; /* Algorithm for Segment Routing */
-+ uint32_t sid; /* Segment Routing ID */
-+ } prefix_sid;
-+
-+ /* Opaque Node Attribute (TLV 1025/1097/1157) */
-+ uint16_t opaque_len;
-+ uint8_t *opaque_data;
-+
-+ unsigned long refcnt; /* Reference count */
-+
-+ /* Hash table linkage */
-+ struct bgp_ls_attr_hash_item hash_item;
-+};
-+
-+/* Function prototypes */
-+
-+/* Descriptor comparison functions */
-+extern int bgp_ls_node_descriptor_cmp(const struct bgp_ls_node_descriptor *d1,
-+ const struct bgp_ls_node_descriptor *d2);
-+extern int bgp_ls_link_descriptor_cmp(const struct bgp_ls_link_descriptor *d1,
-+ const struct bgp_ls_link_descriptor *d2);
-+extern int bgp_ls_prefix_descriptor_cmp(const struct bgp_ls_prefix_descriptor *d1,
-+ const struct bgp_ls_prefix_descriptor *d2);
-+
-+/* NLRI comparison functions */
-+extern int bgp_ls_nlri_cmp(const struct bgp_ls_nlri *nlri1, const struct bgp_ls_nlri *nlri2);
-+extern int bgp_ls_attr_cmp(const struct bgp_ls_attr *attr1, const struct bgp_ls_attr *attr2);
-+extern bool bgp_ls_attr_same(const struct bgp_ls_attr *attr1, const struct bgp_ls_attr *attr2);
-+
-+/* NLRI memory management functions */
-+extern struct bgp_ls_nlri *bgp_ls_nlri_alloc(void);
-+extern void bgp_ls_nlri_free(struct bgp_ls_nlri *nlri);
-+extern struct bgp_ls_attr *bgp_ls_attr_alloc(void);
-+extern void bgp_ls_attr_free(struct bgp_ls_attr *attr);
-+extern struct bgp_ls_nlri *bgp_ls_nlri_copy(const struct bgp_ls_nlri *nlri);
-+struct bgp_ls_attr *bgp_ls_attr_copy(const struct bgp_ls_attr *src);
-+
-+/* NLRI validation functions */
-+extern bool bgp_ls_nlri_validate(const struct bgp_ls_nlri *nlri);
-+
-+/* NLRI size calculation helpers */
-+extern size_t bgp_ls_nlri_size(const struct bgp_ls_nlri *nlri);
-+
-+/* NLRI get protocol_id helper */
-+extern enum bgp_ls_protocol_id bgp_ls_nlri_protocol_id(const struct bgp_ls_nlri *nlri);
-+
-+/* String conversion helpers */
-+extern const char *bgp_ls_protocol_id_str(enum bgp_ls_protocol_id proto_id);
-+extern const char *bgp_ls_nlri_type_str(enum bgp_ls_nlri_type nlri_type);
-+extern const char *bgp_ls_node_descriptor_tlv_str(enum bgp_ls_node_descriptor_tlv tlv_type);
-+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);
-+
-+/*
-+ * ===========================================================================
-+ * Hash Table Management
-+ * ===========================================================================
-+ */
-+
-+/* Unified hash functions - dispatch internally based on nlri_type */
-+extern unsigned int bgp_ls_nlri_hash_key(const struct bgp_ls_nlri *nlri);
-+extern int bgp_ls_nlri_hash_cmp(const struct bgp_ls_nlri *n1, const struct bgp_ls_nlri *n2);
-+
-+extern unsigned int bgp_ls_attr_hash_key(const struct bgp_ls_attr *ls_attr);
-+extern int bgp_ls_attr_hash_cmp(const struct bgp_ls_attr *a1, const struct bgp_ls_attr *a2);
-+
-+/* Declare the typesafe hash table */
-+DECLARE_HASH(bgp_ls_nlri_hash, struct bgp_ls_nlri, hash_item, bgp_ls_nlri_hash_cmp,
-+ bgp_ls_nlri_hash_key);
-+
-+/* Declare the typesafe hash table */
-+DECLARE_HASH(bgp_ls_attr_hash, struct bgp_ls_attr, hash_item, bgp_ls_attr_hash_cmp,
-+ bgp_ls_attr_hash_key);
-+
-+/*
-+ * ===========================================================================
-+ * BGP-LS NLRI Hash Table Management Functions
-+ * ===========================================================================
-+ */
-+
-+/* Insert NLRI into unified hash table */
-+extern struct bgp_ls_nlri *bgp_ls_nlri_get(struct bgp_ls_nlri_hash_head *hash, struct bgp *bgp,
-+ struct bgp_ls_nlri *nlri);
-+
-+/* Lookup NLRI in unified hash table */
-+extern struct bgp_ls_nlri *bgp_ls_nlri_lookup(struct bgp_ls_nlri_hash_head *hash,
-+ struct bgp_ls_nlri *nlri);
-+
-+/* NLRI interning (lookup + lock/unlock) */
-+extern struct bgp_ls_nlri *bgp_ls_nlri_intern(struct bgp_ls_nlri *ls_nlri);
-+extern void bgp_ls_nlri_unintern(struct bgp_ls_nlri **pls_nlri);
-+
-+/* Insert BGP-LS attribute into unified hash table */
-+extern struct bgp_ls_attr *bgp_ls_attr_get(struct bgp_ls_attr_hash_head *hash, struct bgp *bgp,
-+ struct bgp_ls_attr *ls_attr);
-+
-+/* Lookup BGP-LS attribute in unified hash table */
-+extern struct bgp_ls_attr *bgp_ls_attr_lookup(struct bgp_ls_attr_hash_head *hash,
-+ const struct bgp_ls_attr *ls_attr);
-+
-+/* BGP-LS attribute interning (lookup + lock/unlock) */
-+extern struct bgp_ls_attr *bgp_ls_attr_intern(struct bgp_ls_attr *ls_attr);
-+extern void bgp_ls_attr_unintern(struct bgp_ls_attr **pls_attr);
-+
-+/*
-+ * ===========================================================================
-+ * NLRI Encoding Functions
-+ * ===========================================================================
-+ */
-+
-+/* Encode Node Descriptor TLVs into stream */
-+extern int bgp_ls_encode_node_descriptor(struct stream *s,
-+ const struct bgp_ls_node_descriptor *desc,
-+ uint16_t tlv_type);
-+
-+/* Encode Link Descriptor TLVs into stream */
-+extern int bgp_ls_encode_link_descriptor(struct stream *s,
-+ const struct bgp_ls_link_descriptor *desc);
-+
-+/* Encode Prefix Descriptor TLVs into stream */
-+extern int bgp_ls_encode_prefix_descriptor(struct stream *s,
-+ const struct bgp_ls_prefix_descriptor *desc);
-+
-+/* Encode Node NLRI to wire format */
-+extern int bgp_ls_encode_node_nlri(struct stream *s, const struct bgp_ls_node_nlri *nlri);
-+
-+/* Encode Link NLRI to wire format */
-+extern int bgp_ls_encode_link_nlri(struct stream *s, const struct bgp_ls_link_nlri *nlri);
-+
-+/* Encode Prefix NLRI to wire format */
-+extern int bgp_ls_encode_prefix_nlri(struct stream *s, const struct bgp_ls_prefix_nlri *nlri,
-+ enum bgp_ls_nlri_type nlri_type);
-+
-+/* Encode complete NLRI */
-+extern int bgp_ls_encode_nlri(struct stream *s, const struct bgp_ls_nlri *nlri);
-+
-+/* Encode BGP-LS Attributes (Type 29 TLVs) */
-+extern int bgp_ls_encode_attr(struct stream *s, const struct bgp_ls_attr *attr,
-+ enum bgp_ls_protocol_id protocol_id);
-+
-+/*
-+ * Get Prefix-SID attribute SID length by flags
-+ *
-+ * @return 3 or 4 in normal case, -1 in error case
-+ */
-+extern int bgp_ls_attr_prefix_sid_len(uint8_t flags);
-+
-+/*
-+ * Get Adjacency SID attribute Label/Index SID length by flags
-+ *
-+ * @return 3 or 4 in normal case, -1 in error case
-+ */
-+extern int bgp_ls_attr_adjacency_sid_len(uint8_t flags, enum bgp_ls_protocol_id protocol_id);
-+
-+/*
-+ * ===========================================================================
-+ * NLRI Decoding Functions
-+ * ===========================================================================
-+ */
-+
-+/* Decode Node Descriptor TLVs from stream */
-+extern int bgp_ls_decode_node_descriptor(struct stream *s, struct bgp_ls_node_descriptor *desc,
-+ uint16_t desc_length);
-+
-+/* Decode Link Descriptor TLVs from stream */
-+extern int bgp_ls_decode_link_descriptor(struct stream *s, struct bgp_ls_link_descriptor *desc,
-+ size_t total_length);
-+
-+/* Decode Prefix Descriptor TLVs from stream */
-+extern int bgp_ls_decode_prefix_descriptor(struct stream *s, struct bgp_ls_prefix_descriptor *desc,
-+ size_t total_length, bool is_ipv6);
-+
-+/* Decode Node NLRI from wire format */
-+extern int bgp_ls_decode_node_nlri(struct stream *s, struct bgp_ls_nlri *nlri,
-+ uint16_t nlri_length);
-+
-+/* Decode Link NLRI from wire format */
-+extern int bgp_ls_decode_link_nlri(struct stream *s, struct bgp_ls_nlri *nlri,
-+ uint16_t nlri_length);
-+
-+/* Decode Prefix NLRI from wire format */
-+extern int bgp_ls_decode_prefix_nlri(struct stream *s, struct bgp_ls_nlri *nlri,
-+ uint16_t nlri_type, uint16_t nlri_length);
-+
-+/* Decode complete NLRI */
-+extern int bgp_ls_decode_nlri(struct stream *s, struct bgp_ls_nlri *nlri);
-+
-+/*
-+ * Parse BGP-LS Attributes (Type 29 TLVs)
-+ *
-+ * @param s Stream containing the attribute TLVs
-+ * @param total_length Total length of all TLVs in bytes
-+ * @param attr Pointer to node attribute structure to populate
-+ * @return 0 on success, -1 on error
-+ */
-+extern int bgp_ls_parse_attr(struct stream *s, uint16_t total_length, struct bgp_ls_attr *attr);
-+
-+/*
-+ * Process BGP-LS Attributes when protocol id is known
-+ *
-+ * Parsing of some attributes require protocol id, but bgp_ls_parse_attr
-+ * is called before NLRI is parsed, so at that time protocol id is unknown
-+ *
-+ * This function is called after bgp_ls_parse_attr when protocol id is known
-+ *
-+ * @param attr Pointer to node attribute structure to populate
-+ * @param protocol_id IGP protocol
-+ * @return 0 on success, -1 on error
-+ */
-+extern int bgp_ls_attr_process_with_protocol_id(struct bgp_ls_attr *attr,
-+ enum bgp_ls_protocol_id protocol_id);
-+
-+/*
-+ * Convert BGP-LS Attributes to JSON object
-+ *
-+ * @param ls_attr Pointer to BGP-LS attribute structure to convert
-+ *
-+ * Used for "show bgp" commands to display link-state topology information in json
-+ *
-+ * @return json object
-+ */
-+extern struct json_object *bgp_ls_attr_to_json(struct bgp_ls_attr *ls_attr);
-+
-+/*
-+ * Display BGP-LS Attributes to VTY output
-+ *
-+ * @param vty VTY output context
-+ * @param ls_attr Pointer to BGP-LS attribute structure to display
-+ *
-+ * Used for "show bgp" commands to display link-state topology information
-+ */
-+extern void bgp_ls_attr_display(struct vty *vty, struct bgp_ls_attr *ls_attr);
-+
-+/*
-+ * Display BGP-LS NLRI details to VTY output
-+ *
-+ * @param vty VTY output context
-+ * @param nlri Pointer to BGP-LS NLRI structure to display
-+ *
-+ * Displays Node/Link/Prefix NLRI information including descriptors,
-+ * protocol information, and multi-topology details
-+ */
-+extern void bgp_ls_nlri_display(struct vty *vty, struct bgp_ls_nlri *nlri);
-+
-+#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..6a0eb49907
---- /dev/null
-+++ b/bgpd/bgp_ls_ted.c
-@@ -0,0 +1,1381 @@
-+// SPDX-License-Identifier: GPL-2.0-or-later
-+/*
-+ * BGP Link-State Traffic Engineering Database (RFC 9552)
-+ * Copyright (C) 2025 Carmine Scarpitta
-+ */
-+
-+#include <zebra.h>
-+
-+#include "lib/memory.h"
-+#include "lib/log.h"
-+#include "lib/prefix.h"
-+#include "lib/stream.h"
-+#define UNKNOWN LS_UNKNOWN
-+#include "lib/link_state.h"
-+#undef UNKNOWN
-+
-+#include "bgpd/bgpd.h"
-+#include "bgpd/bgp_debug.h"
-+#include "bgpd/bgp_errors.h"
-+#include "bgpd/bgp_ls.h"
-+#include "bgpd/bgp_ls_nlri.h"
-+#include "bgpd/bgp_ls_ted.h"
-+#include "bgpd/bgp_route.h"
-+
-+/*
-+ * ===========================================================================
-+ * Node Attribute Population
-+ * ===========================================================================
-+ */
-+
-+/*
-+ * Populate BGP-LS Attributes from Link State Node
-+ */
-+int bgp_ls_populate_node_attr(struct ls_node *ls_node, struct bgp_ls_attr *attr)
-+{
-+ if (!ls_node || !attr)
-+ return -1;
-+
-+ /* Node Flag Bits (TLV 1024) */
-+ if (CHECK_FLAG(ls_node->flags, LS_NODE_FLAG)) {
-+ attr->node_flags = ls_node->node_flag;
-+ SET_FLAG(attr->present_tlvs, BGP_LS_ATTR_NODE_FLAGS_BIT);
-+ }
-+
-+ /* Node Name (TLV 1026) */
-+ if (CHECK_FLAG(ls_node->flags, LS_NODE_NAME) && ls_node->name[0] != '\0') {
-+ size_t name_len = strlen(ls_node->name);
-+
-+ attr->node_name = XCALLOC(MTYPE_BGP_LS_ATTR, name_len + 1);
-+ memcpy(attr->node_name, ls_node->name, name_len + 1);
-+ SET_FLAG(attr->present_tlvs, BGP_LS_ATTR_NODE_NAME_BIT);
-+ }
-+
-+ /* IS-IS Area Identifier (TLV 1027) */
-+ if (CHECK_FLAG(ls_node->flags, LS_NODE_ISIS_AREA_ID)) {
-+ 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);
-+ SET_FLAG(attr->present_tlvs, BGP_LS_ATTR_ISIS_AREA_BIT);
-+ }
-+
-+ /* SR Capabilities (TLV 1034) */
-+ if (CHECK_FLAG(ls_node->flags, LS_NODE_SR)) {
-+ 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;
-+ 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;
-+ 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;
-+ 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;
-+ SET_FLAG(attr->present_tlvs, BGP_LS_ATTR_IPV6_ROUTER_ID_LOCAL_BIT);
-+ }
-+
-+ return 0;
-+}
-+
-+/*
-+ * ===========================================================================
-+ * Link Attribute Population
-+ * ===========================================================================
-+ */
-+
-+static void populate_adj_sid(struct ls_attributes *ls_attr, int index, int flag,
-+ struct bgp_ls_attr *attr, enum bgp_ls_protocol_id protocol_id)
-+{
-+ int sid_len;
-+
-+ if (!CHECK_FLAG(ls_attr->flags, flag))
-+ return;
-+
-+ if (attr->adj_sid_count == BGP_LS_ADJ_MAX) {
-+ zlog_warn("BGP-LS: %s maximum supported number of Adjacency SID is %d, ignoring others",
-+ __func__, BGP_LS_ADJ_MAX);
-+ return;
-+ }
-+
-+ sid_len = bgp_ls_attr_adjacency_sid_len(ls_attr->adj_sid[index].flags, protocol_id);
-+ if (sid_len == -1) {
-+ zlog_warn("BGP-LS: %s TED contains wrong combination of V-Flag and L-Flag for Adjacent SID",
-+ __func__);
-+ return;
-+ }
-+
-+ attr->adj_sid[attr->adj_sid_count].sid = ls_attr->adj_sid[index].sid;
-+ attr->adj_sid[attr->adj_sid_count].flags = ls_attr->adj_sid[index].flags;
-+ attr->adj_sid[attr->adj_sid_count].weight = ls_attr->adj_sid[index].weight;
-+ attr->adj_sid_count++;
-+
-+ SET_FLAG(attr->present_tlvs, BGP_LS_ATTR_ADJ_SID_BIT);
-+}
-+
-+/*
-+ * Populate BGP-LS Attributes from Link State Attributes
-+ */
-+int bgp_ls_populate_link_attr(struct ls_attributes *ls_attr, struct bgp_ls_attr *attr,
-+ enum bgp_ls_protocol_id protocol_id)
-+{
-+ if (!ls_attr || !attr)
-+ return -1;
-+
-+ /* Administrative Group (TLV 1088) */
-+ if (CHECK_FLAG(ls_attr->flags, LS_ATTR_ADM_GRP)) {
-+ attr->admin_group = ls_attr->standard.admin_group;
-+ 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;
-+ 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;
-+ 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];
-+ 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;
-+ SET_FLAG(attr->present_tlvs, BGP_LS_ATTR_TE_METRIC_BIT);
-+ }
-+
-+ /* IGP Metric (TLV 1095) */
-+ if (CHECK_FLAG(ls_attr->flags, LS_ATTR_METRIC)) {
-+ /* IGP metric can be 1, 2, or 3 bytes */
-+ if (ls_attr->metric <= 0xFF)
-+ attr->igp_metric_len = 1;
-+ else if (ls_attr->metric <= 0xFFFF)
-+ attr->igp_metric_len = 2;
-+ else
-+ attr->igp_metric_len = 3;
-+ attr->igp_metric = ls_attr->metric;
-+ SET_FLAG(attr->present_tlvs, BGP_LS_ATTR_IGP_METRIC_BIT);
-+ }
-+
-+ /* Shared Risk Link Group (TLV 1096) */
-+ if (CHECK_FLAG(ls_attr->flags, LS_ATTR_SRLG) && ls_attr->srlg_len > 0) {
-+ uint8_t count = ls_attr->srlg_len;
-+
-+ if (count > BGP_LS_MAX_SRLG)
-+ count = BGP_LS_MAX_SRLG;
-+
-+ attr->srlg_count = count;
-+ 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];
-+ SET_FLAG(attr->present_tlvs, BGP_LS_ATTR_SRLG_BIT);
-+ }
-+
-+ /* Link Name (TLV 1098) */
-+ if (CHECK_FLAG(ls_attr->flags, LS_ATTR_NAME) && ls_attr->name[0] != '\0') {
-+ size_t name_len = strlen(ls_attr->name);
-+
-+ attr->link_name = XCALLOC(MTYPE_BGP_LS_ATTR, name_len + 1);
-+ memcpy(attr->link_name, ls_attr->name, name_len + 1);
-+ SET_FLAG(attr->present_tlvs, BGP_LS_ATTR_LINK_NAME_BIT);
-+ }
-+
-+ /* Adjacency SID (TLV 1099) */
-+ populate_adj_sid(ls_attr, ADJ_PRI_IPV4, LS_ATTR_ADJ_SID, attr, protocol_id);
-+ populate_adj_sid(ls_attr, ADJ_BCK_IPV4, LS_ATTR_BCK_ADJ_SID, attr, protocol_id);
-+ populate_adj_sid(ls_attr, ADJ_PRI_IPV6, LS_ATTR_ADJ_SID6, attr, protocol_id);
-+ populate_adj_sid(ls_attr, ADJ_BCK_IPV6, LS_ATTR_BCK_ADJ_SID6, attr, protocol_id);
-+
-+ /* 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;
-+ 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;
-+ 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);
-+ 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;
-+ 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;
-+ 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;
-+ 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;
-+ 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;
-+ 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;
-+ 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;
-+ SET_FLAG(attr->present_tlvs, BGP_LS_ATTR_UTILIZED_BW_BIT);
-+ }
-+
-+ return 0;
-+}
-+
-+/*
-+ * ===========================================================================
-+ * Prefix Attribute Population
-+ * ===========================================================================
-+ */
-+
-+/*
-+ * Populate BGP-LS Attributes from Link State Prefix
-+ */
-+int bgp_ls_populate_prefix_attr(struct ls_prefix *ls_prefix, struct bgp_ls_attr *attr)
-+{
-+ if (!ls_prefix || !attr)
-+ return -1;
-+
-+ /* IGP Flags (TLV 1152) */
-+ if (CHECK_FLAG(ls_prefix->flags, LS_PREF_IGP_FLAG)) {
-+ attr->igp_flags = ls_prefix->igp_flag;
-+ SET_FLAG(attr->present_tlvs, BGP_LS_ATTR_IGP_FLAGS_BIT);
-+ }
-+
-+ /* Route Tags (TLV 1153) - single tag */
-+ if (CHECK_FLAG(ls_prefix->flags, LS_PREF_ROUTE_TAG)) {
-+ attr->route_tag_count = 1;
-+ attr->route_tags = XCALLOC(MTYPE_BGP_LS_ATTR, sizeof(uint32_t));
-+ attr->route_tags[0] = ls_prefix->route_tag;
-+ SET_FLAG(attr->present_tlvs, BGP_LS_ATTR_ROUTE_TAG_BIT);
-+ }
-+
-+ /* Extended Tags (TLV 1154) - single extended tag */
-+ if (CHECK_FLAG(ls_prefix->flags, LS_PREF_EXTENDED_TAG)) {
-+ attr->extended_tag_count = 1;
-+ attr->extended_tags = XCALLOC(MTYPE_BGP_LS_ATTR, sizeof(uint64_t));
-+ attr->extended_tags[0] = ls_prefix->extended_tag;
-+ 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;
-+ SET_FLAG(attr->present_tlvs, BGP_LS_ATTR_PREFIX_METRIC_BIT);
-+ }
-+
-+ /* Prefix-SID (TLV 1158) */
-+ if (CHECK_FLAG(ls_prefix->flags, LS_PREF_SR)) {
-+ if (bgp_ls_attr_prefix_sid_len(ls_prefix->sr.sid_flag) == -1) {
-+ zlog_warn("BGP-LS: %s TED contains wrong combination of V-Flag and L-Flag for Prefix SID",
-+ __func__);
-+ } else {
-+ 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;
-+ SET_FLAG(attr->present_tlvs, BGP_LS_ATTR_PREFIX_SID_BIT);
-+ }
-+ }
-+
-+ return 0;
-+}
-+
-+/*
-+ * ===========================================================================
-+ * IGP Origination Functions
-+ * ===========================================================================
-+ */
-+
-+static bool bgp_ls_link_valid(struct ls_edge *edge)
-+{
-+ if ((CHECK_FLAG(edge->attributes->flags, LS_ATTR_LOCAL_ID) &&
-+ CHECK_FLAG(edge->attributes->flags, LS_ATTR_NEIGH_ID)))
-+ return true;
-+
-+ if (CHECK_FLAG(edge->attributes->flags, LS_ATTR_LOCAL_ADDR) &&
-+ CHECK_FLAG(edge->attributes->flags, LS_ATTR_NEIGH_ADDR))
-+ return true;
-+
-+ if (CHECK_FLAG(edge->attributes->flags, LS_ATTR_LOCAL_ADDR6) &&
-+ CHECK_FLAG(edge->attributes->flags, LS_ATTR_NEIGH_ADDR6) &&
-+ !IN6_IS_ADDR_LINKLOCAL(&edge->attributes->standard.local6) &&
-+ !IN6_IS_ADDR_LINKLOCAL(&edge->attributes->standard.remote6))
-+ return true;
-+
-+ return false;
-+}
-+
-+/*
-+ * Originate Node NLRI from IGP router information
-+ *
-+ * This function creates a BGP-LS Node NLRI from IGP router data
-+ * and installs it in the RIB for advertisement to BGP-LS peers.
-+ */
-+int bgp_ls_originate_node(struct bgp *bgp, uint8_t protocol_id, uint8_t *router_id,
-+ uint16_t router_id_len, uint32_t area_id, struct ls_vertex *vertex)
-+{
-+ struct bgp_ls_nlri nlri;
-+ struct bgp_ls_attr *ls_attr = NULL;
-+ int ret;
-+
-+ if (!bgp || !router_id)
-+ return -1;
-+
-+ if (!vertex || !vertex->node)
-+ return -1;
-+
-+ /* Validate router ID length */
-+ if (router_id_len < BGP_LS_IGP_ROUTER_ID_MIN_SIZE ||
-+ router_id_len > BGP_LS_IGP_ROUTER_ID_MAX_SIZE) {
-+ flog_err(EC_BGP_LS_PACKET, "BGP-LS: Invalid router ID length %u", router_id_len);
-+ return -1;
-+ }
-+
-+ /* Clear NLRI structure */
-+ memset(&nlri, 0, sizeof(nlri));
-+
-+ /* Build Node NLRI */
-+ nlri.nlri_type = BGP_LS_NLRI_TYPE_NODE;
-+ nlri.nlri_data.node.protocol_id = protocol_id;
-+ nlri.nlri_data.node.identifier = 0; /* Instance ID, use 0 for default */
-+
-+ /* 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);
-+ 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;
-+ 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;
-+ 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 */
-+ ls_attr = bgp_ls_attr_alloc();
-+ if (bgp_ls_populate_node_attr(vertex->node, ls_attr) < 0) {
-+ zlog_warn("BGP-LS: Failed to populate Node attributes");
-+ bgp_ls_attr_free(ls_attr);
-+ return -1;
-+ }
-+
-+ /* Install in RIB */
-+ ret = bgp_ls_update(bgp, &nlri, ls_attr);
-+ if (ret < 0) {
-+ flog_err(EC_BGP_LS_PACKET, "BGP-LS: Failed to originate Node NLRI");
-+ bgp_ls_attr_free(ls_attr);
-+ return -1;
-+ }
-+
-+ if (BGP_DEBUG(linkstate, LINKSTATE))
-+ zlog_debug("BGP-LS: Originated Node NLRI for protocol %u", protocol_id);
-+
-+ bgp_ls_attr_free(ls_attr);
-+
-+ return 0;
-+}
-+
-+
-+int bgp_ls_withdraw_node(struct bgp *bgp, uint8_t protocol_id, uint8_t *router_id,
-+ uint16_t router_id_len, uint32_t area_id, struct ls_vertex *vertex)
-+{
-+ struct bgp_ls_nlri nlri;
-+ int ret;
-+
-+ if (!bgp || !router_id)
-+ return -1;
-+
-+ if (!vertex || !vertex->node)
-+ return -1;
-+
-+ /* Validate router ID length */
-+ if (router_id_len < BGP_LS_IGP_ROUTER_ID_MIN_SIZE ||
-+ router_id_len > BGP_LS_IGP_ROUTER_ID_MAX_SIZE) {
-+ flog_err(EC_BGP_LS_PACKET, "BGP-LS: Invalid router ID length %u", router_id_len);
-+ return -1;
-+ }
-+
-+ /* Clear NLRI structure */
-+ memset(&nlri, 0, sizeof(nlri));
-+
-+ /* Build Node NLRI */
-+ nlri.nlri_type = BGP_LS_NLRI_TYPE_NODE;
-+ nlri.nlri_data.node.protocol_id = protocol_id;
-+ nlri.nlri_data.node.identifier = 0; /* Instance ID, use 0 for default */
-+
-+ /* 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);
-+ 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;
-+ 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;
-+ SET_FLAG(nlri.nlri_data.node.local_node.present_tlvs,
-+ BGP_LS_NODE_DESC_OSPF_AREA_BIT);
-+ }
-+
-+ /* Withdraw from RIB */
-+ ret = bgp_ls_withdraw(bgp, &nlri);
-+ if (ret < 0) {
-+ flog_err(EC_BGP_LS_PACKET, "BGP-LS: Failed to withdraw Node NLRI");
-+ return -1;
-+ }
-+
-+ if (BGP_DEBUG(linkstate, LINKSTATE))
-+ zlog_debug("BGP-LS: Withdrawn Node NLRI for protocol %u", protocol_id);
-+
-+ return 0;
-+}
-+
-+
-+/*
-+ * Originate Link NLRI from IGP adjacency information
-+ *
-+ * This function creates a BGP-LS Link NLRI from IGP adjacency data
-+ * and installs it in the RIB for advertisement to BGP-LS peers.
-+ */
-+int bgp_ls_originate_link(struct bgp *bgp, uint8_t protocol_id, uint8_t *local_router_id,
-+ uint16_t local_router_id_len, uint8_t *remote_router_id,
-+ uint16_t remote_router_id_len, uint32_t area_id, struct ls_edge *edge)
-+{
-+ struct bgp_ls_nlri nlri;
-+ struct bgp_ls_attr *ls_attr = NULL;
-+ int ret;
-+
-+ if (!bgp || !local_router_id || !remote_router_id)
-+ return -1;
-+
-+ if (!edge || !edge->attributes)
-+ return -1;
-+
-+ /* Validate router ID lengths */
-+ if (local_router_id_len < BGP_LS_IGP_ROUTER_ID_MIN_SIZE ||
-+ local_router_id_len > BGP_LS_IGP_ROUTER_ID_MAX_SIZE ||
-+ remote_router_id_len < BGP_LS_IGP_ROUTER_ID_MIN_SIZE ||
-+ remote_router_id_len > BGP_LS_IGP_ROUTER_ID_MAX_SIZE) {
-+ flog_err(EC_BGP_LS_PACKET, "BGP-LS: Invalid router ID lengths");
-+ return -1;
-+ }
-+
-+ /* Clear NLRI structure */
-+ memset(&nlri, 0, sizeof(nlri));
-+
-+ /* Build Link NLRI */
-+ nlri.nlri_type = BGP_LS_NLRI_TYPE_LINK;
-+ nlri.nlri_data.link.protocol_id = protocol_id;
-+ nlri.nlri_data.link.identifier = 0; /* Instance ID, use 0 for default */
-+
-+ /* 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);
-+ 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;
-+ 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);
-+ 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;
-+ 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;
-+ 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;
-+ 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;
-+ 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;
-+ 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;
-+ 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;
-+ 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;
-+ 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;
-+ 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;
-+ 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 */
-+ ls_attr = bgp_ls_attr_alloc();
-+ if (bgp_ls_populate_link_attr(edge->attributes, ls_attr, protocol_id) < 0) {
-+ zlog_warn("BGP-LS: Failed to populate Link attributes");
-+ bgp_ls_attr_free(ls_attr);
-+ return -1;
-+ }
-+
-+ /* Install in RIB */
-+ ret = bgp_ls_update(bgp, &nlri, ls_attr);
-+ if (ret < 0) {
-+ flog_err(EC_BGP_LS_PACKET, "BGP-LS: Failed to originate Link NLRI");
-+ bgp_ls_attr_free(ls_attr);
-+ return -1;
-+ }
-+
-+ if (BGP_DEBUG(linkstate, LINKSTATE))
-+ zlog_debug("BGP-LS: Originated Link NLRI for protocol %u", protocol_id);
-+
-+ bgp_ls_attr_free(ls_attr);
-+
-+ return 0;
-+}
-+
-+
-+int bgp_ls_withdraw_link(struct bgp *bgp, uint8_t protocol_id, uint8_t *local_router_id,
-+ uint16_t local_router_id_len, uint8_t *remote_router_id,
-+ uint16_t remote_router_id_len, uint32_t area_id, struct ls_edge *edge)
-+{
-+ struct bgp_ls_nlri nlri;
-+ int ret;
-+
-+ if (!bgp || !local_router_id || !remote_router_id)
-+ return -1;
-+
-+ if (!edge || !edge->attributes)
-+ return -1;
-+
-+ /* Validate router ID lengths */
-+ if (local_router_id_len < BGP_LS_IGP_ROUTER_ID_MIN_SIZE ||
-+ local_router_id_len > BGP_LS_IGP_ROUTER_ID_MAX_SIZE ||
-+ remote_router_id_len < BGP_LS_IGP_ROUTER_ID_MIN_SIZE ||
-+ remote_router_id_len > BGP_LS_IGP_ROUTER_ID_MAX_SIZE) {
-+ flog_err(EC_BGP_LS_PACKET, "BGP-LS: Invalid router ID lengths");
-+ return -1;
-+ }
-+
-+ /* Clear NLRI structure */
-+ memset(&nlri, 0, sizeof(nlri));
-+
-+ /* Build Link NLRI */
-+ nlri.nlri_type = BGP_LS_NLRI_TYPE_LINK;
-+ nlri.nlri_data.link.protocol_id = protocol_id;
-+ nlri.nlri_data.link.identifier = 0; /* Instance ID, use 0 for default */
-+
-+ /* 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);
-+ 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;
-+ 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);
-+ 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;
-+ 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;
-+ 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;
-+ 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;
-+ 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;
-+ 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;
-+ 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;
-+ 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;
-+ 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;
-+ 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;
-+ SET_FLAG(nlri.nlri_data.link.link_desc.present_tlvs,
-+ BGP_LS_LINK_DESC_REMOTE_AS_BIT);
-+ }
-+
-+ /* Withdraw from RIB */
-+ ret = bgp_ls_withdraw(bgp, &nlri);
-+ if (ret < 0) {
-+ flog_err(EC_BGP_LS_PACKET, "BGP-LS: Failed to withdraw Link NLRI");
-+ return -1;
-+ }
-+
-+ if (BGP_DEBUG(linkstate, LINKSTATE))
-+ zlog_debug("BGP-LS: Withdrawn Link NLRI for protocol %u", protocol_id);
-+
-+ return 0;
-+}
-+
-+
-+/*
-+ * Originate Prefix NLRI from IGP prefix information
-+ *
-+ * This function creates a BGP-LS Prefix NLRI from IGP prefix data
-+ * and installs it in the RIB for advertisement to BGP-LS peers.
-+ */
-+int bgp_ls_originate_prefix(struct bgp *bgp, uint8_t protocol_id, uint8_t *router_id,
-+ uint16_t router_id_len, struct prefix *prefix, uint32_t area_id,
-+ struct ls_subnet *subnet)
-+{
-+ struct bgp_ls_nlri nlri;
-+ struct bgp_ls_attr *ls_attr = NULL;
-+ int ret;
-+
-+ if (!bgp || !router_id || !prefix)
-+ return -1;
-+
-+ if (!subnet || !subnet->ls_pref)
-+ return -1;
-+
-+ /* Validate router ID length */
-+ if (router_id_len < BGP_LS_IGP_ROUTER_ID_MIN_SIZE ||
-+ router_id_len > BGP_LS_IGP_ROUTER_ID_MAX_SIZE) {
-+ flog_err(EC_BGP_LS_PACKET, "BGP-LS: Invalid router ID length %u", router_id_len);
-+ return -1;
-+ }
-+
-+ /* Clear NLRI structure */
-+ memset(&nlri, 0, sizeof(nlri));
-+
-+ /* Determine NLRI type based on prefix family */
-+ if (prefix->family == AF_INET)
-+ nlri.nlri_type = BGP_LS_NLRI_TYPE_IPV4_PREFIX;
-+ else if (prefix->family == AF_INET6)
-+ nlri.nlri_type = BGP_LS_NLRI_TYPE_IPV6_PREFIX;
-+ else {
-+ flog_err(EC_BGP_LS_PACKET, "BGP-LS: Unsupported prefix family %u", prefix->family);
-+ return -1;
-+ }
-+
-+ /* Build Prefix NLRI */
-+ nlri.nlri_data.prefix.protocol_id = protocol_id;
-+ nlri.nlri_data.prefix.identifier = 0; /* Instance ID, use 0 for default */
-+
-+ /* 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);
-+ 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;
-+ 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;
-+ 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);
-+ 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();
-+ if (bgp_ls_populate_prefix_attr(subnet->ls_pref, ls_attr) < 0) {
-+ zlog_warn("BGP-LS: Failed to populate Prefix attributes");
-+ bgp_ls_attr_free(ls_attr);
-+ return -1;
-+ }
-+
-+ /* Install in RIB */
-+ ret = bgp_ls_update(bgp, &nlri, ls_attr);
-+ if (ret < 0) {
-+ flog_err(EC_BGP_LS_PACKET, "BGP-LS: Failed to originate Prefix NLRI");
-+ bgp_ls_attr_free(ls_attr);
-+ return -1;
-+ }
-+
-+ if (BGP_DEBUG(linkstate, LINKSTATE))
-+ zlog_debug("BGP-LS: Originated Prefix NLRI %pFX for protocol %u", prefix,
-+ protocol_id);
-+
-+ bgp_ls_attr_free(ls_attr);
-+
-+ return 0;
-+}
-+
-+
-+int bgp_ls_withdraw_prefix(struct bgp *bgp, uint8_t protocol_id, uint8_t *router_id,
-+ uint16_t router_id_len, struct prefix *prefix, uint32_t area_id,
-+ struct ls_subnet *subnet)
-+{
-+ struct bgp_ls_nlri nlri;
-+ int ret;
-+
-+ if (!bgp || !router_id || !prefix)
-+ return -1;
-+
-+ if (!subnet || !subnet->ls_pref)
-+ return -1;
-+
-+ /* Validate router ID length */
-+ if (router_id_len < BGP_LS_IGP_ROUTER_ID_MIN_SIZE ||
-+ router_id_len > BGP_LS_IGP_ROUTER_ID_MAX_SIZE) {
-+ flog_err(EC_BGP_LS_PACKET, "BGP-LS: Invalid router ID length %u", router_id_len);
-+ return -1;
-+ }
-+
-+ /* Clear NLRI structure */
-+ memset(&nlri, 0, sizeof(nlri));
-+
-+ /* Determine NLRI type based on prefix family */
-+ if (prefix->family == AF_INET)
-+ nlri.nlri_type = BGP_LS_NLRI_TYPE_IPV4_PREFIX;
-+ else if (prefix->family == AF_INET6)
-+ nlri.nlri_type = BGP_LS_NLRI_TYPE_IPV6_PREFIX;
-+ else {
-+ flog_err(EC_BGP_LS_PACKET, "BGP-LS: Unsupported prefix family %u", prefix->family);
-+ return -1;
-+ }
-+
-+ /* Build Prefix NLRI */
-+ nlri.nlri_data.prefix.protocol_id = protocol_id;
-+ nlri.nlri_data.prefix.identifier = 0; /* Instance ID, use 0 for default */
-+
-+ /* 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);
-+ 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;
-+ 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;
-+ 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;
-+ 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);
-+ if (ret < 0) {
-+ flog_err(EC_BGP_LS_PACKET, "BGP-LS: Failed to withdraw Prefix NLRI");
-+ return -1;
-+ }
-+
-+ if (BGP_DEBUG(linkstate, LINKSTATE))
-+ zlog_debug("BGP-LS: Withdrawn Prefix NLRI %pFX for protocol %u", prefix,
-+ protocol_id);
-+
-+ return 0;
-+}
-+
-+/*
-+ * ===========================================================================
-+ * Link State Message Processing
-+ * ===========================================================================
-+ */
-+
-+/*
-+ * Map Link State origin to BGP-LS Protocol-ID
-+ */
-+static enum bgp_ls_protocol_id ls_origin_to_protocol_id(enum ls_origin origin)
-+{
-+ switch (origin) {
-+ case ISIS_L1:
-+ return BGP_LS_PROTO_ISIS_L1;
-+ case ISIS_L2:
-+ return BGP_LS_PROTO_ISIS_L2;
-+ case OSPFv2:
-+ return BGP_LS_PROTO_OSPFV2;
-+ case DIRECT:
-+ return BGP_LS_PROTO_DIRECT;
-+ case STATIC:
-+ return BGP_LS_PROTO_STATIC;
-+ case LS_UNKNOWN:
-+ default:
-+ return BGP_LS_PROTO_RESERVED;
-+ }
-+}
-+
-+/*
-+ * Process Link State vertex and originate/withdraw BGP-LS Node NLRI
-+ */
-+int bgp_ls_process_vertex(struct bgp *bgp, struct ls_vertex *vertex, uint8_t event)
-+{
-+ enum bgp_ls_protocol_id protocol_id;
-+ uint8_t router_id[BGP_LS_IGP_ROUTER_ID_MAX_SIZE];
-+ uint16_t router_id_len = 0;
-+ uint32_t area_id = 0;
-+
-+ if (!bgp || !vertex || !vertex->node)
-+ return -1;
-+
-+ protocol_id = ls_origin_to_protocol_id(vertex->node->adv.origin);
-+
-+ switch (protocol_id) {
-+ case BGP_LS_PROTO_OSPFV2:
-+ /* OSPF uses 4-byte IPv4 router ID */
-+ memcpy(router_id, &vertex->node->adv.id.ip.addr.s_addr, 4);
-+ router_id_len = 4;
-+ area_id = ntohl(vertex->node->adv.id.ip.area_id.s_addr);
-+ break;
-+ case BGP_LS_PROTO_ISIS_L1:
-+ case BGP_LS_PROTO_ISIS_L2:
-+ /* IS-IS uses 6-byte System ID + 1 byte pseudonode ID = 7 bytes */
-+ memcpy(router_id, vertex->node->adv.id.iso.sys_id, ISO_SYS_ID_LEN);
-+ // router_id[6] = 0; /* Pseudonode ID = 0 for router */
-+ // router_id_len = 7;
-+ router_id_len = 6;
-+ break;
-+ case BGP_LS_PROTO_DIRECT:
-+ case BGP_LS_PROTO_STATIC:
-+ case BGP_LS_PROTO_OSPFV3:
-+ case BGP_LS_PROTO_BGP:
-+ case BGP_LS_PROTO_RESERVED:
-+ zlog_err("BGP-LS: Unsupported protocol %u", protocol_id);
-+ return -1;
-+ }
-+
-+ switch (event) {
-+ case LS_MSG_EVENT_SYNC:
-+ case LS_MSG_EVENT_ADD:
-+ case LS_MSG_EVENT_UPDATE:
-+ return bgp_ls_originate_node(bgp, protocol_id, router_id, router_id_len, area_id,
-+ vertex);
-+
-+ case LS_MSG_EVENT_DELETE:
-+ return bgp_ls_withdraw_node(bgp, protocol_id, router_id, router_id_len, area_id,
-+ vertex);
-+
-+ default:
-+ zlog_warn("BGP-LS: Unknown event type %u for vertex", event);
-+ return -1;
-+ }
-+}
-+
-+/*
-+ * Process Link State edge and originate/withdraw BGP-LS Link NLRI
-+ */
-+int bgp_ls_process_edge(struct bgp *bgp, struct ls_edge *edge, uint8_t event)
-+{
-+ uint8_t protocol_id;
-+ uint8_t local_router_id[BGP_LS_IGP_ROUTER_ID_MAX_SIZE];
-+ uint8_t remote_router_id[BGP_LS_IGP_ROUTER_ID_MAX_SIZE];
-+ uint16_t local_router_id_len = 0;
-+ uint16_t remote_router_id_len = 0;
-+ uint32_t area_id = 0;
-+
-+ if (!bgp || !edge || !edge->attributes)
-+ return -1;
-+
-+ /* Map origin to protocol ID */
-+ protocol_id = ls_origin_to_protocol_id(edge->attributes->adv.origin);
-+
-+ /* Extract router IDs from source and destination vertices */
-+ if (!edge->source || !edge->destination) {
-+ flog_err(EC_BGP_LS_PACKET, "BGP-LS: Edge missing source or destination vertex");
-+ return -1;
-+ }
-+
-+ if (!bgp_ls_link_valid(edge))
-+ return -1;
-+
-+ switch (protocol_id) {
-+ case BGP_LS_PROTO_OSPFV2:
-+ /* OSPF uses 4-byte IPv4 router ID */
-+ memcpy(local_router_id, &edge->source->node->adv.id.ip.addr.s_addr, 4);
-+ local_router_id_len = 4;
-+
-+ memcpy(remote_router_id, &edge->destination->node->adv.id.ip.addr.s_addr, 4);
-+ remote_router_id_len = 4;
-+
-+ area_id = ntohl(edge->attributes->adv.id.ip.area_id.s_addr);
-+ break;
-+ case BGP_LS_PROTO_ISIS_L1:
-+ case BGP_LS_PROTO_ISIS_L2:
-+ /* IS-IS non-pseudonode uses 6-byte System ID */
-+ memcpy(local_router_id, edge->source->node->adv.id.iso.sys_id, ISO_SYS_ID_LEN);
-+ local_router_id_len = 6;
-+
-+ memcpy(remote_router_id, edge->destination->node->adv.id.iso.sys_id,
-+ ISO_SYS_ID_LEN);
-+ remote_router_id_len = 6;
-+ break;
-+ case BGP_LS_PROTO_DIRECT:
-+ case BGP_LS_PROTO_STATIC:
-+ case BGP_LS_PROTO_OSPFV3:
-+ case BGP_LS_PROTO_BGP:
-+ case BGP_LS_PROTO_RESERVED:
-+ zlog_err("BGP-LS: Unsupported protocol %u", protocol_id);
-+ return -1;
-+ }
-+
-+ switch (event) {
-+ case LS_MSG_EVENT_SYNC:
-+ case LS_MSG_EVENT_ADD:
-+ case LS_MSG_EVENT_UPDATE:
-+ return bgp_ls_originate_link(bgp, protocol_id, local_router_id,
-+ local_router_id_len, remote_router_id,
-+ remote_router_id_len, area_id, edge);
-+
-+ case LS_MSG_EVENT_DELETE:
-+ return bgp_ls_withdraw_link(bgp, protocol_id, local_router_id, local_router_id_len,
-+ remote_router_id, remote_router_id_len, area_id, edge);
-+
-+ default:
-+ zlog_warn("BGP-LS: Unknown event type %u for edge", event);
-+ return -1;
-+ }
-+}
-+
-+/*
-+ * Process Link State subnet and originate/withdraw BGP-LS Prefix NLRI
-+ */
-+int bgp_ls_process_subnet(struct bgp *bgp, struct ls_subnet *subnet, uint8_t event)
-+{
-+ uint8_t protocol_id;
-+ uint8_t router_id[BGP_LS_IGP_ROUTER_ID_MAX_SIZE];
-+ uint16_t router_id_len = 0;
-+ uint32_t area_id = 0;
-+
-+ if (!bgp || !subnet || !subnet->ls_pref)
-+ return -1;
-+
-+ protocol_id = ls_origin_to_protocol_id(subnet->ls_pref->adv.origin);
-+
-+ /* Extract router ID from advertising vertex */
-+ if (!subnet->vertex) {
-+ flog_err(EC_BGP_LS_PACKET, "BGP-LS: Subnet missing advertising vertex");
-+ return -1;
-+ }
-+
-+ switch (protocol_id) {
-+ case BGP_LS_PROTO_OSPFV2:
-+ /* OSPF uses 4-byte IPv4 router ID */
-+ memcpy(router_id, &subnet->vertex->node->adv.id.ip.addr.s_addr, 4);
-+ router_id_len = 4;
-+ area_id = ntohl(subnet->ls_pref->adv.id.ip.area_id.s_addr);
-+ break;
-+ case BGP_LS_PROTO_ISIS_L1:
-+ case BGP_LS_PROTO_ISIS_L2:
-+ /* IS-IS uses 6-byte System ID + 1 byte pseudonode ID = 7 bytes */
-+ memcpy(router_id, subnet->vertex->node->adv.id.iso.sys_id, ISO_SYS_ID_LEN);
-+ // router_id[6] = 0; /* Pseudonode ID = 0 for router */
-+ // router_id_len = 7;
-+ router_id_len = 6;
-+ break;
-+ case BGP_LS_PROTO_DIRECT:
-+ case BGP_LS_PROTO_STATIC:
-+ case BGP_LS_PROTO_OSPFV3:
-+ case BGP_LS_PROTO_BGP:
-+ case BGP_LS_PROTO_RESERVED:
-+ zlog_err("BGP-LS: Unsupported protocol %u", protocol_id);
-+ return -1;
-+ }
-+
-+ switch (event) {
-+ case LS_MSG_EVENT_SYNC:
-+ case LS_MSG_EVENT_ADD:
-+ case LS_MSG_EVENT_UPDATE:
-+ return bgp_ls_originate_prefix(bgp, protocol_id, router_id, router_id_len,
-+ &subnet->key, area_id, subnet);
-+
-+ case LS_MSG_EVENT_DELETE:
-+ return bgp_ls_withdraw_prefix(bgp, protocol_id, router_id, router_id_len,
-+ &subnet->key, area_id, subnet);
-+
-+ default:
-+ zlog_warn("BGP-LS: Unknown event type %u for subnet", event);
-+ return -1;
-+ }
-+}
-+
-+/*
-+ * ===========================================================================
-+ * Link-State Message Processing from Zebra
-+ * ===========================================================================
-+ */
-+
-+/*
-+ * Process link-state message and update TED
-+ *
-+ * Converts message to TED structure (vertex/edge/subnet) and triggers
-+ * BGP-LS route origination.
-+ */
-+int bgp_ls_process_message(struct bgp *bgp, struct ls_message *msg)
-+{
-+ struct ls_vertex *vertex;
-+ struct ls_edge *edge;
-+ struct ls_edge *reverse_edge = NULL;
-+ bool reverse_edge_dst_updated = false;
-+ struct ls_subnet *subnet;
-+
-+ if (!bgp || !bgp->ls_info || !bgp->ls_info->ted || !msg)
-+ return -1;
-+
-+ if (BGP_DEBUG(zebra, ZEBRA) || BGP_DEBUG(linkstate, LINKSTATE)) {
-+ const char *type_str = msg->type == LS_MSG_TYPE_NODE ? "NODE"
-+ : msg->type == LS_MSG_TYPE_ATTRIBUTES ? "ATTR"
-+ : "PREFIX";
-+ const char *event_str = msg->event == LS_MSG_EVENT_ADD ? "ADD"
-+ : msg->event == LS_MSG_EVENT_UPDATE ? "UPDATE"
-+ : "DELETE";
-+ zlog_debug("%s: Processing %s %s", __func__, type_str, event_str);
-+ }
-+
-+ switch (msg->type) {
-+ case LS_MSG_TYPE_NODE:
-+ vertex = ls_msg2vertex(bgp->ls_info->ted, msg, false);
-+ if (!vertex) {
-+ zlog_err("%s: Failed to convert message to vertex", __func__);
-+ return -1;
-+ }
-+
-+ if (BGP_DEBUG(zebra, ZEBRA) || BGP_DEBUG(linkstate, LINKSTATE))
-+ zlog_debug("%s: Node vertex key=%" PRIu64, __func__, vertex->key);
-+
-+ bgp_ls_process_vertex(bgp, vertex, msg->event);
-+
-+ if (msg->event == LS_MSG_EVENT_DELETE)
-+ ls_vertex_del_all(bgp->ls_info->ted, vertex);
-+
-+ break;
-+
-+ case LS_MSG_TYPE_ATTRIBUTES:
-+ edge = ls_msg2edge(bgp->ls_info->ted, msg, false);
-+ if (!edge) {
-+ zlog_err("%s: Failed to convert message to edge", __func__);
-+ return -1;
-+ }
-+
-+ if (BGP_DEBUG(zebra, ZEBRA) || BGP_DEBUG(linkstate, LINKSTATE))
-+ zlog_debug("%s: Link edge", __func__);
-+
-+ if (msg->event == LS_MSG_EVENT_SYNC || msg->event == LS_MSG_EVENT_ADD ||
-+ msg->event == LS_MSG_EVENT_UPDATE) {
-+ /* Search for the reverse edge and link both directions. */
-+ reverse_edge = ls_find_edge_by_destination(bgp->ls_info->ted,
-+ edge->attributes);
-+ if (reverse_edge) {
-+ /* Attach destination to reverse edge if missing. */
-+ if (reverse_edge->destination == NULL && edge->source) {
-+ vertex = edge->source;
-+ listnode_add_sort_nodup(vertex->incoming_edges,
-+ reverse_edge);
-+ reverse_edge->destination = vertex;
-+ reverse_edge_dst_updated = true;
-+ }
-+ /* Attach destination to this edge if missing. */
-+ if (edge->destination == NULL && reverse_edge->source) {
-+ vertex = reverse_edge->source;
-+ listnode_add_sort_nodup(vertex->incoming_edges, edge);
-+ edge->destination = vertex;
-+ }
-+ }
-+
-+ if (!edge->destination) {
-+ /*
-+ * An ADD for edge A->B may arrive before the reverse edge B->A
-+ * exists in TED. In that case edge->destination is NULL, so we
-+ * cannot originate the link yet and skip it for now. When B->A is
-+ * later added, its ADD/UPDATE processing will also look up and
-+ * process A->B.
-+ */
-+ if (BGP_DEBUG(zebra, ZEBRA) || BGP_DEBUG(linkstate, LINKSTATE))
-+ zlog_debug("%s: Skip edge add/update without destination",
-+ __func__);
-+ break;
-+ }
-+
-+ bgp_ls_process_edge(bgp, edge, msg->event);
-+
-+ /*
-+ * After we process edge A->B, check whether reverse edge B->A is
-+ * already in TED and process it. This originates the Link NLRI for
-+ * the direction that was previously skipped when A->B lacked a
-+ * destination.
-+ */
-+ if (reverse_edge &&
-+ (msg->event == LS_MSG_EVENT_SYNC || msg->event == LS_MSG_EVENT_ADD ||
-+ reverse_edge_dst_updated)) {
-+ uint8_t reverse_event = msg->event;
-+
-+ if (msg->event == LS_MSG_EVENT_UPDATE && reverse_edge_dst_updated)
-+ reverse_event = LS_MSG_EVENT_ADD;
-+
-+ bgp_ls_process_edge(bgp, reverse_edge, reverse_event);
-+ } else if (!reverse_edge &&
-+ (BGP_DEBUG(zebra, ZEBRA) || BGP_DEBUG(linkstate, LINKSTATE)))
-+ zlog_debug("%s: Reverse edge not yet in TED, will be processed on arrival",
-+ __func__);
-+
-+ } else if (msg->event == LS_MSG_EVENT_DELETE) {
-+ bgp_ls_process_edge(bgp, edge, msg->event);
-+ ls_edge_del_all(bgp->ls_info->ted, edge);
-+ } else {
-+ if (BGP_DEBUG(zebra, ZEBRA) || BGP_DEBUG(linkstate, LINKSTATE))
-+ zlog_debug("%s: Unknown event type %u for edge", __func__,
-+ msg->event);
-+ }
-+
-+ break;
-+
-+ case LS_MSG_TYPE_PREFIX:
-+ subnet = ls_msg2subnet(bgp->ls_info->ted, msg, false);
-+ if (!subnet) {
-+ zlog_err("%s: Failed to convert message to subnet", __func__);
-+ return -1;
-+ }
-+
-+ if (BGP_DEBUG(zebra, ZEBRA) || BGP_DEBUG(linkstate, LINKSTATE)) {
-+ char buf[PREFIX2STR_BUFFER];
-+
-+ prefix2str(&subnet->key, buf, sizeof(buf));
-+ zlog_debug("%s: Prefix %s", __func__, buf);
-+ }
-+
-+ bgp_ls_process_subnet(bgp, subnet, msg->event);
-+
-+ if (msg->event == LS_MSG_EVENT_DELETE)
-+ ls_subnet_del_all(bgp->ls_info->ted, subnet);
-+
-+ break;
-+
-+ default:
-+ zlog_warn("%s: Unknown message type %d", __func__, msg->type);
-+ return -1;
-+ }
-+
-+ return 0;
-+}
-+
-+/*
-+ * Remove all entries from the TED.
-+ */
-+static void bgp_ls_ted_clear(struct ls_ted *ted)
-+{
-+ struct ls_vertex *vertex;
-+ struct ls_edge *edge;
-+ struct ls_subnet *subnet;
-+
-+ frr_each_safe (vertices, &ted->vertices, vertex)
-+ ls_vertex_del_all(ted, vertex);
-+ frr_each_safe (edges, &ted->edges, edge)
-+ ls_edge_del_all(ted, edge);
-+ frr_each_safe (subnets, &ted->subnets, subnet)
-+ ls_subnet_del_all(ted, subnet);
-+}
-+
-+/*
-+ * Withdraw all locally originated BGP-LS routes and reset the TED.
-+ *
-+ * Called when the last BGP-LS peer is deactivated: performs a single bulk
-+ * walk of the BGP-LS RIB via bgp_clear_route() to remove all self-originated
-+ * paths at once, then clears all TED entries.
-+ *
-+ * @param bgp - BGP instance
-+ */
-+void bgp_ls_withdraw_ted(struct bgp *bgp)
-+{
-+ if (!bgp || !bgp->ls_info || !bgp->ls_info->ted)
-+ return;
-+
-+ if (BGP_DEBUG(linkstate, LINKSTATE))
-+ zlog_debug("BGP-LS: Withdrawing all locally originated routes and resetting TED");
-+
-+ /* Remove all self-originated BGP-LS paths from the RIB */
-+ bgp_clear_route(bgp->peer_self, AFI_BGP_LS, SAFI_BGP_LS);
-+
-+ /* Clear all TED entries */
-+ bgp_ls_ted_clear(bgp->ls_info->ted);
-+
-+ zlog_info("BGP-LS: All locally originated BGP-LS routes withdrawn and TED reset");
-+}
-+
-+/*
-+ * Handle link-state SYNC/UPDATE messages from zebra
-+ */
-+int bgp_ls_process_linkstate_message(struct stream *s, uint8_t msg_type)
-+{
-+ struct ls_message *msg;
-+ struct bgp *bgp;
-+ int ret;
-+
-+ bgp = bgp_get_default();
-+ if (!bgp || !bgp->ls_info || !bgp->ls_info->ted) {
-+ zlog_warn("%s: TED not initialized, ignoring link-state message", __func__);
-+ return 0;
-+ }
-+
-+ msg = ls_parse_msg(s);
-+ if (!msg) {
-+ zlog_err("%s: Failed to parse link-state message", __func__);
-+ return -1;
-+ }
-+
-+ if (BGP_DEBUG(zebra, ZEBRA) || BGP_DEBUG(linkstate, LINKSTATE))
-+ zlog_debug("%s: Received link-state %s message", __func__,
-+ msg_type == LINK_STATE_UPDATE ? "UPDATE" : "SYNC");
-+
-+ ret = bgp_ls_process_message(bgp, msg);
-+ ls_delete_msg(msg);
-+
-+ return ret;
-+}
-diff --git a/bgpd/bgp_ls_ted.h b/bgpd/bgp_ls_ted.h
-new file mode 100644
-index 0000000000..9404523c13
---- /dev/null
-+++ b/bgpd/bgp_ls_ted.h
-@@ -0,0 +1,226 @@
-+// SPDX-License-Identifier: GPL-2.0-or-later
-+/*
-+ * BGP Link-State Traffic Engineering Database (RFC 9552)
-+ * Copyright (C) 2025 Carmine Scarpitta
-+ *
-+ * This file implements the BGP-LS TED management functions for storing
-+ * topology information learned from BGP-LS routes. The TED provides a
-+ * graph-based view of the network for applications (PCE, SDN controllers).
-+ */
-+
-+#ifndef _FRR_BGP_LS_TED_H
-+#define _FRR_BGP_LS_TED_H
-+
-+#include "bgpd/bgp_ls_nlri.h"
-+
-+#define UNKNOWN LS_UNKNOWN
-+#include "lib/link_state.h"
-+#undef UNKNOWN
-+
-+/* Forward declarations */
-+struct bgp;
-+struct bgp_ls_nlri;
-+
-+/*
-+ * Populate BGP-LS Attributes from Link State Node
-+ *
-+ * @param ls_node Link State node from TED
-+ * @param attr BGP-LS node attribute structure to populate
-+ * @return 0 on success, -1 on error
-+ */
-+extern int bgp_ls_populate_node_attr(struct ls_node *ls_node, struct bgp_ls_attr *attr);
-+
-+/*
-+ * Populate BGP-LS Attributes from Link State Attributes
-+ *
-+ * @param ls_attr Link State attributes from TED
-+ * @param attr BGP-LS link attribute structure to populate
-+ * @param protocol_id - IGP protocol (ISIS/OSPF)
-+ * @return 0 on success, -1 on error
-+ */
-+extern int bgp_ls_populate_link_attr(struct ls_attributes *ls_attr, struct bgp_ls_attr *attr,
-+ enum bgp_ls_protocol_id protocol_id);
-+
-+/*
-+ * Populate BGP-LS Attributes from Link State Prefix
-+ *
-+ * @param ls_prefix Link State prefix from TED
-+ * @param attr BGP-LS prefix attribute structure to populate
-+ * @return 0 on success, -1 on error
-+ */
-+extern int bgp_ls_populate_prefix_attr(struct ls_prefix *ls_prefix, struct bgp_ls_attr *attr);
-+
-+/*
-+ * ===========================================================================
-+ * IGP Origination Functions
-+ * ===========================================================================
-+ */
-+
-+/*
-+ * Originate Node NLRI from IGP router information
-+ *
-+ * Creates a BGP-LS Node NLRI from IGP router advertisements
-+ * (ISIS LSP or OSPF Router-LSA) and installs it in the RIB.
-+ *
-+ * @param bgp - BGP instance
-+ * @param protocol_id - IGP protocol (ISIS/OSPF)
-+ * @param router_id - IGP router ID
-+ * @param router_id_len - Length of router ID
-+ * @param area_id - IGP area/level identifier
-+ * @param vertex - Link State vertex (for attribute population)
-+ * @return 0 on success, -1 on error
-+ */
-+extern int bgp_ls_originate_node(struct bgp *bgp, uint8_t protocol_id, uint8_t *router_id,
-+ uint16_t router_id_len, uint32_t area_id,
-+ struct ls_vertex *vertex);
-+
-+/*
-+ * Originate Link NLRI from IGP adjacency information
-+ *
-+ * Creates a BGP-LS Link NLRI from IGP adjacency advertisements
-+ * (ISIS IS Reachability or OSPF Link-LSA) and installs it in the RIB.
-+ *
-+ * @param bgp - BGP instance
-+ * @param protocol_id - IGP protocol (ISIS/OSPF)
-+ * @param local_router_id - Local router IGP ID
-+ * @param local_router_id_len - Length of local router ID
-+ * @param remote_router_id - Remote router IGP ID
-+ * @param remote_router_id_len - Length of remote router ID
-+ * @param area_id - IGP area/level identifier
-+ * @param edge - Link State edge (for attribute population)
-+ * @return 0 on success, -1 on error
-+ */
-+extern int bgp_ls_originate_link(struct bgp *bgp, uint8_t protocol_id, uint8_t *local_router_id,
-+ uint16_t local_router_id_len, uint8_t *remote_router_id,
-+ uint16_t remote_router_id_len, uint32_t area_id,
-+ struct ls_edge *edge);
-+
-+/*
-+ * Originate Prefix NLRI from IGP prefix information
-+ *
-+ * Creates a BGP-LS Prefix NLRI from IGP prefix advertisements
-+ * (ISIS IP Reachability or OSPF Summary-LSA) and installs it in the RIB.
-+ *
-+ * @param bgp - BGP instance
-+ * @param protocol_id - IGP protocol (ISIS/OSPF)
-+ * @param router_id - Advertising router IGP ID
-+ * @param router_id_len - Length of router ID
-+ * @param prefix - IP prefix
-+ * @param area_id - IGP area/level identifier
-+ * @param subnet - Link State subnet (for attribute population)
-+ * @return 0 on success, -1 on error
-+ */
-+extern int bgp_ls_originate_prefix(struct bgp *bgp, uint8_t protocol_id, uint8_t *router_id,
-+ uint16_t router_id_len, struct prefix *prefix, uint32_t area_id,
-+ struct ls_subnet *subnet);
-+
-+/*
-+ * ===========================================================================
-+ * Link State Message Processing
-+ * ===========================================================================
-+ */
-+
-+/*
-+ * Process Link State vertex (node) and originate BGP-LS Node NLRI
-+ *
-+ * Extracts IGP router information from ls_vertex and calls
-+ * bgp_ls_originate_node() to create and install BGP-LS route.
-+ *
-+ * @param bgp - BGP instance
-+ * @param vertex - Link State vertex from ls_msg2vertex()
-+ * @param event - LS_MSG_EVENT_ADD, UPDATE, or DELETE
-+ * @return 0 on success, -1 on error
-+ */
-+extern int bgp_ls_process_vertex(struct bgp *bgp, struct ls_vertex *vertex, uint8_t event);
-+
-+/*
-+ * Process Link State edge (link) and originate BGP-LS Link NLRI
-+ *
-+ * Extracts IGP adjacency information from ls_edge and calls
-+ * bgp_ls_originate_link() to create and install BGP-LS route.
-+ *
-+ * @param bgp - BGP instance
-+ * @param edge - Link State edge from ls_msg2edge()
-+ * @param event - LS_MSG_EVENT_ADD, UPDATE, or DELETE
-+ * @return 0 on success, -1 on error
-+ */
-+extern int bgp_ls_process_edge(struct bgp *bgp, struct ls_edge *edge, uint8_t event);
-+
-+/*
-+ * Process Link State subnet (prefix) and originate BGP-LS Prefix NLRI
-+ *
-+ * Extracts IGP prefix information from ls_subnet and calls
-+ * bgp_ls_originate_prefix() to create and install BGP-LS route.
-+ *
-+ * @param bgp - BGP instance
-+ * @param subnet - Link State subnet from ls_msg2subnet()
-+ * @param event - LS_MSG_EVENT_ADD, UPDATE, or DELETE
-+ * @return 0 on success, -1 on error
-+ */
-+extern int bgp_ls_process_subnet(struct bgp *bgp, struct ls_subnet *subnet, uint8_t event);
-+
-+/*
-+ * Withdraw Node NLRI from BGP-LS RIB
-+ *
-+ * Removes a BGP-LS Node NLRI from the RIB when the IGP router is removed.
-+ *
-+ * @param bgp - BGP instance
-+ * @param protocol_id - IGP protocol (ISIS/OSPF)
-+ * @param router_id - IGP router ID
-+ * @param router_id_len - Length of router ID
-+ * @param area_id - IGP area/level identifier
-+ * @return 0 on success, -1 on error
-+ */
-+int bgp_ls_withdraw_node(struct bgp *bgp, uint8_t protocol_id, uint8_t *router_id,
-+ uint16_t router_id_len, uint32_t area_id, struct ls_vertex *vertex);
-+
-+int bgp_ls_withdraw_link(struct bgp *bgp, uint8_t protocol_id, uint8_t *local_router_id,
-+ uint16_t local_router_id_len, uint8_t *remote_router_id,
-+ uint16_t remote_router_id_len, uint32_t area_id, struct ls_edge *edge);
-+
-+int bgp_ls_withdraw_prefix(struct bgp *bgp, uint8_t protocol_id, uint8_t *router_id,
-+ uint16_t router_id_len, struct prefix *prefix, uint32_t area_id,
-+ struct ls_subnet *subnet);
-+
-+/*
-+ * ===========================================================================
-+ * Link-State Message Processing
-+ * ===========================================================================
-+ */
-+
-+/*
-+ * Process link-state message from zebra
-+ *
-+ * Handles link-state SYNC/UPDATE messages received from zebra.
-+ * Parses the message and delegates to bgp_ls_process_message().
-+ *
-+ * @param s - Stream containing the message
-+ * @param msg_type - Message type (LINK_STATE_UPDATE or LINK_STATE_SYNC)
-+ * @return 0 on success, -1 on error
-+ */
-+extern int bgp_ls_process_linkstate_message(struct stream *s, uint8_t msg_type);
-+
-+/*
-+ * Process link-state message and update TED
-+ *
-+ * Converts message to TED structure (vertex/edge/subnet) and triggers
-+ * BGP-LS route origination.
-+ *
-+ * @param bgp - BGP instance
-+ * @param msg - Link-state message to process
-+ * @return 0 on success, -1 on error
-+ */
-+extern int bgp_ls_process_message(struct bgp *bgp, struct ls_message *msg);
-+
-+/*
-+ * Withdraw all locally originated BGP-LS routes and reset the TED.
-+ *
-+ * Called when the last BGP-LS peer is deactivated. Withdraws every node,
-+ * link, and prefix NLRI that was originated from the TED, then discards
-+ * the stale TED.
-+ *
-+ * @param bgp - BGP instance
-+ */
-+extern void bgp_ls_withdraw_ted(struct bgp *bgp);
-+
-+#endif /* _FRR_BGP_LS_TED_H */
-diff --git a/bgpd/bgp_open.c b/bgpd/bgp_open.c
-index dce2a1990c..c39e71adf2 100644
---- a/bgpd/bgp_open.c
-+++ b/bgpd/bgp_open.c
-@@ -158,6 +158,11 @@ void bgp_capability_vty_out(struct vty *vty, struct peer *peer, bool use_json,
- "capabilityErrorMultiProtocolAfi",
- "L2VPN");
- break;
-+ case AFI_BGP_LS:
-+ json_object_string_add(json_cap,
-+ "capabilityErrorMultiProtocolAfi",
-+ "BGP-LS");
-+ break;
- case AFI_UNSPEC:
- case AFI_MAX:
- json_object_int_add(
-@@ -209,6 +214,11 @@ void bgp_capability_vty_out(struct vty *vty, struct peer *peer, bool use_json,
- "capabilityErrorMultiProtocolSafi",
- "flowspec");
- break;
-+ case SAFI_BGP_LS:
-+ json_object_string_add(json_cap,
-+ "capabilityErrorMultiProtocolSafi",
-+ "BGP-LS");
-+ break;
- case SAFI_UNSPEC:
- case SAFI_MAX:
- json_object_int_add(
-@@ -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;
-+ case AFI_BGP_LS:
-+ vty_out(vty, "AFI BGP-LS, ");
-+ break;
- case AFI_UNSPEC:
- case AFI_MAX:
- vty_out(vty, "AFI Unknown %d, ",
-@@ -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;
-+ case SAFI_BGP_LS:
-+ vty_out(vty, "SAFI BGP-LS");
-+ break;
- case SAFI_UNSPEC:
- case SAFI_MAX:
- vty_out(vty, "SAFI Unknown %d ",
-@@ -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]
-- && !peer->afc_nego[AFI_L2VPN][SAFI_EVPN]) {
-+ && !peer->afc_nego[AFI_L2VPN][SAFI_EVPN]
-+ && !peer->afc_nego[AFI_BGP_LS][SAFI_BGP_LS]) {
- flog_err(EC_BGP_PKT_OPEN,
- "%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 dfb03c6cec..fe1a652d1b 100644
---- a/bgpd/bgp_packet.c
-+++ b/bgpd/bgp_packet.c
-@@ -50,6 +50,7 @@
- #include "bgpd/bgp_keepalives.h"
- #include "bgpd/bgp_flowspec.h"
- #include "bgpd/bgp_trace.h"
-+#include "bgpd/bgp_ls.h"
-
- DEFINE_HOOK(bgp_packet_dump,
- (struct peer *peer, uint8_t type, bgp_size_t size,
-@@ -322,6 +323,8 @@ int bgp_nlri_parse(struct peer *peer, struct attr *attr,
- return bgp_nlri_parse_evpn(peer, attr, packet, mp_withdraw);
- case SAFI_FLOWSPEC:
- return bgp_nlri_parse_flowspec(peer, attr, packet, mp_withdraw);
-+ case SAFI_BGP_LS:
-+ return bgp_nlri_parse_ls(peer, mp_withdraw ? NULL : attr, packet);
- }
- return BGP_NLRI_PARSE_ERROR;
- }
-diff --git a/bgpd/bgp_route.c b/bgpd/bgp_route.c
-index 351f01ef40..0ad77f1486 100644
---- a/bgpd/bgp_route.c
-+++ b/bgpd/bgp_route.c
-@@ -67,6 +67,8 @@
- #include "bgpd/bgp_rpki.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"
-@@ -84,6 +86,10 @@
-
- #include "bgpd/bgp_route_clippy.c"
-
-+void bgp_ls_nlri_format(struct bgp_ls_nlri *nlri, char *buf, size_t buf_len);
-+struct json_object *bgp_ls_nlri_to_json(struct bgp_ls_nlri *nlri);
-+
-+
- 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);
-@@ -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);
-+ } else if (dest->ls_nlri) {
-+ char nlri_str[1024];
-+
-+ bgp_ls_nlri_format(dest->ls_nlri, nlri_str, sizeof(nlri_str));
-+ if (!json) {
-+ len = vty_out(vty, "%s", nlri_str);
-+ } else {
-+ json_object *json_nlri = bgp_ls_nlri_to_json(dest->ls_nlri);
-+
-+ json_object_string_add(json, "nlriStr", nlri_str);
-+ json_object_object_add(json, "nlri", json_nlri);
-+ }
- } else {
- if (!json)
- len = vty_out(vty, "%pFX", 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, " ");
- }
- }
-- } else if (p->family == AF_INET && !BGP_ATTR_MP_NEXTHOP_LEN_IP6(attr)) {
-+ } else if ((p->family == AF_INET || safi == SAFI_BGP_LS) &&
-+ !BGP_ATTR_MP_NEXTHOP_LEN_IP6(attr)) {
- if (json_paths) {
- json_nexthop_global = json_object_new_object();
-
-@@ -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);
-+ } else if (safi == SAFI_BGP_LS) {
-+ json_object_string_addf(json_net, "nextHop", "%pI4",
-+ &attr->nexthop);
- }
-
- if (use_bgp_med_value(attr, bgp)) {
-@@ -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, " ");
-+ } else if (safi == SAFI_BGP_LS) {
-+ if (wide)
-+ vty_out(vty, "%-41pI4", &attr->nexthop);
-+ else
-+ vty_out(vty, "%-16pI4", &attr->nexthop);
- }
-
- if (use_bgp_med_value(attr, bgp)) {
-@@ -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;
-+ json_object *json_ls_attr = NULL;
- int first = 0;
- struct listnode *node, *nnode;
- struct peer *peer;
-@@ -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");
-+ } else if (safi == SAFI_BGP_LS) {
-+ if (json_paths) {
-+ json_object_string_addf(json_nexthop_global, "ip", "%pI4",
-+ &attr->mp_nexthop_global_in);
-+
-+ if (path->peer->hostname)
-+ json_object_string_add(json_nexthop_global, "hostname",
-+ path->peer->hostname);
-+ } else {
-+ if (nexthop_hostname)
-+ vty_out(vty, " %pI4(%s)", &attr->mp_nexthop_global_in,
-+ nexthop_hostname);
-+ else
-+ vty_out(vty, " %pI4", &attr->mp_nexthop_global_in);
-+ }
- } else {
- if (json_paths) {
- bool ll_nexthop = IN6_IS_ADDR_LINKLOCAL(&attr->mp_nexthop_global);
-@@ -13307,6 +13407,15 @@ void route_vty_out_detail(struct vty *vty, struct bgp *bgp, struct bgp_dest *bn,
- }
- }
-
-+ /* Display BGP-LS attributes if this is link-state SAFI */
-+ if (safi == SAFI_BGP_LS && attr && attr->ls_attr) {
-+ if (json_paths) {
-+ json_ls_attr = bgp_ls_attr_to_json(attr->ls_attr);
-+ json_object_object_add(json_path, "linkStateAttrs", json_ls_attr);
-+ } else
-+ bgp_ls_attr_display(vty, attr->ls_attr);
-+ }
-+
- /* Output some debug about internal state of the dest flags */
- if (json_paths) {
- if (CHECK_FLAG(bn->flags, BGP_NODE_PROCESS_SCHEDULED))
-@@ -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);
-+ if (safi == SAFI_BGP_LS)
-+ vty_out(vty, BGP_SHOW_BGP_LS_PREFIX_CODES_HEADER);
- }
- if (type == bgp_show_type_dampend_paths
- || type == bgp_show_type_damp_neighbor)
-@@ -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);
-+ } else if (safi == SAFI_BGP_LS) {
-+ char nlri_str[1024];
-+
-+ bgp_ls_nlri_format(dest->ls_nlri, nlri_str, sizeof(nlri_str));
-+ if (first)
-+ vty_out(vty, "\"%s\": ", nlri_str);
-+ else
-+ vty_out(vty, ",\"%s\": ", nlri_str);
- } else {
- if (first)
- vty_out(vty, "\"%pFX\": ", dest_p);
-@@ -14090,6 +14209,33 @@ void route_vty_out_detail_header(struct vty *vty, struct bgp *bgp,
- : "");
- bgp_evpn_route2json((struct prefix_evpn *)p, json);
- }
-+ } else if (safi == SAFI_BGP_LS) {
-+ /* BGP-LS: Display NLRI in format [V][L2][I0x0][N[s0000.0000.0001]]/length */
-+ char nlri_str[512];
-+
-+ if (dest->ls_nlri) {
-+ bgp_ls_nlri_format(dest->ls_nlri, nlri_str, sizeof(nlri_str));
-+ size_t nlri_len = bgp_ls_nlri_size(dest->ls_nlri);
-+
-+ if (!json) {
-+ vty_out(vty, "BGP routing table entry for %s/%zu\n", nlri_str,
-+ nlri_len * 8);
-+ bgp_ls_nlri_display(vty, dest->ls_nlri);
-+ } else {
-+ json_object *json_nlri = bgp_ls_nlri_to_json(dest->ls_nlri);
-+
-+ /* Use structured JSON format for NLRI */
-+ snprintfrr(nlri_str, sizeof(nlri_str), "%s/%zu", nlri_str,
-+ nlri_len * 8);
-+ json_object_string_add(json, "nlriStr", nlri_str);
-+ json_object_object_add(json, "nlri", json_nlri);
-+ }
-+ } else {
-+ if (!json)
-+ vty_out(vty, "BGP routing table entry for (unknown BGP-LS NLRI)\n");
-+ else
-+ json_object_string_add(json, "nlri", "unknown");
-+ }
- } else {
- if (!json) {
- vty_out(vty,
-@@ -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));
-+ case SAFI_BGP_LS:
- case SAFI_UNSPEC:
- case SAFI_UNICAST:
- case SAFI_MULTICAST:
-@@ -15281,6 +15428,65 @@ DEFPY(show_ip_bgp, show_ip_bgp_cmd,
- return CMD_SUCCESS;
- }
-
-+DEFUN (show_bgp_link_state_route,
-+ show_bgp_link_state_route_cmd,
-+ "show bgp link-state link-state WORD [json]",
-+ SHOW_STR
-+ BGP_STR
-+ "Link-State information\n"
-+ "BGP-LS routes\n"
-+ "NLRI string\n"
-+ JSON_STR)
-+{
-+ struct bgp *bgp = NULL;
-+ struct bgp_dest *dest;
-+ int display = 0;
-+ const char *nlri_str;
-+ bool uj = false;
-+ json_object *json = NULL;
-+ uint16_t show_opts = 0;
-+
-+ bgp = bgp_get_default();
-+ if (!bgp) {
-+ vty_out(vty, "%% No BGP instance found\n");
-+ return CMD_WARNING;
-+ }
-+
-+ if (!bgp->ls_info) {
-+ vty_out(vty, "%% BGP-LS not available\n");
-+ return CMD_WARNING;
-+ }
-+
-+ nlri_str = argv[4]->arg;
-+ uj = use_json(argc, argv);
-+
-+ if (uj) {
-+ json = json_object_new_object();
-+ SET_FLAG(show_opts, BGP_SHOW_OPT_JSON);
-+ }
-+
-+ /* Lookup the NLRI */
-+ dest = bgp_ls_lookup_nlri_by_str(bgp, nlri_str);
-+ if (!dest) {
-+ if (uj) {
-+ json_object_string_add(json, "error", "NLRI not found");
-+ vty_json(vty, json);
-+ } else {
-+ vty_out(vty, "%% BGP-LS NLRI not found\n");
-+ }
-+ return CMD_WARNING;
-+ }
-+
-+ /* Display the path info */
-+ bgp_show_path_info(NULL, dest, vty, bgp, AFI_BGP_LS, SAFI_BGP_LS, json, BGP_PATH_SHOW_ALL,
-+ &display, RPKI_NOT_BEING_USED, NULL, show_opts);
-+
-+ if (uj)
-+ vty_json(vty, json);
-+
-+ return CMD_SUCCESS;
-+}
-+
- 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]",
-@@ -15632,6 +15838,8 @@ static void bgp_table_stats_walker(struct event *t)
- case AFI_L2VPN:
- space = EVPN_ROUTE_PREFIXLEN;
- break;
-+ case AFI_BGP_LS:
-+ /* TODO */
- case AFI_UNSPEC:
- case AFI_MAX:
- return;
-@@ -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;
-+ case AFI_BGP_LS:
-+ /* TODO */
- case AFI_UNSPEC:
- case AFI_MAX:
- break;
-@@ -16262,6 +16472,24 @@ DEFUN (show_bgp_l2vpn_evpn_route_prefix,
- BGP_PATH_SHOW_ALL, RPKI_NOT_BEING_USED, use_json(argc, argv), 0);
- }
-
-+DEFUN (show_bgp_ls,
-+ show_bgp_ls_cmd,
-+ "show bgp link-state link-state [json]",
-+ SHOW_STR
-+ BGP_STR
-+ "Link-State information\n"
-+ "BGP-LS routes\n"
-+ JSON_STR)
-+{
-+ uint16_t show_flags = 0;
-+
-+ if (use_json(argc, argv))
-+ SET_FLAG(show_flags, BGP_SHOW_OPT_JSON);
-+
-+ return bgp_show(vty, NULL, AFI_BGP_LS, SAFI_BGP_LS, bgp_show_type_normal, NULL, show_flags,
-+ RPKI_NOT_BEING_USED);
-+}
-+
- 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,
-@@ -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);
-+ install_element(VIEW_NODE, &show_bgp_ls_cmd);
-
- /* BGP dampening clear commands */
- install_element(ENABLE_NODE, &clear_ip_bgp_dampening_cmd);
-@@ -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);
-
-+ /* BGP Link-State */
-+ install_element(VIEW_NODE, &show_bgp_link_state_route_cmd);
-+
- /* show bgp vrf <afi> <safi> detailed */
- 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 8c26de1b45..9cd5d26b6a 100644
---- a/bgpd/bgp_route.h
-+++ b/bgpd/bgp_route.h
-@@ -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"
-+#define BGP_SHOW_BGP_LS_PREFIX_CODES_HEADER \
-+ "Prefix codes: E link, V node, T IP reachable route, S SRv6 SID, SP SRTE Policy, u/U unknown\n" \
-+ " I Identifier, N local node, R remote node, L link, P prefix, S SID, C candidate path\n" \
-+ " L1/L2 ISIS level-1/level-2, O OSPF, O3 OSPFv3, D direct, ST static/peer-node, SR Segment Routing\n" \
-+ " a area-ID, l link-ID, t topology-ID, s ISO-ID,\n" \
-+ " c confed-ID/ASN, b bgp-identifier, r router-ID, te te-router-ID, sd SID\n" \
-+ " i if-address, n nbr-address, o OSPF Route-type, p IP-prefix\n" \
-+ " d designated router address, po protocol-origin, f flag\n" \
-+ " e endpoint-ip, cl color, as originator-asn oa originator-address\n" \
-+ " di discriminator, q bgp-router-ID\n"
-
- /* 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 17b417895f..6ed11ec69e 100644
---- a/bgpd/bgp_table.c
-+++ b/bgpd/bgp_table.c
-@@ -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)
- {
-@@ -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);
-+ bgp_ls_nlri_free(dest->ls_nlri);
-+ }
-+
- XFREE(MTYPE_BGP_NODE, dest);
- dest = NULL;
- rn->info = NULL;
-diff --git a/bgpd/bgp_table.h b/bgpd/bgp_table.h
-index 153be63df6..7a11a46448 100644
---- a/bgpd/bgp_table.h
-+++ b/bgpd/bgp_table.h
-@@ -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;
-diff --git a/bgpd/bgp_updgrp.c b/bgpd/bgp_updgrp.c
-index f92e19ad69..cf7bd498c5 100644
---- a/bgpd/bgp_updgrp.c
-+++ b/bgpd/bgp_updgrp.c
-@@ -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);
-
-+ key = jhash_1word(CHECK_FLAG(peer->af_flags[AFI_BGP_LS][SAFI_BGP_LS], PEER_FLAG_BGP_LS_IPV4), key);
-+
-+ key = jhash_1word(CHECK_FLAG(peer->af_flags[AFI_BGP_LS][SAFI_BGP_LS], PEER_FLAG_BGP_LS_IPV6), key);
-+
- /*
- * 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 e8a0997601..e4066f1f9a 100644
---- a/bgpd/bgp_updgrp_packet.c
-+++ b/bgpd/bgp_updgrp_packet.c
-@@ -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
-@@ -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;
-+ struct bgp_ls_nlri *ls_nlri = NULL;
-
- if (!subgrp)
- return NULL;
-@@ -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, dest->srv6_unicast, 0, 0,
-- path);
--
-+ path, dest->ls_nlri);
- space_remaining =
- STREAM_CONCAT_REMAIN(s, snlri, STREAM_SIZE(s))
- - BGP_MAX_PACKET_SIZE_OVERFLOW;
-@@ -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;
-- } else {
-+ } else if (afi == AFI_BGP_LS && safi == SAFI_BGP_LS) {
-+ ls_nlri = dest->ls_nlri;
-+ if (!ls_nlri) {
-+ flog_err(EC_BGP_UPDATE_SND,
-+ "BGP-LS path missing ls_nlri data");
-+ adv = bgp_advertise_clean_subgroup(subgrp, adj);
-+ continue;
-+ }
-+
-+ if (bgp_debug_update(NULL, NULL, subgrp->update_group, 0)) {
-+ if (!send_attr_printed) {
-+ iana_afi_t pkt_afi = afi_int2iana(afi);
-+ iana_safi_t pkt_safi = safi_int2iana(safi);
-+
-+ zlog_debug("u%" PRIu64 ":s%" PRIu64
-+ " send UPDATE w/ attr: %s",
-+ subgrp->update_group->id, subgrp->id,
-+ send_attr_str);
-+
-+ zlog_debug("u%" PRIu64 ":s%" PRIu64
-+ " send MP_REACH for afi/safi %s/%s",
-+ subgrp->update_group->id, subgrp->id,
-+ iana_afi2str(pkt_afi),
-+ iana_safi2str(pkt_safi));
-+ send_attr_printed = 1;
-+ }
-+ zlog_debug("u%" PRIu64 ":s%" PRIu64
-+ " send UPDATE BGP-LS NLRI",
-+ subgrp->update_group->id, subgrp->id);
-+ }
-+ }
-+
-+ else {
- num_labels = BGP_PATH_INFO_NUM_LABELS(path);
- label_pnt =
- num_labels
-@@ -844,10 +877,9 @@ struct bpacket *subgroup_update_packet(struct update_subgroup *subgrp)
- snlri, peer, afi, safi, &vecarr,
- adv->baa->attr);
-
-- bgp_packet_mpattr_prefix(snlri, afi, safi, dest_p, prd,
-- label_pnt, num_labels,
-- addpath_capable, addpath_tx_id,
-- adv->baa->attr);
-+ bgp_packet_mpattr_prefix(snlri, afi, safi, dest_p, prd, label_pnt,
-+ num_labels, addpath_capable, addpath_tx_id,
-+ adv->baa->attr, ls_nlri);
- }
-
- num_pfx++;
-@@ -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;
-+ struct bgp_ls_nlri *ls_nlri = NULL;
-
-
- if (!subgrp)
-@@ -1002,6 +1035,53 @@ struct bpacket *subgroup_withdraw_packet(struct update_subgroup *subgrp)
- } else
- first_time = 0;
-
-+ /* BGP-LS uses special encoding - handle before standard cases */
-+ if (afi == AFI_BGP_LS && safi == SAFI_BGP_LS) {
-+ /* Format MP_UNREACH header if first time */
-+ if (first_time) {
-+ iana_afi_t pkt_afi = afi_int2iana(afi);
-+ iana_safi_t pkt_safi = safi_int2iana(safi);
-+
-+ attrlen_pos = stream_get_endp(s);
-+ stream_putw(s, 0); /* total attr length = 0 for now */
-+ mp_start = stream_get_endp(s);
-+ mplen_pos = bgp_packet_mpunreach_start(s, afi, safi);
-+
-+ if (bgp_debug_update(NULL, NULL, subgrp->update_group, 0))
-+ zlog_debug("u%" PRIu64 ":s%" PRIu64
-+ " send MP_UNREACH for afi/safi %s/%s",
-+ subgrp->update_group->id, subgrp->id,
-+ iana_afi2str(pkt_afi), iana_safi2str(pkt_safi));
-+ }
-+
-+ ls_nlri = dest->ls_nlri;
-+ if (ls_nlri) {
-+ /* Encode the BGP-LS NLRI into the stream */
-+ if (bgp_ls_encode_nlri(s, ls_nlri) < 0) {
-+ flog_err(EC_BGP_UPDATE_SND,
-+ "Failed to encode BGP-LS NLRI withdrawal");
-+ bgp_adj_out_remove_subgroup(dest, adj, subgrp);
-+ continue;
-+ }
-+ } else {
-+ flog_err(EC_BGP_UPDATE_SND,
-+ "BGP-LS withdrawal missing ls_nlri data");
-+ bgp_adj_out_remove_subgroup(dest, adj, subgrp);
-+ continue;
-+ }
-+
-+ num_pfx++;
-+
-+ if (bgp_debug_update(NULL, NULL, subgrp->update_group, 0))
-+ zlog_debug("u%" PRIu64 ":s%" PRIu64
-+ " send UPDATE BGP-LS NLRI -- unreachable",
-+ subgrp->update_group->id, subgrp->id);
-+
-+ subgrp->scount--;
-+ bgp_adj_out_remove_subgroup(dest, adj, subgrp);
-+ continue;
-+ }
-+
- if (afi == AFI_IP && safi == SAFI_UNICAST
- && !peer_cap_enhe(peer, afi, safi))
- 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);
-
-+ if (safi == SAFI_BGP_LS) {
-+ ls_nlri = dest->ls_nlri;
-+ if (!ls_nlri) {
-+ flog_err(EC_BGP_UPDATE_SND,
-+ "BGP-LS path missing ls_nlri data");
-+ bgp_advertise_clean_subgroup(subgrp, adj);
-+ continue;
-+ }
-+ }
-+
- /* If first time, format the MP_UNREACH header
- */
- if (first_time) {
-@@ -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,
-- NULL, 0, addpath_capable,
-- addpath_tx_id, NULL);
-+ bgp_packet_mpunreach_prefix(s, dest_p, afi, safi, prd, NULL, 0,
-+ addpath_capable, addpath_tx_id, NULL, ls_nlri);
- }
-
- num_pfx++;
-@@ -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, 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);
-@@ -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);
-- bgp_packet_mpunreach_prefix(
-- s, &p, afi, safi, NULL, NULL, 0, addpath_capable,
-- BGP_ADDPATH_TX_ID_FOR_DEFAULT_ORIGINATE, NULL);
-+ bgp_packet_mpunreach_prefix(s, &p, afi, safi, NULL, NULL, 0, addpath_capable,
-+ BGP_ADDPATH_TX_ID_FOR_DEFAULT_ORIGINATE, NULL, NULL);
-
- /* 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 fc78a59f3d..ea8d7f62e2 100644
---- a/bgpd/bgp_vty.c
-+++ b/bgpd/bgp_vty.c
-@@ -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;
-+ case SAFI_BGP_LS:
- case SAFI_UNSPEC:
- case SAFI_ENCAP:
- case SAFI_EVPN:
-@@ -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;
-+ case SAFI_BGP_LS:
- case SAFI_UNSPEC:
- case SAFI_ENCAP:
- case SAFI_EVPN:
-@@ -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;
-+ case AFI_BGP_LS:
-+ return BGP_LS_NODE;
- case AFI_UNSPEC:
- case AFI_MAX:
- // We should never be here but to clarify the switch statement..
-@@ -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";
-+ } else if (afi == AFI_BGP_LS) {
-+ if (safi == SAFI_BGP_LS)
-+ return "Link-State Link-State";
- }
-
- return "Unknown";
-@@ -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";
-+ } else if (afi == AFI_BGP_LS) {
-+ if (safi == SAFI_BGP_LS)
-+ return "linkState";
- }
-
- return "Unknown";
-@@ -442,6 +453,9 @@ afi_t bgp_node_afi(struct vty *vty)
- case BGP_EVPN_NODE:
- afi = AFI_L2VPN;
- break;
-+ case BGP_LS_NODE:
-+ afi = AFI_BGP_LS;
-+ break;
- default:
- afi = AFI_IP;
- break;
-@@ -474,6 +488,9 @@ safi_t bgp_node_safi(struct vty *vty)
- case BGP_FLOWSPECV6_NODE:
- safi = SAFI_FLOWSPEC;
- break;
-+ case BGP_LS_NODE:
-+ safi = SAFI_BGP_LS;
-+ break;
- default:
- safi = SAFI_UNICAST;
- break;
-@@ -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";
-+ case SAFI_BGP_LS:
- case SAFI_UNSPEC:
- case SAFI_EVPN:
- case SAFI_MAX:
-@@ -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";
-+ case SAFI_BGP_LS:
- case SAFI_UNSPEC:
- case SAFI_EVPN:
- case SAFI_MAX:
-@@ -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";
-+ case SAFI_BGP_LS:
- case SAFI_UNICAST:
- case SAFI_MULTICAST:
- case SAFI_MPLS_VPN:
-@@ -645,6 +665,22 @@ static const char *get_bgp_default_af_flag(afi_t afi, safi_t safi)
- return "unknown-afi/safi";
- }
- break;
-+ case AFI_BGP_LS:
-+ switch (safi) {
-+ case SAFI_BGP_LS:
-+ return "link-state";
-+ case SAFI_UNICAST:
-+ case SAFI_MULTICAST:
-+ case SAFI_MPLS_VPN:
-+ case SAFI_ENCAP:
-+ case SAFI_LABELED_UNICAST:
-+ case SAFI_FLOWSPEC:
-+ case SAFI_EVPN:
-+ case SAFI_UNSPEC:
-+ case SAFI_MAX:
-+ return "unknown-afi/safi";
-+ }
-+ break;
- case AFI_UNSPEC:
- case AFI_MAX:
- return "unknown-afi/safi";
-@@ -11427,6 +11463,18 @@ DEFUN_NOSH (address_family_evpn,
- return CMD_SUCCESS;
- }
-
-+DEFUN_NOSH(address_family_link_state,
-+ address_family_link_state_cmd,
-+ "address-family link-state [link-state]",
-+ "Enter Address Family command mode\n"
-+ "Link-State Address Family\n"
-+ "Link-State Subsequent Address Family\n")
-+{
-+ VTY_DECLVAR_CONTEXT(bgp, bgp);
-+ vty->node = BGP_LS_NODE;
-+ return CMD_SUCCESS;
-+}
-+
- DEFUN_NOSH (bgp_segment_routing_srv6,
- bgp_segment_routing_srv6_cmd,
- "segment-routing srv6",
-@@ -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
-- || vty->node == BGP_FLOWSPECV6_NODE)
-+ || vty->node == BGP_FLOWSPECV6_NODE
-+ || vty->node == BGP_LS_NODE)
- vty->node = BGP_NODE;
- return CMD_SUCCESS;
- }
-@@ -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");
-+ } else if (afi == AFI_BGP_LS) {
-+ if (safi == SAFI_BGP_LS)
-+ vty_frame(vty, "link-state link-state");
- }
- vty_frame(vty, "\n");
-
-+ /* 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);
-
-+ /* BGP-LS configuration. */
-+ bgp_config_write_family(vty, bgp, AFI_BGP_LS, SAFI_BGP_LS);
-+
- hook_call(bgp_inst_config_write, bgp, vty);
-
- #ifdef ENABLE_BGP_VNC
-@@ -20857,6 +21150,14 @@ static struct cmd_node bgp_srv6_node = {
- .prompt = "%s(config-router-srv6)# ",
- };
-
-+static struct cmd_node bgp_ls_node = {
-+ .name = "bgp link-state",
-+ .node = BGP_LS_NODE,
-+ .parent_node = BGP_NODE,
-+ .prompt = "%s(config-router-af)# ",
-+ .no_xpath = true,
-+};
-+
- static void community_list_vty(void);
-
- static void bgp_ac_peergroup(vector comps, struct cmd_token *token)
-@@ -21176,6 +21477,7 @@ void bgp_vty_init(void)
- install_node(&bgp_flowspecv4_node);
- install_node(&bgp_flowspecv6_node);
- install_node(&bgp_srv6_node);
-+ install_node(&bgp_ls_node);
-
- /* Install default VTY commands to new nodes. */
- install_default(BGP_NODE);
-@@ -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);
-+ install_default(BGP_LS_NODE);
-
- /* "global bgp inq-limit command */
- install_element(CONFIG_NODE, &bgp_inq_limit_cmd);
-@@ -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);
-+ install_element(BGP_LS_NODE, &neighbor_activate_cmd);
-
- /* "no neighbor activate" commands. */
- install_element(BGP_NODE, &no_neighbor_activate_hidden_cmd);
-@@ -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);
-+ install_element(BGP_LS_NODE, &no_neighbor_activate_cmd);
-
- /* "neighbor peer-group" set commands. */
- install_element(BGP_NODE, &neighbor_set_peer_group_cmd);
-@@ -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);
-+ install_element(BGP_NODE, &address_family_link_state_cmd);
-
- /* "exit-address-family" command. */
- install_element(BGP_IPV4_NODE, &exit_address_family_cmd);
-@@ -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);
-+ install_element(BGP_LS_NODE, &exit_address_family_cmd);
-
- /* 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 d1034ff42a..cda7ad659f 100644
---- a/bgpd/bgp_zebra.c
-+++ b/bgpd/bgp_zebra.c
-@@ -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;
-@@ -4148,6 +4149,35 @@ static int bgp_zebra_process_srv6_locator_delete(ZAPI_CALLBACK_ARGS)
- return 0;
- }
-
-+/* Handle ZEBRA_OPAQUE_MESSAGE for BGP-LS link-state updates */
-+static int bgp_zebra_opaque_msg_handler(ZAPI_CALLBACK_ARGS)
-+{
-+ int ret = 0;
-+ struct stream *s;
-+ struct zapi_opaque_msg info;
-+
-+ s = zclient->ibuf;
-+ if (zclient_opaque_decode(s, &info) != 0) {
-+ zlog_err("%s: Failed to decode opaque message", __func__);
-+ return -1;
-+ }
-+
-+ switch (info.type) {
-+ case LINK_STATE_SYNC:
-+ case LINK_STATE_UPDATE:
-+ ret = bgp_ls_process_linkstate_message(s, info.type);
-+ break;
-+
-+ default:
-+ if (BGP_DEBUG(zebra, ZEBRA))
-+ zlog_debug("%s: Ignoring unknown opaque message type %d", __func__,
-+ info.type);
-+ break;
-+ }
-+
-+ return ret;
-+}
-+
- static zclient_handler *const bgp_handlers[] = {
- [ZEBRA_ROUTER_ID_UPDATE] = bgp_router_id_update,
- [ZEBRA_INTERFACE_ADDRESS_ADD] = bgp_interface_address_add,
-@@ -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,
-- [ZEBRA_SRV6_MANAGER_GET_LOCATOR_CHUNK] =
-- bgp_zebra_process_srv6_locator_chunk,
-+ [ZEBRA_SRV6_MANAGER_GET_LOCATOR_CHUNK] = bgp_zebra_process_srv6_locator_chunk,
- [ZEBRA_SRV6_SID_NOTIFY] = bgp_zebra_srv6_sid_notify,
-+ [ZEBRA_OPAQUE_MESSAGE] = bgp_zebra_opaque_msg_handler,
- };
-
- static int bgp_if_new_hook(struct interface *ifp)
-diff --git a/bgpd/bgpd.c b/bgpd/bgpd.c
-index 116bef9339..a8e74fa748 100644
---- a/bgpd/bgpd.c
-+++ b/bgpd/bgpd.c
-@@ -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);
-@@ -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);
- }
-
-+ if (afi == AFI_BGP_LS && safi == SAFI_BGP_LS) {
-+ if (peer->connection->su.sa.sa_family == AF_INET6) {
-+ SET_FLAG(peer->af_flags[AFI_BGP_LS][SAFI_BGP_LS], PEER_FLAG_BGP_LS_IPV6);
-+ UNSET_FLAG(peer->af_flags[AFI_BGP_LS][SAFI_BGP_LS], PEER_FLAG_BGP_LS_IPV4);
-+ } else if (peer->connection->su.sa.sa_family == AF_INET) {
-+ SET_FLAG(peer->af_flags[AFI_BGP_LS][SAFI_BGP_LS], PEER_FLAG_BGP_LS_IPV4);
-+ UNSET_FLAG(peer->af_flags[AFI_BGP_LS][SAFI_BGP_LS], PEER_FLAG_BGP_LS_IPV6);
-+ } else {
-+ UNSET_FLAG(peer->af_flags[AFI_BGP_LS][SAFI_BGP_LS], PEER_FLAG_BGP_LS_IPV4);
-+ UNSET_FLAG(peer->af_flags[AFI_BGP_LS][SAFI_BGP_LS], PEER_FLAG_BGP_LS_IPV6);
-+ }
-+ }
-+
- return 0;
- }
-
-@@ -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);
- }
-+
-+ /*
-+ * Register with zebra link-state database when the first peer is
-+ * activated for BGP-LS. This allows BGP to receive IGP topology
-+ * updates from ISIS/OSPF for distribution to BGP-LS peers.
-+ */
-+ if (afi == AFI_BGP_LS && safi == SAFI_BGP_LS && !bgp_ls_is_registered(bgp)) {
-+ if (!bgp_ls_register(bgp)) {
-+ zlog_err("BGP-LS: Failed to register with link-state database for instance %s",
-+ bgp->name_pretty);
-+ return -1;
-+ }
-+
-+ if (BGP_DEBUG(linkstate, LINKSTATE) || BGP_DEBUG(zebra, ZEBRA))
-+ zlog_debug("BGP-LS: Registered with link-state database for instance %s (first peer with BGP-LS activated: %s)",
-+ bgp->name_pretty, peer->host);
-+ }
-+
- return ret;
- }
-
-@@ -2695,6 +2734,11 @@ static bool non_peergroup_deactivate_af(struct peer *peer, afi_t afi,
- peer_notify_config_change(peer->connection);
- }
-
-+ if (afi == AFI_BGP_LS && safi == SAFI_BGP_LS) {
-+ UNSET_FLAG(peer->af_flags[AFI_BGP_LS][SAFI_BGP_LS], PEER_FLAG_BGP_LS_IPV4);
-+ UNSET_FLAG(peer->af_flags[AFI_BGP_LS][SAFI_BGP_LS], PEER_FLAG_BGP_LS_IPV6);
-+ }
-+
- return false;
- }
-
-@@ -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);
- }
-+
-+ /*
-+ * Unregister from zebra link-state database when the last peer is
-+ * deactivated for BGP-LS. This stops receiving IGP topology updates
-+ * from ISIS/OSPF since no BGP-LS peers remain active.
-+ */
-+ if (afi == AFI_BGP_LS && safi == SAFI_BGP_LS && bgp_ls_is_registered(bgp)) {
-+ bool last_peer = true;
-+
-+ for (ALL_LIST_ELEMENTS_RO(bgp->peer, node, tmp_peer)) {
-+ if (tmp_peer != peer && tmp_peer->afc[AFI_BGP_LS][SAFI_BGP_LS]) {
-+ last_peer = false;
-+ break;
-+ }
-+ }
-+
-+ if (last_peer) {
-+ bgp_ls_withdraw_ted(bgp);
-+
-+ if (!bgp_ls_unregister(bgp)) {
-+ zlog_err("BGP-LS: Failed to unregister from link-state database for instance %s",
-+ bgp->name_pretty);
-+ } else {
-+ if (BGP_DEBUG(linkstate, LINKSTATE) || BGP_DEBUG(zebra, ZEBRA))
-+ zlog_debug("BGP-LS: Unregistered from link-state database for instance %s (last peer with BGP-LS deactivated: %s)",
-+ bgp->name_pretty, peer->host);
-+ }
-+ }
-+ }
-+
- return ret;
- }
-
-@@ -3767,6 +3841,7 @@ peer_init:
- bgp_evpn_vrf_es_init(bgp);
- bgp_pbr_init(bgp);
- bgp_srv6_init(bgp);
-+ bgp_ls_init(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);
-+ bgp_ls_cleanup(bgp);
-
- for (afi = AFI_IP; afi < AFI_MAX; afi++) {
- enum vpn_policy_direction dir;
-@@ -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]
-- || peer->afc[AFI_L2VPN][SAFI_EVPN])
-+ || peer->afc[AFI_L2VPN][SAFI_EVPN]
-+ || peer->afc[AFI_BGP_LS][SAFI_BGP_LS])
- return BGP_PEER_ACTIVE;
-
- return BGP_PEER_AF_UNCONFIGURED;
-@@ -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]
-- || peer->afc_nego[AFI_L2VPN][SAFI_EVPN])
-+ || peer->afc_nego[AFI_L2VPN][SAFI_EVPN]
-+ || peer->afc_nego[AFI_BGP_LS][SAFI_BGP_LS])
- return true;
- return false;
- }
-diff --git a/bgpd/bgpd.h b/bgpd/bgpd.h
-index 406a2232bf..777ae9d80c 100644
---- a/bgpd/bgpd.h
-+++ b/bgpd/bgpd.h
-@@ -95,6 +95,7 @@ enum bgp_af_index {
- BGP_AF_IPV6_LBL_UNICAST,
- BGP_AF_IPV4_FLOWSPEC,
- BGP_AF_IPV6_FLOWSPEC,
-+ BGP_AF_BGP_LS,
- BGP_AF_MAX
- };
-
-@@ -810,6 +811,9 @@ struct bgp {
- /* BGP routing information base. */
- struct bgp_table *rib[AFI_MAX][SAFI_MAX];
-
-+ /* BGP-LS specific data */
-+ struct bgp_ls *ls_info;
-+
- /* BGP table route-map. */
- struct bgp_rmap table_map[AFI_MAX][SAFI_MAX];
-
-@@ -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_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];
-@@ -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
-+#define BGP_ATTR_LINK_STATE 29 /* BGP-LS Attribute (RFC 9552) */
- #define BGP_ATTR_LARGE_COMMUNITIES 32
- #define BGP_ATTR_OTC 35
- #define BGP_ATTR_NHC 39
-@@ -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;
-+ case SAFI_BGP_LS:
- case SAFI_EVPN:
- case SAFI_UNSPEC:
- case SAFI_MAX:
-@@ -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;
-+ case SAFI_BGP_LS:
- case SAFI_EVPN:
- case SAFI_UNSPEC:
- case SAFI_MAX:
-@@ -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_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]
-- || peer->afc[AFI_L2VPN][SAFI_EVPN])
-+ || peer->afc[AFI_L2VPN][SAFI_EVPN]
-+ || peer->afc[AFI_BGP_LS][SAFI_BGP_LS])
- return 1;
- return 0;
- }
-diff --git a/bgpd/rfapi/rfapi_import.c b/bgpd/rfapi/rfapi_import.c
-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)
- }
- break;
-
-+ case SAFI_BGP_LS:
- case SAFI_UNSPEC:
- case SAFI_UNICAST:
- case SAFI_MULTICAST:
-@@ -2981,6 +2982,7 @@ static void rfapiBgpInfoFilteredImportEncap(
- rt = import_table->imported_encap[afi];
- break;
-
-+ case AFI_BGP_LS:
- case AFI_UNSPEC:
- case AFI_L2VPN:
- case AFI_MAX:
-@@ -3435,6 +3437,7 @@ void rfapiBgpInfoFilteredImportVPN(
- rt = import_table->imported_vpn[afi];
- break;
-
-+ case AFI_BGP_LS:
- case AFI_UNSPEC:
- case AFI_MAX:
- flog_err(EC_LIB_DEVELOPMENT, "%s: bad afi %d", __func__, afi);
-@@ -3831,6 +3834,7 @@ rfapiBgpInfoFilteredImportFunction(safi_t safi)
- case SAFI_ENCAP:
- return rfapiBgpInfoFilteredImportEncap;
-
-+ case SAFI_BGP_LS:
- case SAFI_UNSPEC:
- case SAFI_UNICAST:
- case SAFI_MULTICAST:
-@@ -4076,6 +4080,7 @@ static void rfapiProcessPeerDownRt(struct peer *peer,
- rt = import_table->imported_encap[afi];
- timer_service_func = rfapiWithdrawTimerEncap;
- break;
-+ case SAFI_BGP_LS:
- case SAFI_UNSPEC:
- case SAFI_UNICAST:
- case SAFI_MULTICAST:
-diff --git a/bgpd/rfapi/rfapi_monitor.c b/bgpd/rfapi/rfapi_monitor.c
-index 83e8afa0e5..45ada52202 100644
---- a/bgpd/rfapi/rfapi_monitor.c
-+++ b/bgpd/rfapi/rfapi_monitor.c
-@@ -231,6 +231,7 @@ void rfapiMonitorExtraFlush(safi_t safi, struct agg_node *rn)
- }
- break;
-
-+ case SAFI_BGP_LS:
- case SAFI_UNSPEC:
- case SAFI_UNICAST:
- case SAFI_MULTICAST:
-@@ -299,6 +300,7 @@ void rfapiMonitorExtraPrune(safi_t safi, struct agg_node *rn)
- }
- break;
-
-+ case SAFI_BGP_LS:
- case SAFI_UNSPEC:
- case SAFI_UNICAST:
- case SAFI_MULTICAST:
-diff --git a/bgpd/subdir.am b/bgpd/subdir.am
-index 07a9df979c..3e179075d6 100644
---- a/bgpd/subdir.am
-+++ b/bgpd/subdir.am
-@@ -51,6 +51,9 @@ bgpd_libbgp_a_SOURCES = \
- bgpd/bgp_label.c \
- bgpd/bgp_labelpool.c \
- bgpd/bgp_lcommunity.c \
-+ bgpd/bgp_ls.c \
-+ bgpd/bgp_ls_nlri.c \
-+ bgpd/bgp_ls_ted.c \
- bgpd/bgp_mac.c \
- bgpd/bgp_memory.c \
- bgpd/bgp_mpath.c \
-@@ -136,6 +139,9 @@ noinst_HEADERS += \
- bgpd/bgp_label.h \
- bgpd/bgp_labelpool.h \
- bgpd/bgp_lcommunity.h \
-+ bgpd/bgp_ls.h \
-+ bgpd/bgp_ls_nlri.h \
-+ bgpd/bgp_ls_ted.h \
- bgpd/bgp_mac.h \
- bgpd/bgp_memory.h \
- bgpd/bgp_mpath.h \
-diff --git a/doc/user/bgp-linkstate.rst b/doc/user/bgp-linkstate.rst
-new file mode 100644
-index 0000000000..5a65c7add0
---- /dev/null
-+++ b/doc/user/bgp-linkstate.rst
-@@ -0,0 +1,274 @@
-+.. _bgp-link-state:
-+
-+BGP Link-State
-+==============
-+
-+BGP Link-State (BGP-LS) is a mechanism to distribute network topology information
-+via BGP. It enables BGP to carry link-state information learned from IGP protocols
-+(IS-IS, OSPF) to other BGP speakers. This is useful for applications such as
-+Software-Defined Networking (SDN) controllers, Traffic Engineering, and network
-+visualization tools that need a complete view of the network topology.
-+
-+BGP-LS is defined in :rfc:`9552`.
-+
-+Overview
-+--------
-+
-+BGP-LS introduces a new BGP AFI/SAFI combination (AFI 16388, SAFI 71) to carry
-+link-state Network Layer Reachability Information (NLRI). The link-state NLRI
-+describes nodes, links, and prefixes in the network topology.
-+
-+Three types of BGP-LS NLRI are defined:
-+
-+- **Node NLRI** - Describes routers/nodes in the network
-+- **Link NLRI** - Describes links between nodes
-+- **Prefix NLRI** - Describes IPv4/IPv6 prefixes advertised by nodes
-+
-+BGP-LS carries topology information in a new BGP path attribute called the
-+BGP-LS Attribute (type 29), which contains various TLVs describing node
-+properties, link metrics, bandwidth, prefix attributes, and other topology
-+characteristics.
-+
-+Configuration
-+-------------
-+
-+BGP-LS Producer Configuration
-+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-+
-+To enable BGP-LS and originate topology information from the local IGP, configure
-+the link-state address family:
-+
-+.. code-block:: frr
-+
-+ router bgp 65001
-+ neighbor 192.0.2.1 remote-as 65002
-+ !
-+ address-family link-state
-+ neighbor 192.0.2.1 activate
-+ exit-address-family
-+
-+When the link-state address family is activated on a neighbor, BGP will:
-+
-+1. Negotiate BGP-LS capability during session establishment
-+2. Receive IGP topology updates from Zebra (IS-IS and OSPF)
-+3. Convert IGP topology to BGP-LS NLRI (Node, Link, Prefix)
-+4. Advertise BGP-LS routes to the activated neighbor
-+
-+BGP-LS Consumer Configuration
-+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-+
-+To receive and process BGP-LS information from peers without originating routes:
-+
-+.. code-block:: frr
-+
-+ router bgp 65002
-+ neighbor 192.0.2.2 remote-as 65001
-+ !
-+ address-family link-state
-+ 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
-+------------------------------
-+
-+Show All BGP-LS NLRIs
-+^^^^^^^^^^^^^^^^^^^^^^^
-+
-+.. clicmd:: show bgp link-state link-state [json]
-+
-+ Display all BGP-LS routes in the link-state RIB. Shows Node, Link, and Prefix
-+ NLRI with their associated attributes.
-+
-+ Example output:
-+
-+ .. code-block:: frr
-+
-+ router# show bgp link-state link-state
-+ BGP table version is 15, local router ID is 192.0.2.1
-+ Status codes: s suppressed, d damped, h history, * valid, > best, = multipath,
-+ i internal, r RIB-failure, S Stale, R Removed
-+ Nexthop codes: @NNN nexthop's vrf id, < announce-nh-self
-+ Origin codes: i - IGP, e - EGP, ? - incomplete
-+ RPKI validation codes: V valid, I invalid, N Not found
-+
-+ Network Next Hop Metric LocPrf Weight Path
-+ *> [1][0.0.0.0][6][0][0][0300.0000.0001]
-+ 0.0.0.0(vrf1) 0 32768 ?
-+ *> [2][0.0.0.0][6][0][0][0300.0000.0001][0300.0000.0002][192.0.2.1][192.0.2.2]
-+ 0.0.0.0(vrf1) 0 32768 ?
-+
-+ With the ``json`` option, output is displayed in JSON format suitable for
-+ programmatic processing.
-+
-+Show Specific BGP-LS NLRI
-+^^^^^^^^^^^^^^^^^^^^^^^^^^^
-+
-+.. clicmd:: show bgp link-state link-state NLRI [json]
-+
-+ Display detailed information about a specific BGP-LS NLRI identified by its
-+ NLRI string. The NLRI string format is:
-+
-+ - Node NLRI: ``[1][IGP-ID][Protocol][Instance][AS][Area/Domain][IGP-Router-ID]``
-+ - Link NLRI: ``[2][IGP-ID][Protocol][Instance][AS][Area][Local-Router-ID][Remote-Router-ID][Local-IP][Remote-IP]``
-+ - Prefix NLRI: ``[3|4][IGP-ID][Protocol][Instance][AS][Area][Router-ID][Prefix]``
-+
-+ Example:
-+
-+ .. code-block:: frr
-+
-+ router# show bgp link-state link-state [1][0.0.0.0][6][0][0][0300.0000.0001]
-+ BGP routing table entry for [1][0.0.0.0][6][0][0][0300.0000.0001]
-+ Paths: (1 available, best #1, table link-state)
-+ Not advertised to any peer
-+ Local from 0.0.0.0 (192.0.2.1)
-+ Origin incomplete, metric 0, localpref 100, valid, sourced, local, best (First path received)
-+ Last update: Thu Jan 23 10:15:42 2026
-+
-+ Node Descriptors:
-+ Protocol-ID: IS-IS Level 2
-+ AS Number: 0
-+ IGP Router-ID: 0300.0000.0001
-+
-+ Node Attributes:
-+ Router-ID: 192.0.2.1
-+ ISIS Area-ID: 49.0001
-+ Node Flags: 0x00
-+
-+Use ``show bgp [afi] [safi] summary`` to display BGP session summary including
-+link-state address family statistics.
-+
-+Debugging
-+---------
-+
-+.. clicmd:: debug bgp linkstate
-+
-+ Enable debugging for BGP-LS operations including:
-+
-+ - NLRI encoding/decoding
-+ - Attribute processing
-+ - IGP topology updates
-+ - Route origination and withdrawal
-+ - TED updates
-+
-+Configuration Example
-+---------------------
-+
-+Complete example with IS-IS IGP and BGP-LS:
-+
-+.. code-block:: frr
-+
-+ ! IS-IS configuration
-+ router isis 1
-+ net 49.0001.0300.0000.0001.00
-+ is-type level-2-only
-+ topology ipv6-unicast
-+ !
-+ interface eth0
-+ ip router isis 1
-+ isis network point-to-point
-+ !
-+ ! BGP configuration with Link-State
-+ router bgp 65001
-+ bgp router-id 192.0.2.1
-+ neighbor 192.0.2.100 remote-as 65002
-+ neighbor 192.0.2.100 description SDN-Controller
-+ !
-+ address-family link-state
-+ neighbor 192.0.2.100 activate
-+ exit-address-family
-+
-+In this example:
-+
-+- IS-IS is configured to run on the local router
-+- BGP session is established with an SDN controller (192.0.2.100)
-+- Link-state address family is activated for the controller neighbor
-+- BGP-LS will automatically convert IS-IS topology to BGP-LS NLRI and
-+ advertise it to the controller
-+
-+Use Cases
-+---------
-+
-+BGP-LS is typically used in the following scenarios:
-+
-+**SDN Controllers**
-+ SDN controllers use BGP-LS to obtain a complete view of the network topology
-+ for path computation, traffic engineering, and network optimization.
-+
-+**PCE (Path Computation Element)**
-+ PCE servers use BGP-LS to maintain an up-to-date TED for computing MPLS-TE
-+ and SR-TE paths.
-+
-+**Network Monitoring and Visualization**
-+ Network management systems use BGP-LS to visualize network topology and
-+ monitor link utilization, metrics, and failures.
-+
-+**Multi-Domain TE**
-+ BGP-LS enables traffic engineering across multiple IGP domains by sharing
-+ 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 4bd4c7f019..121ceeb437 100644
---- a/doc/user/bgp.rst
-+++ b/doc/user/bgp.rst
-@@ -5811,6 +5811,8 @@ Show command json output:
-
- .. include:: flowspec.rst
-
-+.. include:: bgp-linkstate.rst
-+
- .. [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_te.c b/isisd/isis_te.c
-index 84504086e2..0c5c870f68 100644
---- a/isisd/isis_te.c
-+++ b/isisd/isis_te.c
-@@ -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;
-
- /* 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) {
-+ 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 fe6bca11f9..ce9d894855 100644
---- a/lib/command.h
-+++ b/lib/command.h
-@@ -120,6 +120,7 @@ enum node_type {
- BGP_VNC_L2_GROUP_NODE, /* BGP VNC L2 group */
- BGP_EVPN_NODE, /* BGP EVPN node. */
- BGP_SRV6_NODE, /* BGP SRv6 node. */
-+ BGP_LS_NODE, /* BGP Link-State node. */
- OSPF_NODE, /* OSPF protocol mode */
- OSPF6_NODE, /* OSPF protocol for IPv6 mode */
- LDP_NODE, /* LDP protocol mode */
-diff --git a/lib/iana_afi.h b/lib/iana_afi.h
-index b9c19cc3d5..4d4687c4c4 100644
---- a/lib/iana_afi.h
-+++ b/lib/iana_afi.h
-@@ -26,6 +26,7 @@ typedef enum {
- IANA_AFI_IPV4 = 1,
- IANA_AFI_IPV6 = 2,
- IANA_AFI_L2VPN = 25,
-+ IANA_AFI_BGP_LS = 16388, /* BGP-LS per RFC 9552 */
- } iana_afi_t;
-
- typedef enum {
-@@ -35,6 +36,7 @@ typedef enum {
- IANA_SAFI_LABELED_UNICAST = 4,
- IANA_SAFI_ENCAP = 7,
- IANA_SAFI_EVPN = 70,
-+ IANA_SAFI_BGP_LS = 71, /* BGP-LS per RFC 9552 */
- IANA_SAFI_MPLS_VPN = 128,
- IANA_SAFI_FLOWSPEC = 133
- } iana_safi_t;
-@@ -48,6 +50,8 @@ static inline afi_t afi_iana2int(iana_afi_t afi)
- return AFI_IP6;
- case IANA_AFI_L2VPN:
- return AFI_L2VPN;
-+ case IANA_AFI_BGP_LS:
-+ return AFI_BGP_LS;
- case IANA_AFI_RESERVED:
- return AFI_MAX;
- }
-@@ -64,6 +68,8 @@ static inline iana_afi_t afi_int2iana(afi_t afi)
- return IANA_AFI_IPV6;
- case AFI_L2VPN:
- return IANA_AFI_L2VPN;
-+ case AFI_BGP_LS:
-+ return IANA_AFI_BGP_LS;
- case AFI_UNSPEC:
- case AFI_MAX:
- return IANA_AFI_RESERVED;
-@@ -94,6 +100,8 @@ static inline safi_t safi_iana2int(iana_safi_t safi)
- return SAFI_LABELED_UNICAST;
- case IANA_SAFI_FLOWSPEC:
- return SAFI_FLOWSPEC;
-+ case IANA_SAFI_BGP_LS:
-+ return SAFI_BGP_LS;
- case IANA_SAFI_RESERVED:
- return SAFI_MAX;
- }
-@@ -118,6 +126,8 @@ static inline iana_safi_t safi_int2iana(safi_t safi)
- return IANA_SAFI_LABELED_UNICAST;
- case SAFI_FLOWSPEC:
- return IANA_SAFI_FLOWSPEC;
-+ case SAFI_BGP_LS:
-+ return IANA_SAFI_BGP_LS;
- case SAFI_UNSPEC:
- case SAFI_MAX:
- return IANA_SAFI_RESERVED;
-diff --git a/lib/link_state.c b/lib/link_state.c
-index 3603407edb..8a9acc51ee 100644
---- a/lib/link_state.c
-+++ b/lib/link_state.c
-@@ -146,6 +146,12 @@ int ls_node_same(struct ls_node *n1, struct ls_node *n2)
- if (memcmp(&n1->srv6_msd, &n2->srv6_msd, sizeof(n1->srv6_msd)))
- return 0;
- }
-+ if (CHECK_FLAG(n1->flags, LS_NODE_ISIS_AREA_ID)) {
-+ if (n1->isis_area_id_len != n2->isis_area_id_len)
-+ return 0;
-+ if (memcmp(n1->isis_area_id, n2->isis_area_id, n1->isis_area_id_len))
-+ return 0;
-+ }
-
- /* OK, n1 & n2 are equal */
- return 1;
-@@ -1218,6 +1224,15 @@ static struct ls_node *ls_parse_node(struct stream *s)
- }
- if (CHECK_FLAG(node->flags, LS_NODE_MSD))
- STREAM_GETC(s, node->msd);
-+ if (CHECK_FLAG(node->flags, LS_NODE_ISIS_AREA_ID)) {
-+ STREAM_GETC(s, node->isis_area_id_len);
-+ if (node->isis_area_id_len > ISO_ADDR_SIZE) {
-+ zlog_err("LS(%s): IS-IS Area ID length exceeds maximum size (%d)",
-+ __func__, node->isis_area_id_len);
-+ goto stream_failure;
-+ }
-+ STREAM_GET(node->isis_area_id, s, node->isis_area_id_len);
-+ }
-
- return node;
-
-@@ -1485,6 +1500,10 @@ static int ls_format_node(struct stream *s, struct ls_node *node)
- }
- if (CHECK_FLAG(node->flags, LS_NODE_MSD))
- stream_putc(s, node->msd);
-+ if (CHECK_FLAG(node->flags, LS_NODE_ISIS_AREA_ID)) {
-+ stream_putc(s, node->isis_area_id_len);
-+ stream_put(s, node->isis_area_id, node->isis_area_id_len);
-+ }
-
- return 0;
- }
-@@ -2114,6 +2133,14 @@ static void ls_show_vertex_vty(struct ls_vertex *vertex, struct vty *vty,
- sbuf_push(&sbuf, 4, "Type: %s\n", type2txt[lsn->type]);
- if (CHECK_FLAG(lsn->flags, LS_NODE_AS_NUMBER))
- sbuf_push(&sbuf, 4, "AS number: %u\n", lsn->as_number);
-+ if (CHECK_FLAG(lsn->flags, LS_NODE_ISIS_AREA_ID)) {
-+ struct iso_address area_addr;
-+
-+ area_addr.addr_len = lsn->isis_area_id_len;
-+ memcpy(area_addr.area_addr, lsn->isis_area_id, lsn->isis_area_id_len);
-+
-+ sbuf_push(&sbuf, 4, "IS-IS Area ID: %pIS\n", &area_addr);
-+ }
- if (CHECK_FLAG(lsn->flags, LS_NODE_SR)) {
- sbuf_push(&sbuf, 4, "Segment Routing Capabilities:\n");
- upper = lsn->srgb.lower_bound + lsn->srgb.range_size - 1;
-@@ -2246,6 +2273,13 @@ static void ls_show_vertex_json(struct ls_vertex *vertex,
- if (CHECK_FLAG(lsn->flags, LS_NODE_MSD))
- json_object_int_add(jsr, "msd", lsn->msd);
- }
-+ if (CHECK_FLAG(lsn->flags, LS_NODE_ISIS_AREA_ID)) {
-+ struct iso_address area_addr;
-+
-+ area_addr.addr_len = lsn->isis_area_id_len;
-+ memcpy(area_addr.area_addr, lsn->isis_area_id, lsn->isis_area_id_len);
-+ json_object_string_addf(json, "isis-area-id", "%pIS", &area_addr);
-+ }
- }
-
- 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..7f0ba2f336 100644
---- a/lib/link_state.h
-+++ b/lib/link_state.h
-@@ -13,6 +13,7 @@
- #define _FRR_LINK_STATE_H_
-
- #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
- #define LS_NODE_SRV6 0x0200
-+#define LS_NODE_ISIS_AREA_ID 0x0400
-
- /* Link State Node structure */
- struct ls_node {
- uint16_t flags; /* Flag for parameters validity */
- struct ls_node_id adv; /* Adv. Router of this Link State */
- char name[MAX_NAME_LENGTH]; /* Name of the Node (IS-IS only) */
-+ uint8_t isis_area_id_len; /* Length of isis_area_id data */
-+ uint8_t isis_area_id[ISO_ADDR_SIZE]; /* IS-IS Area ID */
- struct in_addr router_id; /* IPv4 Router ID */
- 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 fb66e39f38..1f7e28c8ae 100644
---- a/lib/prefix.c
-+++ b/lib/prefix.c
-@@ -132,6 +132,8 @@ const char *afi2str_lower(afi_t afi)
- return "ipv6";
- case AFI_L2VPN:
- return "l2vpn";
-+ case AFI_BGP_LS:
-+ return "bgp-ls";
- case AFI_MAX:
- case AFI_UNSPEC:
- return "bad-value";
-@@ -150,6 +152,8 @@ const char *afi2str(afi_t afi)
- return "IPv6";
- case AFI_L2VPN:
- return "l2vpn";
-+ case AFI_BGP_LS:
-+ return "BGP-LS";
- case AFI_MAX:
- case AFI_UNSPEC:
- return "bad-value";
-@@ -176,6 +180,8 @@ const char *safi2str(safi_t safi)
- return "labeled-unicast";
- case SAFI_FLOWSPEC:
- return "flowspec";
-+ case SAFI_BGP_LS:
-+ return "bgp-ls";
- case SAFI_UNSPEC:
- case SAFI_MAX:
- return "unknown";
-diff --git a/lib/zebra.h b/lib/zebra.h
-index 81b762b593..3985b80f78 100644
---- a/lib/zebra.h
-+++ b/lib/zebra.h
-@@ -167,7 +167,8 @@ typedef enum {
- AFI_IP = 1,
- AFI_IP6 = 2,
- AFI_L2VPN = 3,
-- AFI_MAX = 4
-+ AFI_BGP_LS = 4, /* BGP-LS (RFC 9552) */
-+ AFI_MAX = 5
- } afi_t;
-
- #define IS_VALID_AFI(a) ((a) > AFI_UNSPEC && (a) < AFI_MAX)
-@@ -182,7 +183,8 @@ typedef enum {
- SAFI_EVPN = 5,
- SAFI_LABELED_UNICAST = 6,
- SAFI_FLOWSPEC = 7,
-- SAFI_MAX = 8
-+ SAFI_BGP_LS = 8, /* BGP-LS (RFC 9552) */
-+ SAFI_MAX = 9
- } safi_t;
-
- #define FOREACH_AFI_SAFI(afi, safi) \
-diff --git a/pbrd/pbr_zebra.c b/pbrd/pbr_zebra.c
-index c8b5c1e814..e1cd2f038e 100644
---- a/pbrd/pbr_zebra.c
-+++ b/pbrd/pbr_zebra.c
-@@ -311,6 +311,10 @@ void route_add(struct pbr_nexthop_group_cache *pnhgc, struct nexthop_group nhg,
- case AFI_IP6:
- route_add_helper(&api, nhg, AF_INET6);
- break;
-+ case AFI_BGP_LS:
-+ DEBUGD(&pbr_dbg_zebra, "%s: Asked to install unsupported route type: BGP-LS",
-+ __func__);
-+ break;
- case AFI_L2VPN:
- DEBUGD(&pbr_dbg_zebra,
- "%s: Asked to install unsupported route type: L2VPN",
-@@ -356,6 +360,10 @@ void route_delete(struct pbr_nexthop_group_cache *pnhgc, afi_t afi)
- api.prefix.family = AF_INET6;
- zclient_route_send(ZEBRA_ROUTE_DELETE, pbr_zclient, &api);
- break;
-+ case AFI_BGP_LS:
-+ DEBUGD(&pbr_dbg_zebra, "%s: Asked to delete unsupported route type: BGP-LS",
-+ __func__);
-+ break;
- case AFI_L2VPN:
- 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 eab3faff6b..84c2ea2187 100644
---- a/staticd/static_vty.c
-+++ b/staticd/static_vty.c
-@@ -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:
-+ case AFI_BGP_LS:
- case AFI_UNSPEC:
- case AFI_MAX:
- break;
-diff --git a/tests/bgpd/test_peer_attr.c b/tests/bgpd/test_peer_attr.c
-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)
- return "ipv6";
- case AFI_L2VPN:
- return "l2vpn";
-+ case AFI_BGP_LS:
-+ return "bgp-ls";
- case AFI_MAX:
- case AFI_UNSPEC:
- return "bad-value";
-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..3c1ee57dee
---- /dev/null
-+++ b/tests/topotests/bgp_link_state/r1/frr.conf
-@@ -0,0 +1,57 @@
-+
-+hostname r1
-+!
-+!debug isis te-events
-+!
-+interface lo
-+ ip address 1.1.1.1/32
-+ ipv6 address fc00:0:1::1/128
-+ ip router isis 1
-+ ipv6 router isis 1
-+ isis passive
-+!
-+interface r1-eth0
-+ ip address 10.0.1.1/24
-+ ipv6 address fc00:10:0:1::1/64
-+ link-params
-+ neighbor 10.0.255.2
-+ ip router isis 1
-+ ipv6 router isis 1
-+ isis circuit-type level-2
-+ isis hello-interval 1
-+ isis hello-multiplier 3
-+ isis network point-to-point
-+!
-+interface r1-eth1
-+ ip address 10.0.4.1/24
-+ ipv6 address fc00:10:0:4::1/64
-+ link-params
-+ neighbor 10.0.255.4
-+ ip router isis 1
-+ ipv6 router isis 1
-+ isis circuit-type level-2
-+ isis hello-interval 1
-+ isis hello-multiplier 3
-+ isis network point-to-point
-+!
-+router isis 1
-+ net 49.0001.0000.0000.0001.00
-+ is-type level-2-only
-+ topology ipv6-unicast
-+ redistribute ipv4 static level-2
-+ redistribute ipv6 static level-2
-+ redistribute ipv4 connected level-2
-+ redistribute ipv6 connected level-2
-+ lsp-gen-interval 1
-+ lsp-refresh-interval 10
-+ max-lsp-lifetime 20
-+ mpls-te on
-+ mpls-te router-address 1.1.1.1
-+ mpls-te router-address ipv6 fc00:0:1::1
-+ segment-routing on
-+ segment-routing node-msd 7
-+ segment-routing global-block 100000 100999
-+ segment-routing prefix 1.1.1.1/32 index 1
-+!
-+ip forwarding
-+!
-diff --git a/tests/topotests/bgp_link_state/r1/isis_adj.json b/tests/topotests/bgp_link_state/r1/isis_adj.json
-new file mode 100644
-index 0000000000..03b7d29a04
---- /dev/null
-+++ b/tests/topotests/bgp_link_state/r1/isis_adj.json
-@@ -0,0 +1,18 @@
-+{
-+ "areas": [
-+ {
-+ "circuits": [
-+ {
-+ "adj": "r2",
-+ "interface": "r1-eth0",
-+ "state": "Up"
-+ },
-+ {
-+ "adj": "r4",
-+ "interface": "r1-eth1",
-+ "state": "Up"
-+ }
-+ ]
-+ }
-+ ]
-+}
-diff --git a/tests/topotests/bgp_link_state/r2/bgp_capability.json b/tests/topotests/bgp_link_state/r2/bgp_capability.json
-new file mode 100644
-index 0000000000..720bc7cc70
---- /dev/null
-+++ b/tests/topotests/bgp_link_state/r2/bgp_capability.json
-@@ -0,0 +1,11 @@
-+{
-+ "10.0.3.4": {
-+ "neighborCapabilities": {
-+ "multiprotocolExtensions": {
-+ "linkState": {
-+ "advertisedAndReceived": true
-+ }
-+ }
-+ }
-+ }
-+}
-diff --git a/tests/topotests/bgp_link_state/r2/bgp_ls_nlri.json b/tests/topotests/bgp_link_state/r2/bgp_ls_nlri.json
-new file mode 100644
-index 0000000000..dee6768b71
---- /dev/null
-+++ b/tests/topotests/bgp_link_state/r2/bgp_ls_nlri.json
-@@ -0,0 +1,1507 @@
-+{
-+ "vrfId": 0,
-+ "vrfName": "default",
-+ "routerId": "2.2.2.2",
-+ "routes": {
-+ "[V][L2][I0x0][N[s0000.0000.0002]]": [
-+ {
-+ "valid": true,
-+ "bestpath": true,
-+ "selectionReason": "First path received",
-+ "pathFrom": "external",
-+ "weight": 32768,
-+ "peerId": "(unspec)",
-+ "path": "",
-+ "origin": "incomplete",
-+ "nexthops": [
-+ {
-+ "ip": "::",
-+ "hostname": "r2",
-+ "afi": "ipv6",
-+ "used": true
-+ }
-+ ],
-+ "nlri": {
-+ "nlriType": "node",
-+ "protocolId": 2,
-+ "identifier": 0,
-+ "localNodeDescriptors": {
-+ "igpRouterId": "0000.0000.0002"
-+ }
-+ },
-+ "nlriStr": "[V][L2][I0x0][N[s0000.0000.0002]]"
-+ }
-+ ],
-+ "[T][L2][I0x0][N[s0000.0000.0002]][P[p10.0.1.0/24]]": [
-+ {
-+ "valid": true,
-+ "bestpath": true,
-+ "selectionReason": "First path received",
-+ "pathFrom": "external",
-+ "weight": 32768,
-+ "peerId": "(unspec)",
-+ "path": "",
-+ "origin": "incomplete",
-+ "nexthops": [
-+ {
-+ "ip": "::",
-+ "hostname": "r2",
-+ "afi": "ipv6",
-+ "used": true
-+ }
-+ ],
-+ "nlri": {
-+ "nlriType": "ipv4Prefix",
-+ "protocolId": 2,
-+ "identifier": 0,
-+ "localNodeDescriptors": {
-+ "igpRouterId": "0000.0000.0002"
-+ },
-+ "prefixDescriptors": {
-+ "ipReachabilityInformation": "10.0.1.0/24"
-+ }
-+ },
-+ "nlriStr": "[T][L2][I0x0][N[s0000.0000.0002]][P[p10.0.1.0/24]]"
-+ }
-+ ],
-+ "[T][L2][I0x0][N[s0000.0000.0002]][P[p10.0.2.0/24]]": [
-+ {
-+ "valid": true,
-+ "bestpath": true,
-+ "selectionReason": "First path received",
-+ "pathFrom": "external",
-+ "weight": 32768,
-+ "peerId": "(unspec)",
-+ "path": "",
-+ "origin": "incomplete",
-+ "nexthops": [
-+ {
-+ "ip": "::",
-+ "hostname": "r2",
-+ "afi": "ipv6",
-+ "used": true
-+ }
-+ ],
-+ "nlri": {
-+ "nlriType": "ipv4Prefix",
-+ "protocolId": 2,
-+ "identifier": 0,
-+ "localNodeDescriptors": {
-+ "igpRouterId": "0000.0000.0002"
-+ },
-+ "prefixDescriptors": {
-+ "ipReachabilityInformation": "10.0.2.0/24"
-+ }
-+ },
-+ "nlriStr": "[T][L2][I0x0][N[s0000.0000.0002]][P[p10.0.2.0/24]]"
-+ }
-+ ],
-+ "[T][L2][I0x0][N[s0000.0000.0002]][P[p2.2.2.2/32]]": [
-+ {
-+ "valid": true,
-+ "bestpath": true,
-+ "selectionReason": "First path received",
-+ "pathFrom": "external",
-+ "weight": 32768,
-+ "peerId": "(unspec)",
-+ "path": "",
-+ "origin": "incomplete",
-+ "nexthops": [
-+ {
-+ "ip": "::",
-+ "hostname": "r2",
-+ "afi": "ipv6",
-+ "used": true
-+ }
-+ ],
-+ "nlri": {
-+ "nlriType": "ipv4Prefix",
-+ "protocolId": 2,
-+ "identifier": 0,
-+ "localNodeDescriptors": {
-+ "igpRouterId": "0000.0000.0002"
-+ },
-+ "prefixDescriptors": {
-+ "ipReachabilityInformation": "2.2.2.2/32"
-+ }
-+ },
-+ "nlriStr": "[T][L2][I0x0][N[s0000.0000.0002]][P[p2.2.2.2/32]]"
-+ }
-+ ],
-+ "[T][L2][I0x0][N[s0000.0000.0002]][P[pfc00:0:2::1/128]]": [
-+ {
-+ "valid": true,
-+ "bestpath": true,
-+ "selectionReason": "First path received",
-+ "pathFrom": "external",
-+ "weight": 32768,
-+ "peerId": "(unspec)",
-+ "path": "",
-+ "origin": "incomplete",
-+ "nexthops": [
-+ {
-+ "ip": "::",
-+ "hostname": "r2",
-+ "afi": "ipv6",
-+ "used": true
-+ }
-+ ],
-+ "nlri": {
-+ "nlriType": "ipv6Prefix",
-+ "protocolId": 2,
-+ "identifier": 0,
-+ "localNodeDescriptors": {
-+ "igpRouterId": "0000.0000.0002"
-+ },
-+ "prefixDescriptors": {
-+ "ipReachabilityInformation": "fc00:0:2::1/128"
-+ }
-+ },
-+ "nlriStr": "[T][L2][I0x0][N[s0000.0000.0002]][P[pfc00:0:2::1/128]]"
-+ }
-+ ],
-+ "[V][L2][I0x0][N[s0000.0000.0001]]": [
-+ {
-+ "valid": true,
-+ "bestpath": true,
-+ "selectionReason": "First path received",
-+ "pathFrom": "external",
-+ "weight": 32768,
-+ "peerId": "(unspec)",
-+ "path": "",
-+ "origin": "incomplete",
-+ "nexthops": [
-+ {
-+ "ip": "::",
-+ "hostname": "r2",
-+ "afi": "ipv6",
-+ "used": true
-+ }
-+ ],
-+ "nlri": {
-+ "nlriType": "node",
-+ "protocolId": 2,
-+ "identifier": 0,
-+ "localNodeDescriptors": {
-+ "igpRouterId": "0000.0000.0001"
-+ }
-+ },
-+ "nlriStr": "[V][L2][I0x0][N[s0000.0000.0001]]"
-+ }
-+ ],
-+ "[T][L2][I0x0][N[s0000.0000.0001]][P[p1.1.1.1/32]]": [
-+ {
-+ "valid": true,
-+ "bestpath": true,
-+ "selectionReason": "First path received",
-+ "pathFrom": "external",
-+ "weight": 32768,
-+ "peerId": "(unspec)",
-+ "path": "",
-+ "origin": "incomplete",
-+ "nexthops": [
-+ {
-+ "ip": "::",
-+ "hostname": "r2",
-+ "afi": "ipv6",
-+ "used": true
-+ }
-+ ],
-+ "nlri": {
-+ "nlriType": "ipv4Prefix",
-+ "protocolId": 2,
-+ "identifier": 0,
-+ "localNodeDescriptors": {
-+ "igpRouterId": "0000.0000.0001"
-+ },
-+ "prefixDescriptors": {
-+ "ipReachabilityInformation": "1.1.1.1/32"
-+ }
-+ },
-+ "nlriStr": "[T][L2][I0x0][N[s0000.0000.0001]][P[p1.1.1.1/32]]"
-+ }
-+ ],
-+ "[T][L2][I0x0][N[s0000.0000.0001]][P[p10.0.4.0/24]]": [
-+ {
-+ "valid": true,
-+ "bestpath": true,
-+ "selectionReason": "First path received",
-+ "pathFrom": "external",
-+ "weight": 32768,
-+ "peerId": "(unspec)",
-+ "path": "",
-+ "origin": "incomplete",
-+ "nexthops": [
-+ {
-+ "ip": "::",
-+ "hostname": "r2",
-+ "afi": "ipv6",
-+ "used": true
-+ }
-+ ],
-+ "nlri": {
-+ "nlriType": "ipv4Prefix",
-+ "protocolId": 2,
-+ "identifier": 0,
-+ "localNodeDescriptors": {
-+ "igpRouterId": "0000.0000.0001"
-+ },
-+ "prefixDescriptors": {
-+ "ipReachabilityInformation": "10.0.4.0/24"
-+ }
-+ },
-+ "nlriStr": "[T][L2][I0x0][N[s0000.0000.0001]][P[p10.0.4.0/24]]"
-+ }
-+ ],
-+ "[T][L2][I0x0][N[s0000.0000.0001]][P[pfc00:0:1::1/128]]": [
-+ {
-+ "valid": true,
-+ "bestpath": true,
-+ "selectionReason": "First path received",
-+ "pathFrom": "external",
-+ "weight": 32768,
-+ "peerId": "(unspec)",
-+ "path": "",
-+ "origin": "incomplete",
-+ "nexthops": [
-+ {
-+ "ip": "::",
-+ "hostname": "r2",
-+ "afi": "ipv6",
-+ "used": true
-+ }
-+ ],
-+ "nlri": {
-+ "nlriType": "ipv6Prefix",
-+ "protocolId": 2,
-+ "identifier": 0,
-+ "localNodeDescriptors": {
-+ "igpRouterId": "0000.0000.0001"
-+ },
-+ "prefixDescriptors": {
-+ "ipReachabilityInformation": "fc00:0:1::1/128"
-+ }
-+ },
-+ "nlriStr": "[T][L2][I0x0][N[s0000.0000.0001]][P[pfc00:0:1::1/128]]"
-+ }
-+ ],
-+ "[T][L2][I0x0][N[s0000.0000.0001]][P[pfc00:10:0:1::/64]]": [
-+ {
-+ "valid": true,
-+ "bestpath": true,
-+ "selectionReason": "First path received",
-+ "pathFrom": "external",
-+ "weight": 32768,
-+ "peerId": "(unspec)",
-+ "path": "",
-+ "origin": "incomplete",
-+ "nexthops": [
-+ {
-+ "ip": "::",
-+ "hostname": "r2",
-+ "afi": "ipv6",
-+ "used": true
-+ }
-+ ],
-+ "nlri": {
-+ "nlriType": "ipv6Prefix",
-+ "protocolId": 2,
-+ "identifier": 0,
-+ "localNodeDescriptors": {
-+ "igpRouterId": "0000.0000.0001"
-+ },
-+ "prefixDescriptors": {
-+ "ipReachabilityInformation": "fc00:10:0:1::/64"
-+ }
-+ },
-+ "nlriStr": "[T][L2][I0x0][N[s0000.0000.0001]][P[pfc00:10:0:1::/64]]"
-+ }
-+ ],
-+ "[T][L2][I0x0][N[s0000.0000.0001]][P[pfc00:10:0:4::/64]]": [
-+ {
-+ "valid": true,
-+ "bestpath": true,
-+ "selectionReason": "First path received",
-+ "pathFrom": "external",
-+ "weight": 32768,
-+ "peerId": "(unspec)",
-+ "path": "",
-+ "origin": "incomplete",
-+ "nexthops": [
-+ {
-+ "ip": "::",
-+ "hostname": "r2",
-+ "afi": "ipv6",
-+ "used": true
-+ }
-+ ],
-+ "nlri": {
-+ "nlriType": "ipv6Prefix",
-+ "protocolId": 2,
-+ "identifier": 0,
-+ "localNodeDescriptors": {
-+ "igpRouterId": "0000.0000.0001"
-+ },
-+ "prefixDescriptors": {
-+ "ipReachabilityInformation": "fc00:10:0:4::/64"
-+ }
-+ },
-+ "nlriStr": "[T][L2][I0x0][N[s0000.0000.0001]][P[pfc00:10:0:4::/64]]"
-+ }
-+ ],
-+ "[T][L2][I0x0][N[s0000.0000.0002]][P[p10.0.3.0/24]]": [
-+ {
-+ "valid": true,
-+ "bestpath": true,
-+ "selectionReason": "First path received",
-+ "pathFrom": "external",
-+ "weight": 32768,
-+ "peerId": "(unspec)",
-+ "path": "",
-+ "origin": "incomplete",
-+ "nexthops": [
-+ {
-+ "ip": "::",
-+ "hostname": "r2",
-+ "afi": "ipv6",
-+ "used": true
-+ }
-+ ],
-+ "nlri": {
-+ "nlriType": "ipv4Prefix",
-+ "protocolId": 2,
-+ "identifier": 0,
-+ "localNodeDescriptors": {
-+ "igpRouterId": "0000.0000.0002"
-+ },
-+ "prefixDescriptors": {
-+ "ipReachabilityInformation": "10.0.3.0/24"
-+ }
-+ },
-+ "nlriStr": "[T][L2][I0x0][N[s0000.0000.0002]][P[p10.0.3.0/24]]"
-+ }
-+ ],
-+ "[T][L2][I0x0][N[s0000.0000.0001]][P[p10.0.1.0/24]]": [
-+ {
-+ "valid": true,
-+ "bestpath": true,
-+ "selectionReason": "First path received",
-+ "pathFrom": "external",
-+ "weight": 32768,
-+ "peerId": "(unspec)",
-+ "path": "",
-+ "origin": "incomplete",
-+ "nexthops": [
-+ {
-+ "ip": "::",
-+ "hostname": "r2",
-+ "afi": "ipv6",
-+ "used": true
-+ }
-+ ],
-+ "nlri": {
-+ "nlriType": "ipv4Prefix",
-+ "protocolId": 2,
-+ "identifier": 0,
-+ "localNodeDescriptors": {
-+ "igpRouterId": "0000.0000.0001"
-+ },
-+ "prefixDescriptors": {
-+ "ipReachabilityInformation": "10.0.1.0/24"
-+ }
-+ },
-+ "nlriStr": "[T][L2][I0x0][N[s0000.0000.0001]][P[p10.0.1.0/24]]"
-+ }
-+ ],
-+ "[V][L2][I0x0][N[s0000.0000.0003]]": [
-+ {
-+ "valid": true,
-+ "bestpath": true,
-+ "selectionReason": "First path received",
-+ "pathFrom": "external",
-+ "weight": 32768,
-+ "peerId": "(unspec)",
-+ "path": "",
-+ "origin": "incomplete",
-+ "nexthops": [
-+ {
-+ "ip": "::",
-+ "hostname": "r2",
-+ "afi": "ipv6",
-+ "used": true
-+ }
-+ ],
-+ "nlri": {
-+ "nlriType": "node",
-+ "protocolId": 2,
-+ "identifier": 0,
-+ "localNodeDescriptors": {
-+ "igpRouterId": "0000.0000.0003"
-+ }
-+ },
-+ "nlriStr": "[V][L2][I0x0][N[s0000.0000.0003]]"
-+ }
-+ ],
-+ "[T][L2][I0x0][N[s0000.0000.0003]][P[p3.3.3.3/32]]": [
-+ {
-+ "valid": true,
-+ "bestpath": true,
-+ "selectionReason": "First path received",
-+ "pathFrom": "external",
-+ "weight": 32768,
-+ "peerId": "(unspec)",
-+ "path": "",
-+ "origin": "incomplete",
-+ "nexthops": [
-+ {
-+ "ip": "::",
-+ "hostname": "r2",
-+ "afi": "ipv6",
-+ "used": true
-+ }
-+ ],
-+ "nlri": {
-+ "nlriType": "ipv4Prefix",
-+ "protocolId": 2,
-+ "identifier": 0,
-+ "localNodeDescriptors": {
-+ "igpRouterId": "0000.0000.0003"
-+ },
-+ "prefixDescriptors": {
-+ "ipReachabilityInformation": "3.3.3.3/32"
-+ }
-+ },
-+ "nlriStr": "[T][L2][I0x0][N[s0000.0000.0003]][P[p3.3.3.3/32]]"
-+ }
-+ ],
-+ "[T][L2][I0x0][N[s0000.0000.0003]][P[p10.0.5.0/24]]": [
-+ {
-+ "valid": true,
-+ "bestpath": true,
-+ "selectionReason": "First path received",
-+ "pathFrom": "external",
-+ "weight": 32768,
-+ "peerId": "(unspec)",
-+ "path": "",
-+ "origin": "incomplete",
-+ "nexthops": [
-+ {
-+ "ip": "::",
-+ "hostname": "r2",
-+ "afi": "ipv6",
-+ "used": true
-+ }
-+ ],
-+ "nlri": {
-+ "nlriType": "ipv4Prefix",
-+ "protocolId": 2,
-+ "identifier": 0,
-+ "localNodeDescriptors": {
-+ "igpRouterId": "0000.0000.0003"
-+ },
-+ "prefixDescriptors": {
-+ "ipReachabilityInformation": "10.0.5.0/24"
-+ }
-+ },
-+ "nlriStr": "[T][L2][I0x0][N[s0000.0000.0003]][P[p10.0.5.0/24]]"
-+ }
-+ ],
-+ "[T][L2][I0x0][N[s0000.0000.0003]][P[pfc00:0:3::1/128]]": [
-+ {
-+ "valid": true,
-+ "bestpath": true,
-+ "selectionReason": "First path received",
-+ "pathFrom": "external",
-+ "weight": 32768,
-+ "peerId": "(unspec)",
-+ "path": "",
-+ "origin": "incomplete",
-+ "nexthops": [
-+ {
-+ "ip": "::",
-+ "hostname": "r2",
-+ "afi": "ipv6",
-+ "used": true
-+ }
-+ ],
-+ "nlri": {
-+ "nlriType": "ipv6Prefix",
-+ "protocolId": 2,
-+ "identifier": 0,
-+ "localNodeDescriptors": {
-+ "igpRouterId": "0000.0000.0003"
-+ },
-+ "prefixDescriptors": {
-+ "ipReachabilityInformation": "fc00:0:3::1/128"
-+ }
-+ },
-+ "nlriStr": "[T][L2][I0x0][N[s0000.0000.0003]][P[pfc00:0:3::1/128]]"
-+ }
-+ ],
-+ "[T][L2][I0x0][N[s0000.0000.0002]][P[pfc00:10:0:1::/64]]": [
-+ {
-+ "valid": true,
-+ "bestpath": true,
-+ "selectionReason": "First path received",
-+ "pathFrom": "external",
-+ "weight": 32768,
-+ "peerId": "(unspec)",
-+ "path": "",
-+ "origin": "incomplete",
-+ "nexthops": [
-+ {
-+ "ip": "::",
-+ "hostname": "r2",
-+ "afi": "ipv6",
-+ "used": true
-+ }
-+ ],
-+ "nlri": {
-+ "nlriType": "ipv6Prefix",
-+ "protocolId": 2,
-+ "identifier": 0,
-+ "localNodeDescriptors": {
-+ "igpRouterId": "0000.0000.0002"
-+ },
-+ "prefixDescriptors": {
-+ "ipReachabilityInformation": "fc00:10:0:1::/64"
-+ }
-+ },
-+ "nlriStr": "[T][L2][I0x0][N[s0000.0000.0002]][P[pfc00:10:0:1::/64]]"
-+ }
-+ ],
-+ "[T][L2][I0x0][N[s0000.0000.0002]][P[pfc00:10:0:2::/64]]": [
-+ {
-+ "valid": true,
-+ "bestpath": true,
-+ "selectionReason": "First path received",
-+ "pathFrom": "external",
-+ "weight": 32768,
-+ "peerId": "(unspec)",
-+ "path": "",
-+ "origin": "incomplete",
-+ "nexthops": [
-+ {
-+ "ip": "::",
-+ "hostname": "r2",
-+ "afi": "ipv6",
-+ "used": true
-+ }
-+ ],
-+ "nlri": {
-+ "nlriType": "ipv6Prefix",
-+ "protocolId": 2,
-+ "identifier": 0,
-+ "localNodeDescriptors": {
-+ "igpRouterId": "0000.0000.0002"
-+ },
-+ "prefixDescriptors": {
-+ "ipReachabilityInformation": "fc00:10:0:2::/64"
-+ }
-+ },
-+ "nlriStr": "[T][L2][I0x0][N[s0000.0000.0002]][P[pfc00:10:0:2::/64]]"
-+ }
-+ ],
-+ "[T][L2][I0x0][N[s0000.0000.0003]][P[p10.0.2.0/24]]": [
-+ {
-+ "valid": true,
-+ "bestpath": true,
-+ "selectionReason": "First path received",
-+ "pathFrom": "external",
-+ "weight": 32768,
-+ "peerId": "(unspec)",
-+ "path": "",
-+ "origin": "incomplete",
-+ "nexthops": [
-+ {
-+ "ip": "::",
-+ "hostname": "r2",
-+ "afi": "ipv6",
-+ "used": true
-+ }
-+ ],
-+ "nlri": {
-+ "nlriType": "ipv4Prefix",
-+ "protocolId": 2,
-+ "identifier": 0,
-+ "localNodeDescriptors": {
-+ "igpRouterId": "0000.0000.0003"
-+ },
-+ "prefixDescriptors": {
-+ "ipReachabilityInformation": "10.0.2.0/24"
-+ }
-+ },
-+ "nlriStr": "[T][L2][I0x0][N[s0000.0000.0003]][P[p10.0.2.0/24]]"
-+ }
-+ ],
-+ "[T][L2][I0x0][N[s0000.0000.0003]][P[pfc00:10:0:5::/64]]": [
-+ {
-+ "valid": true,
-+ "bestpath": true,
-+ "selectionReason": "First path received",
-+ "pathFrom": "external",
-+ "weight": 32768,
-+ "peerId": "(unspec)",
-+ "path": "",
-+ "origin": "incomplete",
-+ "nexthops": [
-+ {
-+ "ip": "::",
-+ "hostname": "r2",
-+ "afi": "ipv6",
-+ "used": true
-+ }
-+ ],
-+ "nlri": {
-+ "nlriType": "ipv6Prefix",
-+ "protocolId": 2,
-+ "identifier": 0,
-+ "localNodeDescriptors": {
-+ "igpRouterId": "0000.0000.0003"
-+ },
-+ "prefixDescriptors": {
-+ "ipReachabilityInformation": "fc00:10:0:5::/64"
-+ }
-+ },
-+ "nlriStr": "[T][L2][I0x0][N[s0000.0000.0003]][P[pfc00:10:0:5::/64]]"
-+ }
-+ ],
-+ "[V][L2][I0x0][N[s0000.0000.0004]]": [
-+ {
-+ "valid": true,
-+ "bestpath": true,
-+ "selectionReason": "First path received",
-+ "pathFrom": "external",
-+ "weight": 32768,
-+ "peerId": "(unspec)",
-+ "path": "",
-+ "origin": "incomplete",
-+ "nexthops": [
-+ {
-+ "ip": "::",
-+ "hostname": "r2",
-+ "afi": "ipv6",
-+ "used": true
-+ }
-+ ],
-+ "nlri": {
-+ "nlriType": "node",
-+ "protocolId": 2,
-+ "identifier": 0,
-+ "localNodeDescriptors": {
-+ "igpRouterId": "0000.0000.0004"
-+ }
-+ },
-+ "nlriStr": "[V][L2][I0x0][N[s0000.0000.0004]]"
-+ }
-+ ],
-+ "[T][L2][I0x0][N[s0000.0000.0004]][P[p4.4.4.4/32]]": [
-+ {
-+ "valid": true,
-+ "bestpath": true,
-+ "selectionReason": "First path received",
-+ "pathFrom": "external",
-+ "weight": 32768,
-+ "peerId": "(unspec)",
-+ "path": "",
-+ "origin": "incomplete",
-+ "nexthops": [
-+ {
-+ "ip": "::",
-+ "hostname": "r2",
-+ "afi": "ipv6",
-+ "used": true
-+ }
-+ ],
-+ "nlri": {
-+ "nlriType": "ipv4Prefix",
-+ "protocolId": 2,
-+ "identifier": 0,
-+ "localNodeDescriptors": {
-+ "igpRouterId": "0000.0000.0004"
-+ },
-+ "prefixDescriptors": {
-+ "ipReachabilityInformation": "4.4.4.4/32"
-+ }
-+ },
-+ "nlriStr": "[T][L2][I0x0][N[s0000.0000.0004]][P[p4.4.4.4/32]]"
-+ }
-+ ],
-+ "[T][L2][I0x0][N[s0000.0000.0004]][P[pfc00:0:4::1/128]]": [
-+ {
-+ "valid": true,
-+ "bestpath": true,
-+ "selectionReason": "First path received",
-+ "pathFrom": "external",
-+ "weight": 32768,
-+ "peerId": "(unspec)",
-+ "path": "",
-+ "origin": "incomplete",
-+ "nexthops": [
-+ {
-+ "ip": "::",
-+ "hostname": "r2",
-+ "afi": "ipv6",
-+ "used": true
-+ }
-+ ],
-+ "nlri": {
-+ "nlriType": "ipv6Prefix",
-+ "protocolId": 2,
-+ "identifier": 0,
-+ "localNodeDescriptors": {
-+ "igpRouterId": "0000.0000.0004"
-+ },
-+ "prefixDescriptors": {
-+ "ipReachabilityInformation": "fc00:0:4::1/128"
-+ }
-+ },
-+ "nlriStr": "[T][L2][I0x0][N[s0000.0000.0004]][P[pfc00:0:4::1/128]]"
-+ }
-+ ],
-+ "[T][L2][I0x0][N[s0000.0000.0003]][P[pfc00:10:0:2::/64]]": [
-+ {
-+ "valid": true,
-+ "bestpath": true,
-+ "selectionReason": "First path received",
-+ "pathFrom": "external",
-+ "weight": 32768,
-+ "peerId": "(unspec)",
-+ "path": "",
-+ "origin": "incomplete",
-+ "nexthops": [
-+ {
-+ "ip": "::",
-+ "hostname": "r2",
-+ "afi": "ipv6",
-+ "used": true
-+ }
-+ ],
-+ "nlri": {
-+ "nlriType": "ipv6Prefix",
-+ "protocolId": 2,
-+ "identifier": 0,
-+ "localNodeDescriptors": {
-+ "igpRouterId": "0000.0000.0003"
-+ },
-+ "prefixDescriptors": {
-+ "ipReachabilityInformation": "fc00:10:0:2::/64"
-+ }
-+ },
-+ "nlriStr": "[T][L2][I0x0][N[s0000.0000.0003]][P[pfc00:10:0:2::/64]]"
-+ }
-+ ],
-+ "[T][L2][I0x0][N[s0000.0000.0004]][P[p10.0.4.0/24]]": [
-+ {
-+ "valid": true,
-+ "bestpath": true,
-+ "selectionReason": "First path received",
-+ "pathFrom": "external",
-+ "weight": 32768,
-+ "peerId": "(unspec)",
-+ "path": "",
-+ "origin": "incomplete",
-+ "nexthops": [
-+ {
-+ "ip": "::",
-+ "hostname": "r2",
-+ "afi": "ipv6",
-+ "used": true
-+ }
-+ ],
-+ "nlri": {
-+ "nlriType": "ipv4Prefix",
-+ "protocolId": 2,
-+ "identifier": 0,
-+ "localNodeDescriptors": {
-+ "igpRouterId": "0000.0000.0004"
-+ },
-+ "prefixDescriptors": {
-+ "ipReachabilityInformation": "10.0.4.0/24"
-+ }
-+ },
-+ "nlriStr": "[T][L2][I0x0][N[s0000.0000.0004]][P[p10.0.4.0/24]]"
-+ }
-+ ],
-+ "[T][L2][I0x0][N[s0000.0000.0004]][P[p10.0.5.0/24]]": [
-+ {
-+ "valid": true,
-+ "bestpath": true,
-+ "selectionReason": "First path received",
-+ "pathFrom": "external",
-+ "weight": 32768,
-+ "peerId": "(unspec)",
-+ "path": "",
-+ "origin": "incomplete",
-+ "nexthops": [
-+ {
-+ "ip": "::",
-+ "hostname": "r2",
-+ "afi": "ipv6",
-+ "used": true
-+ }
-+ ],
-+ "nlri": {
-+ "nlriType": "ipv4Prefix",
-+ "protocolId": 2,
-+ "identifier": 0,
-+ "localNodeDescriptors": {
-+ "igpRouterId": "0000.0000.0004"
-+ },
-+ "prefixDescriptors": {
-+ "ipReachabilityInformation": "10.0.5.0/24"
-+ }
-+ },
-+ "nlriStr": "[T][L2][I0x0][N[s0000.0000.0004]][P[p10.0.5.0/24]]"
-+ }
-+ ],
-+ "[T][L2][I0x0][N[s0000.0000.0004]][P[pfc00:10:0:5::/64]]": [
-+ {
-+ "valid": true,
-+ "bestpath": true,
-+ "selectionReason": "First path received",
-+ "pathFrom": "external",
-+ "weight": 32768,
-+ "peerId": "(unspec)",
-+ "path": "",
-+ "origin": "incomplete",
-+ "nexthops": [
-+ {
-+ "ip": "::",
-+ "hostname": "r2",
-+ "afi": "ipv6",
-+ "used": true
-+ }
-+ ],
-+ "nlri": {
-+ "nlriType": "ipv6Prefix",
-+ "protocolId": 2,
-+ "identifier": 0,
-+ "localNodeDescriptors": {
-+ "igpRouterId": "0000.0000.0004"
-+ },
-+ "prefixDescriptors": {
-+ "ipReachabilityInformation": "fc00:10:0:5::/64"
-+ }
-+ },
-+ "nlriStr": "[T][L2][I0x0][N[s0000.0000.0004]][P[pfc00:10:0:5::/64]]"
-+ }
-+ ],
-+ "[T][L2][I0x0][N[s0000.0000.0004]][P[pfc00:10:0:4::/64]]": [
-+ {
-+ "valid": true,
-+ "bestpath": true,
-+ "selectionReason": "First path received",
-+ "pathFrom": "external",
-+ "weight": 32768,
-+ "peerId": "(unspec)",
-+ "path": "",
-+ "origin": "incomplete",
-+ "nexthops": [
-+ {
-+ "ip": "::",
-+ "hostname": "r2",
-+ "afi": "ipv6",
-+ "used": true
-+ }
-+ ],
-+ "nlri": {
-+ "nlriType": "ipv6Prefix",
-+ "protocolId": 2,
-+ "identifier": 0,
-+ "localNodeDescriptors": {
-+ "igpRouterId": "0000.0000.0004"
-+ },
-+ "prefixDescriptors": {
-+ "ipReachabilityInformation": "fc00:10:0:4::/64"
-+ }
-+ },
-+ "nlriStr": "[T][L2][I0x0][N[s0000.0000.0004]][P[pfc00:10:0:4::/64]]"
-+ }
-+ ],
-+ "[E][L2][I0x0][N[s0000.0000.0001]][R[s0000.0000.0002]][L[i10.0.1.1][n10.0.1.2]]": [
-+ {
-+ "valid": true,
-+ "bestpath": true,
-+ "selectionReason": "First path received",
-+ "pathFrom": "external",
-+ "weight": 32768,
-+ "peerId": "(unspec)",
-+ "path": "",
-+ "origin": "incomplete",
-+ "nexthops": [
-+ {
-+ "ip": "::",
-+ "hostname": "r2",
-+ "afi": "ipv6",
-+ "used": true
-+ }
-+ ],
-+ "nlri": {
-+ "nlriType": "link",
-+ "protocolId": 2,
-+ "identifier": 0,
-+ "localNodeDescriptors": {
-+ "igpRouterId": "0000.0000.0001"
-+ },
-+ "remoteNodeDescriptors": {
-+ "igpRouterId": "0000.0000.0002"
-+ },
-+ "linkDescriptors": {
-+ "ipv4InterfaceAddress": "10.0.1.1",
-+ "ipv4NeighborAddress": "10.0.1.2"
-+ }
-+ },
-+ "nlriStr": "[E][L2][I0x0][N[s0000.0000.0001]][R[s0000.0000.0002]][L[i10.0.1.1][n10.0.1.2]]"
-+ }
-+ ],
-+ "[E][L2][I0x0][N[s0000.0000.0002]][R[s0000.0000.0001]][L[i10.0.1.2][n10.0.1.1]]": [
-+ {
-+ "valid": true,
-+ "bestpath": true,
-+ "selectionReason": "First path received",
-+ "pathFrom": "external",
-+ "weight": 32768,
-+ "peerId": "(unspec)",
-+ "path": "",
-+ "origin": "incomplete",
-+ "nexthops": [
-+ {
-+ "ip": "::",
-+ "hostname": "r2",
-+ "afi": "ipv6",
-+ "used": true
-+ }
-+ ],
-+ "nlri": {
-+ "nlriType": "link",
-+ "protocolId": 2,
-+ "identifier": 0,
-+ "localNodeDescriptors": {
-+ "igpRouterId": "0000.0000.0002"
-+ },
-+ "remoteNodeDescriptors": {
-+ "igpRouterId": "0000.0000.0001"
-+ },
-+ "linkDescriptors": {
-+ "ipv4InterfaceAddress": "10.0.1.2",
-+ "ipv4NeighborAddress": "10.0.1.1"
-+ }
-+ },
-+ "nlriStr": "[E][L2][I0x0][N[s0000.0000.0002]][R[s0000.0000.0001]][L[i10.0.1.2][n10.0.1.1]]"
-+ }
-+ ],
-+ "[E][L2][I0x0][N[s0000.0000.0002]][R[s0000.0000.0003]][L[i10.0.2.2][n10.0.2.3]]": [
-+ {
-+ "valid": true,
-+ "bestpath": true,
-+ "selectionReason": "First path received",
-+ "pathFrom": "external",
-+ "weight": 32768,
-+ "peerId": "(unspec)",
-+ "path": "",
-+ "origin": "incomplete",
-+ "nexthops": [
-+ {
-+ "ip": "::",
-+ "hostname": "r2",
-+ "afi": "ipv6",
-+ "used": true
-+ }
-+ ],
-+ "nlri": {
-+ "nlriType": "link",
-+ "protocolId": 2,
-+ "identifier": 0,
-+ "localNodeDescriptors": {
-+ "igpRouterId": "0000.0000.0002"
-+ },
-+ "remoteNodeDescriptors": {
-+ "igpRouterId": "0000.0000.0003"
-+ },
-+ "linkDescriptors": {
-+ "ipv4InterfaceAddress": "10.0.2.2",
-+ "ipv4NeighborAddress": "10.0.2.3"
-+ }
-+ },
-+ "nlriStr": "[E][L2][I0x0][N[s0000.0000.0002]][R[s0000.0000.0003]][L[i10.0.2.2][n10.0.2.3]]"
-+ }
-+ ],
-+ "[E][L2][I0x0][N[s0000.0000.0003]][R[s0000.0000.0002]][L[i10.0.2.3][n10.0.2.2]]": [
-+ {
-+ "valid": true,
-+ "bestpath": true,
-+ "selectionReason": "First path received",
-+ "pathFrom": "external",
-+ "weight": 32768,
-+ "peerId": "(unspec)",
-+ "path": "",
-+ "origin": "incomplete",
-+ "nexthops": [
-+ {
-+ "ip": "::",
-+ "hostname": "r2",
-+ "afi": "ipv6",
-+ "used": true
-+ }
-+ ],
-+ "nlri": {
-+ "nlriType": "link",
-+ "protocolId": 2,
-+ "identifier": 0,
-+ "localNodeDescriptors": {
-+ "igpRouterId": "0000.0000.0003"
-+ },
-+ "remoteNodeDescriptors": {
-+ "igpRouterId": "0000.0000.0002"
-+ },
-+ "linkDescriptors": {
-+ "ipv4InterfaceAddress": "10.0.2.3",
-+ "ipv4NeighborAddress": "10.0.2.2"
-+ }
-+ },
-+ "nlriStr": "[E][L2][I0x0][N[s0000.0000.0003]][R[s0000.0000.0002]][L[i10.0.2.3][n10.0.2.2]]"
-+ }
-+ ],
-+ "[E][L2][I0x0][N[s0000.0000.0001]][R[s0000.0000.0004]][L[i10.0.4.1][n10.0.4.4]]": [
-+ {
-+ "valid": true,
-+ "bestpath": true,
-+ "selectionReason": "First path received",
-+ "pathFrom": "external",
-+ "weight": 32768,
-+ "peerId": "(unspec)",
-+ "path": "",
-+ "origin": "incomplete",
-+ "nexthops": [
-+ {
-+ "ip": "::",
-+ "hostname": "r2",
-+ "afi": "ipv6",
-+ "used": true
-+ }
-+ ],
-+ "nlri": {
-+ "nlriType": "link",
-+ "protocolId": 2,
-+ "identifier": 0,
-+ "localNodeDescriptors": {
-+ "igpRouterId": "0000.0000.0001"
-+ },
-+ "remoteNodeDescriptors": {
-+ "igpRouterId": "0000.0000.0004"
-+ },
-+ "linkDescriptors": {
-+ "ipv4InterfaceAddress": "10.0.4.1",
-+ "ipv4NeighborAddress": "10.0.4.4"
-+ }
-+ },
-+ "nlriStr": "[E][L2][I0x0][N[s0000.0000.0001]][R[s0000.0000.0004]][L[i10.0.4.1][n10.0.4.4]]"
-+ }
-+ ],
-+ "[E][L2][I0x0][N[s0000.0000.0004]][R[s0000.0000.0001]][L[i10.0.4.4][n10.0.4.1]]": [
-+ {
-+ "valid": true,
-+ "bestpath": true,
-+ "selectionReason": "First path received",
-+ "pathFrom": "external",
-+ "weight": 32768,
-+ "peerId": "(unspec)",
-+ "path": "",
-+ "origin": "incomplete",
-+ "nexthops": [
-+ {
-+ "ip": "::",
-+ "hostname": "r2",
-+ "afi": "ipv6",
-+ "used": true
-+ }
-+ ],
-+ "nlri": {
-+ "nlriType": "link",
-+ "protocolId": 2,
-+ "identifier": 0,
-+ "localNodeDescriptors": {
-+ "igpRouterId": "0000.0000.0004"
-+ },
-+ "remoteNodeDescriptors": {
-+ "igpRouterId": "0000.0000.0001"
-+ },
-+ "linkDescriptors": {
-+ "ipv4InterfaceAddress": "10.0.4.4",
-+ "ipv4NeighborAddress": "10.0.4.1"
-+ }
-+ },
-+ "nlriStr": "[E][L2][I0x0][N[s0000.0000.0004]][R[s0000.0000.0001]][L[i10.0.4.4][n10.0.4.1]]"
-+ }
-+ ],
-+ "[E][L2][I0x0][N[s0000.0000.0003]][R[s0000.0000.0004]][L[i10.0.5.3][n10.0.5.4]]": [
-+ {
-+ "valid": true,
-+ "bestpath": true,
-+ "selectionReason": "First path received",
-+ "pathFrom": "external",
-+ "weight": 32768,
-+ "peerId": "(unspec)",
-+ "path": "",
-+ "origin": "incomplete",
-+ "nexthops": [
-+ {
-+ "ip": "::",
-+ "hostname": "r2",
-+ "afi": "ipv6",
-+ "used": true
-+ }
-+ ],
-+ "nlri": {
-+ "nlriType": "link",
-+ "protocolId": 2,
-+ "identifier": 0,
-+ "localNodeDescriptors": {
-+ "igpRouterId": "0000.0000.0003"
-+ },
-+ "remoteNodeDescriptors": {
-+ "igpRouterId": "0000.0000.0004"
-+ },
-+ "linkDescriptors": {
-+ "ipv4InterfaceAddress": "10.0.5.3",
-+ "ipv4NeighborAddress": "10.0.5.4"
-+ }
-+ },
-+ "nlriStr": "[E][L2][I0x0][N[s0000.0000.0003]][R[s0000.0000.0004]][L[i10.0.5.3][n10.0.5.4]]"
-+ }
-+ ],
-+ "[E][L2][I0x0][N[s0000.0000.0004]][R[s0000.0000.0003]][L[i10.0.5.4][n10.0.5.3]]": [
-+ {
-+ "valid": true,
-+ "bestpath": true,
-+ "selectionReason": "First path received",
-+ "pathFrom": "external",
-+ "weight": 32768,
-+ "peerId": "(unspec)",
-+ "path": "",
-+ "origin": "incomplete",
-+ "nexthops": [
-+ {
-+ "ip": "::",
-+ "hostname": "r2",
-+ "afi": "ipv6",
-+ "used": true
-+ }
-+ ],
-+ "nlri": {
-+ "nlriType": "link",
-+ "protocolId": 2,
-+ "identifier": 0,
-+ "localNodeDescriptors": {
-+ "igpRouterId": "0000.0000.0004"
-+ },
-+ "remoteNodeDescriptors": {
-+ "igpRouterId": "0000.0000.0003"
-+ },
-+ "linkDescriptors": {
-+ "ipv4InterfaceAddress": "10.0.5.4",
-+ "ipv4NeighborAddress": "10.0.5.3"
-+ }
-+ },
-+ "nlriStr": "[E][L2][I0x0][N[s0000.0000.0004]][R[s0000.0000.0003]][L[i10.0.5.4][n10.0.5.3]]"
-+ }
-+ ],
-+ "[E][L2][I0x0][N[s0000.0000.0001]][R[s0000.0000.0002]][L[ifc00:10:0:1::1][nfc00:10:0:1::2]]":
-+ [
-+ {
-+ "valid": true,
-+ "bestpath": true,
-+ "selectionReason": "First path received",
-+ "pathFrom": "external",
-+ "weight": 32768,
-+ "peerId": "(unspec)",
-+ "path": "",
-+ "origin": "incomplete",
-+ "nexthops": [
-+ {
-+ "ip": "::",
-+ "hostname": "r2",
-+ "afi": "ipv6",
-+ "used": true
-+ }
-+ ],
-+ "nlri": {
-+ "nlriType": "link",
-+ "protocolId": 2,
-+ "identifier": 0,
-+ "localNodeDescriptors": {
-+ "igpRouterId": "0000.0000.0001"
-+ },
-+ "remoteNodeDescriptors": {
-+ "igpRouterId": "0000.0000.0002"
-+ },
-+ "linkDescriptors": {
-+ "ipv6InterfaceAddress": "fc00:10:0:1::1",
-+ "ipv6NeighborAddress": "fc00:10:0:1::2"
-+ }
-+ },
-+ "nlriStr": "[E][L2][I0x0][N[s0000.0000.0001]][R[s0000.0000.0002]][L[ifc00:10:0:1::1][nfc00:10:0:1::2]]"
-+ }
-+ ],
-+ "[E][L2][I0x0][N[s0000.0000.0002]][R[s0000.0000.0001]][L[ifc00:10:0:1::2][nfc00:10:0:1::1]]":
-+ [
-+ {
-+ "valid": true,
-+ "bestpath": true,
-+ "selectionReason": "First path received",
-+ "pathFrom": "external",
-+ "weight": 32768,
-+ "peerId": "(unspec)",
-+ "path": "",
-+ "origin": "incomplete",
-+ "nexthops": [
-+ {
-+ "ip": "::",
-+ "hostname": "r2",
-+ "afi": "ipv6",
-+ "used": true
-+ }
-+ ],
-+ "nlri": {
-+ "nlriType": "link",
-+ "protocolId": 2,
-+ "identifier": 0,
-+ "localNodeDescriptors": {
-+ "igpRouterId": "0000.0000.0002"
-+ },
-+ "remoteNodeDescriptors": {
-+ "igpRouterId": "0000.0000.0001"
-+ },
-+ "linkDescriptors": {
-+ "ipv6InterfaceAddress": "fc00:10:0:1::2",
-+ "ipv6NeighborAddress": "fc00:10:0:1::1"
-+ }
-+ },
-+ "nlriStr": "[E][L2][I0x0][N[s0000.0000.0002]][R[s0000.0000.0001]][L[ifc00:10:0:1::2][nfc00:10:0:1::1]]"
-+ }
-+ ],
-+ "[E][L2][I0x0][N[s0000.0000.0002]][R[s0000.0000.0003]][L[ifc00:10:0:2::2][nfc00:10:0:2::3]]":
-+ [
-+ {
-+ "valid": true,
-+ "bestpath": true,
-+ "selectionReason": "First path received",
-+ "pathFrom": "external",
-+ "weight": 32768,
-+ "peerId": "(unspec)",
-+ "path": "",
-+ "origin": "incomplete",
-+ "nexthops": [
-+ {
-+ "ip": "::",
-+ "hostname": "r2",
-+ "afi": "ipv6",
-+ "used": true
-+ }
-+ ],
-+ "nlri": {
-+ "nlriType": "link",
-+ "protocolId": 2,
-+ "identifier": 0,
-+ "localNodeDescriptors": {
-+ "igpRouterId": "0000.0000.0002"
-+ },
-+ "remoteNodeDescriptors": {
-+ "igpRouterId": "0000.0000.0003"
-+ },
-+ "linkDescriptors": {
-+ "ipv6InterfaceAddress": "fc00:10:0:2::2",
-+ "ipv6NeighborAddress": "fc00:10:0:2::3"
-+ }
-+ },
-+ "nlriStr": "[E][L2][I0x0][N[s0000.0000.0002]][R[s0000.0000.0003]][L[ifc00:10:0:2::2][nfc00:10:0:2::3]]"
-+ }
-+ ],
-+ "[E][L2][I0x0][N[s0000.0000.0003]][R[s0000.0000.0002]][L[ifc00:10:0:2::3][nfc00:10:0:2::2]]":
-+ [
-+ {
-+ "valid": true,
-+ "bestpath": true,
-+ "selectionReason": "First path received",
-+ "pathFrom": "external",
-+ "weight": 32768,
-+ "peerId": "(unspec)",
-+ "path": "",
-+ "origin": "incomplete",
-+ "nexthops": [
-+ {
-+ "ip": "::",
-+ "hostname": "r2",
-+ "afi": "ipv6",
-+ "used": true
-+ }
-+ ],
-+ "nlri": {
-+ "nlriType": "link",
-+ "protocolId": 2,
-+ "identifier": 0,
-+ "localNodeDescriptors": {
-+ "igpRouterId": "0000.0000.0003"
-+ },
-+ "remoteNodeDescriptors": {
-+ "igpRouterId": "0000.0000.0002"
-+ },
-+ "linkDescriptors": {
-+ "ipv6InterfaceAddress": "fc00:10:0:2::3",
-+ "ipv6NeighborAddress": "fc00:10:0:2::2"
-+ }
-+ },
-+ "nlriStr": "[E][L2][I0x0][N[s0000.0000.0003]][R[s0000.0000.0002]][L[ifc00:10:0:2::3][nfc00:10:0:2::2]]"
-+ }
-+ ],
-+ "[E][L2][I0x0][N[s0000.0000.0001]][R[s0000.0000.0004]][L[ifc00:10:0:4::1][nfc00:10:0:4::4]]":
-+ [
-+ {
-+ "valid": true,
-+ "bestpath": true,
-+ "selectionReason": "First path received",
-+ "pathFrom": "external",
-+ "weight": 32768,
-+ "peerId": "(unspec)",
-+ "path": "",
-+ "origin": "incomplete",
-+ "nexthops": [
-+ {
-+ "ip": "::",
-+ "hostname": "r2",
-+ "afi": "ipv6",
-+ "used": true
-+ }
-+ ],
-+ "nlri": {
-+ "nlriType": "link",
-+ "protocolId": 2,
-+ "identifier": 0,
-+ "localNodeDescriptors": {
-+ "igpRouterId": "0000.0000.0001"
-+ },
-+ "remoteNodeDescriptors": {
-+ "igpRouterId": "0000.0000.0004"
-+ },
-+ "linkDescriptors": {
-+ "ipv6InterfaceAddress": "fc00:10:0:4::1",
-+ "ipv6NeighborAddress": "fc00:10:0:4::4"
-+ }
-+ },
-+ "nlriStr": "[E][L2][I0x0][N[s0000.0000.0001]][R[s0000.0000.0004]][L[ifc00:10:0:4::1][nfc00:10:0:4::4]]"
-+ }
-+ ],
-+ "[E][L2][I0x0][N[s0000.0000.0004]][R[s0000.0000.0001]][L[ifc00:10:0:4::4][nfc00:10:0:4::1]]":
-+ [
-+ {
-+ "valid": true,
-+ "bestpath": true,
-+ "selectionReason": "First path received",
-+ "pathFrom": "external",
-+ "weight": 32768,
-+ "peerId": "(unspec)",
-+ "path": "",
-+ "origin": "incomplete",
-+ "nexthops": [
-+ {
-+ "ip": "::",
-+ "hostname": "r2",
-+ "afi": "ipv6",
-+ "used": true
-+ }
-+ ],
-+ "nlri": {
-+ "nlriType": "link",
-+ "protocolId": 2,
-+ "identifier": 0,
-+ "localNodeDescriptors": {
-+ "igpRouterId": "0000.0000.0004"
-+ },
-+ "remoteNodeDescriptors": {
-+ "igpRouterId": "0000.0000.0001"
-+ },
-+ "linkDescriptors": {
-+ "ipv6InterfaceAddress": "fc00:10:0:4::4",
-+ "ipv6NeighborAddress": "fc00:10:0:4::1"
-+ }
-+ },
-+ "nlriStr": "[E][L2][I0x0][N[s0000.0000.0004]][R[s0000.0000.0001]][L[ifc00:10:0:4::4][nfc00:10:0:4::1]]"
-+ }
-+ ],
-+ "[E][L2][I0x0][N[s0000.0000.0003]][R[s0000.0000.0004]][L[ifc00:10:0:5::3][nfc00:10:0:5::4]]":
-+ [
-+ {
-+ "valid": true,
-+ "bestpath": true,
-+ "selectionReason": "First path received",
-+ "pathFrom": "external",
-+ "weight": 32768,
-+ "peerId": "(unspec)",
-+ "path": "",
-+ "origin": "incomplete",
-+ "nexthops": [
-+ {
-+ "ip": "::",
-+ "hostname": "r2",
-+ "afi": "ipv6",
-+ "used": true
-+ }
-+ ],
-+ "nlri": {
-+ "nlriType": "link",
-+ "protocolId": 2,
-+ "identifier": 0,
-+ "localNodeDescriptors": {
-+ "igpRouterId": "0000.0000.0003"
-+ },
-+ "remoteNodeDescriptors": {
-+ "igpRouterId": "0000.0000.0004"
-+ },
-+ "linkDescriptors": {
-+ "ipv6InterfaceAddress": "fc00:10:0:5::3",
-+ "ipv6NeighborAddress": "fc00:10:0:5::4"
-+ }
-+ },
-+ "nlriStr": "[E][L2][I0x0][N[s0000.0000.0003]][R[s0000.0000.0004]][L[ifc00:10:0:5::3][nfc00:10:0:5::4]]"
-+ }
-+ ],
-+ "[E][L2][I0x0][N[s0000.0000.0004]][R[s0000.0000.0003]][L[ifc00:10:0:5::4][nfc00:10:0:5::3]]":
-+ [
-+ {
-+ "valid": true,
-+ "bestpath": true,
-+ "selectionReason": "First path received",
-+ "pathFrom": "external",
-+ "weight": 32768,
-+ "peerId": "(unspec)",
-+ "path": "",
-+ "origin": "incomplete",
-+ "nexthops": [
-+ {
-+ "ip": "::",
-+ "hostname": "r2",
-+ "afi": "ipv6",
-+ "used": true
-+ }
-+ ],
-+ "nlri": {
-+ "nlriType": "link",
-+ "protocolId": 2,
-+ "identifier": 0,
-+ "localNodeDescriptors": {
-+ "igpRouterId": "0000.0000.0004"
-+ },
-+ "remoteNodeDescriptors": {
-+ "igpRouterId": "0000.0000.0003"
-+ },
-+ "linkDescriptors": {
-+ "ipv6InterfaceAddress": "fc00:10:0:5::4",
-+ "ipv6NeighborAddress": "fc00:10:0:5::3"
-+ }
-+ },
-+ "nlriStr": "[E][L2][I0x0][N[s0000.0000.0004]][R[s0000.0000.0003]][L[ifc00:10:0:5::4][nfc00:10:0:5::3]]"
-+ }
-+ ]
-+ }
-+}
-\ No newline at end of file
-diff --git a/tests/topotests/bgp_link_state/r2/bgp_neighbor.json b/tests/topotests/bgp_link_state/r2/bgp_neighbor.json
-new file mode 100644
-index 0000000000..d1df9a5102
---- /dev/null
-+++ b/tests/topotests/bgp_link_state/r2/bgp_neighbor.json
-@@ -0,0 +1,7 @@
-+{
-+ "10.0.3.4": {
-+ "remoteAs": 65000,
-+ "localAs": 65000,
-+ "bgpState": "Established"
-+ }
-+}
-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..dd243592cd
---- /dev/null
-+++ b/tests/topotests/bgp_link_state/r2/frr.conf
-@@ -0,0 +1,72 @@
-+hostname r2
-+!
-+!debug isis te-events
-+!debug bgp link-state
-+!debug bgp zebra
-+!debug bgp updates
-+!
-+router bgp 65000
-+ bgp router-id 2.2.2.2
-+ no bgp ebgp-requires-policy
-+ neighbor 10.0.3.4 remote-as 65000
-+ !
-+ address-family link-state
-+ neighbor 10.0.3.4 activate
-+ exit-address-family
-+!
-+interface r2-eth0
-+ ip address 10.0.1.2/24
-+ ipv6 address fc00:10:0:1::2/64
-+ link-params
-+ neighbor 10.0.255.1
-+ ip router isis 1
-+ ipv6 router isis 1
-+ isis circuit-type level-2
-+ isis hello-interval 1
-+ isis hello-multiplier 3
-+ isis network point-to-point
-+!
-+interface r2-eth1
-+ ip address 10.0.2.2/24
-+ ipv6 address fc00:10:0:2::2/64
-+ link-params
-+ neighbor 10.0.255.3
-+ ip router isis 1
-+ ipv6 router isis 1
-+ isis circuit-type level-2
-+ isis hello-interval 1
-+ isis hello-multiplier 3
-+ isis network point-to-point
-+!
-+interface r2-eth2
-+ ip address 10.0.3.2/24
-+!
-+interface lo
-+ ip address 2.2.2.2/32
-+ ipv6 address fc00:0:2::1/128
-+ ip router isis 1
-+ ipv6 router isis 1
-+ isis passive
-+!
-+router isis 1
-+ net 49.0002.0000.0000.0002.00
-+ is-type level-2-only
-+ topology ipv6-unicast
-+ redistribute ipv4 static level-2
-+ redistribute ipv6 static level-2
-+ redistribute ipv4 connected level-2
-+ redistribute ipv6 connected level-2
-+ lsp-gen-interval 1
-+ lsp-refresh-interval 10
-+ max-lsp-lifetime 20
-+ mpls-te on
-+ mpls-te export
-+ mpls-te router-address 2.2.2.2
-+ mpls-te router-address ipv6 fc00:0:2::1
-+ segment-routing on
-+ segment-routing node-msd 7
-+ segment-routing global-block 200000 201999
-+ segment-routing prefix 2.2.2.2/32 index 2
-+!
-+ip forwarding
-+!
-diff --git a/tests/topotests/bgp_link_state/r2/isis_adj.json b/tests/topotests/bgp_link_state/r2/isis_adj.json
-new file mode 100644
-index 0000000000..199f548177
---- /dev/null
-+++ b/tests/topotests/bgp_link_state/r2/isis_adj.json
-@@ -0,0 +1,18 @@
-+{
-+ "areas": [
-+ {
-+ "circuits": [
-+ {
-+ "adj": "r1",
-+ "interface": "r2-eth0",
-+ "state": "Up"
-+ },
-+ {
-+ "adj": "r3",
-+ "interface": "r2-eth1",
-+ "state": "Up"
-+ }
-+ ]
-+ }
-+ ]
-+}
-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..f53feafd77
---- /dev/null
-+++ b/tests/topotests/bgp_link_state/r3/frr.conf
-@@ -0,0 +1,56 @@
-+hostname r3
-+!
-+!debug isis te-events
-+!
-+interface lo
-+ ip address 3.3.3.3/32
-+ ipv6 address fc00:0:3::1/128
-+ ip router isis 1
-+ ipv6 router isis 1
-+ isis passive
-+!
-+interface r3-eth0
-+ ip address 10.0.2.3/24
-+ ipv6 address fc00:10:0:2::3/64
-+ link-params
-+ neighbor 10.0.255.2
-+ ip router isis 1
-+ ipv6 router isis 1
-+ isis circuit-type level-2
-+ isis hello-interval 1
-+ isis hello-multiplier 3
-+ isis network point-to-point
-+!
-+interface r3-eth1
-+ ip address 10.0.5.3/24
-+ ipv6 address fc00:10:0:5::3/64
-+ link-params
-+ neighbor 10.0.255.4
-+ ip router isis 1
-+ ipv6 router isis 1
-+ isis circuit-type level-2
-+ isis hello-interval 1
-+ isis hello-multiplier 3
-+ isis network point-to-point
-+!
-+router isis 1
-+ net 49.0003.0000.0000.0003.00
-+ is-type level-2-only
-+ topology ipv6-unicast
-+ redistribute ipv4 static level-2
-+ redistribute ipv6 static level-2
-+ redistribute ipv4 connected level-2
-+ redistribute ipv6 connected level-2
-+ lsp-gen-interval 1
-+ lsp-refresh-interval 10
-+ max-lsp-lifetime 20
-+ mpls-te on
-+ mpls-te router-address 3.3.3.3
-+ mpls-te router-address ipv6 fc00:0:3::1
-+ segment-routing on
-+ segment-routing node-msd 7
-+ segment-routing global-block 300000 302999
-+ segment-routing prefix 3.3.3.3/32 index 3
-+!
-+ip forwarding
-+!
-diff --git a/tests/topotests/bgp_link_state/r3/isis_adj.json b/tests/topotests/bgp_link_state/r3/isis_adj.json
-new file mode 100644
-index 0000000000..c6ef44912f
---- /dev/null
-+++ b/tests/topotests/bgp_link_state/r3/isis_adj.json
-@@ -0,0 +1,18 @@
-+{
-+ "areas": [
-+ {
-+ "circuits": [
-+ {
-+ "adj": "r2",
-+ "interface": "r3-eth0",
-+ "state": "Up"
-+ },
-+ {
-+ "adj": "r4",
-+ "interface": "r3-eth1",
-+ "state": "Up"
-+ }
-+ ]
-+ }
-+ ]
-+}
-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..168b41d839
---- /dev/null
-+++ b/tests/topotests/bgp_link_state/r4/frr.conf
-@@ -0,0 +1,54 @@
-+!
-+hostname r4
-+!
-+interface lo
-+ ip address 4.4.4.4/32
-+ ipv6 address fc00:0:4::1/128
-+ ip router isis 1
-+ ipv6 router isis 1
-+ isis passive
-+!
-+interface r4-eth0
-+ ip address 10.0.4.4/24
-+ ipv6 address fc00:10:0:4::4/64
-+ link-params
-+ neighbor 10.0.255.1
-+ ip router isis 1
-+ ipv6 router isis 1
-+ isis circuit-type level-2
-+ isis hello-interval 1
-+ isis hello-multiplier 3
-+ isis network point-to-point
-+!
-+interface r4-eth1
-+ ip address 10.0.5.4/24
-+ ipv6 address fc00:10:0:5::4/64
-+ link-params
-+ neighbor 10.0.255.3
-+ ip router isis 1
-+ ipv6 router isis 1
-+ isis circuit-type level-2
-+ isis hello-interval 1
-+ isis hello-multiplier 3
-+ isis network point-to-point
-+!
-+router isis 1
-+ net 49.0004.0000.0000.0004.00
-+ is-type level-2-only
-+ topology ipv6-unicast
-+ redistribute ipv4 static level-2
-+ redistribute ipv6 static level-2
-+ redistribute ipv4 connected level-2
-+ redistribute ipv6 connected level-2
-+ lsp-gen-interval 1
-+ lsp-refresh-interval 10
-+ max-lsp-lifetime 20
-+ mpls-te on
-+ mpls-te router-address 4.4.4.4
-+ mpls-te router-address ipv6 fc00:0:4::1
-+ segment-routing on
-+ segment-routing node-msd 7
-+ segment-routing global-block 400000 403999
-+ segment-routing prefix 4.4.4.4/32 index 4
-+!
-+end
-diff --git a/tests/topotests/bgp_link_state/r4/isis_adj.json b/tests/topotests/bgp_link_state/r4/isis_adj.json
-new file mode 100644
-index 0000000000..7139b66acf
---- /dev/null
-+++ b/tests/topotests/bgp_link_state/r4/isis_adj.json
-@@ -0,0 +1,18 @@
-+{
-+ "areas": [
-+ {
-+ "circuits": [
-+ {
-+ "adj": "r1",
-+ "interface": "r4-eth0",
-+ "state": "Up"
-+ },
-+ {
-+ "adj": "r3",
-+ "interface": "r4-eth1",
-+ "state": "Up"
-+ }
-+ ]
-+ }
-+ ]
-+}
-diff --git a/tests/topotests/bgp_link_state/rr/bgp_capability.json b/tests/topotests/bgp_link_state/rr/bgp_capability.json
-new file mode 100644
-index 0000000000..34a040af60
---- /dev/null
-+++ b/tests/topotests/bgp_link_state/rr/bgp_capability.json
-@@ -0,0 +1,11 @@
-+{
-+ "10.0.3.2": {
-+ "neighborCapabilities": {
-+ "multiprotocolExtensions": {
-+ "linkState": {
-+ "advertisedAndReceived": true
-+ }
-+ }
-+ }
-+ }
-+}
-diff --git a/tests/topotests/bgp_link_state/rr/bgp_ls_attrs_link43.json b/tests/topotests/bgp_link_state/rr/bgp_ls_attrs_link43.json
-new file mode 100644
-index 0000000000..4ce4cb631b
---- /dev/null
-+++ b/tests/topotests/bgp_link_state/rr/bgp_ls_attrs_link43.json
-@@ -0,0 +1,14 @@
-+{
-+ "paths": [
-+ {
-+ "linkStateAttrs": {
-+ "adjSids": [
-+ {
-+ "flags": "0x30",
-+ "weight": 0
-+ }
-+ ]
-+ }
-+ }
-+ ]
-+}
-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..35487d195a
---- /dev/null
-+++ b/tests/topotests/bgp_link_state/rr/bgp_ls_attrs_node4.json
-@@ -0,0 +1,20 @@
-+{
-+ "paths": [
-+ {
-+ "linkStateAttrs": {
-+ "srCapabilities": {
-+ "lowerBound": 400000,
-+ "rangeSize": 4000,
-+ "flags": "0xc0"
-+ },
-+ "msd": [
-+ {
-+ "type": 1,
-+ "value": 7
-+ }
-+ ],
-+ "isisAreaId": "49.0004"
-+ }
-+ }
-+ ]
-+}
-diff --git a/tests/topotests/bgp_link_state/rr/bgp_ls_nlri.json b/tests/topotests/bgp_link_state/rr/bgp_ls_nlri.json
-new file mode 100644
-index 0000000000..c5b5b687e9
---- /dev/null
-+++ b/tests/topotests/bgp_link_state/rr/bgp_ls_nlri.json
-@@ -0,0 +1,1552 @@
-+{
-+ "vrfId": 0,
-+ "vrfName": "default",
-+ "routerId": "10.10.10.10",
-+ "routes": {
-+ "[V][L2][I0x0][N[s0000.0000.0002]]": [
-+ {
-+ "valid": true,
-+ "bestpath": true,
-+ "selectionReason": "First path received",
-+ "pathFrom": "internal",
-+ "weight": 0,
-+ "peerId": "10.0.3.2",
-+ "path": "",
-+ "origin": "incomplete",
-+ "locPrf": 100,
-+ "nexthops": [
-+ {
-+ "ip": "10.0.3.2",
-+ "hostname": "r2",
-+ "afi": "ipv4",
-+ "used": true
-+ }
-+ ],
-+ "nlri": {
-+ "nlriType": "node",
-+ "protocolId": 2,
-+ "identifier": 0,
-+ "localNodeDescriptors": {
-+ "igpRouterId": "0000.0000.0002"
-+ }
-+ },
-+ "nlriStr": "[V][L2][I0x0][N[s0000.0000.0002]]"
-+ }
-+ ],
-+ "[T][L2][I0x0][N[s0000.0000.0002]][P[p10.0.1.0/24]]": [
-+ {
-+ "valid": true,
-+ "bestpath": true,
-+ "selectionReason": "First path received",
-+ "pathFrom": "internal",
-+ "weight": 0,
-+ "peerId": "10.0.3.2",
-+ "path": "",
-+ "origin": "incomplete",
-+ "locPrf": 100,
-+ "nexthops": [
-+ {
-+ "ip": "10.0.3.2",
-+ "hostname": "r2",
-+ "afi": "ipv4",
-+ "used": true
-+ }
-+ ],
-+ "nlri": {
-+ "nlriType": "ipv4Prefix",
-+ "protocolId": 2,
-+ "identifier": 0,
-+ "localNodeDescriptors": {
-+ "igpRouterId": "0000.0000.0002"
-+ },
-+ "prefixDescriptors": {
-+ "ipReachabilityInformation": "10.0.1.0/24"
-+ }
-+ },
-+ "nlriStr": "[T][L2][I0x0][N[s0000.0000.0002]][P[p10.0.1.0/24]]"
-+ }
-+ ],
-+ "[T][L2][I0x0][N[s0000.0000.0002]][P[p10.0.2.0/24]]": [
-+ {
-+ "valid": true,
-+ "bestpath": true,
-+ "selectionReason": "First path received",
-+ "pathFrom": "internal",
-+ "weight": 0,
-+ "peerId": "10.0.3.2",
-+ "path": "",
-+ "origin": "incomplete",
-+ "locPrf": 100,
-+ "nexthops": [
-+ {
-+ "ip": "10.0.3.2",
-+ "hostname": "r2",
-+ "afi": "ipv4",
-+ "used": true
-+ }
-+ ],
-+ "nlri": {
-+ "nlriType": "ipv4Prefix",
-+ "protocolId": 2,
-+ "identifier": 0,
-+ "localNodeDescriptors": {
-+ "igpRouterId": "0000.0000.0002"
-+ },
-+ "prefixDescriptors": {
-+ "ipReachabilityInformation": "10.0.2.0/24"
-+ }
-+ },
-+ "nlriStr": "[T][L2][I0x0][N[s0000.0000.0002]][P[p10.0.2.0/24]]"
-+ }
-+ ],
-+ "[T][L2][I0x0][N[s0000.0000.0002]][P[p2.2.2.2/32]]": [
-+ {
-+ "valid": true,
-+ "bestpath": true,
-+ "selectionReason": "First path received",
-+ "pathFrom": "internal",
-+ "weight": 0,
-+ "peerId": "10.0.3.2",
-+ "path": "",
-+ "origin": "incomplete",
-+ "locPrf": 100,
-+ "nexthops": [
-+ {
-+ "ip": "10.0.3.2",
-+ "hostname": "r2",
-+ "afi": "ipv4",
-+ "used": true
-+ }
-+ ],
-+ "nlri": {
-+ "nlriType": "ipv4Prefix",
-+ "protocolId": 2,
-+ "identifier": 0,
-+ "localNodeDescriptors": {
-+ "igpRouterId": "0000.0000.0002"
-+ },
-+ "prefixDescriptors": {
-+ "ipReachabilityInformation": "2.2.2.2/32"
-+ }
-+ },
-+ "nlriStr": "[T][L2][I0x0][N[s0000.0000.0002]][P[p2.2.2.2/32]]"
-+ }
-+ ],
-+ "[T][L2][I0x0][N[s0000.0000.0002]][P[pfc00:0:2::1/128]]": [
-+ {
-+ "valid": true,
-+ "bestpath": true,
-+ "selectionReason": "First path received",
-+ "pathFrom": "internal",
-+ "weight": 0,
-+ "peerId": "10.0.3.2",
-+ "path": "",
-+ "origin": "incomplete",
-+ "locPrf": 100,
-+ "nexthops": [
-+ {
-+ "ip": "10.0.3.2",
-+ "hostname": "r2",
-+ "afi": "ipv4",
-+ "used": true
-+ }
-+ ],
-+ "nlri": {
-+ "nlriType": "ipv6Prefix",
-+ "protocolId": 2,
-+ "identifier": 0,
-+ "localNodeDescriptors": {
-+ "igpRouterId": "0000.0000.0002"
-+ },
-+ "prefixDescriptors": {
-+ "ipReachabilityInformation": "fc00:0:2::1/128"
-+ }
-+ },
-+ "nlriStr": "[T][L2][I0x0][N[s0000.0000.0002]][P[pfc00:0:2::1/128]]"
-+ }
-+ ],
-+ "[V][L2][I0x0][N[s0000.0000.0001]]": [
-+ {
-+ "valid": true,
-+ "bestpath": true,
-+ "selectionReason": "First path received",
-+ "pathFrom": "internal",
-+ "weight": 0,
-+ "peerId": "10.0.3.2",
-+ "path": "",
-+ "origin": "incomplete",
-+ "locPrf": 100,
-+ "nexthops": [
-+ {
-+ "ip": "10.0.3.2",
-+ "hostname": "r2",
-+ "afi": "ipv4",
-+ "used": true
-+ }
-+ ],
-+ "nlri": {
-+ "nlriType": "node",
-+ "protocolId": 2,
-+ "identifier": 0,
-+ "localNodeDescriptors": {
-+ "igpRouterId": "0000.0000.0001"
-+ }
-+ },
-+ "nlriStr": "[V][L2][I0x0][N[s0000.0000.0001]]"
-+ }
-+ ],
-+ "[T][L2][I0x0][N[s0000.0000.0001]][P[p1.1.1.1/32]]": [
-+ {
-+ "valid": true,
-+ "bestpath": true,
-+ "selectionReason": "First path received",
-+ "pathFrom": "internal",
-+ "weight": 0,
-+ "peerId": "10.0.3.2",
-+ "path": "",
-+ "origin": "incomplete",
-+ "locPrf": 100,
-+ "nexthops": [
-+ {
-+ "ip": "10.0.3.2",
-+ "hostname": "r2",
-+ "afi": "ipv4",
-+ "used": true
-+ }
-+ ],
-+ "nlri": {
-+ "nlriType": "ipv4Prefix",
-+ "protocolId": 2,
-+ "identifier": 0,
-+ "localNodeDescriptors": {
-+ "igpRouterId": "0000.0000.0001"
-+ },
-+ "prefixDescriptors": {
-+ "ipReachabilityInformation": "1.1.1.1/32"
-+ }
-+ },
-+ "nlriStr": "[T][L2][I0x0][N[s0000.0000.0001]][P[p1.1.1.1/32]]"
-+ }
-+ ],
-+ "[T][L2][I0x0][N[s0000.0000.0001]][P[p10.0.4.0/24]]": [
-+ {
-+ "valid": true,
-+ "bestpath": true,
-+ "selectionReason": "First path received",
-+ "pathFrom": "internal",
-+ "weight": 0,
-+ "peerId": "10.0.3.2",
-+ "path": "",
-+ "origin": "incomplete",
-+ "locPrf": 100,
-+ "nexthops": [
-+ {
-+ "ip": "10.0.3.2",
-+ "hostname": "r2",
-+ "afi": "ipv4",
-+ "used": true
-+ }
-+ ],
-+ "nlri": {
-+ "nlriType": "ipv4Prefix",
-+ "protocolId": 2,
-+ "identifier": 0,
-+ "localNodeDescriptors": {
-+ "igpRouterId": "0000.0000.0001"
-+ },
-+ "prefixDescriptors": {
-+ "ipReachabilityInformation": "10.0.4.0/24"
-+ }
-+ },
-+ "nlriStr": "[T][L2][I0x0][N[s0000.0000.0001]][P[p10.0.4.0/24]]"
-+ }
-+ ],
-+ "[T][L2][I0x0][N[s0000.0000.0001]][P[pfc00:0:1::1/128]]": [
-+ {
-+ "valid": true,
-+ "bestpath": true,
-+ "selectionReason": "First path received",
-+ "pathFrom": "internal",
-+ "weight": 0,
-+ "peerId": "10.0.3.2",
-+ "path": "",
-+ "origin": "incomplete",
-+ "locPrf": 100,
-+ "nexthops": [
-+ {
-+ "ip": "10.0.3.2",
-+ "hostname": "r2",
-+ "afi": "ipv4",
-+ "used": true
-+ }
-+ ],
-+ "nlri": {
-+ "nlriType": "ipv6Prefix",
-+ "protocolId": 2,
-+ "identifier": 0,
-+ "localNodeDescriptors": {
-+ "igpRouterId": "0000.0000.0001"
-+ },
-+ "prefixDescriptors": {
-+ "ipReachabilityInformation": "fc00:0:1::1/128"
-+ }
-+ },
-+ "nlriStr": "[T][L2][I0x0][N[s0000.0000.0001]][P[pfc00:0:1::1/128]]"
-+ }
-+ ],
-+ "[T][L2][I0x0][N[s0000.0000.0001]][P[pfc00:10:0:1::/64]]": [
-+ {
-+ "valid": true,
-+ "bestpath": true,
-+ "selectionReason": "First path received",
-+ "pathFrom": "internal",
-+ "weight": 0,
-+ "peerId": "10.0.3.2",
-+ "path": "",
-+ "origin": "incomplete",
-+ "locPrf": 100,
-+ "nexthops": [
-+ {
-+ "ip": "10.0.3.2",
-+ "hostname": "r2",
-+ "afi": "ipv4",
-+ "used": true
-+ }
-+ ],
-+ "nlri": {
-+ "nlriType": "ipv6Prefix",
-+ "protocolId": 2,
-+ "identifier": 0,
-+ "localNodeDescriptors": {
-+ "igpRouterId": "0000.0000.0001"
-+ },
-+ "prefixDescriptors": {
-+ "ipReachabilityInformation": "fc00:10:0:1::/64"
-+ }
-+ },
-+ "nlriStr": "[T][L2][I0x0][N[s0000.0000.0001]][P[pfc00:10:0:1::/64]]"
-+ }
-+ ],
-+ "[T][L2][I0x0][N[s0000.0000.0001]][P[pfc00:10:0:4::/64]]": [
-+ {
-+ "valid": true,
-+ "bestpath": true,
-+ "selectionReason": "First path received",
-+ "pathFrom": "internal",
-+ "weight": 0,
-+ "peerId": "10.0.3.2",
-+ "path": "",
-+ "origin": "incomplete",
-+ "locPrf": 100,
-+ "nexthops": [
-+ {
-+ "ip": "10.0.3.2",
-+ "hostname": "r2",
-+ "afi": "ipv4",
-+ "used": true
-+ }
-+ ],
-+ "nlri": {
-+ "nlriType": "ipv6Prefix",
-+ "protocolId": 2,
-+ "identifier": 0,
-+ "localNodeDescriptors": {
-+ "igpRouterId": "0000.0000.0001"
-+ },
-+ "prefixDescriptors": {
-+ "ipReachabilityInformation": "fc00:10:0:4::/64"
-+ }
-+ },
-+ "nlriStr": "[T][L2][I0x0][N[s0000.0000.0001]][P[pfc00:10:0:4::/64]]"
-+ }
-+ ],
-+ "[T][L2][I0x0][N[s0000.0000.0002]][P[p10.0.3.0/24]]": [
-+ {
-+ "valid": true,
-+ "bestpath": true,
-+ "selectionReason": "First path received",
-+ "pathFrom": "internal",
-+ "weight": 0,
-+ "peerId": "10.0.3.2",
-+ "path": "",
-+ "origin": "incomplete",
-+ "locPrf": 100,
-+ "nexthops": [
-+ {
-+ "ip": "10.0.3.2",
-+ "hostname": "r2",
-+ "afi": "ipv4",
-+ "used": true
-+ }
-+ ],
-+ "nlri": {
-+ "nlriType": "ipv4Prefix",
-+ "protocolId": 2,
-+ "identifier": 0,
-+ "localNodeDescriptors": {
-+ "igpRouterId": "0000.0000.0002"
-+ },
-+ "prefixDescriptors": {
-+ "ipReachabilityInformation": "10.0.3.0/24"
-+ }
-+ },
-+ "nlriStr": "[T][L2][I0x0][N[s0000.0000.0002]][P[p10.0.3.0/24]]"
-+ }
-+ ],
-+ "[T][L2][I0x0][N[s0000.0000.0001]][P[p10.0.1.0/24]]": [
-+ {
-+ "valid": true,
-+ "bestpath": true,
-+ "selectionReason": "First path received",
-+ "pathFrom": "internal",
-+ "weight": 0,
-+ "peerId": "10.0.3.2",
-+ "path": "",
-+ "origin": "incomplete",
-+ "locPrf": 100,
-+ "nexthops": [
-+ {
-+ "ip": "10.0.3.2",
-+ "hostname": "r2",
-+ "afi": "ipv4",
-+ "used": true
-+ }
-+ ],
-+ "nlri": {
-+ "nlriType": "ipv4Prefix",
-+ "protocolId": 2,
-+ "identifier": 0,
-+ "localNodeDescriptors": {
-+ "igpRouterId": "0000.0000.0001"
-+ },
-+ "prefixDescriptors": {
-+ "ipReachabilityInformation": "10.0.1.0/24"
-+ }
-+ },
-+ "nlriStr": "[T][L2][I0x0][N[s0000.0000.0001]][P[p10.0.1.0/24]]"
-+ }
-+ ],
-+ "[V][L2][I0x0][N[s0000.0000.0003]]": [
-+ {
-+ "valid": true,
-+ "bestpath": true,
-+ "selectionReason": "First path received",
-+ "pathFrom": "internal",
-+ "weight": 0,
-+ "peerId": "10.0.3.2",
-+ "path": "",
-+ "origin": "incomplete",
-+ "locPrf": 100,
-+ "nexthops": [
-+ {
-+ "ip": "10.0.3.2",
-+ "hostname": "r2",
-+ "afi": "ipv4",
-+ "used": true
-+ }
-+ ],
-+ "nlri": {
-+ "nlriType": "node",
-+ "protocolId": 2,
-+ "identifier": 0,
-+ "localNodeDescriptors": {
-+ "igpRouterId": "0000.0000.0003"
-+ }
-+ },
-+ "nlriStr": "[V][L2][I0x0][N[s0000.0000.0003]]"
-+ }
-+ ],
-+ "[T][L2][I0x0][N[s0000.0000.0003]][P[p3.3.3.3/32]]": [
-+ {
-+ "valid": true,
-+ "bestpath": true,
-+ "selectionReason": "First path received",
-+ "pathFrom": "internal",
-+ "weight": 0,
-+ "peerId": "10.0.3.2",
-+ "path": "",
-+ "origin": "incomplete",
-+ "locPrf": 100,
-+ "nexthops": [
-+ {
-+ "ip": "10.0.3.2",
-+ "hostname": "r2",
-+ "afi": "ipv4",
-+ "used": true
-+ }
-+ ],
-+ "nlri": {
-+ "nlriType": "ipv4Prefix",
-+ "protocolId": 2,
-+ "identifier": 0,
-+ "localNodeDescriptors": {
-+ "igpRouterId": "0000.0000.0003"
-+ },
-+ "prefixDescriptors": {
-+ "ipReachabilityInformation": "3.3.3.3/32"
-+ }
-+ },
-+ "nlriStr": "[T][L2][I0x0][N[s0000.0000.0003]][P[p3.3.3.3/32]]"
-+ }
-+ ],
-+ "[T][L2][I0x0][N[s0000.0000.0003]][P[p10.0.5.0/24]]": [
-+ {
-+ "valid": true,
-+ "bestpath": true,
-+ "selectionReason": "First path received",
-+ "pathFrom": "internal",
-+ "weight": 0,
-+ "peerId": "10.0.3.2",
-+ "path": "",
-+ "origin": "incomplete",
-+ "locPrf": 100,
-+ "nexthops": [
-+ {
-+ "ip": "10.0.3.2",
-+ "hostname": "r2",
-+ "afi": "ipv4",
-+ "used": true
-+ }
-+ ],
-+ "nlri": {
-+ "nlriType": "ipv4Prefix",
-+ "protocolId": 2,
-+ "identifier": 0,
-+ "localNodeDescriptors": {
-+ "igpRouterId": "0000.0000.0003"
-+ },
-+ "prefixDescriptors": {
-+ "ipReachabilityInformation": "10.0.5.0/24"
-+ }
-+ },
-+ "nlriStr": "[T][L2][I0x0][N[s0000.0000.0003]][P[p10.0.5.0/24]]"
-+ }
-+ ],
-+ "[T][L2][I0x0][N[s0000.0000.0003]][P[pfc00:0:3::1/128]]": [
-+ {
-+ "valid": true,
-+ "bestpath": true,
-+ "selectionReason": "First path received",
-+ "pathFrom": "internal",
-+ "weight": 0,
-+ "peerId": "10.0.3.2",
-+ "path": "",
-+ "origin": "incomplete",
-+ "locPrf": 100,
-+ "nexthops": [
-+ {
-+ "ip": "10.0.3.2",
-+ "hostname": "r2",
-+ "afi": "ipv4",
-+ "used": true
-+ }
-+ ],
-+ "nlri": {
-+ "nlriType": "ipv6Prefix",
-+ "protocolId": 2,
-+ "identifier": 0,
-+ "localNodeDescriptors": {
-+ "igpRouterId": "0000.0000.0003"
-+ },
-+ "prefixDescriptors": {
-+ "ipReachabilityInformation": "fc00:0:3::1/128"
-+ }
-+ },
-+ "nlriStr": "[T][L2][I0x0][N[s0000.0000.0003]][P[pfc00:0:3::1/128]]"
-+ }
-+ ],
-+ "[T][L2][I0x0][N[s0000.0000.0002]][P[pfc00:10:0:1::/64]]": [
-+ {
-+ "valid": true,
-+ "bestpath": true,
-+ "selectionReason": "First path received",
-+ "pathFrom": "internal",
-+ "weight": 0,
-+ "peerId": "10.0.3.2",
-+ "path": "",
-+ "origin": "incomplete",
-+ "locPrf": 100,
-+ "nexthops": [
-+ {
-+ "ip": "10.0.3.2",
-+ "hostname": "r2",
-+ "afi": "ipv4",
-+ "used": true
-+ }
-+ ],
-+ "nlri": {
-+ "nlriType": "ipv6Prefix",
-+ "protocolId": 2,
-+ "identifier": 0,
-+ "localNodeDescriptors": {
-+ "igpRouterId": "0000.0000.0002"
-+ },
-+ "prefixDescriptors": {
-+ "ipReachabilityInformation": "fc00:10:0:1::/64"
-+ }
-+ },
-+ "nlriStr": "[T][L2][I0x0][N[s0000.0000.0002]][P[pfc00:10:0:1::/64]]"
-+ }
-+ ],
-+ "[T][L2][I0x0][N[s0000.0000.0002]][P[pfc00:10:0:2::/64]]": [
-+ {
-+ "valid": true,
-+ "bestpath": true,
-+ "selectionReason": "First path received",
-+ "pathFrom": "internal",
-+ "weight": 0,
-+ "peerId": "10.0.3.2",
-+ "path": "",
-+ "origin": "incomplete",
-+ "locPrf": 100,
-+ "nexthops": [
-+ {
-+ "ip": "10.0.3.2",
-+ "hostname": "r2",
-+ "afi": "ipv4",
-+ "used": true
-+ }
-+ ],
-+ "nlri": {
-+ "nlriType": "ipv6Prefix",
-+ "protocolId": 2,
-+ "identifier": 0,
-+ "localNodeDescriptors": {
-+ "igpRouterId": "0000.0000.0002"
-+ },
-+ "prefixDescriptors": {
-+ "ipReachabilityInformation": "fc00:10:0:2::/64"
-+ }
-+ },
-+ "nlriStr": "[T][L2][I0x0][N[s0000.0000.0002]][P[pfc00:10:0:2::/64]]"
-+ }
-+ ],
-+ "[T][L2][I0x0][N[s0000.0000.0003]][P[p10.0.2.0/24]]": [
-+ {
-+ "valid": true,
-+ "bestpath": true,
-+ "selectionReason": "First path received",
-+ "pathFrom": "internal",
-+ "weight": 0,
-+ "peerId": "10.0.3.2",
-+ "path": "",
-+ "origin": "incomplete",
-+ "locPrf": 100,
-+ "nexthops": [
-+ {
-+ "ip": "10.0.3.2",
-+ "hostname": "r2",
-+ "afi": "ipv4",
-+ "used": true
-+ }
-+ ],
-+ "nlri": {
-+ "nlriType": "ipv4Prefix",
-+ "protocolId": 2,
-+ "identifier": 0,
-+ "localNodeDescriptors": {
-+ "igpRouterId": "0000.0000.0003"
-+ },
-+ "prefixDescriptors": {
-+ "ipReachabilityInformation": "10.0.2.0/24"
-+ }
-+ },
-+ "nlriStr": "[T][L2][I0x0][N[s0000.0000.0003]][P[p10.0.2.0/24]]"
-+ }
-+ ],
-+ "[T][L2][I0x0][N[s0000.0000.0003]][P[pfc00:10:0:5::/64]]": [
-+ {
-+ "valid": true,
-+ "bestpath": true,
-+ "selectionReason": "First path received",
-+ "pathFrom": "internal",
-+ "weight": 0,
-+ "peerId": "10.0.3.2",
-+ "path": "",
-+ "origin": "incomplete",
-+ "locPrf": 100,
-+ "nexthops": [
-+ {
-+ "ip": "10.0.3.2",
-+ "hostname": "r2",
-+ "afi": "ipv4",
-+ "used": true
-+ }
-+ ],
-+ "nlri": {
-+ "nlriType": "ipv6Prefix",
-+ "protocolId": 2,
-+ "identifier": 0,
-+ "localNodeDescriptors": {
-+ "igpRouterId": "0000.0000.0003"
-+ },
-+ "prefixDescriptors": {
-+ "ipReachabilityInformation": "fc00:10:0:5::/64"
-+ }
-+ },
-+ "nlriStr": "[T][L2][I0x0][N[s0000.0000.0003]][P[pfc00:10:0:5::/64]]"
-+ }
-+ ],
-+ "[V][L2][I0x0][N[s0000.0000.0004]]": [
-+ {
-+ "valid": true,
-+ "bestpath": true,
-+ "selectionReason": "First path received",
-+ "pathFrom": "internal",
-+ "weight": 0,
-+ "peerId": "10.0.3.2",
-+ "path": "",
-+ "origin": "incomplete",
-+ "locPrf": 100,
-+ "nexthops": [
-+ {
-+ "ip": "10.0.3.2",
-+ "hostname": "r2",
-+ "afi": "ipv4",
-+ "used": true
-+ }
-+ ],
-+ "nlri": {
-+ "nlriType": "node",
-+ "protocolId": 2,
-+ "identifier": 0,
-+ "localNodeDescriptors": {
-+ "igpRouterId": "0000.0000.0004"
-+ }
-+ },
-+ "nlriStr": "[V][L2][I0x0][N[s0000.0000.0004]]"
-+ }
-+ ],
-+ "[T][L2][I0x0][N[s0000.0000.0004]][P[p4.4.4.4/32]]": [
-+ {
-+ "valid": true,
-+ "bestpath": true,
-+ "selectionReason": "First path received",
-+ "pathFrom": "internal",
-+ "weight": 0,
-+ "peerId": "10.0.3.2",
-+ "path": "",
-+ "origin": "incomplete",
-+ "locPrf": 100,
-+ "nexthops": [
-+ {
-+ "ip": "10.0.3.2",
-+ "hostname": "r2",
-+ "afi": "ipv4",
-+ "used": true
-+ }
-+ ],
-+ "nlri": {
-+ "nlriType": "ipv4Prefix",
-+ "protocolId": 2,
-+ "identifier": 0,
-+ "localNodeDescriptors": {
-+ "igpRouterId": "0000.0000.0004"
-+ },
-+ "prefixDescriptors": {
-+ "ipReachabilityInformation": "4.4.4.4/32"
-+ }
-+ },
-+ "nlriStr": "[T][L2][I0x0][N[s0000.0000.0004]][P[p4.4.4.4/32]]"
-+ }
-+ ],
-+ "[T][L2][I0x0][N[s0000.0000.0004]][P[pfc00:0:4::1/128]]": [
-+ {
-+ "valid": true,
-+ "bestpath": true,
-+ "selectionReason": "First path received",
-+ "pathFrom": "internal",
-+ "weight": 0,
-+ "peerId": "10.0.3.2",
-+ "path": "",
-+ "origin": "incomplete",
-+ "locPrf": 100,
-+ "nexthops": [
-+ {
-+ "ip": "10.0.3.2",
-+ "hostname": "r2",
-+ "afi": "ipv4",
-+ "used": true
-+ }
-+ ],
-+ "nlri": {
-+ "nlriType": "ipv6Prefix",
-+ "protocolId": 2,
-+ "identifier": 0,
-+ "localNodeDescriptors": {
-+ "igpRouterId": "0000.0000.0004"
-+ },
-+ "prefixDescriptors": {
-+ "ipReachabilityInformation": "fc00:0:4::1/128"
-+ }
-+ },
-+ "nlriStr": "[T][L2][I0x0][N[s0000.0000.0004]][P[pfc00:0:4::1/128]]"
-+ }
-+ ],
-+ "[T][L2][I0x0][N[s0000.0000.0003]][P[pfc00:10:0:2::/64]]": [
-+ {
-+ "valid": true,
-+ "bestpath": true,
-+ "selectionReason": "First path received",
-+ "pathFrom": "internal",
-+ "weight": 0,
-+ "peerId": "10.0.3.2",
-+ "path": "",
-+ "origin": "incomplete",
-+ "locPrf": 100,
-+ "nexthops": [
-+ {
-+ "ip": "10.0.3.2",
-+ "hostname": "r2",
-+ "afi": "ipv4",
-+ "used": true
-+ }
-+ ],
-+ "nlri": {
-+ "nlriType": "ipv6Prefix",
-+ "protocolId": 2,
-+ "identifier": 0,
-+ "localNodeDescriptors": {
-+ "igpRouterId": "0000.0000.0003"
-+ },
-+ "prefixDescriptors": {
-+ "ipReachabilityInformation": "fc00:10:0:2::/64"
-+ }
-+ },
-+ "nlriStr": "[T][L2][I0x0][N[s0000.0000.0003]][P[pfc00:10:0:2::/64]]"
-+ }
-+ ],
-+ "[T][L2][I0x0][N[s0000.0000.0004]][P[p10.0.4.0/24]]": [
-+ {
-+ "valid": true,
-+ "bestpath": true,
-+ "selectionReason": "First path received",
-+ "pathFrom": "internal",
-+ "weight": 0,
-+ "peerId": "10.0.3.2",
-+ "path": "",
-+ "origin": "incomplete",
-+ "locPrf": 100,
-+ "nexthops": [
-+ {
-+ "ip": "10.0.3.2",
-+ "hostname": "r2",
-+ "afi": "ipv4",
-+ "used": true
-+ }
-+ ],
-+ "nlri": {
-+ "nlriType": "ipv4Prefix",
-+ "protocolId": 2,
-+ "identifier": 0,
-+ "localNodeDescriptors": {
-+ "igpRouterId": "0000.0000.0004"
-+ },
-+ "prefixDescriptors": {
-+ "ipReachabilityInformation": "10.0.4.0/24"
-+ }
-+ },
-+ "nlriStr": "[T][L2][I0x0][N[s0000.0000.0004]][P[p10.0.4.0/24]]"
-+ }
-+ ],
-+ "[T][L2][I0x0][N[s0000.0000.0004]][P[p10.0.5.0/24]]": [
-+ {
-+ "valid": true,
-+ "bestpath": true,
-+ "selectionReason": "First path received",
-+ "pathFrom": "internal",
-+ "weight": 0,
-+ "peerId": "10.0.3.2",
-+ "path": "",
-+ "origin": "incomplete",
-+ "locPrf": 100,
-+ "nexthops": [
-+ {
-+ "ip": "10.0.3.2",
-+ "hostname": "r2",
-+ "afi": "ipv4",
-+ "used": true
-+ }
-+ ],
-+ "nlri": {
-+ "nlriType": "ipv4Prefix",
-+ "protocolId": 2,
-+ "identifier": 0,
-+ "localNodeDescriptors": {
-+ "igpRouterId": "0000.0000.0004"
-+ },
-+ "prefixDescriptors": {
-+ "ipReachabilityInformation": "10.0.5.0/24"
-+ }
-+ },
-+ "nlriStr": "[T][L2][I0x0][N[s0000.0000.0004]][P[p10.0.5.0/24]]"
-+ }
-+ ],
-+ "[T][L2][I0x0][N[s0000.0000.0004]][P[pfc00:10:0:5::/64]]": [
-+ {
-+ "valid": true,
-+ "bestpath": true,
-+ "selectionReason": "First path received",
-+ "pathFrom": "internal",
-+ "weight": 0,
-+ "peerId": "10.0.3.2",
-+ "path": "",
-+ "origin": "incomplete",
-+ "locPrf": 100,
-+ "nexthops": [
-+ {
-+ "ip": "10.0.3.2",
-+ "hostname": "r2",
-+ "afi": "ipv4",
-+ "used": true
-+ }
-+ ],
-+ "nlri": {
-+ "nlriType": "ipv6Prefix",
-+ "protocolId": 2,
-+ "identifier": 0,
-+ "localNodeDescriptors": {
-+ "igpRouterId": "0000.0000.0004"
-+ },
-+ "prefixDescriptors": {
-+ "ipReachabilityInformation": "fc00:10:0:5::/64"
-+ }
-+ },
-+ "nlriStr": "[T][L2][I0x0][N[s0000.0000.0004]][P[pfc00:10:0:5::/64]]"
-+ }
-+ ],
-+ "[T][L2][I0x0][N[s0000.0000.0004]][P[pfc00:10:0:4::/64]]": [
-+ {
-+ "valid": true,
-+ "bestpath": true,
-+ "selectionReason": "First path received",
-+ "pathFrom": "internal",
-+ "weight": 0,
-+ "peerId": "10.0.3.2",
-+ "path": "",
-+ "origin": "incomplete",
-+ "locPrf": 100,
-+ "nexthops": [
-+ {
-+ "ip": "10.0.3.2",
-+ "hostname": "r2",
-+ "afi": "ipv4",
-+ "used": true
-+ }
-+ ],
-+ "nlri": {
-+ "nlriType": "ipv6Prefix",
-+ "protocolId": 2,
-+ "identifier": 0,
-+ "localNodeDescriptors": {
-+ "igpRouterId": "0000.0000.0004"
-+ },
-+ "prefixDescriptors": {
-+ "ipReachabilityInformation": "fc00:10:0:4::/64"
-+ }
-+ },
-+ "nlriStr": "[T][L2][I0x0][N[s0000.0000.0004]][P[pfc00:10:0:4::/64]]"
-+ }
-+ ],
-+ "[E][L2][I0x0][N[s0000.0000.0001]][R[s0000.0000.0002]][L[i10.0.1.1][n10.0.1.2]]": [
-+ {
-+ "valid": true,
-+ "bestpath": true,
-+ "selectionReason": "First path received",
-+ "pathFrom": "internal",
-+ "weight": 0,
-+ "peerId": "10.0.3.2",
-+ "path": "",
-+ "origin": "incomplete",
-+ "locPrf": 100,
-+ "nexthops": [
-+ {
-+ "ip": "10.0.3.2",
-+ "hostname": "r2",
-+ "afi": "ipv4",
-+ "used": true
-+ }
-+ ],
-+ "nlri": {
-+ "nlriType": "link",
-+ "protocolId": 2,
-+ "identifier": 0,
-+ "localNodeDescriptors": {
-+ "igpRouterId": "0000.0000.0001"
-+ },
-+ "remoteNodeDescriptors": {
-+ "igpRouterId": "0000.0000.0002"
-+ },
-+ "linkDescriptors": {
-+ "ipv4InterfaceAddress": "10.0.1.1",
-+ "ipv4NeighborAddress": "10.0.1.2"
-+ }
-+ },
-+ "nlriStr": "[E][L2][I0x0][N[s0000.0000.0001]][R[s0000.0000.0002]][L[i10.0.1.1][n10.0.1.2]]"
-+ }
-+ ],
-+ "[E][L2][I0x0][N[s0000.0000.0002]][R[s0000.0000.0001]][L[i10.0.1.2][n10.0.1.1]]": [
-+ {
-+ "valid": true,
-+ "bestpath": true,
-+ "selectionReason": "First path received",
-+ "pathFrom": "internal",
-+ "weight": 0,
-+ "peerId": "10.0.3.2",
-+ "path": "",
-+ "origin": "incomplete",
-+ "locPrf": 100,
-+ "nexthops": [
-+ {
-+ "ip": "10.0.3.2",
-+ "hostname": "r2",
-+ "afi": "ipv4",
-+ "used": true
-+ }
-+ ],
-+ "nlri": {
-+ "nlriType": "link",
-+ "protocolId": 2,
-+ "identifier": 0,
-+ "localNodeDescriptors": {
-+ "igpRouterId": "0000.0000.0002"
-+ },
-+ "remoteNodeDescriptors": {
-+ "igpRouterId": "0000.0000.0001"
-+ },
-+ "linkDescriptors": {
-+ "ipv4InterfaceAddress": "10.0.1.2",
-+ "ipv4NeighborAddress": "10.0.1.1"
-+ }
-+ },
-+ "nlriStr": "[E][L2][I0x0][N[s0000.0000.0002]][R[s0000.0000.0001]][L[i10.0.1.2][n10.0.1.1]]"
-+ }
-+ ],
-+ "[E][L2][I0x0][N[s0000.0000.0002]][R[s0000.0000.0003]][L[i10.0.2.2][n10.0.2.3]]": [
-+ {
-+ "valid": true,
-+ "bestpath": true,
-+ "selectionReason": "First path received",
-+ "pathFrom": "internal",
-+ "weight": 0,
-+ "peerId": "10.0.3.2",
-+ "path": "",
-+ "origin": "incomplete",
-+ "locPrf": 100,
-+ "nexthops": [
-+ {
-+ "ip": "10.0.3.2",
-+ "hostname": "r2",
-+ "afi": "ipv4",
-+ "used": true
-+ }
-+ ],
-+ "nlri": {
-+ "nlriType": "link",
-+ "protocolId": 2,
-+ "identifier": 0,
-+ "localNodeDescriptors": {
-+ "igpRouterId": "0000.0000.0002"
-+ },
-+ "remoteNodeDescriptors": {
-+ "igpRouterId": "0000.0000.0003"
-+ },
-+ "linkDescriptors": {
-+ "ipv4InterfaceAddress": "10.0.2.2",
-+ "ipv4NeighborAddress": "10.0.2.3"
-+ }
-+ },
-+ "nlriStr": "[E][L2][I0x0][N[s0000.0000.0002]][R[s0000.0000.0003]][L[i10.0.2.2][n10.0.2.3]]"
-+ }
-+ ],
-+ "[E][L2][I0x0][N[s0000.0000.0003]][R[s0000.0000.0002]][L[i10.0.2.3][n10.0.2.2]]": [
-+ {
-+ "valid": true,
-+ "bestpath": true,
-+ "selectionReason": "First path received",
-+ "pathFrom": "internal",
-+ "weight": 0,
-+ "peerId": "10.0.3.2",
-+ "path": "",
-+ "origin": "incomplete",
-+ "locPrf": 100,
-+ "nexthops": [
-+ {
-+ "ip": "10.0.3.2",
-+ "hostname": "r2",
-+ "afi": "ipv4",
-+ "used": true
-+ }
-+ ],
-+ "nlri": {
-+ "nlriType": "link",
-+ "protocolId": 2,
-+ "identifier": 0,
-+ "localNodeDescriptors": {
-+ "igpRouterId": "0000.0000.0003"
-+ },
-+ "remoteNodeDescriptors": {
-+ "igpRouterId": "0000.0000.0002"
-+ },
-+ "linkDescriptors": {
-+ "ipv4InterfaceAddress": "10.0.2.3",
-+ "ipv4NeighborAddress": "10.0.2.2"
-+ }
-+ },
-+ "nlriStr": "[E][L2][I0x0][N[s0000.0000.0003]][R[s0000.0000.0002]][L[i10.0.2.3][n10.0.2.2]]"
-+ }
-+ ],
-+ "[E][L2][I0x0][N[s0000.0000.0001]][R[s0000.0000.0004]][L[i10.0.4.1][n10.0.4.4]]": [
-+ {
-+ "valid": true,
-+ "bestpath": true,
-+ "selectionReason": "First path received",
-+ "pathFrom": "internal",
-+ "weight": 0,
-+ "peerId": "10.0.3.2",
-+ "path": "",
-+ "origin": "incomplete",
-+ "locPrf": 100,
-+ "nexthops": [
-+ {
-+ "ip": "10.0.3.2",
-+ "hostname": "r2",
-+ "afi": "ipv4",
-+ "used": true
-+ }
-+ ],
-+ "nlri": {
-+ "nlriType": "link",
-+ "protocolId": 2,
-+ "identifier": 0,
-+ "localNodeDescriptors": {
-+ "igpRouterId": "0000.0000.0001"
-+ },
-+ "remoteNodeDescriptors": {
-+ "igpRouterId": "0000.0000.0004"
-+ },
-+ "linkDescriptors": {
-+ "ipv4InterfaceAddress": "10.0.4.1",
-+ "ipv4NeighborAddress": "10.0.4.4"
-+ }
-+ },
-+ "nlriStr": "[E][L2][I0x0][N[s0000.0000.0001]][R[s0000.0000.0004]][L[i10.0.4.1][n10.0.4.4]]"
-+ }
-+ ],
-+ "[E][L2][I0x0][N[s0000.0000.0004]][R[s0000.0000.0001]][L[i10.0.4.4][n10.0.4.1]]": [
-+ {
-+ "valid": true,
-+ "bestpath": true,
-+ "selectionReason": "First path received",
-+ "pathFrom": "internal",
-+ "weight": 0,
-+ "peerId": "10.0.3.2",
-+ "path": "",
-+ "origin": "incomplete",
-+ "locPrf": 100,
-+ "nexthops": [
-+ {
-+ "ip": "10.0.3.2",
-+ "hostname": "r2",
-+ "afi": "ipv4",
-+ "used": true
-+ }
-+ ],
-+ "nlri": {
-+ "nlriType": "link",
-+ "protocolId": 2,
-+ "identifier": 0,
-+ "localNodeDescriptors": {
-+ "igpRouterId": "0000.0000.0004"
-+ },
-+ "remoteNodeDescriptors": {
-+ "igpRouterId": "0000.0000.0001"
-+ },
-+ "linkDescriptors": {
-+ "ipv4InterfaceAddress": "10.0.4.4",
-+ "ipv4NeighborAddress": "10.0.4.1"
-+ }
-+ },
-+ "nlriStr": "[E][L2][I0x0][N[s0000.0000.0004]][R[s0000.0000.0001]][L[i10.0.4.4][n10.0.4.1]]"
-+ }
-+ ],
-+ "[E][L2][I0x0][N[s0000.0000.0003]][R[s0000.0000.0004]][L[i10.0.5.3][n10.0.5.4]]": [
-+ {
-+ "valid": true,
-+ "bestpath": true,
-+ "selectionReason": "First path received",
-+ "pathFrom": "internal",
-+ "weight": 0,
-+ "peerId": "10.0.3.2",
-+ "path": "",
-+ "origin": "incomplete",
-+ "locPrf": 100,
-+ "nexthops": [
-+ {
-+ "ip": "10.0.3.2",
-+ "hostname": "r2",
-+ "afi": "ipv4",
-+ "used": true
-+ }
-+ ],
-+ "nlri": {
-+ "nlriType": "link",
-+ "protocolId": 2,
-+ "identifier": 0,
-+ "localNodeDescriptors": {
-+ "igpRouterId": "0000.0000.0003"
-+ },
-+ "remoteNodeDescriptors": {
-+ "igpRouterId": "0000.0000.0004"
-+ },
-+ "linkDescriptors": {
-+ "ipv4InterfaceAddress": "10.0.5.3",
-+ "ipv4NeighborAddress": "10.0.5.4"
-+ }
-+ },
-+ "nlriStr": "[E][L2][I0x0][N[s0000.0000.0003]][R[s0000.0000.0004]][L[i10.0.5.3][n10.0.5.4]]"
-+ }
-+ ],
-+ "[E][L2][I0x0][N[s0000.0000.0004]][R[s0000.0000.0003]][L[i10.0.5.4][n10.0.5.3]]": [
-+ {
-+ "valid": true,
-+ "bestpath": true,
-+ "selectionReason": "First path received",
-+ "pathFrom": "internal",
-+ "weight": 0,
-+ "peerId": "10.0.3.2",
-+ "path": "",
-+ "origin": "incomplete",
-+ "locPrf": 100,
-+ "nexthops": [
-+ {
-+ "ip": "10.0.3.2",
-+ "hostname": "r2",
-+ "afi": "ipv4",
-+ "used": true
-+ }
-+ ],
-+ "nlri": {
-+ "nlriType": "link",
-+ "protocolId": 2,
-+ "identifier": 0,
-+ "localNodeDescriptors": {
-+ "igpRouterId": "0000.0000.0004"
-+ },
-+ "remoteNodeDescriptors": {
-+ "igpRouterId": "0000.0000.0003"
-+ },
-+ "linkDescriptors": {
-+ "ipv4InterfaceAddress": "10.0.5.4",
-+ "ipv4NeighborAddress": "10.0.5.3"
-+ }
-+ },
-+ "nlriStr": "[E][L2][I0x0][N[s0000.0000.0004]][R[s0000.0000.0003]][L[i10.0.5.4][n10.0.5.3]]"
-+ }
-+ ],
-+ "[E][L2][I0x0][N[s0000.0000.0001]][R[s0000.0000.0002]][L[ifc00:10:0:1::1][nfc00:10:0:1::2]]":
-+ [
-+ {
-+ "valid": true,
-+ "bestpath": true,
-+ "selectionReason": "First path received",
-+ "pathFrom": "internal",
-+ "weight": 0,
-+ "peerId": "10.0.3.2",
-+ "path": "",
-+ "origin": "incomplete",
-+ "locPrf": 100,
-+ "nexthops": [
-+ {
-+ "ip": "10.0.3.2",
-+ "hostname": "r2",
-+ "afi": "ipv4",
-+ "used": true
-+ }
-+ ],
-+ "nlri": {
-+ "nlriType": "link",
-+ "protocolId": 2,
-+ "identifier": 0,
-+ "localNodeDescriptors": {
-+ "igpRouterId": "0000.0000.0001"
-+ },
-+ "remoteNodeDescriptors": {
-+ "igpRouterId": "0000.0000.0002"
-+ },
-+ "linkDescriptors": {
-+ "ipv6InterfaceAddress": "fc00:10:0:1::1",
-+ "ipv6NeighborAddress": "fc00:10:0:1::2"
-+ }
-+ },
-+ "nlriStr": "[E][L2][I0x0][N[s0000.0000.0001]][R[s0000.0000.0002]][L[ifc00:10:0:1::1][nfc00:10:0:1::2]]"
-+ }
-+ ],
-+ "[E][L2][I0x0][N[s0000.0000.0002]][R[s0000.0000.0001]][L[ifc00:10:0:1::2][nfc00:10:0:1::1]]":
-+ [
-+ {
-+ "valid": true,
-+ "bestpath": true,
-+ "selectionReason": "First path received",
-+ "pathFrom": "internal",
-+ "weight": 0,
-+ "peerId": "10.0.3.2",
-+ "path": "",
-+ "origin": "incomplete",
-+ "locPrf": 100,
-+ "nexthops": [
-+ {
-+ "ip": "10.0.3.2",
-+ "hostname": "r2",
-+ "afi": "ipv4",
-+ "used": true
-+ }
-+ ],
-+ "nlri": {
-+ "nlriType": "link",
-+ "protocolId": 2,
-+ "identifier": 0,
-+ "localNodeDescriptors": {
-+ "igpRouterId": "0000.0000.0002"
-+ },
-+ "remoteNodeDescriptors": {
-+ "igpRouterId": "0000.0000.0001"
-+ },
-+ "linkDescriptors": {
-+ "ipv6InterfaceAddress": "fc00:10:0:1::2",
-+ "ipv6NeighborAddress": "fc00:10:0:1::1"
-+ }
-+ },
-+ "nlriStr": "[E][L2][I0x0][N[s0000.0000.0002]][R[s0000.0000.0001]][L[ifc00:10:0:1::2][nfc00:10:0:1::1]]"
-+ }
-+ ],
-+ "[E][L2][I0x0][N[s0000.0000.0002]][R[s0000.0000.0003]][L[ifc00:10:0:2::2][nfc00:10:0:2::3]]":
-+ [
-+ {
-+ "valid": true,
-+ "bestpath": true,
-+ "selectionReason": "First path received",
-+ "pathFrom": "internal",
-+ "weight": 0,
-+ "peerId": "10.0.3.2",
-+ "path": "",
-+ "origin": "incomplete",
-+ "locPrf": 100,
-+ "nexthops": [
-+ {
-+ "ip": "10.0.3.2",
-+ "hostname": "r2",
-+ "afi": "ipv4",
-+ "used": true
-+ }
-+ ],
-+ "nlri": {
-+ "nlriType": "link",
-+ "protocolId": 2,
-+ "identifier": 0,
-+ "localNodeDescriptors": {
-+ "igpRouterId": "0000.0000.0002"
-+ },
-+ "remoteNodeDescriptors": {
-+ "igpRouterId": "0000.0000.0003"
-+ },
-+ "linkDescriptors": {
-+ "ipv6InterfaceAddress": "fc00:10:0:2::2",
-+ "ipv6NeighborAddress": "fc00:10:0:2::3"
-+ }
-+ },
-+ "nlriStr": "[E][L2][I0x0][N[s0000.0000.0002]][R[s0000.0000.0003]][L[ifc00:10:0:2::2][nfc00:10:0:2::3]]"
-+ }
-+ ],
-+ "[E][L2][I0x0][N[s0000.0000.0003]][R[s0000.0000.0002]][L[ifc00:10:0:2::3][nfc00:10:0:2::2]]":
-+ [
-+ {
-+ "valid": true,
-+ "bestpath": true,
-+ "selectionReason": "First path received",
-+ "pathFrom": "internal",
-+ "weight": 0,
-+ "peerId": "10.0.3.2",
-+ "path": "",
-+ "origin": "incomplete",
-+ "locPrf": 100,
-+ "nexthops": [
-+ {
-+ "ip": "10.0.3.2",
-+ "hostname": "r2",
-+ "afi": "ipv4",
-+ "used": true
-+ }
-+ ],
-+ "nlri": {
-+ "nlriType": "link",
-+ "protocolId": 2,
-+ "identifier": 0,
-+ "localNodeDescriptors": {
-+ "igpRouterId": "0000.0000.0003"
-+ },
-+ "remoteNodeDescriptors": {
-+ "igpRouterId": "0000.0000.0002"
-+ },
-+ "linkDescriptors": {
-+ "ipv6InterfaceAddress": "fc00:10:0:2::3",
-+ "ipv6NeighborAddress": "fc00:10:0:2::2"
-+ }
-+ },
-+ "nlriStr": "[E][L2][I0x0][N[s0000.0000.0003]][R[s0000.0000.0002]][L[ifc00:10:0:2::3][nfc00:10:0:2::2]]"
-+ }
-+ ],
-+ "[E][L2][I0x0][N[s0000.0000.0001]][R[s0000.0000.0004]][L[ifc00:10:0:4::1][nfc00:10:0:4::4]]":
-+ [
-+ {
-+ "valid": true,
-+ "bestpath": true,
-+ "selectionReason": "First path received",
-+ "pathFrom": "internal",
-+ "weight": 0,
-+ "peerId": "10.0.3.2",
-+ "path": "",
-+ "origin": "incomplete",
-+ "locPrf": 100,
-+ "nexthops": [
-+ {
-+ "ip": "10.0.3.2",
-+ "hostname": "r2",
-+ "afi": "ipv4",
-+ "used": true
-+ }
-+ ],
-+ "nlri": {
-+ "nlriType": "link",
-+ "protocolId": 2,
-+ "identifier": 0,
-+ "localNodeDescriptors": {
-+ "igpRouterId": "0000.0000.0001"
-+ },
-+ "remoteNodeDescriptors": {
-+ "igpRouterId": "0000.0000.0004"
-+ },
-+ "linkDescriptors": {
-+ "ipv6InterfaceAddress": "fc00:10:0:4::1",
-+ "ipv6NeighborAddress": "fc00:10:0:4::4"
-+ }
-+ },
-+ "nlriStr": "[E][L2][I0x0][N[s0000.0000.0001]][R[s0000.0000.0004]][L[ifc00:10:0:4::1][nfc00:10:0:4::4]]"
-+ }
-+ ],
-+ "[E][L2][I0x0][N[s0000.0000.0004]][R[s0000.0000.0001]][L[ifc00:10:0:4::4][nfc00:10:0:4::1]]":
-+ [
-+ {
-+ "valid": true,
-+ "bestpath": true,
-+ "selectionReason": "First path received",
-+ "pathFrom": "internal",
-+ "weight": 0,
-+ "peerId": "10.0.3.2",
-+ "path": "",
-+ "origin": "incomplete",
-+ "locPrf": 100,
-+ "nexthops": [
-+ {
-+ "ip": "10.0.3.2",
-+ "hostname": "r2",
-+ "afi": "ipv4",
-+ "used": true
-+ }
-+ ],
-+ "nlri": {
-+ "nlriType": "link",
-+ "protocolId": 2,
-+ "identifier": 0,
-+ "localNodeDescriptors": {
-+ "igpRouterId": "0000.0000.0004"
-+ },
-+ "remoteNodeDescriptors": {
-+ "igpRouterId": "0000.0000.0001"
-+ },
-+ "linkDescriptors": {
-+ "ipv6InterfaceAddress": "fc00:10:0:4::4",
-+ "ipv6NeighborAddress": "fc00:10:0:4::1"
-+ }
-+ },
-+ "nlriStr": "[E][L2][I0x0][N[s0000.0000.0004]][R[s0000.0000.0001]][L[ifc00:10:0:4::4][nfc00:10:0:4::1]]"
-+ }
-+ ],
-+ "[E][L2][I0x0][N[s0000.0000.0003]][R[s0000.0000.0004]][L[ifc00:10:0:5::3][nfc00:10:0:5::4]]":
-+ [
-+ {
-+ "valid": true,
-+ "bestpath": true,
-+ "selectionReason": "First path received",
-+ "pathFrom": "internal",
-+ "weight": 0,
-+ "peerId": "10.0.3.2",
-+ "path": "",
-+ "origin": "incomplete",
-+ "locPrf": 100,
-+ "nexthops": [
-+ {
-+ "ip": "10.0.3.2",
-+ "hostname": "r2",
-+ "afi": "ipv4",
-+ "used": true
-+ }
-+ ],
-+ "nlri": {
-+ "nlriType": "link",
-+ "protocolId": 2,
-+ "identifier": 0,
-+ "localNodeDescriptors": {
-+ "igpRouterId": "0000.0000.0003"
-+ },
-+ "remoteNodeDescriptors": {
-+ "igpRouterId": "0000.0000.0004"
-+ },
-+ "linkDescriptors": {
-+ "ipv6InterfaceAddress": "fc00:10:0:5::3",
-+ "ipv6NeighborAddress": "fc00:10:0:5::4"
-+ }
-+ },
-+ "nlriStr": "[E][L2][I0x0][N[s0000.0000.0003]][R[s0000.0000.0004]][L[ifc00:10:0:5::3][nfc00:10:0:5::4]]"
-+ }
-+ ],
-+ "[E][L2][I0x0][N[s0000.0000.0004]][R[s0000.0000.0003]][L[ifc00:10:0:5::4][nfc00:10:0:5::3]]":
-+ [
-+ {
-+ "valid": true,
-+ "bestpath": true,
-+ "selectionReason": "First path received",
-+ "pathFrom": "internal",
-+ "weight": 0,
-+ "peerId": "10.0.3.2",
-+ "path": "",
-+ "origin": "incomplete",
-+ "locPrf": 100,
-+ "nexthops": [
-+ {
-+ "ip": "10.0.3.2",
-+ "hostname": "r2",
-+ "afi": "ipv4",
-+ "used": true
-+ }
-+ ],
-+ "nlri": {
-+ "nlriType": "link",
-+ "protocolId": 2,
-+ "identifier": 0,
-+ "localNodeDescriptors": {
-+ "igpRouterId": "0000.0000.0004"
-+ },
-+ "remoteNodeDescriptors": {
-+ "igpRouterId": "0000.0000.0003"
-+ },
-+ "linkDescriptors": {
-+ "ipv6InterfaceAddress": "fc00:10:0:5::4",
-+ "ipv6NeighborAddress": "fc00:10:0:5::3"
-+ }
-+ },
-+ "nlriStr": "[E][L2][I0x0][N[s0000.0000.0004]][R[s0000.0000.0003]][L[ifc00:10:0:5::4][nfc00:10:0:5::3]]"
-+ }
-+ ]
-+ }
-+}
-\ No newline at end of file
-diff --git a/tests/topotests/bgp_link_state/rr/bgp_ls_prefix4.json b/tests/topotests/bgp_link_state/rr/bgp_ls_prefix4.json
-new file mode 100644
-index 0000000000..46339e92fe
---- /dev/null
-+++ b/tests/topotests/bgp_link_state/rr/bgp_ls_prefix4.json
-@@ -0,0 +1,13 @@
-+{
-+ "paths": [
-+ {
-+ "linkStateAttrs": {
-+ "prefixSid": {
-+ "sid": 4,
-+ "flags": "0x40",
-+ "algo": 0
-+ }
-+ }
-+ }
-+ ]
-+}
-diff --git a/tests/topotests/bgp_link_state/rr/bgp_neighbor.json b/tests/topotests/bgp_link_state/rr/bgp_neighbor.json
-new file mode 100644
-index 0000000000..986514610b
---- /dev/null
-+++ b/tests/topotests/bgp_link_state/rr/bgp_neighbor.json
-@@ -0,0 +1,7 @@
-+{
-+ "10.0.3.2": {
-+ "remoteAs": 65000,
-+ "localAs": 65000,
-+ "bgpState": "Established"
-+ }
-+}
-diff --git a/tests/topotests/bgp_link_state/rr/frr.conf b/tests/topotests/bgp_link_state/rr/frr.conf
-new file mode 100644
-index 0000000000..7f8ade7e6a
---- /dev/null
-+++ b/tests/topotests/bgp_link_state/rr/frr.conf
-@@ -0,0 +1,23 @@
-+hostname rr
-+!
-+!debug bgp link-state
-+!debug bgp zebra
-+!
-+router bgp 65000
-+ bgp router-id 10.10.10.10
-+ no bgp ebgp-requires-policy
-+ neighbor 10.0.3.2 remote-as 65000
-+ neighbor 10.0.3.2 update-source 10.0.3.4
-+ !
-+ address-family link-state
-+ neighbor 10.0.3.2 activate
-+ exit-address-family
-+!
-+interface rr-eth0
-+ ip address 10.0.3.4/24
-+!
-+interface lo
-+ ip address 10.10.10.10/32
-+!
-+ip forwarding
-+!
-diff --git a/tests/topotests/bgp_link_state/test_bgp_link_state.py b/tests/topotests/bgp_link_state/test_bgp_link_state.py
-new file mode 100644
-index 0000000000..97444690ba
---- /dev/null
-+++ b/tests/topotests/bgp_link_state/test_bgp_link_state.py
-@@ -0,0 +1,1007 @@
-+#!/usr/bin/env python
-+# SPDX-License-Identifier: ISC
-+#
-+# Copyright (c) 2025 by Carmine Scarpitta
-+#
-+"""
-+Test BGP Link-State (RFC 9552) functionality:
-+- BGP-LS capability negotiation
-+- Producer mode: Export IGP topology to BGP-LS
-+- Consumer mode: Build TED from BGP-LS routes
-+
-+Topology:
-+
-+ +-----+
-+ | rr | (Consumer)
-+ +-----+
-+ |
-+ | BGP-LS
-+ |
-+ +-----+ +-----+ +-----+
-+ | r1 |--| r2 |--| r3 |
-+ +-----+ +-----+ +-----+
-+ \ (Producer) /
-+ \ /
-+ \ ISIS /
-+ \ /
-+ \ /
-+ \ /
-+ +-----+
-+ | r4 |
-+ +-----+
-+
-+- r1, r2, r3, r4: Run ISIS L2 IGP
-+- r2: BGP-LS Producer (collects ISIS topology and exports via BGP-LS)
-+- rr: BGP-LS Consumer (receives BGP-LS routes and builds TED)
-+"""
-+
-+import os
-+import sys
-+import json
-+import pytest
-+import functools
-+
-+# pylint: disable=C0413
-+# Import topogen and topotest helpers
-+from lib import topotest
-+from lib.topogen import Topogen, TopoRouter, get_topogen
-+from lib.topolog import logger
-+
-+# Required to instantiate the topology builder class.
-+pytestmark = [pytest.mark.bgpd, pytest.mark.isisd]
-+
-+
-+#
-+# Helper functions for BGP-LS validation
-+#
-+
-+def extract_bgp_ls_routes(json_data):
-+ """
-+ Extract routes from BGP-LS JSON output.
-+
-+ The actual output has format: {"routes": {"[key]": [{"nlri": {...}, ...}]}}
-+
-+ Args:
-+ json_data: Parsed JSON dictionary from show bgp link-state command
-+
-+ Returns:
-+ Array of route objects with nlri and path attributes
-+ """
-+ routes_array = []
-+
-+ if isinstance(json_data, dict) and "routes" in json_data:
-+ routes_dict = json_data["routes"]
-+ for nlri_key, paths_list in routes_dict.items():
-+ if paths_list and isinstance(paths_list, list):
-+ for path in paths_list:
-+ routes_array.append(path)
-+ elif isinstance(json_data, list):
-+ routes_array = json_data
-+
-+ return routes_array
-+
-+
-+def check_bgp_ls_prefix(router, prefix, protocol_id=None, should_exist=True):
-+ """
-+ Check if a prefix exists in BGP-LS NLRI
-+
-+ Args:
-+ router: Router instance
-+ prefix: IP prefix to check (e.g., "192.168.100.0/24")
-+ protocol_id: Optional protocol ID filter (e.g., 5 for static)
-+ should_exist: True to verify presence, False to verify absence
-+
-+ Returns:
-+ None if check passes, error message otherwise
-+ """
-+ output = router.vtysh_cmd("show bgp link-state link-state json")
-+ json_data = json.loads(output)
-+ data = extract_bgp_ls_routes(json_data)
-+
-+ for entry in data:
-+ nlri = entry.get("nlri", {})
-+ # Filter for prefix NLRI types (ipv4Prefix or ipv6Prefix)
-+ nlri_type = nlri.get("nlriType")
-+ if nlri_type not in ["ipv4Prefix", "ipv6Prefix"]:
-+ continue
-+
-+ if nlri.get("prefixDescriptors", {}).get("ipReachabilityInformation") == prefix:
-+ if protocol_id is not None and nlri.get("protocolId") != protocol_id:
-+ continue
-+ # Found the prefix
-+ if should_exist:
-+ return None
-+ else:
-+ return f"Prefix {prefix} still present in BGP-LS"
-+
-+ # Prefix not found
-+ if should_exist:
-+ return f"Prefix {prefix} not found in BGP-LS"
-+ else:
-+ return None
-+
-+
-+def check_bgp_ls_link(router, local_id, remote_id, should_exist=True):
-+ """
-+ Check if a link exists in BGP-LS NLRI
-+
-+ Args:
-+ router: Router instance
-+ local_id: Local node IGP Router ID (e.g., "0000.0000.0002")
-+ remote_id: Remote node IGP Router ID (e.g., "0000.0000.0001")
-+ should_exist: True to verify presence, False to verify absence
-+
-+ Returns:
-+ None if check passes, error message otherwise
-+ """
-+ output = router.vtysh_cmd("show bgp link-state link-state json")
-+ json_data = json.loads(output)
-+ data = extract_bgp_ls_routes(json_data)
-+
-+ for entry in data:
-+ nlri = entry.get("nlri", {})
-+ # Filter for link NLRI type
-+ if nlri.get("nlriType") != "link":
-+ continue
-+
-+ nlri_local_id = nlri.get("localNodeDescriptors", {}).get("igpRouterId")
-+ nlri_remote_id = nlri.get("remoteNodeDescriptors", {}).get("igpRouterId")
-+ if nlri_local_id == local_id and nlri_remote_id == remote_id:
-+ # Found the link
-+ if should_exist:
-+ return None
-+ else:
-+ return f"Link {local_id}->{remote_id} still present in BGP-LS"
-+
-+ # Link not found
-+ if should_exist:
-+ return f"Link {local_id}->{remote_id} not found in BGP-LS"
-+ else:
-+ return None
-+
-+
-+def check_bgp_ls_node(router, node_id, should_exist=True):
-+ """
-+ Check if a node exists in BGP-LS NLRI
-+
-+ Args:
-+ router: Router instance
-+ node_id: Node IGP Router ID (e.g., "0000.0000.0001")
-+ should_exist: True to verify presence, False to verify absence
-+
-+ Returns:
-+ None if check passes, error message otherwise
-+ """
-+ output = router.vtysh_cmd("show bgp link-state link-state json")
-+ json_data = json.loads(output)
-+ data = extract_bgp_ls_routes(json_data)
-+
-+ for entry in data:
-+ nlri = entry.get("nlri", {})
-+ # Filter for node NLRI type
-+ if nlri.get("nlriType") != "node":
-+ continue
-+
-+ nlri_node_id = nlri.get("localNodeDescriptors", {}).get("igpRouterId")
-+ if nlri_node_id == node_id:
-+ # Found the node
-+ if should_exist:
-+ return None
-+ else:
-+ return f"Node {node_id} still present in BGP-LS"
-+
-+ # Node not found
-+ if should_exist:
-+ return f"Node {node_id} not found in BGP-LS"
-+ else:
-+ return None
-+
-+
-+def get_bgp_ls_count(router):
-+ """
-+ Get the count of BGP-LS NLRIs
-+
-+ Args:
-+ router: Router instance
-+
-+ Returns:
-+ Count of BGP-LS routes
-+ """
-+ output = router.vtysh_cmd("show bgp link-state link-state json")
-+ json_data = json.loads(output)
-+ data = extract_bgp_ls_routes(json_data)
-+ return len(data)
-+
-+
-+def check_bgp_ls_empty(router):
-+ """
-+ Check that the BGP-LS routing table is empty.
-+
-+ Args:
-+ router: Router instance
-+
-+ Returns:
-+ None if table is empty, error message otherwise
-+ """
-+ count = get_bgp_ls_count(router)
-+ if count > 0:
-+ return f"BGP-LS table not empty: {count} route(s) still present"
-+ return None
-+
-+
-+def build_topo(tgen):
-+ """Build the test topology"""
-+
-+ # Create routers
-+ for routern in [1, 2, 3, 4]:
-+ tgen.add_router("r{}".format(routern))
-+ tgen.add_router("rr")
-+
-+ # Create switches
-+ switch = tgen.add_switch("s1")
-+ switch.add_link(tgen.gears["r1"])
-+ switch.add_link(tgen.gears["r2"])
-+
-+ switch = tgen.add_switch("s2")
-+ switch.add_link(tgen.gears["r2"])
-+ switch.add_link(tgen.gears["r3"])
-+
-+ switch = tgen.add_switch("s3")
-+ switch.add_link(tgen.gears["r2"])
-+ switch.add_link(tgen.gears["rr"])
-+
-+ # r4 connections
-+ switch = tgen.add_switch("s4")
-+ switch.add_link(tgen.gears["r1"])
-+ switch.add_link(tgen.gears["r4"])
-+
-+ switch = tgen.add_switch("s5")
-+ switch.add_link(tgen.gears["r3"])
-+ switch.add_link(tgen.gears["r4"])
-+
-+
-+def setup_module(mod):
-+ """Setup module for the tests"""
-+ tgen = Topogen(build_topo, mod.__name__)
-+ tgen.start_topology()
-+
-+ router_list = tgen.routers()
-+
-+ # Initialize all routers
-+ for rname, router in router_list.items():
-+ router.load_frr_config(os.path.join(CWD, "{}/frr.conf".format(rname)))
-+
-+ # Start routers
-+ tgen.start_router()
-+
-+
-+def teardown_module(mod):
-+ """Teardown the pytest environment"""
-+ tgen = get_topogen()
-+ tgen.stop_topology()
-+
-+
-+def test_isis_convergence():
-+ """Test ISIS convergence"""
-+ tgen = get_topogen()
-+
-+ if tgen.routers_have_failure():
-+ pytest.skip(tgen.errors)
-+
-+ logger.info("Checking ISIS convergence")
-+
-+ for rname in ["r1", "r2", "r3", "r4"]:
-+ router = tgen.gears[rname]
-+
-+ # Check ISIS adjacencies
-+ reffile = os.path.join(CWD, "{}/isis_adj.json".format(rname))
-+ expected = json.loads(open(reffile).read())
-+
-+ test_func = functools.partial(
-+ topotest.router_json_cmp,
-+ router,
-+ "show isis neighbor json",
-+ expected,
-+ )
-+ _, result = topotest.run_and_expect(test_func, None, count=60, wait=1)
-+ assertmsg = '"{}" JSON output mismatches'.format(rname)
-+ assert result is None, assertmsg
-+
-+
-+def test_bgp_convergence():
-+ """Test BGP convergence between r2 (producer) and rr (consumer)"""
-+ tgen = get_topogen()
-+
-+ if tgen.routers_have_failure():
-+ pytest.skip(tgen.errors)
-+
-+ logger.info("Checking BGP convergence")
-+
-+ # Check BGP neighbor status on r2 (producer)
-+ router = tgen.gears["r2"]
-+ reffile = os.path.join(CWD, "r2/bgp_neighbor.json")
-+ expected = json.loads(open(reffile).read())
-+
-+ test_func = functools.partial(
-+ topotest.router_json_cmp,
-+ router,
-+ "show bgp neighbor json",
-+ expected,
-+ )
-+ _, result = topotest.run_and_expect(test_func, None, count=60, wait=1)
-+ assert result is None, '"r2" BGP neighbor not established'
-+
-+ # Check BGP neighbor status on rr (consumer)
-+ router = tgen.gears["rr"]
-+ reffile = os.path.join(CWD, "rr/bgp_neighbor.json")
-+ expected = json.loads(open(reffile).read())
-+
-+ test_func = functools.partial(
-+ topotest.router_json_cmp,
-+ router,
-+ "show bgp neighbor json",
-+ expected,
-+ )
-+ _, result = topotest.run_and_expect(test_func, None, count=60, wait=1)
-+ assert result is None, '"rr" BGP neighbor not established'
-+
-+
-+def test_bgp_ls_capability():
-+ """Test BGP-LS capability negotiation"""
-+ tgen = get_topogen()
-+
-+ if tgen.routers_have_failure():
-+ pytest.skip(tgen.errors)
-+
-+ logger.info("Checking BGP-LS capability negotiation")
-+
-+ # Check r2 advertised and received BGP-LS capability
-+ router = tgen.gears["r2"]
-+ reffile = os.path.join(CWD, "r2/bgp_capability.json")
-+ expected = json.loads(open(reffile).read())
-+
-+ test_func = functools.partial(
-+ topotest.router_json_cmp,
-+ router,
-+ "show bgp neighbor 10.0.3.4 json",
-+ expected,
-+ )
-+ _, result = topotest.run_and_expect(test_func, None, count=30, wait=1)
-+ assert result is None, '"r2" BGP-LS capability not negotiated'
-+
-+ # Check rr advertised and received BGP-LS capability
-+ router = tgen.gears["rr"]
-+ reffile = os.path.join(CWD, "rr/bgp_capability.json")
-+ expected = json.loads(open(reffile).read())
-+
-+ test_func = functools.partial(
-+ topotest.router_json_cmp,
-+ router,
-+ "show bgp neighbor 10.0.3.2 json",
-+ expected,
-+ )
-+ _, result = topotest.run_and_expect(test_func, None, count=30, wait=1)
-+ assert result is None, '"rr" BGP-LS capability not negotiated'
-+
-+
-+def test_bgp_ls_routes_producer():
-+ """Test BGP-LS routes on producer (r2)"""
-+ tgen = get_topogen()
-+
-+ if tgen.routers_have_failure():
-+ pytest.skip(tgen.errors)
-+
-+ logger.info("Checking BGP-LS routes on producer")
-+
-+ router = tgen.gears["r2"]
-+
-+ # Check BGP-LS routes are originated
-+ reffile = os.path.join(CWD, "r2/bgp_ls_nlri.json")
-+ expected = json.loads(open(reffile).read())
-+
-+ test_func = functools.partial(
-+ topotest.router_json_cmp,
-+ router,
-+ "show bgp link-state link-state json",
-+ expected,
-+ )
-+ _, result = topotest.run_and_expect(test_func, None, count=60, wait=1)
-+ assertmsg = '"r2" BGP-LS routes not originated correctly'
-+ assert result is None, assertmsg
-+
-+
-+def test_bgp_ls_routes_consumer():
-+ """Test BGP-LS routes on consumer (rr)"""
-+ tgen = get_topogen()
-+
-+ if tgen.routers_have_failure():
-+ pytest.skip(tgen.errors)
-+
-+ logger.info("Checking BGP-LS routes on consumer")
-+
-+ router = tgen.gears["rr"]
-+
-+ # Check BGP-LS routes are received
-+ reffile = os.path.join(CWD, "rr/bgp_ls_nlri.json")
-+ expected = json.loads(open(reffile).read())
-+
-+ test_func = functools.partial(
-+ topotest.router_json_cmp,
-+ router,
-+ "show bgp link-state link-state json",
-+ expected,
-+ )
-+ _, result = topotest.run_and_expect(test_func, None, count=60, wait=1)
-+ assertmsg = '"rr" BGP-LS routes not received correctly'
-+ assert result is None, assertmsg
-+
-+def test_bgp_ls_attributes_consumer():
-+ tgen = get_topogen()
-+
-+ if tgen.routers_have_failure():
-+ pytest.skip(tgen.errors)
-+
-+ logger.info("Checking BGP-LS prefix attributes on consumer")
-+
-+ router = tgen.gears["rr"]
-+
-+ # Check BGP-LS attributes are received for prefix
-+ reffile = os.path.join(CWD, "rr/bgp_ls_prefix4.json")
-+ expected = json.loads(open(reffile).read())
-+
-+ test_func = functools.partial(
-+ topotest.router_json_cmp,
-+ router,
-+ "show bgp link-state link-state [T][L2][I0x0][N[s0000.0000.0004]][P[p4.4.4.4/32]] json",
-+ expected,
-+ )
-+ _, result = topotest.run_and_expect(test_func, None, count=60, wait=1)
-+ assertmsg = '"rr" BGP-LS prefix attributes not received correctly'
-+ assert result is None, assertmsg
-+
-+ # Check BGP-LS attributes are received for node
-+ reffile = os.path.join(CWD, "rr/bgp_ls_attrs_node4.json")
-+ expected = json.loads(open(reffile).read())
-+
-+ test_func = functools.partial(
-+ topotest.router_json_cmp,
-+ router,
-+ "show bgp link-state link-state [V][L2][I0x0][N[s0000.0000.0004]] json",
-+ expected,
-+ )
-+ _, result = topotest.run_and_expect(test_func, None, count=60, wait=1)
-+ assertmsg = '"rr" BGP-LS node attributes not received correctly'
-+ assert result is None, assertmsg
-+
-+ # Check BGP-LS attributes are received for link
-+ reffile = os.path.join(CWD, "rr/bgp_ls_attrs_link43.json")
-+ expected = json.loads(open(reffile).read())
-+
-+ test_func = functools.partial(
-+ topotest.router_json_cmp,
-+ router,
-+ "show bgp link-state link-state [E][L2][I0x0][N[s0000.0000.0004]][R[s0000.0000.0003]][L[i10.0.5.4][n10.0.5.3]] json",
-+ expected,
-+ )
-+ _, result = topotest.run_and_expect(test_func, None, count=60, wait=1)
-+ assertmsg = '"rr" BGP-LS link attributes not received correctly'
-+ assert result is None, assertmsg
-+
-+
-+
-+def test_bgp_ls_static_route_add():
-+ """Test adding a static route and verifying BGP-LS update"""
-+ tgen = get_topogen()
-+
-+ if tgen.routers_have_failure():
-+ pytest.skip(tgen.errors)
-+
-+ logger.info("Adding static route on r1 and checking BGP-LS update")
-+
-+ r2 = tgen.gears["r2"]
-+ consumer = tgen.gears["rr"]
-+
-+ # Get initial route count
-+ initial_count_r2 = get_bgp_ls_count(r2)
-+ initial_count_rr = get_bgp_ls_count(consumer)
-+
-+ router = tgen.gears["r1"]
-+
-+ # Add static route on r1
-+ router.vtysh_cmd("configure terminal\nip route 192.168.100.0/24 Null0")
-+
-+ # Wait for ISIS to propagate to r2, then BGP-LS to advertise the new prefix (protocol ID 2 = ISIS)
-+ test_func = functools.partial(
-+ check_bgp_ls_prefix, r2, "192.168.100.0/24", protocol_id=2, should_exist=True
-+ )
-+ _, result = topotest.run_and_expect(test_func, None, count=30, wait=1)
-+ assert result is None, '"r2" BGP-LS static route not advertised'
-+
-+ # Verify route count increased by 1
-+ new_count_r2 = get_bgp_ls_count(r2)
-+ assert new_count_r2 == initial_count_r2 + 1, f'"r2" route count should be {initial_count_r2 + 1}, got {new_count_r2}'
-+
-+ # Verify consumer rr received the update
-+ test_func = functools.partial(
-+ check_bgp_ls_prefix, consumer, "192.168.100.0/24", protocol_id=2, should_exist=True
-+ )
-+ _, result = topotest.run_and_expect(test_func, None, count=30, wait=1)
-+ assert result is None, '"rr" did not receive BGP-LS static route update'
-+
-+ # Verify route count increased by 1
-+ new_count_rr = get_bgp_ls_count(consumer)
-+ assert new_count_rr == initial_count_rr + 1, f'"rr" route count should be {initial_count_rr + 1}, got {new_count_rr}'
-+
-+
-+def test_bgp_ls_static_route_remove():
-+ """Test removing static route and verifying BGP-LS withdrawal"""
-+ tgen = get_topogen()
-+
-+ if tgen.routers_have_failure():
-+ pytest.skip(tgen.errors)
-+
-+ logger.info("Removing static route on r1 and checking BGP-LS withdrawal")
-+
-+ r2 = tgen.gears["r2"]
-+ consumer = tgen.gears["rr"]
-+
-+ # Get current route count (should have the static route from previous test)
-+ initial_count_r2 = get_bgp_ls_count(r2)
-+ initial_count_rr = get_bgp_ls_count(consumer)
-+
-+ router = tgen.gears["r1"]
-+
-+ # Remove static route from r1
-+ router.vtysh_cmd("configure terminal\nno ip route 192.168.100.0/24 Null0")
-+
-+ # Wait for ISIS to propagate to r2, then BGP-LS to withdraw the prefix
-+ test_func = functools.partial(
-+ check_bgp_ls_prefix, r2, "192.168.100.0/24", protocol_id=2, should_exist=False
-+ )
-+ _, result = topotest.run_and_expect(test_func, None, count=30, wait=1)
-+ assert result is None, '"r2" BGP-LS static route not withdrawn'
-+
-+ # Verify route count decreased by 1
-+ new_count_r2 = get_bgp_ls_count(r2)
-+ assert new_count_r2 == initial_count_r2 - 1, f'"r2" route count should be {initial_count_r2 - 1}, got {new_count_r2}'
-+
-+ # Verify consumer rr received the withdrawal
-+ test_func = functools.partial(
-+ check_bgp_ls_prefix, consumer, "192.168.100.0/24", protocol_id=2, should_exist=False
-+ )
-+ _, result = topotest.run_and_expect(test_func, None, count=30, wait=1)
-+ assert result is None, '"rr" did not receive BGP-LS static route withdrawal'
-+
-+ # Verify route count decreased by 1
-+ new_count_rr = get_bgp_ls_count(consumer)
-+ assert new_count_rr == initial_count_rr - 1, f'"rr" route count should be {initial_count_rr - 1}, got {new_count_rr}'
-+
-+ # Verify r2 routes match original reference file
-+ reffile = os.path.join(CWD, "r2/bgp_ls_nlri.json")
-+ expected = json.loads(open(reffile).read())
-+ test_func = functools.partial(
-+ topotest.router_json_cmp,
-+ r2,
-+ "show bgp link-state link-state json",
-+ expected,
-+ )
-+ _, result = topotest.run_and_expect(test_func, None, count=30, wait=1)
-+ assert result is None, '"r2" BGP-LS routes do not match expected after route removal'
-+
-+ # Verify rr routes match original reference file
-+ reffile = os.path.join(CWD, "rr/bgp_ls_nlri.json")
-+ expected = json.loads(open(reffile).read())
-+ test_func = functools.partial(
-+ topotest.router_json_cmp,
-+ consumer,
-+ "show bgp link-state link-state json",
-+ expected,
-+ )
-+ _, result = topotest.run_and_expect(test_func, None, count=30, wait=1)
-+ assert result is None, '"rr" BGP-LS routes do not match expected after route removal'
-+
-+
-+def test_bgp_ls_interface_address_add():
-+ """Test adding interface address and verifying BGP-LS update"""
-+ tgen = get_topogen()
-+
-+ if tgen.routers_have_failure():
-+ pytest.skip(tgen.errors)
-+
-+ logger.info("Adding interface address on r1 and checking BGP-LS update")
-+
-+ r2 = tgen.gears["r2"]
-+ consumer = tgen.gears["rr"]
-+
-+ # Get initial route count
-+ initial_count_r2 = get_bgp_ls_count(r2)
-+ initial_count_rr = get_bgp_ls_count(consumer)
-+
-+ router = tgen.gears["r1"]
-+
-+ # Add secondary IP address to loopback on r1
-+ router.vtysh_cmd("configure terminal\ninterface lo\nip address 1.1.1.11/32")
-+
-+ # Wait for ISIS to propagate to r2, then BGP-LS to advertise the new prefix
-+ test_func = functools.partial(
-+ check_bgp_ls_prefix, r2, "1.1.1.11/32", should_exist=True
-+ )
-+ _, result = topotest.run_and_expect(test_func, None, count=30, wait=1)
-+ assert result is None, '"r2" BGP-LS secondary address not advertised'
-+
-+ # Verify route count increased by 1
-+ new_count_r2 = get_bgp_ls_count(r2)
-+ assert new_count_r2 == initial_count_r2 + 1, f'"r2" route count should be {initial_count_r2 + 1}, got {new_count_r2}'
-+
-+ # Verify consumer rr received the update
-+ test_func = functools.partial(
-+ check_bgp_ls_prefix, consumer, "1.1.1.11/32", should_exist=True
-+ )
-+ _, result = topotest.run_and_expect(test_func, None, count=30, wait=1)
-+ assert result is None, '"rr" did not receive BGP-LS secondary address update'
-+
-+ # Verify route count increased by 1
-+ new_count_rr = get_bgp_ls_count(consumer)
-+ assert new_count_rr == initial_count_rr + 1, f'"rr" route count should be {initial_count_rr + 1}, got {new_count_rr}'
-+
-+
-+def test_bgp_ls_interface_address_remove():
-+ """Test removing interface address and verifying BGP-LS withdrawal"""
-+ tgen = get_topogen()
-+
-+ if tgen.routers_have_failure():
-+ pytest.skip(tgen.errors)
-+
-+ logger.info("Removing interface address on r1 and checking BGP-LS withdrawal")
-+
-+ r2 = tgen.gears["r2"]
-+ consumer = tgen.gears["rr"]
-+
-+ # Get current route count
-+ initial_count_r2 = get_bgp_ls_count(r2)
-+ initial_count_rr = get_bgp_ls_count(consumer)
-+
-+ router = tgen.gears["r1"]
-+
-+ # Remove secondary IP address from r1
-+ router.vtysh_cmd("configure terminal\ninterface lo\nno ip address 1.1.1.11/32")
-+
-+ # Wait for ISIS to propagate to r2, then BGP-LS to withdraw the prefix
-+ test_func = functools.partial(
-+ check_bgp_ls_prefix, r2, "1.1.1.11/32", should_exist=False
-+ )
-+ _, result = topotest.run_and_expect(test_func, None, count=30, wait=1)
-+ assert result is None, '"r2" BGP-LS secondary address not withdrawn'
-+
-+ # Verify route count decreased by 1
-+ new_count_r2 = get_bgp_ls_count(r2)
-+ assert new_count_r2 == initial_count_r2 - 1, f'"r2" route count should be {initial_count_r2 - 1}, got {new_count_r2}'
-+
-+ # Verify consumer rr received the withdrawal
-+ test_func = functools.partial(
-+ check_bgp_ls_prefix, consumer, "1.1.1.11/32", should_exist=False
-+ )
-+ _, result = topotest.run_and_expect(test_func, None, count=30, wait=1)
-+ assert result is None, '"rr" did not receive BGP-LS secondary address withdrawal'
-+
-+ # Verify route count decreased by 1
-+ new_count_rr = get_bgp_ls_count(consumer)
-+ assert new_count_rr == initial_count_rr - 1, f'"rr" route count should be {initial_count_rr - 1}, got {new_count_rr}'
-+
-+ # Verify routes match original reference files
-+ reffile = os.path.join(CWD, "r2/bgp_ls_nlri.json")
-+ expected = json.loads(open(reffile).read())
-+ test_func = functools.partial(
-+ topotest.router_json_cmp,
-+ r2,
-+ "show bgp link-state link-state json",
-+ expected,
-+ )
-+ _, result = topotest.run_and_expect(test_func, None, count=30, wait=1)
-+ assert result is None, '"r2" BGP-LS routes do not match expected'
-+
-+ reffile = os.path.join(CWD, "rr/bgp_ls_nlri.json")
-+ expected = json.loads(open(reffile).read())
-+ test_func = functools.partial(
-+ topotest.router_json_cmp,
-+ consumer,
-+ "show bgp link-state link-state json",
-+ expected,
-+ )
-+ _, result = topotest.run_and_expect(test_func, None, count=30, wait=1)
-+ assert result is None, '"rr" BGP-LS routes do not match expected'
-+
-+
-+def test_bgp_ls_ipv6_address_add():
-+ """Test adding IPv6 interface address and verifying BGP-LS update"""
-+ tgen = get_topogen()
-+
-+ if tgen.routers_have_failure():
-+ pytest.skip(tgen.errors)
-+
-+ logger.info("Adding IPv6 interface address on r1 and checking BGP-LS update")
-+
-+ router = tgen.gears["r1"]
-+
-+ # Add secondary IPv6 address to loopback on r1
-+ router.vtysh_cmd("configure terminal\ninterface lo\nipv6 address fc00:0:1::11/128")
-+
-+ # Wait for ISIS to propagate to r2, then BGP-LS to advertise the new prefix
-+ r2 = tgen.gears["r2"]
-+ test_func = functools.partial(
-+ check_bgp_ls_prefix, r2, "fc00:0:1::11/128", should_exist=True
-+ )
-+ _, result = topotest.run_and_expect(test_func, None, count=30, wait=1)
-+ assert result is None, '"r2" BGP-LS IPv6 secondary address not advertised'
-+
-+ # Verify consumer rr received the update
-+ consumer = tgen.gears["rr"]
-+ test_func = functools.partial(
-+ check_bgp_ls_prefix, consumer, "fc00:0:1::11/128", should_exist=True
-+ )
-+ _, result = topotest.run_and_expect(test_func, None, count=30, wait=1)
-+ assert result is None, '"rr" did not receive BGP-LS IPv6 secondary address update'
-+
-+
-+def test_bgp_ls_ipv6_address_remove():
-+ """Test removing IPv6 interface address and verifying BGP-LS withdrawal"""
-+ tgen = get_topogen()
-+
-+ if tgen.routers_have_failure():
-+ pytest.skip(tgen.errors)
-+
-+ logger.info("Removing IPv6 interface address on r1 and checking BGP-LS withdrawal")
-+
-+ router = tgen.gears["r1"]
-+
-+ # Remove secondary IPv6 address from r1
-+ router.vtysh_cmd("configure terminal\ninterface lo\nno ipv6 address fc00:0:1::11/128")
-+
-+ # Wait for ISIS to propagate to r2, then BGP-LS to withdraw the prefix
-+ r2 = tgen.gears["r2"]
-+ test_func = functools.partial(
-+ check_bgp_ls_prefix, r2, "fc00:0:1::11/128", should_exist=False
-+ )
-+ _, result = topotest.run_and_expect(test_func, None, count=30, wait=1)
-+ assert result is None, '"r2" BGP-LS IPv6 secondary address not withdrawn'
-+
-+ # Verify consumer rr received the withdrawal
-+ consumer = tgen.gears["rr"]
-+ test_func = functools.partial(
-+ check_bgp_ls_prefix, consumer, "fc00:0:1::11/128", should_exist=False
-+ )
-+ _, result = topotest.run_and_expect(test_func, None, count=30, wait=1)
-+ assert result is None, '"rr" did not receive BGP-LS IPv6 secondary address withdrawal'
-+
-+
-+def test_bgp_ls_ipv6_static_route_add():
-+ """Test adding a static IPv6 route and verifying BGP-LS update"""
-+ tgen = get_topogen()
-+
-+ if tgen.routers_have_failure():
-+ pytest.skip(tgen.errors)
-+
-+ logger.info("Adding static IPv6 route on r1 and checking BGP-LS update")
-+
-+ router = tgen.gears["r1"]
-+
-+ # Add static IPv6 route on r1
-+ router.vtysh_cmd("configure terminal\nipv6 route fc00:100::/64 Null0")
-+
-+ # Wait for ISIS to propagate to r2, then BGP-LS to advertise the new prefix (protocol ID 2 = ISIS)
-+ r2 = tgen.gears["r2"]
-+ test_func = functools.partial(
-+ check_bgp_ls_prefix, r2, "fc00:100::/64", protocol_id=2, should_exist=True
-+ )
-+ _, result = topotest.run_and_expect(test_func, None, count=30, wait=1)
-+ assert result is None, '"r2" BGP-LS static IPv6 route not advertised'
-+
-+ # Verify consumer rr received the update
-+ consumer = tgen.gears["rr"]
-+ test_func = functools.partial(
-+ check_bgp_ls_prefix, consumer, "fc00:100::/64", protocol_id=2, should_exist=True
-+ )
-+ _, result = topotest.run_and_expect(test_func, None, count=30, wait=1)
-+ assert result is None, '"rr" did not receive BGP-LS static IPv6 route update'
-+
-+
-+def test_bgp_ls_ipv6_static_route_remove():
-+ """Test removing static IPv6 route and verifying BGP-LS withdrawal"""
-+ tgen = get_topogen()
-+
-+ if tgen.routers_have_failure():
-+ pytest.skip(tgen.errors)
-+
-+ logger.info("Removing static IPv6 route on r1 and checking BGP-LS withdrawal")
-+
-+ router = tgen.gears["r1"]
-+
-+ # Remove static IPv6 route from r1
-+ router.vtysh_cmd("configure terminal\nno ipv6 route fc00:100::/64 Null0")
-+
-+ # Wait for ISIS to propagate to r2, then BGP-LS to withdraw the prefix
-+ r2 = tgen.gears["r2"]
-+ test_func = functools.partial(
-+ check_bgp_ls_prefix, r2, "fc00:100::/64", protocol_id=2, should_exist=False
-+ )
-+ _, result = topotest.run_and_expect(test_func, None, count=30, wait=1)
-+ assert result is None, '"r2" BGP-LS static IPv6 route not withdrawn'
-+
-+ # Verify consumer rr received the withdrawal
-+ consumer = tgen.gears["rr"]
-+ test_func = functools.partial(
-+ check_bgp_ls_prefix, consumer, "fc00:100::/64", protocol_id=2, should_exist=False
-+ )
-+ _, result = topotest.run_and_expect(test_func, None, count=30, wait=1)
-+ assert result is None, '"rr" did not receive BGP-LS static IPv6 route withdrawal'
-+
-+
-+def test_bgp_ls_r4_link_shutdown():
-+ """Test shutting down r4 link and verifying BGP-LS updates"""
-+ tgen = get_topogen()
-+
-+ if tgen.routers_have_failure():
-+ pytest.skip(tgen.errors)
-+
-+ logger.info("Shutting down link r4-eth0 (to r1) and checking BGP-LS update")
-+
-+ router = tgen.gears["r4"]
-+
-+ # Shutdown interface r4-eth0 (link to r1)
-+ router.vtysh_cmd("configure terminal\ninterface r4-eth0\nshutdown")
-+
-+ # Wait for ISIS to detect the link down and propagate to r2, then BGP-LS to withdraw link NLRI
-+ r2 = tgen.gears["r2"]
-+ test_func = functools.partial(
-+ check_bgp_ls_link, r2, "0000.0000.0004", "0000.0000.0001", should_exist=False
-+ )
-+ _, result = topotest.run_and_expect(test_func, None, count=30, wait=1)
-+ assert result is None, '"r2" BGP-LS r4 link down not reflected'
-+
-+ # Verify consumer rr received the update
-+ consumer = tgen.gears["rr"]
-+ test_func = functools.partial(
-+ check_bgp_ls_link, consumer, "0000.0000.0004", "0000.0000.0001", should_exist=False
-+ )
-+ _, result = topotest.run_and_expect(test_func, None, count=30, wait=1)
-+ assert result is None, '"rr" did not receive BGP-LS r4 link down update'
-+
-+
-+def test_bgp_ls_r4_link_no_shutdown():
-+ """Test bringing up r4 link and verifying BGP-LS updates"""
-+ tgen = get_topogen()
-+
-+ if tgen.routers_have_failure():
-+ pytest.skip(tgen.errors)
-+
-+ logger.info("Bringing up link r4-eth0 and checking BGP-LS update")
-+
-+ router = tgen.gears["r4"]
-+
-+ # Bring up interface r4-eth0
-+ router.vtysh_cmd("configure terminal\ninterface r4-eth0\nno shutdown")
-+
-+ # Wait for ISIS adjacency to re-establish and propagate to r2, then BGP-LS to advertise link NLRI
-+ r2 = tgen.gears["r2"]
-+ test_func = functools.partial(
-+ check_bgp_ls_link, r2, "0000.0000.0004", "0000.0000.0001", should_exist=True
-+ )
-+ _, result = topotest.run_and_expect(test_func, None, count=30, wait=1)
-+ assert result is None, '"r2" BGP-LS r4 link up not reflected'
-+
-+ # Verify consumer rr received the update
-+ consumer = tgen.gears["rr"]
-+ test_func = functools.partial(
-+ check_bgp_ls_link, consumer, "0000.0000.0004", "0000.0000.0001", should_exist=True
-+ )
-+ _, result = topotest.run_and_expect(test_func, None, count=30, wait=1)
-+ assert result is None, '"rr" did not receive BGP-LS r4 link up update'
-+
-+
-+def test_bgp_ls_peer_deactivate():
-+ """
-+ Test that deactivating the last BGP-LS peer:
-+ - Withdraws all locally originated BGP-LS routes on the producer (r2)
-+ - Clears all received BGP-LS routes on the consumer (rr)
-+ """
-+ tgen = get_topogen()
-+
-+ if tgen.routers_have_failure():
-+ pytest.skip(tgen.errors)
-+
-+ logger.info(
-+ "Deactivating BGP-LS peer on r2 and verifying route withdrawal on r2 and rr"
-+ )
-+
-+ r2 = tgen.gears["r2"]
-+
-+ # Deactivate the only BGP-LS peer on r2 (neighbor 10.0.3.4 = rr).
-+ r2.vtysh_cmd(
-+ "configure terminal\n"
-+ "router bgp 65000\n"
-+ "address-family link-state\n"
-+ "no neighbor 10.0.3.4 activate"
-+ )
-+
-+ # r2 must have withdrawn all its locally originated BGP-LS routes
-+ test_func = functools.partial(check_bgp_ls_empty, r2)
-+ _, result = topotest.run_and_expect(test_func, None, count=30, wait=1)
-+ assert result is None, '"r2" BGP-LS routes not withdrawn after peer deactivation'
-+
-+ # rr (consumer) must have no BGP-LS routes once r2's session goes down
-+ consumer = tgen.gears["rr"]
-+ test_func = functools.partial(check_bgp_ls_empty, consumer)
-+ _, result = topotest.run_and_expect(test_func, None, count=30, wait=1)
-+ assert result is None, '"rr" BGP-LS routes not cleared after r2 peer deactivation'
-+
-+
-+def test_bgp_ls_peer_reactivate():
-+ """
-+ Test that reactivating a BGP-LS peer after deactivation:
-+ - Triggers a fresh TED sync from the IGP
-+ - Re-originates all IGP topology as BGP-LS NLRIs
-+ - Re-advertises all routes to the consumer (rr)
-+
-+ The previous test (test_bgp_ls_peer_deactivate) must run first.
-+ """
-+ tgen = get_topogen()
-+
-+ if tgen.routers_have_failure():
-+ pytest.skip(tgen.errors)
-+
-+ logger.info(
-+ "Reactivating BGP-LS peer on r2 and verifying route re-advertisement on r2 and rr"
-+ )
-+
-+ r2 = tgen.gears["r2"]
-+
-+ # Reactivate the BGP-LS peer.
-+ r2.vtysh_cmd(
-+ "configure terminal\n"
-+ "router bgp 65000\n"
-+ "address-family link-state\n"
-+ "neighbor 10.0.3.4 activate"
-+ )
-+
-+ # r2 must re-originate all BGP-LS NLRIs for the full ISIS topology
-+ reffile = os.path.join(CWD, "r2/bgp_ls_nlri.json")
-+ expected = json.loads(open(reffile).read())
-+ test_func = functools.partial(
-+ topotest.router_json_cmp,
-+ r2,
-+ "show bgp link-state link-state json",
-+ expected,
-+ )
-+ _, result = topotest.run_and_expect(test_func, None, count=60, wait=1)
-+ assert result is None, '"r2" did not re-originate BGP-LS routes after peer reactivation'
-+
-+ # rr (consumer) must receive all BGP-LS routes again
-+ consumer = tgen.gears["rr"]
-+ reffile = os.path.join(CWD, "rr/bgp_ls_nlri.json")
-+ expected = json.loads(open(reffile).read())
-+ test_func = functools.partial(
-+ topotest.router_json_cmp,
-+ consumer,
-+ "show bgp link-state link-state json",
-+ expected,
-+ )
-+ _, result = topotest.run_and_expect(test_func, None, count=60, wait=1)
-+ assert result is None, '"rr" did not receive BGP-LS routes after r2 peer reactivation'
-+
-+
-+def test_memory_leak():
-+ """Run the memory leak test and report results"""
-+ tgen = get_topogen()
-+ if not tgen.is_memleak_enabled():
-+ pytest.skip("Memory leak test is disabled")
-+
-+ tgen.report_memory_leaks()
-+
-+
-+if __name__ == "__main__":
-+ args = ["-s"] + sys.argv[1:]
-+ sys.exit(pytest.main(args))
-+
-+
-+# 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 3123f39ba1..0d1728efe8 100644
---- a/vtysh/vtysh.c
-+++ b/vtysh/vtysh.c
-@@ -1517,6 +1517,13 @@ static struct cmd_node bgp_srv6_node = {
- .prompt = "%s(config-router-srv6)# ",
- };
-
-+static struct cmd_node bgp_ls_node = {
-+ .name = "bgp link-state",
-+ .node = BGP_LS_NODE,
-+ .parent_node = BGP_NODE,
-+ .prompt = "%s(config-router-af)# ",
-+};
-+
- static struct cmd_node ospf_node = {
- .name = "ospf",
- .node = OSPF_NODE,
-@@ -1995,6 +2002,16 @@ DEFUNSH(VTYSH_BGPD, bgp_evpn_vni, bgp_evpn_vni_cmd, "vni " CMD_VNI_RANGE,
- return CMD_SUCCESS;
- }
-
-+DEFUNSH(VTYSH_BGPD, address_family_link_state, address_family_link_state_cmd,
-+ "address-family link-state [link-state]",
-+ "Enter Address Family command mode\n"
-+ "Link-State Address Family\n"
-+ "Link-State Subsequent Address Family\n")
-+{
-+ vty->node = BGP_LS_NODE;
-+ return CMD_SUCCESS;
-+}
-+
- #if defined(ENABLE_BGP_VNC)
- DEFUNSH(VTYSH_BGPD, vnc_defaults, vnc_defaults_cmd, "vnc defaults",
- "VNC/RFP related configuration\n"
-@@ -2598,7 +2615,8 @@ DEFUNSH(VTYSH_BGPD, exit_address_family, exit_address_family_cmd,
- || vty->node == BGP_IPV6L_NODE || vty->node == BGP_IPV6M_NODE
- || vty->node == BGP_EVPN_NODE
- || vty->node == BGP_FLOWSPECV4_NODE
-- || vty->node == BGP_FLOWSPECV6_NODE)
-+ || vty->node == BGP_FLOWSPECV6_NODE
-+ || vty->node == BGP_LS_NODE)
- vty->node = BGP_NODE;
- return CMD_SUCCESS;
- }
-@@ -5167,6 +5185,7 @@ void vtysh_init_vty(void)
- install_node(&rpki_node);
- install_node(&bmp_node);
- install_node(&bgp_srv6_node);
-+ install_node(&bgp_ls_node);
- install_node(&rip_node);
- install_node(&ripng_node);
- install_node(&ospf_node);
-@@ -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);
-+
-+ install_element(BGP_NODE, &address_family_link_state_cmd);
-+ install_element(BGP_LS_NODE, &vtysh_quit_bgpd_cmd);
-+ install_element(BGP_LS_NODE, &vtysh_exit_bgpd_cmd);
-+ install_element(BGP_LS_NODE, &vtysh_end_all_cmd);
-+ install_element(BGP_LS_NODE, &exit_address_family_cmd);
- #endif /* HAVE_BGPD */
-
- /* ripd */
-diff --git a/zebra/connected.c b/zebra/connected.c
-index 0a2744ef71..d30a07fb84 100644
---- a/zebra/connected.c
-+++ b/zebra/connected.c
-@@ -284,6 +284,7 @@ void connected_up(struct interface *ifp, struct connected *ifc)
- break;
- case AFI_UNSPEC:
- case AFI_L2VPN:
-+ case AFI_BGP_LS:
- case AFI_MAX:
- flog_warn(EC_ZEBRA_CONNECTED_AFI_UNKNOWN,
- "Received unknown AFI: %s", afi2str(afi));
-@@ -494,6 +495,7 @@ void connected_down(struct interface *ifp, struct connected *ifc)
- break;
- case AFI_UNSPEC:
- case AFI_L2VPN:
-+ case AFI_BGP_LS:
- case AFI_MAX:
- zlog_warn("Unknown AFI: %s", afi2str(afi));
- break;
-diff --git a/zebra/router-id.c b/zebra/router-id.c
-index 945f09cd86..bf91f5f7e2 100644
---- a/zebra/router-id.c
-+++ b/zebra/router-id.c
-@@ -102,6 +102,7 @@ int router_id_get(afi_t afi, struct prefix *p, struct zebra_vrf *zvrf)
- return 0;
- case AFI_UNSPEC:
- case AFI_L2VPN:
-+ case AFI_BGP_LS:
- case AFI_MAX:
- return -1;
- }
-@@ -126,6 +127,7 @@ int router_id_set(afi_t afi, struct prefix *p, struct zebra_vrf *zvrf)
- break;
- case AFI_UNSPEC:
- case AFI_L2VPN:
-+ case AFI_BGP_LS:
- case AFI_MAX:
- return -1;
- }
-diff --git a/zebra/zapi_msg.c b/zebra/zapi_msg.c
-index 79b377754a..ad68dbe84d 100644
---- a/zebra/zapi_msg.c
-+++ b/zebra/zapi_msg.c
-@@ -550,6 +550,7 @@ int zsend_redistribute_route(int cmd, struct zserv *client, const struct route_n
- break;
- case AFI_L2VPN:
- case AFI_MAX:
-+ case AFI_BGP_LS:
- case AFI_UNSPEC:
- break;
- }
-diff --git a/zebra/zebra_nhg.c b/zebra/zebra_nhg.c
-index c77b047661..5ef2fd610f 100644
---- a/zebra/zebra_nhg.c
-+++ b/zebra/zebra_nhg.c
-@@ -2374,6 +2374,7 @@ static int nexthop_active(struct nexthop *nexthop, struct nhg_hash_entry *nhe,
- break;
- case AFI_UNSPEC:
- case AFI_L2VPN:
-+ case AFI_BGP_LS:
- case AFI_MAX:
- flog_err(EC_LIB_DEVELOPMENT,
- "%s: unknown address-family: %u", __func__,
-@@ -2416,6 +2417,7 @@ static int nexthop_active(struct nexthop *nexthop, struct nhg_hash_entry *nhe,
- break;
- case AFI_UNSPEC:
- case AFI_L2VPN:
-+ case AFI_BGP_LS:
- case AFI_MAX:
- assert(afi != AFI_IP && afi != AFI_IP6);
- break;
---
-2.51.2
-
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
deleted file mode 100644
index aec83562..00000000
--- a/scripts/package-build/frr/patches/frr/0021-pathd-add-optional-protocol-to-no-mpls-te-import.patch
+++ /dev/null
@@ -1,178 +0,0 @@
-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'
-
-Command `mpls-te import ospfv2|ospfv3|isis` had `no` version without
-last option, so frr-reload.py failed when it was removed.
-
-To fix:
-* Merge `no` and not `no` versions
-* Make protocol optional parameter in case of `no`
-* If protocol is given in `no` version, check that currently import for
- this protocol is activated, otherwise ignore command.
-* 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 05bd8cbf93..9dcd398d43 100644
---- a/pathd/path_ted.c
-+++ b/pathd/path_ted.c
-@@ -25,11 +25,12 @@
- static struct ls_ted *path_ted_create_ted(void);
- static void path_ted_register_vty(void);
- static void path_ted_unregister_vty(void);
--static uint32_t path_ted_start_importing_igp(const char *daemon_str);
-+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 *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;
-
-@@ -66,18 +67,18 @@ uint32_t path_ted_teardown(void)
- * @return true if ok
- *
- */
--uint32_t path_ted_start_importing_igp(const char *daemon_str)
-+uint32_t path_ted_start_importing_igp(enum igp_import daemon)
- {
- uint32_t status = 0;
-
-- if (strcmp(daemon_str, "ospfv2") == 0)
-- ted_state_g.import = IMPORT_OSPFv2;
-- else if (strcmp(daemon_str, "ospfv3") == 0) {
-- ted_state_g.import = IMPORT_UNKNOWN;
-- return 1;
-- } else if (strcmp(daemon_str, "isis") == 0)
-- ted_state_g.import = IMPORT_ISIS;
-- else {
-+ switch (daemon) {
-+ case IMPORT_OSPFv2:
-+ case IMPORT_ISIS:
-+ ted_state_g.import = daemon;
-+ break;
-+ case IMPORT_OSPFv3:
-+ case IMPORT_UNKNOWN:
-+ default:
- ted_state_g.import = IMPORT_UNKNOWN;
- return 1;
- }
-@@ -406,7 +407,8 @@ DEFUN (no_path_ted,
- /* clang-format off */
- DEFPY(path_ted_import,
- path_ted_import_cmd,
-- "mpls-te import <ospfv2|ospfv3|isis>$import_daemon",
-+ "[no] mpls-te import ![ospfv2|ospfv3|isis]$import_daemon",
-+ NO_STR
- "Enable the TE database (TED) fill with remote igp data\n"
- "import\n"
- "Origin ospfv2\n"
-@@ -414,34 +416,32 @@ DEFPY(path_ted_import,
- "Origin isis\n")
- /* clang-format on */
- {
--
-- if (ted_state_g.enabled)
-- if (path_ted_start_importing_igp(import_daemon)) {
-- vty_out(vty, "Unable to start importing\n");
-- return CMD_WARNING;
-- }
-- return CMD_SUCCESS;
--}
--
--/* clang-format off */
--DEFUN (no_path_ted_import,
-- no_path_ted_import_cmd,
-- "no mpls-te import",
-- NO_STR
-- NO_STR
-- "Disable the TE Database fill with remote igp data\n")
--/* clang-format on */
--{
--
-- if (ted_state_g.import) {
-- if (path_ted_stop_importing_igp()) {
-- vty_out(vty, "Unable to stop importing\n");
-- return CMD_WARNING;
-+ enum igp_import daemon;
-+
-+ if (no) {
-+ if (ted_state_g.import) {
-+ /* If no import_daemon given - disable current, otherwise check */
-+ if (import_daemon != NULL) {
-+ daemon = str2igp_import(import_daemon);
-+ if (daemon != ted_state_g.import) {
-+ PATH_TED_DEBUG("%s: PATHD-TED: Requested to disable import for %s, but import is active for another daemon. Ignoring.",
-+ __func__, import_daemon);
-+ return CMD_SUCCESS;
-+ }
-+ }
-+ if (path_ted_stop_importing_igp()) {
-+ vty_out(vty, "Unable to stop importing\n");
-+ return CMD_WARNING;
-+ }
- } else {
-- PATH_TED_DEBUG(
-- "%s: PATHD-TED: Importing igp data already OFF",
-- __func__);
-+ PATH_TED_DEBUG("%s: PATHD-TED: Importing igp data already OFF", __func__);
- }
-+ } else {
-+ if (ted_state_g.enabled)
-+ if (path_ted_start_importing_igp(str2igp_import(import_daemon))) {
-+ vty_out(vty, "Unable to start importing\n");
-+ return CMD_WARNING;
-+ }
- }
- return CMD_SUCCESS;
- }
-@@ -505,6 +505,22 @@ uint32_t path_ted_config_write(struct vty *vty)
- return 0;
- }
-
-+/**
-+ * Convert string to igp_import
-+ */
-+static enum igp_import str2igp_import(const char *str)
-+{
-+ if (!str)
-+ return IMPORT_UNKNOWN;
-+ if (strcmp(str, "isis") == 0)
-+ return IMPORT_ISIS;
-+ else if (strcmp(str, "ospfv2") == 0)
-+ return IMPORT_OSPFv2;
-+ else if (strcmp(str, "ospfv3") == 0)
-+ return IMPORT_OSPFv3;
-+ return IMPORT_UNKNOWN;
-+}
-+
- /**
- * Register the fn's for CLI and hook for config show
- *
-@@ -517,7 +533,6 @@ static void path_ted_register_vty(void)
- install_element(SR_TRAFFIC_ENG_NODE, &path_ted_on_cmd);
- install_element(SR_TRAFFIC_ENG_NODE, &no_path_ted_cmd);
- install_element(SR_TRAFFIC_ENG_NODE, &path_ted_import_cmd);
-- install_element(SR_TRAFFIC_ENG_NODE, &no_path_ted_import_cmd);
-
- install_element(CONFIG_NODE, &debug_path_ted_cmd);
- install_element(ENABLE_NODE, &debug_path_ted_cmd);
-@@ -537,7 +552,6 @@ static void path_ted_unregister_vty(void)
- uninstall_element(SR_TRAFFIC_ENG_NODE, &path_ted_on_cmd);
- uninstall_element(SR_TRAFFIC_ENG_NODE, &no_path_ted_cmd);
- uninstall_element(SR_TRAFFIC_ENG_NODE, &path_ted_import_cmd);
-- uninstall_element(SR_TRAFFIC_ENG_NODE, &no_path_ted_import_cmd);
- }
-
- /**
---
-2.51.2
-
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
deleted file mode 100644
index 32193f88..00000000
--- a/scripts/package-build/frr/patches/frr/0022-pathd-add-optional-parameters-to-no-index-cmd.patch
+++ /dev/null
@@ -1,94 +0,0 @@
-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
-
-`no index` didn't have all parameters of `index` command, so
-frr-reload.py had problems with that.
-
-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/pathd/path_cli.c b/pathd/path_cli.c
-index 7eeec7b40e..e81ead545e 100644
---- a/pathd/path_cli.c
-+++ b/pathd/path_cli.c
-@@ -486,15 +486,16 @@ int segment_list_has_prefix(
- */
- /* clang-format off */
- DEFPY_YANG(srte_segment_list_segment, srte_segment_list_segment_cmd,
-- "index (0-4294967295)$index <[mpls$has_mpls_label label (16-1048575)$label] "
-+ "[no] index (0-4294967295)$index ![mpls$has_mpls_label label (16-1048575)$label"
- "|"
-- "[nai$has_nai <"
-+ "nai$has_nai <"
- "prefix <A.B.C.D/M$prefix_ipv4|X:X::X:X/M$prefix_ipv6>"
- "<algorithm$has_algo (0-1)$algo| iface$has_iface_id (0-4294967295)$iface_id>"
- "| adjacency$has_adj "
- "<A.B.C.D$adj_src_ipv4 A.B.C.D$adj_dst_ipv4|X:X::X:X$adj_src_ipv6 X:X::X:X$adj_dst_ipv6>"
-- ">]"
-- ">",
-+ ">"
-+ "]",
-+ NO_STR
- "Index\n"
- "Index Value\n"
- "MPLS or IP Label\n"
-@@ -518,8 +519,13 @@ DEFPY_YANG(srte_segment_list_segment, srte_segment_list_segment_cmd,
- char xpath[XPATH_MAXLEN];
- int status = CMD_SUCCESS;
-
--
- snprintf(xpath, sizeof(xpath), "./segment[index='%s']", index_str);
-+
-+ if (no) {
-+ nb_cli_enqueue_change(vty, xpath, NB_OP_DESTROY, NULL);
-+ return nb_cli_apply_changes(vty, NULL);
-+ }
-+
- nb_cli_enqueue_change(vty, xpath, NB_OP_CREATE, NULL);
-
- if (has_mpls_label != NULL) {
-@@ -549,21 +555,6 @@ DEFPY_YANG(srte_segment_list_segment, srte_segment_list_segment_cmd,
- return nb_cli_apply_changes(vty, NULL);
- }
-
--DEFPY_YANG(srte_segment_list_no_segment,
-- srte_segment_list_no_segment_cmd,
-- "no index (0-4294967295)$index",
-- NO_STR
-- "Index\n"
-- "Index Value\n")
--{
-- char xpath[XPATH_MAXLEN];
--
-- snprintf(xpath, sizeof(xpath), "./segment[index='%s']", index_str);
-- nb_cli_enqueue_change(vty, xpath, NB_OP_DESTROY, NULL);
--
-- return nb_cli_apply_changes(vty, NULL);
--}
--
- void cli_show_srte_segment_list_segment(struct vty *vty,
- const struct lyd_node *dnode,
- bool show_defaults)
-@@ -1364,10 +1355,7 @@ void path_cli_init(void)
- install_element(SEGMENT_ROUTING_NODE, &sr_no_traffic_eng_cmd);
- install_element(SR_TRAFFIC_ENG_NODE, &srte_segment_list_cmd);
- install_element(SR_TRAFFIC_ENG_NODE, &srte_no_segment_list_cmd);
-- install_element(SR_SEGMENT_LIST_NODE,
-- &srte_segment_list_segment_cmd);
-- install_element(SR_SEGMENT_LIST_NODE,
-- &srte_segment_list_no_segment_cmd);
-+ install_element(SR_SEGMENT_LIST_NODE, &srte_segment_list_segment_cmd);
- install_element(SR_TRAFFIC_ENG_NODE, &srte_policy_cmd);
- install_element(SR_TRAFFIC_ENG_NODE, &srte_no_policy_cmd);
- install_element(SR_POLICY_NODE, &srte_policy_name_cmd);
---
-2.51.2
-
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
deleted file mode 100644
index efd9ebec..00000000
--- a/scripts/package-build/frr/patches/frr/0023-ospf6d-Fix-command-output-for-default-route.patch
+++ /dev/null
@@ -1,60 +0,0 @@
-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
-
diff --git a/scripts/package-build/frr/patches/frr/0025-bgpd-fix-bmp-connect-deletion-with-source-interface.patch b/scripts/package-build/frr/patches/frr/0025-bgpd-fix-bmp-connect-deletion-with-source-interface.patch
deleted file mode 100644
index 80641802..00000000
--- a/scripts/package-build/frr/patches/frr/0025-bgpd-fix-bmp-connect-deletion-with-source-interface.patch
+++ /dev/null
@@ -1,31 +0,0 @@
-From d8923603c8aab212e923dccc16de9bb4c22c6846 Mon Sep 17 00:00:00 2001
-From: Nataliia Solomko <natalirs1985@gmail.com>
-Date: Wed, 24 Jun 2026 16:03:45 +0300
-Subject: [PATCH] bgpd: fix bmp connect deletion with source-interface
-
-bmp_connect() rejects deletion when source-interface is provided.
-The check compares the configured source-interface with the one
-in the command. It uses !strcmp(), which is true when the strings
-are equal. But this result feeds an if-block that rejects the
-deletion. So the command is rejected exactly when the
-source-interface is correct, and accepted when it is wrong.
-Fix by changing !strcmp() to strcmp().
----
- bgpd/bgp_bmp.c | 2 +-
- 1 file changed, 1 insertion(+), 1 deletion(-)
-
-diff --git a/bgpd/bgp_bmp.c b/bgpd/bgp_bmp.c
-index 40f70f5a7c..f8c3f1c838 100644
---- a/bgpd/bgp_bmp.c
-+++ b/bgpd/bgp_bmp.c
-@@ -2996,7 +2996,7 @@ DEFPY(bmp_connect,
- /* connection deletion need same hostname port and interface */
- if (ba->ifsrc || srcif)
- if ((!ba->ifsrc) || (!srcif) ||
-- !strcmp(ba->ifsrc, srcif)) {
-+ strcmp(ba->ifsrc, srcif)) {
- vty_out(vty,
- "%% No such active connection found\n");
- return CMD_WARNING;
---
-2.51.0