diff options
| author | Christian Breunig <christian@breunig.cc> | 2026-10-10 21:59:03 +0200 |
|---|---|---|
| committer | Christian Breunig <christian@breunig.cc> | 2026-10-10 21:59:03 +0200 |
| commit | 8ff706153243997fc33d2e7da735942c88596291 (patch) | |
| tree | aa23694dcca7431e19ed94e85e38362dc07ca46c /scripts/package-build | |
| parent | f5a1e8334f772225d9e3ccdab45f6884cec28e59 (diff) | |
| download | vyos-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')
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 |
