diff options
| author | Viacheslav Hletenko <v.gletenko@vyos.io> | 2026-04-10 12:47:55 +0300 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2026-04-10 12:47:55 +0300 |
| commit | 4d26859088b1463965403c14472fbbff245864b5 (patch) | |
| tree | 46fdf93514b38f4826705c385b2233ba8a32360a /scripts | |
| parent | 1b4f4f5cf23cdc75864eaccbbfcf6fcfd9ebacba (diff) | |
| parent | 08f179acb1e9959d24f55f43b29c1e5dcc8d88cf (diff) | |
| download | vyos-build-4d26859088b1463965403c14472fbbff245864b5.tar.gz vyos-build-4d26859088b1463965403c14472fbbff245864b5.zip | |
Merge pull request #1154 from hedrok/T8457-bgp_link_state
T8457: FRR: backport BGP-LS
Diffstat (limited to 'scripts')
| -rw-r--r-- | scripts/package-build/frr/patches/frr/0020-BGP-LS-support.patch | 17959 |
1 files changed, 17959 insertions, 0 deletions
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 new file mode 100644 index 00000000..44d474f0 --- /dev/null +++ b/scripts/package-build/frr/patches/frr/0020-BGP-LS-support.patch @@ -0,0 +1,17959 @@ +From 5fb319d193444bcb3b92b55190922993a933d4b2 Mon Sep 17 00:00:00 2001 +From: Carmine Scarpitta <cscarpit@cisco.com> +Date: Fri, 23 Jan 2026 10:27:38 +0000 +Subject: [PATCH] *: BGP-LS support +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +Squash commits from PRs: + +* https://github.com/FRRouting/frr/pull/20470 +* https://github.com/FRRouting/frr/pull/21077 +* https://github.com/FRRouting/frr/pull/21078 +* https://github.com/FRRouting/frr/pull/21092 +* https://github.com/FRRouting/frr/pull/21102 +* https://github.com/FRRouting/frr/pull/21108 +* https://github.com/FRRouting/frr/pull/21109 +* https://github.com/FRRouting/frr/pull/21285 +* https://github.com/FRRouting/frr/pull/21286 +* https://github.com/FRRouting/frr/pull/21076 +* https://github.com/FRRouting/frr/pull/21376 + +Plus e62419b8940 that is also needed + +*: Add BGP-LS AFI/SAFI constants + +Add BGP Link-State (BGP-LS) Address Family infrastructure to prepare +for full protocol implementation in subsequent commits (RFC 9552). + +Introduce AFI_BGP_LS (16388) and SAFI_BGP_LS (71) and update all +AFI/SAFI-aware code paths to recognize the new address family. + +Protocol-specific logic is marked with TODO placeholders to be +implemented in subsequent commits. + +Signed-off-by: Carmine Scarpitta <cscarpit@cisco.com> +(cherry picked from commit 314b3eb5e6f3a6688d726661dfb8fb8985c36f5a) + +bgpd: Add debugging support for BGP-LS + +Add debugging support for BGP Link-State operations to aid development +and troubleshooting. + +- Add `debug bgp link-state` command +- Add `no debug bgp link-state` command +- Extend `no debug bgp` to also disable link-state debugging +- Show link-state debugging status in `show debugging bgp` + +Signed-off-by: Carmine Scarpitta <cscarpit@cisco.com> +(cherry picked from commit a98f8055d23ea97fb40343aa97c5cee9483ef535) + +bgpd: Add BGP-LS instance structure and initialization + +Introduce core BGP-LS module infrastructure. + +- Add `struct bgp_ls` to hold BGP-LS state +- Add `bgp_ls_init()` to initialize BGP-LS module during BGP instance + creation +- Add `bgp_ls_terminate()` to cleanup BGP-LS module during BGP instance + destruction + +Subsequent commits will extend this structure with NLRI and TED support. + +Signed-off-by: Carmine Scarpitta <cscarpit@cisco.com> +(cherry picked from commit 301b23373c3ad836f3cf9697c755faff00ba208e) + +bgpd: Add BGP-LS NLRI data structures + +Define structures for BGP Link-State topology elements per RFC 9552. + +- Add Node, Link, and Prefix NLRI structures +- Support Node Descriptor TLVs (AS Number, BGP-LS ID, OSPF Area ID, + IGP Router ID) +- Support Link Descriptor TLVs (Link ID, IPv4/IPv6 interface/neighbor + addresses, MT-ID) +- Support Prefix Descriptor TLVs (OSPF Route Type, IP Reachability, + MT-ID) +- Define BGP-LS Attribute structure for path attribute type 29 + +The BGP-LS Attribute (codepoint 29) carries topology information +for Link-State NLRIs including node attributes (router capabilities, +SR capabilities), link attributes (TE metrics, bandwidth, delay), +and prefix attributes (prefix metrics, SIDs). + +Signed-off-by: Carmine Scarpitta <cscarpit@cisco.com> +(cherry picked from commit e49a5cd8187b3ec1d24ab3b3e38a4546b161d8d7) + +bgpd: Add BGP-LS NLRI allocation and free functions + +Add memory management functions for BGP-LS NLRI structures. + +- Add bgp_ls_nlri_alloc() to allocate NLRI structures +- Add bgp_ls_nlri_free() to free NLRI and cleanup dynamic fields +- Add bgp_ls_attr_alloc() to allocate BGP-LS attribute structures +- Add bgp_ls_attr_free() to free BGP-LS attributes and cleanup + dynamic fields + +These functions handle proper initialization and cleanup of all +NLRI types (Node, Link, Prefix) and their associated BGP-LS +attributes, ensuring no memory leaks from dynamically allocated +descriptor fields. + +Signed-off-by: Carmine Scarpitta <cscarpit@cisco.com> +(cherry picked from commit be4c9269a49894ea80a359f61e49f42518b314aa) + +bgpd: Add BGP-LS NLRI comparison functions + +Add functions to determine if two BGP-LS NLRIs are equal. + +These functions will be used in subsequent commits for NLRI hash table +lookups and duplicate handling. + +Signed-off-by: Carmine Scarpitta <cscarpit@cisco.com> +(cherry picked from commit 3e6d2ddcd36108667f1e9c0230d5cae76fa04a79) + +bgpd: Add BGP-LS NLRI validation functions + +Add validation functions to verify BGP-LS NLRI is valid per RFC 9552. + +Used in subsequent commits to validate NLRIs received from peers. + +Signed-off-by: Carmine Scarpitta <cscarpit@cisco.com> +(cherry picked from commit 86e38b2cdec5b66df6a4698baf5ef8745a822ca6) + +bgpd: Add BGP-LS NLRI size calculation helper + +Add function to calculate wire format size of BGP-LS NLRI structures. + +Used in subsequent commits for NLRI encoding. + +Signed-off-by: Carmine Scarpitta <cscarpit@cisco.com> +(cherry picked from commit 280716cfbeb028b16602d596b091f55b82822339) + +bgpd: Add BGP-LS NLRI string conversion functions + +Add functions to convert BGP-LS enums to human-readable strings. + +Used for debugging output and display purposes. + +Signed-off-by: Carmine Scarpitta <cscarpit@cisco.com> +(cherry picked from commit 186ba5f1db7c20276ab1af4acb385f5cbadadb42) + +bgpd: Add BGP-LS NLRI deep copy function + +Add function to create deep copy of BGP-LS NLRI structures. + +Used in subsequent commits for NLRI storage and management. + +Signed-off-by: Carmine Scarpitta <cscarpit@cisco.com> +(cherry picked from commit e96a272c5020c47c1e1fe88986f55b8197f9c8d2) + +bgpd: Add hash table to store BGP-LS NLRIs + +Add hash table infrastructure to store BGP-LS NLRIs originated by the +local node and received from peers. + +- Add nlri_hash to bgp_ls structure for NLRI storage +- Add hash functions to compute hash keys and compare NLRIs +- Add bgp_ls_nlri_intern() to insert NLRI and increment reference count +- Add bgp_ls_nlri_unintern() to remove NLRI and decrement reference count +- Add bgp_ls_entry_lookup() to lookup NLRI in the hash table +- Add bgp_ls_nlri_get() to return existing NLRI or create new entry +- Integrate ID allocator to assign unique IDs when NLRI is interned + +NLRIs are reference counted and automatically freed when refcount +reaches zero. + +Signed-off-by: Carmine Scarpitta <cscarpit@cisco.com> +(cherry picked from commit 81657bd198fd77a6e2af4eea941a2e05397fc2d1) + +bgpd: Add hash table to store BGP-LS Attributes + +Implement unified hash table for BGP-LS attribute interning and lookup. + +- Implement bgp_ls_attr_get(): Insert BGP-LS attribute into unified + hash table, returning existing entry if already present or creating + and inserting new entry +- Implement bgp_ls_attr_lookup(): Search hash table for BGP-LS + attribute matching the provided attributes +- Implement bgp_ls_attr_intern(): Perform attribute interning by + looking up attribute in hash table, incrementing reference counter + if found, storing pointer for later unintern +- Implement bgp_ls_attr_unintern(): Decrement reference counter for + interned attribute and remove from hash table when counter reaches zero + +The unified hash table ensures that duplicate BGP-LS attributes across +multiple paths and peers are deduplicated, reducing memory consumption +and improving lookup performance for route decision making. + +Signed-off-by: Carmine Scarpitta <cscarpit@cisco.com> +(cherry picked from commit 8076ae20fac48f1be7815ef76cdfed763dc8fdc2) + +bgpd: Add error code for BGP-LS packet errors + +Add EC_BGP_LS_PACKET error code for BGP-LS NLRI and attribute packet +parsing/encoding errors. + +This error code is used by BGP-LS decode functions to log warnings when +malformed packets are received from peers, such as invalid TLV lengths, +missing mandatory fields, or unknown NLRI types. + +Signed-off-by: Carmine Scarpitta <cscarpit@cisco.com> +(cherry picked from commit 5a49df37f5a333b934687183e11a9e4ed076b892) + +bgpd: Add BGP-LS NLRI encode functions + +Add functions to serialize BGP-LS NLRIs to wire format per RFC 9552. + +The encode functions convert in-memory NLRI structures to the TLV-based +wire format used in MP_REACH_NLRI and MP_UNREACH_NLRI path attributes. + +- bgp_ls_encode_node_descriptor() - Encode Node Descriptors +- bgp_ls_encode_link_descriptor() - Encode Link Descriptors +- bgp_ls_encode_prefix_descriptor() - Encode Prefix Descriptors +- bgp_ls_encode_node_nlri() - Encode Node NLRI +- bgp_ls_encode_link_nlri() - Encode Link NLRI +- bgp_ls_encode_prefix_nlri() - Encode IPv4/IPv6 Prefix NLRIs +- bgp_ls_encode_nlri() - Top-level encoder for any BGP-LS NLRI +- bgp_ls_encode_attr() - Encode BGP-LS Attributes (Type 29) + +Signed-off-by: Carmine Scarpitta <cscarpit@cisco.com> +(cherry picked from commit 1ff2315405f719238d3bcb67f5625757c7bdf4c6) + +bgpd: Add BGP-LS NLRI decode functions + +Add functions to parse BGP-LS NLRIs from wire format per RFC 9552. + +The decode functions parse NLRIs received in MP_REACH_NLRI and +MP_UNREACH_NLRI path attributes into in-memory structures. + +- bgp_ls_decode_node_descriptor() - Parse Node Descriptor +- bgp_ls_decode_link_descriptor() - Parse Link Descriptor +- bgp_ls_decode_prefix_descriptor() - Parse Prefix Descriptor +- bgp_ls_decode_node_nlri() - Parse Node NLRI +- bgp_ls_decode_link_nlri() - Parse Link NLRI +- bgp_ls_decode_prefix_nlri() - Parse IPv4/IPv6 Prefix NLRI +- bgp_ls_decode_nlri() - Top-level decoder for BGP-LS NLRI +- bgp_ls_parse_attr() - Main attribute parser dispatcher + +Signed-off-by: Carmine Scarpitta <cscarpit@cisco.com> +(cherry picked from commit 9a95368f300ef4ecfff5e514e1e288bea88e1ca8) + +bgpd: Add TED instance to BGP-LS data structure + +Add Traffic Engineering Database (TED) instance to BGP-LS structure to +store link-state topology. + +In FRR's Link State architecture, IGP protocols (IS-IS, OSPF) act as +producers that build link-state databases from protocol exchanges and +send updates via ZAPI Opaque messages. BGP-LS acts as a consumer that +synchronizes with IGPs and listens for updates, maintaining a TED with +the topology information. + +The TED models network topology as a graph G(V, E) with: +- Vertices (V): Network nodes from BGP-LS Node NLRIs +- Edges (E): Unidirectional links from BGP-LS Link NLRIs +- Subnets (P): Prefixes from BGP-LS Prefix NLRIs + +Signed-off-by: Carmine Scarpitta <cscarpit@cisco.com> +(cherry picked from commit 337e6cd45bd100ab7be58aea80077e8365e01bd3) + +bgpd: Add helper functions to register linkstate db notifications + +Add helper functions to register and unregister BGP-LS for Link State +database notifications from IGP producers. + +In FRR's Link State architecture, consumers must register with zebra to +receive ZAPI Opaque Link State Update messages from producers (IS-IS, OSPF). +These functions wrap the Link State API registration mechanism: + +- bgp_ls_register() - Register to receive LS updates from IGPs +- bgp_ls_unregister() - Unregister from LS updates + +Registration enables BGP-LS to receive Node, Link, and Prefix updates +when IGPs detect topology changes. This is required for BGP-LS to maintain +an up-to-date TED and advertise topology to peers per RFC 9552. + +Signed-off-by: Carmine Scarpitta <cscarpit@cisco.com> +(cherry picked from commit 92404d7ae624f147c823251a7fc8797730257561) + +bgpd: Add BGP-LS Attribute support to attr structure + +Extend the core BGP attribute structure to support BGP-LS Attributes +(RFC 9552 Section 5.3) by adding ls_attr field and updating all related +attribute processing functions. + +Changes: +- Add ls_attr field to struct attr to store BGP-LS Attribute TLVs +- Update attrhash_key_make() to include ls_attr in attribute hash key +- Update attrhash_cmp() to compare ls_attr when present +- Extend bgp_attr_intern() to intern BGP-LS attributes +- Extend bgp_attr_unintern() to properly deintern/free BGP-LS attributes + +This enables the core BGP attribute handling infrastructure to properly +manage BGP-LS attributes through the standard attribute interning mechanism, +ensuring reference counting and deduplication of BGP-LS attribute data +across the RIB. + +Signed-off-by: Carmine Scarpitta <cscarpit@cisco.com> +(cherry picked from commit c8acf7a2c75404e030b16f1326001152d60bfc46) + +bgpd: Add BGP-LS update and withdraw functions + +Implement BGP-LS route installation and withdrawal to enable processing +of BGP-LS NLRIs received in BGP UPDATE messages. + +In BGP-LS, UPDATE messages contain BGP-LS NLRIs (not traditional prefixes). +When a BGP UPDATE with BGP-LS NLRI is received, a bgp_dest is allocated +to represent that NLRI location in the RIB and stored in the BGP RIB tree. +The ls_nlri field in bgp_dest stores the BGP-LS NLRI at that destination. + +Changes: +- Add bgp_ls_update() - Install or update BGP-LS NLRI in the BGP RIB + Called when a BGP UPDATE message contains a BGP-LS NLRI to add/modify + a BGP-LS route in the RIB +- Add bgp_ls_withdraw() - Withdraw BGP-LS NLRI from the BGP RIB + Called when a BGP UPDATE message contains a withdrawn BGP-LS NLRI + to remove the BGP-LS route from the RIB +- Add ls_nlri field to bgp_dest structure + Stores the BGP-LS NLRI at the destination in the RIB (used as the + destination key for BGP-LS routes) +- Extend bgp_dest_unlock_node() cleanup logic + Properly free ls_nlri when destination is being freed from the RIB + +Behavior: +- When a BGP UPDATE is received with BGP-LS NLRI, bgp_ls_update() + creates/updates a bgp_dest with that NLRI and installs it in the RIB +- When a BGP UPDATE is received with withdrawn BGP-LS NLRI, bgp_ls_withdraw() + removes the bgp_dest containing that NLRI from the RIB +- Proper resource cleanup ensures ls_nlri is freed when destinations + are removed from the RIB (bgp_dest_unlock_node) + +Signed-off-by: Carmine Scarpitta <cscarpit@cisco.com> +(cherry picked from commit 31cffad03f28c33e9ac7294efe95005df967412c) + +bgpd: Add functions to parse updates received from linkstate db + +Add callback functions to parse and process Link State updates received +from IGP producers via ZAPI Opaque messages. + +When IGPs (IS-IS, OSPF) detect topology changes, they send Link State +Update messages through zebra to registered consumers. These functions +handle the received messages: + +- bgp_ls_parse_update() - Parse ZAPI Opaque Link State messages +- bgp_ls_process_node() - Process Node updates (ADD/UPDATE/DELETE) +- bgp_ls_process_link() - Process Link updates (ADD/UPDATE/DELETE) +- bgp_ls_process_prefix() - Process Prefix updates (ADD/UPDATE/DELETE) + +The functions use the Link State API to extract Node, Link, or Prefix +information and update the TED accordingly. Updates trigger BGP-LS NLRI +generation for advertisement to peers. + +This enables BGP-LS to maintain a synchronized TED reflecting current +IGP topology and propagate changes to peers per RFC 9552. + +Signed-off-by: Carmine Scarpitta <cscarpit@cisco.com> +(cherry picked from commit baeefede22fbceb7135ac23933bcc2f89670bf06) + +bgpd: Handle zebra opaque messages + +Add callback handler to process ZAPI Opaque messages from zebra containing +Link State updates from IGP producers. + +Register bgp_zebra_opaque_msg_handler() as the callback for +ZEBRA_OPAQUE_MESSAGE type in the zebra client handler table. The handler: + +- Decodes ZAPI Opaque message header using zclient_opaque_decode() +- Routes Link State messages (LINK_STATE_SYNC, LINK_STATE_UPDATE) to + bgp_ls_process_linkstate_message() for TED processing +- Logs warnings for unknown opaque message types + +This completes the communication path between IGP producers (IS-IS, OSPF) +and BGP-LS consumer, enabling BGP-LS to receive topology updates and +database synchronization messages via zebra's ZAPI Opaque mechanism. + +Signed-off-by: Carmine Scarpitta <cscarpit@cisco.com> +(cherry picked from commit 88ced6ec9eed41a5c371a07133ddaf7b5bd3cce4) + +lib: Add CLI node for BGP Link-State address family configuration + +Add BGP_LS_NODE to enum node_type in command.h to enable BGP Link-State +address family configuration mode. + +This allows creating a dedicated configuration context for BGP-LS: + + router bgp 65000 + address-family link-state + <BGP-LS specific commands> + exit-address-family + +Follows the same pattern as other BGP address families (BGP_EVPN_NODE, +BGP_SRV6_NODE). + +Signed-off-by: Carmine Scarpitta <cscarpit@cisco.com> +(cherry picked from commit a3e90577db3b3cdadf1aae1dc8238f09c5cba048) + +bgpd: Add BGP-LS address family CLI commands + +Install BGP_LS_NODE in the CLI node hierarchy and add commands to +activate and configure BGP Link-State address family. + +New commands: +- address-family link-state [link-state] + Enters BGP-LS configuration mode under router bgp + +- neighbor PEER activate + Activates BGP-LS address family for the specified neighbor + +Configuration example: + router bgp 65000 + neighbor 192.0.2.1 remote-as 65001 + address-family link-state + neighbor 192.0.2.1 activate + exit-address-family + +Signed-off-by: Carmine Scarpitta <cscarpit@cisco.com> +(cherry picked from commit d4f914528b784611f6526c213733806e774d3d20) + +bgpd: Add BGP-LS configuration write support + +Extend bgp_config_write() to serialize BGP Link-State address family +configuration. + +This enables BGP-LS configuration to be: +- Displayed via show running-config +- Saved to startup-config (write memory) +- Written to configuration files + +Signed-off-by: Carmine Scarpitta <cscarpit@cisco.com> +(cherry picked from commit a20dfd7c81622071ca5bafe96339051fa1abac5e) + +bgpd: Add show commands for BGP-LS NLRIs + +Add commands to display BGP Link-State NLRIs from the BGP RIB. + +New commands: +- show bgp link-state link-state [json] + Display all BGP-LS routes with node/link/prefix NLRIs + +- show bgp link-state link-state NLRI [json] + Display specific BGP-LS route by NLRI identifier + NLRI format: [Type][ID][Protocol][Instance][AS][Area][Descriptors...] + +Signed-off-by: Carmine Scarpitta <cscarpit@cisco.com> +(cherry picked from commit 5ccafe4b1c163ad83057cb30c249004be340120c) + +vtysh: Integrate BGP-LS node into vtysh + +Integrate BGP_LS_NODE into vtysh to enable link-state address family +configuration. + +This allows users to configure BGP-LS through vtysh using: + router bgp 65000 + address-family link-state + neighbor <peer> activate + exit-address-family + +Signed-off-by: Carmine Scarpitta <cscarpit@cisco.com> +(cherry picked from commit 5251bf3aa4af17331bfc22e898fca9e626696aaa) + +bgpd: Register for linkstate db updates when peer is activated + +Register for Link State database updates when the first BGP-LS peer is +activated, and unregister when the last peer is deactivated. + +Modify peer_activate() and peer_deactivate() to manage Link State +database registration based on active BGP-LS peers: + +- When first peer is activated for link-state address family, call + bgp_ls_register() to subscribe to IGP topology updates from zebra +- When last peer is deactivated, call bgp_ls_unregister() to stop + receiving updates + +This optimizes resource usage by only receiving IGP topology updates +(from IS-IS, OSPF) when there are active BGP-LS peers that need to +receive the information. + +Signed-off-by: Carmine Scarpitta <cscarpit@cisco.com> +(cherry picked from commit c85fff38ea39ab708ae42e00f024f8de74451015) + +bgpd: Add BGP-LS capability display support + +Add display support for BGP-LS multi-protocol capability in BGP OPEN +message output. + +Update bgp_capability_vty_out() to properly display BGP-LS capabilities: + +- Display "AFI BGP-LS" for AFI_BGP_LS (16388) +- Display "SAFI BGP-LS" for SAFI_BGP_LS (71) +- Add JSON output support for BGP-LS capabilities + +This allows users to verify BGP-LS capability negotiation between peers +using "show bgp neighbor" commands, displaying both text and JSON output +for link-state address family capability advertisement and reception. + +Signed-off-by: Carmine Scarpitta <cscarpit@cisco.com> +(cherry picked from commit abccc105243cac9e7bcd94409b9135d04bdf3c19) + +bgpd: Add BGP-LS AFI/SAFI to negotiation functions + +Add BGP-LS capability checks to peer activity and address family +negotiation functions. + +Update peer status and capability negotiation functions to include +BGP-LS AFI/SAFI: + +- peer_active() - Check if peer has BGP-LS configured +- peer_active_nego() - Check if BGP-LS was negotiated with peer +- peer_group_af_configured() - Check if peer group has BGP-LS enabled +- bgp_open_option_parse() - Validate BGP-LS in capability negotiation + +This ensures BGP sessions with link-state address family are properly +recognized as active and that capability negotiation correctly validates +BGP-LS multi-protocol extensions. + +Signed-off-by: Carmine Scarpitta <cscarpit@cisco.com> +(cherry picked from commit 50e92bbb7f4660d427c2a5243f5b54acd50cb4d2) + +bgpd: Encode BGP-LS NLRI and attribute when building UPDATE packet + +Add BGP-LS encoding support to UPDATE message generation for +transmitting Link-State topology information to peers. + +Implement bgp_packet_ls_attribute() to encode BGP-LS Attribute (Type 29) +by dispatching to type-specific encoders (node/link/prefix attributes). + +Extend packet building functions to accept ls_nlri parameter: +- bgp_packet_attribute() - encode BGP-LS attribute when present +- bgp_packet_mpattr_prefix() - call bgp_ls_encode_nlri() for SAFI_BGP_LS +- bgp_packet_mpunreach_prefix() - handle BGP-LS withdrawals + +Update subgroup_update_packet() and subgroup_withdraw_packet() to extract +ls_nlri from path_info->extra and pass to encoding functions. + +This enables BGP-LS routes to be advertised and withdrawn using the +RFC 9552 wire format. + +Signed-off-by: Carmine Scarpitta <cscarpit@cisco.com> +(cherry picked from commit bfb67d5364a18542606299e753c5696f096caf8e) + +bgpd: Receive BGP-LS attribute + +Add bgp_attr_ls() function to parse and intern BGP-LS attributes (Type 29) +from received BGP update messages. The function validates peer configuration, +parses Type 29 TLVs, and interns the resulting attribute for use in route +processing. + +Signed-off-by: Carmine Scarpitta <cscarpit@cisco.com> +(cherry picked from commit aa7c9b97f20069f33e7efad501f8ee0ceb1929a9) + +bgpd: Unregister from lsdb on termination + +Call bgp_ls_unregister() in bgp_ls_terminate() to cleanly unregister +from the zebra link-state database when BGP instance terminates. + +This ensures proper cleanup and stops receiving IGP topology updates +when the BGP-LS functionality is no longer needed. + +Signed-off-by: Carmine Scarpitta <cscarpit@cisco.com> +(cherry picked from commit d70169ee48fc55ea8fe81d5c04ea1d81994b56fc) + +bgpd: Parse BGP-LS NLRI received from peers + +Add inbound BGP-LS message processing to receive Link-State topology +information from BGP peers. + +When BGP receives a BGP UPDATE message from a peer, it calls +bgp_nlri_parse() to parse the NLRI. The bgp_nlri_parse() function +checks the SAFI (Subsequent Address Family Identifier) contained in +the UPDATE message and dispatches to the appropriate SAFI-specific +handler. Currently supported SAFIs include: +- SAFI_UNICAST, SAFI_MULTICAST, SAFI_LABELED_UNICAST → bgp_nlri_parse_ip() +- SAFI_MPLS_VPN → bgp_nlri_parse_vpn() +- SAFI_ENCAP → bgp_nlri_parse_encap() +- SAFI_EVPN → bgp_nlri_parse_evpn() +- SAFI_FLOWSPEC → bgp_nlri_parse_flowspec() + +This commit extends bgp_nlri_parse() to handle SAFI_BGP_LS and +dispatch to a new handler bgp_nlri_parse_ls(). + +The bgp_nlri_parse_ls() function performs the following operations: +- Decodes BGP-LS NLRIs from MP_REACH_NLRI and MP_UNREACH_NLRI + attributes using bgp_ls_decode_nlri() +- Interns decoded NLRI into hash table via bgp_ls_nlri_get() +- Creates synthetic prefix (AF_UNSPEC) with NLRI ID for RIB storage +- Calls bgp_update() for route advertisements or bgp_withdraw() for + route removals to install/remove BGP-LS routes in the RIB + +This completes the inbound UPDATE message processing path, enabling +BGP to receive and store Link-State topology from peers per RFC 9552. + +Signed-off-by: Carmine Scarpitta <cscarpit@cisco.com> +(cherry picked from commit 1da3f4420cbe9f95c4a0468db848b0a9d19b0903) + +bgpd: Set nexthop for BGP-LS based on peer address family + +BGP-LS routes advertised to peers require a nexthop with the same +address family as the peer session (IPv4 peer needs IPv4 nexthop, +IPv6 peer needs IPv6 nexthop). + +The MP_REACH_NLRI and MP_UNREACH_NLRI encoding code in bgp_attr does +not have direct knowledge of the peer's address family, making it +unable to select the appropriate nexthop address family for BGP-LS +advertisements. + +Add peer flags PEER_FLAG_BGP_LS_IPV4 and PEER_FLAG_BGP_LS_IPV6 that +are set during peer activation for BGP-LS address family. The bgp_attr +encoding functions check these flags to determine whether to encode +IPv4 or IPv6 nexthop addresses in MP_REACH_NLRI attributes. + +This ensures BGP-LS routes are advertised with nexthops matching the +peer session's address family, allowing proper route exchange with +both IPv4 and IPv6 BGP-LS peers. + +Signed-off-by: Carmine Scarpitta <cscarpit@cisco.com> +(cherry picked from commit 399c64b834ac488795d303057b8ab5cce5125642) + +tests: Add BGP-LS topotest suite + +Add pytest topology test for BGP Link-State covering producer and +consumer modes with ISIS IGP backend. + +Tests BGP-LS capability negotiation, NLRI advertisement/reception +(Node, Link, Prefix), and dynamic topology change propagation +including route additions/removals and link state changes. + +Verifies BGP-LS updates flow correctly from IGP through BGP-LS +producer to BGP-LS consumer. + +Signed-off-by: Carmine Scarpitta <cscarpit@cisco.com> +(cherry picked from commit 78deb305498ff4cf86fa5063a985f157487eb458) + +doc: Add documentation for BGP-LS + +Signed-off-by: Carmine Scarpitta <cscarpit@cisco.com> +(cherry picked from commit 3a97a2faf9c9886873dc1a1dd23aa413667944ea) + +Fix compilation issues + +bgpd: Fix integer truncation of SRLG count when parsing SRLG TLV + +`count` is declared as `uint8_t` but receives `length / 4` where +`length` is a `uint16_t`. For `length >= 1024`, the division result +exceeds 255, which cannot be stored in `count` since `uint8_t` holds +at most 255, and the value silently truncates. The result is then +stored in `attr->srlg_count`, also `uint8_t`, which would re-truncate +it. + +Widen `count` in `parse_srlg()` and `srlg_count` in `struct +bgp_ls_attr` to `uint16_t`. + +Signed-off-by: Carmine Scarpitta <cscarpit@cisco.com> +(cherry picked from commit ef8a4c68cb7bc7a8dc3dc63d81ffefc53823ba77) + +bgpd: Fix integer truncation of count when parsing Route Tag TLV + +`count` is declared as `uint8_t` but receives `length / 4` where +`length` is a `uint16_t`. For `length >= 1024`, the division result +exceeds 255, which cannot be stored in `count` since `uint8_t` holds +at most 255, and the value silently truncates. The result is then +stored in `attr->route_tag_count`, also `uint8_t`, which would +re-truncate it. + +Widen `count` in `parse_route_tag()` and `route_tag_count` in `struct +bgp_ls_attr` to `uint16_t`. + +Signed-off-by: Carmine Scarpitta <cscarpit@cisco.com> +(cherry picked from commit 83b9a4fb8b0b9168465bf375f78a52eeea15d0b0) + +bgpd: Treat duplicate TLVs as malformed NLRI + +BGP receives BGP Update messages that can contain a BGP-LS NLRI, then +processes the TLVs carried in that NLRI. The NLRI may include duplicate +TLVs. Today, when a duplicate TLV is received, BGP-LS overwrites the +previous value with the new one. + +RFC 9552 requires duplicate TLVs to be treated as a malformed NLRI. +Also, replacing an earlier TLV value with a new one without freeing +previously allocated dynamic memory can cause a memory leak. + +Fix by checking whether the same TLV type appears more than once and +returning an error on duplicates. This propagates the parse failure to +the caller, which then treats the NLRI as malformed. + +Signed-off-by: Carmine Scarpitta <cscarpit@cisco.com> +(cherry picked from commit 3098d5f03e51760c59da71c5d27ee0b3ab9b7e19) + +bgpd: Check stream space before encoding BGP-LS TLV data + +Some encode paths wrote to the stream without first checking that enough +space was available. + +Writing when the stream is full can trigger an assert or crash. + +Fix by adding `STREAM_WRITEABLE()` checks before writing, and return -1 +when space is insufficient. + +Signed-off-by: Carmine Scarpitta <cscarpit@cisco.com> +(cherry picked from commit f02173760f17d350e1a26dc671fd89d1773b1d08) + +bgpd: Bound Extended Admin Group TLV decode size + +parse_ext_admin_group() only verified that the TLV length was a multiple +of 4, with no upper bound on the number of 32-bit words to decode. +A very large length could trigger excessive growth/work in +admin_group_bulk_set(). + +Define BGP_LS_MAX_EXT_ADMIN_GROUP_WORDS in bgp_ls_nlri.h and reject +Extended Admin Group TLVs that exceed this limit during decode. + +Signed-off-by: Carmine Scarpitta <cscarpit@cisco.com> +(cherry picked from commit ff2ab26af4e04b066e1f1be41f58fba6bd74f957) + +bgpd: Fix Extended Admin Group decode byte order + +`parse_ext_admin_group()` applied `ntohl()` to the value returned by +`stream_getl()`. `stream_getl()` already returns the 32-bit value in host +byte order, so this caused a second conversion. + +On little-endian systems, the extra conversion byte-swapped the value +again and corrupted decoded Extended Admin Group words. + +Fix by removing the redundant conversion and passing `stream_getl()` +output directly to `admin_group_bulk_set()`. + +Signed-off-by: Carmine Scarpitta <cscarpit@cisco.com> +(cherry picked from commit d57a9ed3087158f896f825ee57bb0883e89eb9ba) + +bgpd: Fix leak in BGP-LS NLRI parsing loop + +In `bgp_nlri_parse_ls()`, `bgp_ls_nlri` was declared on the stack and then +filled by `bgp_ls_decode_nlri()` from the input stream. + +`bgp_ls_decode_nlri()` can allocate dynamic fields inside the NLRI object. +Because the parsing loop reused the same stack object on each iteration, +those dynamic allocations were leaked across iterations. + +Fix by moving the NLRI object to heap allocation and using the BGP-LS NLRI +helpers for lifecycle management: allocate with `bgp_ls_nlri_alloc()` and +free with `bgp_ls_nlri_free()` on both success and error paths. + +Signed-off-by: Carmine Scarpitta <cscarpit@cisco.com> +(cherry picked from commit fa0eebc6c87091616336b2e8afc442d33425a32f) + +bgpd: Free descriptor MT-ID on BGP-LS parse errors + +bgp_ls_decode_link_descriptor() and bgp_ls_decode_prefix_descriptor() +can allocate desc->mt_id when parsing MT-ID TLVs. + +If a later TLV in the same descriptor fails validation, the functions +returned -1 without releasing that allocation, leaking memory on error +unwind. + +Fix by adding centralized error cleanup paths in both descriptor parsers +that free desc->mt_id before returning -1. + +Signed-off-by: Carmine Scarpitta <cscarpit@cisco.com> +(cherry picked from commit a773e278a2bc61e85d290f9ff5f4bcf8528c40cb) + +bgpd: Use extended length for BGP-LS path attribute + +bgp_packet_ls_attribute() encoded BGP-LS attributes with a 1-byte +length field, which can encode up to 255 bytes of BGP-LS attribute data. + +BGP-LS attributes often exceed 255 bytes when carrying multiple +node/link/prefix TLVs, so a 1-byte length is not sufficient. + +Fix by setting BGP_ATTR_FLAG_EXTLEN for BGP-LS attributes and +encoding the attribute length as 2 bytes. Add a bounds check to +ensure the encoded attribute length fits in the extended-length field. + +Signed-off-by: Carmine Scarpitta <cscarpit@cisco.com> +(cherry picked from commit a8b23e3df8e2966e424bbc3ceb1939ed063f1778) + +bgpd: Guard BGP-LS NLRI parsing when `ls_info` is unavailable + +`bgp_nlri_parse_ls()` dereferenced `peer->bgp->ls_info` without checking +that BGP-LS state was initialized. + +Add defensive checks for peer, peer->bgp, peer->bgp->ls_info, and +packet before parsing, and return BGP_NLRI_PARSE_ERROR on invalid state. + +Signed-off-by: Carmine Scarpitta <cscarpit@cisco.com> +(cherry picked from commit 2e533d3d1310a2516e8366633cb526ec396a49ae) + +bgpd: Fix CID 1670210 remove dead code in `bgp_ls_process_edge` + +`bgp_ls_process_edge()` builds a Link NLRI from an edge, which has both a +source node and a destination node. + +Before this change, edge processing was split into two switch blocks: +the first switch populated source (local) router-id fields, and the second +switch populated destination (remote) router-id fields. + +Coverity reported DEADCODE in the second switch, where several protocol +cases were flagged as unreachable. + +Simplify the logic by merging source and destination handling into a single +switch statement. This removes unreachable branches, keeps behavior unchanged, +and fixes CID 1670210. + +** CID 1670210: (DEADCODE) +/bgpd/bgp_ls_ted.c: 1030 in bgp_ls_process_edge() +/bgpd/bgp_ls_ted.c: 1027 in bgp_ls_process_edge() +/bgpd/bgp_ls_ted.c: 1029 in bgp_ls_process_edge() +/bgpd/bgp_ls_ted.c: 1028 in bgp_ls_process_edge() +/bgpd/bgp_ls_ted.c: 1031 in bgp_ls_process_edge() + +_____________________________________________________________________________________________ +*** CID 1670210: (DEADCODE) +/bgpd/bgp_ls_ted.c: 1030 in bgp_ls_process_edge() +1024 // router_id_len = 7; +1025 remote_router_id_len = 6; +1026 break; +1027 case BGP_LS_PROTO_DIRECT: +1028 case BGP_LS_PROTO_STATIC: +1029 case BGP_LS_PROTO_OSPFV3: +>>> CID 1670210: (DEADCODE) +>>> Execution cannot reach this statement: "case BGP_LS_PROTO_BGP:". +1030 case BGP_LS_PROTO_BGP: +1031 case BGP_LS_PROTO_RESERVED: +1032 zlog_err("BGP-LS: Unsupported protocol %u", protocol_id); +1033 return -1; +1034 } +1035 +/bgpd/bgp_ls_ted.c: 1027 in bgp_ls_process_edge() +1021 memcpy(remote_router_id, edge->destination->node->adv.id.iso.sys_id, +1022 ISO_SYS_ID_LEN); +1023 // router_id[6] = 0; /* Pseudonode ID = 0 for router */ +1024 // router_id_len = 7; +1025 remote_router_id_len = 6; +1026 break; +>>> CID 1670210: (DEADCODE) +>>> Execution cannot reach this statement: "case BGP_LS_PROTO_DIRECT:". +1027 case BGP_LS_PROTO_DIRECT: +1028 case BGP_LS_PROTO_STATIC: +1029 case BGP_LS_PROTO_OSPFV3: +1030 case BGP_LS_PROTO_BGP: +1031 case BGP_LS_PROTO_RESERVED: +1032 zlog_err("BGP-LS: Unsupported protocol %u", protocol_id); +/bgpd/bgp_ls_ted.c: 1029 in bgp_ls_process_edge() +1023 // router_id[6] = 0; /* Pseudonode ID = 0 for router */ +1024 // router_id_len = 7; +1025 remote_router_id_len = 6; +1026 break; +1027 case BGP_LS_PROTO_DIRECT: +1028 case BGP_LS_PROTO_STATIC: +>>> CID 1670210: (DEADCODE) +>>> Execution cannot reach this statement: "case BGP_LS_PROTO_OSPFV3:". +1029 case BGP_LS_PROTO_OSPFV3: +1030 case BGP_LS_PROTO_BGP: +1031 case BGP_LS_PROTO_RESERVED: +1032 zlog_err("BGP-LS: Unsupported protocol %u", protocol_id); +1033 return -1; +1034 } +/bgpd/bgp_ls_ted.c: 1028 in bgp_ls_process_edge() +1022 ISO_SYS_ID_LEN); +1023 // router_id[6] = 0; /* Pseudonode ID = 0 for router */ +1024 // router_id_len = 7; +1025 remote_router_id_len = 6; +1026 break; +1027 case BGP_LS_PROTO_DIRECT: +>>> CID 1670210: (DEADCODE) +>>> Execution cannot reach this statement: "case BGP_LS_PROTO_STATIC:". +1028 case BGP_LS_PROTO_STATIC: +1029 case BGP_LS_PROTO_OSPFV3: +1030 case BGP_LS_PROTO_BGP: +1031 case BGP_LS_PROTO_RESERVED: +1032 zlog_err("BGP-LS: Unsupported protocol %u", protocol_id); +1033 return -1; +/bgpd/bgp_ls_ted.c: 1031 in bgp_ls_process_edge() +1025 remote_router_id_len = 6; +1026 break; +1027 case BGP_LS_PROTO_DIRECT: +1028 case BGP_LS_PROTO_STATIC: +1029 case BGP_LS_PROTO_OSPFV3: +1030 case BGP_LS_PROTO_BGP: +>>> CID 1670210: (DEADCODE) +>>> Execution cannot reach this statement: "case BGP_LS_PROTO_RESERVED:". +1031 case BGP_LS_PROTO_RESERVED: +1032 zlog_err("BGP-LS: Unsupported protocol %u", protocol_id); +1033 return -1; +1034 } +1035 +1036 switch (event) { + +Signed-off-by: Carmine Scarpitta <cscarpit@cisco.com> +(cherry picked from commit 48690880f6386edf56e919f2ff9b52d3adc1f865) + +bgpd: Fix IGP Router-ID buffer overflow in BGP-LS node descriptor + +RFC 9552 defines the IGP Router-ID as variable length from 4 to 16 bytes. +Right now, igp_router_id is defined as an 8-byte array in the +bgp_ls_node_descriptor data structure. + +This can cause an overflow when decoding a valid IGP Router-ID TLV with +length greater than 8, because the parser accepts up to 16 bytes and then +copies up to 16 bytes into the fixed 8-byte buffer. + +Fix by sizing igp_router_id to BGP_LS_IGP_ROUTER_ID_MAX_SIZE (16 bytes), +so any RFC-valid IGP Router-ID length fits safely and decoding cannot +write past the end of the descriptor buffer. + +Fix CID 1670214, 1670212, 1670211, 1670208, 1670207, 1670206, 1670205, 1670204. + +Signed-off-by: Carmine Scarpitta <cscarpit@cisco.com> +(cherry picked from commit 36cd87dc13c6193d3d2ddaeb6c91fe884e4a2807) + +bgpd: Fix null dereference when `bgp->ls_info` is NULL + +`bgp_ls_register()` calls `bgp_ls_is_registered()` as an early-return +guard, but that function returns false when `bgp->ls_info` is NULL. +The code then proceeds to dereference `bgp->ls_info->registered_ls_db` +unconditionally, causing a crash. + +Add explicit null guards at the top of `bgp_ls_register()`, +`bgp_ls_unregister()`, and `bgp_ls_cleanup()` to return early when +`bgp->ls_info` is not initialized. + +Signed-off-by: Carmine Scarpitta <cscarpit@cisco.com> +(cherry picked from commit e310588e9918fcb3707f33e3ef7067b4969e9e0d) + +bgpd: Fix memory leak in `bgp_ls_originate_node()` + +In `bgp_ls_originate_node()`, `ls_attr` is allocated with +`bgp_ls_attr_alloc()` and populated before being passed to +`bgp_ls_update()`. If `bgp_ls_update()` fails, the function +returns -1 without freeing `ls_attr`, causing a memory leak. + +Free `ls_attr` before returning on the `bgp_ls_update()` failure +path. + +Signed-off-by: Carmine Scarpitta <cscarpit@cisco.com> +(cherry picked from commit ef1da17f6dc78cabf5a26502b6fff93525a26651) + +bgpd: Align `bgp_ls_populate_prefix_attr()` with node/link pattern + +`bgp_ls_populate_prefix_attr()` used a double-pointer `**attr` and +allocated `ls_attr` internally, unlike `bgp_ls_populate_node_attr()` +and `bgp_ls_populate_link_attr()` which receive a pre-allocated +`*attr` from the caller. + +The internal `!encoded` path also had a bug: `attr = NULL` set the +local variable instead of `*attr`, leaving the caller with a dangling +pointer to freed memory. + +Refactor `bgp_ls_populate_prefix_attr()` to take a single `*attr` +pointer, removing the internal `bgp_ls_attr_alloc()` call and the +`encoded`/free logic. Update `bgp_ls_originate_prefix()` to +pre-allocate `ls_attr` with `bgp_ls_attr_alloc()` before calling +the function, consistent with how node and link attributes are +handled. + +Signed-off-by: Carmine Scarpitta <cscarpit@cisco.com> +(cherry picked from commit 1a454f66cd811831f0c3c65b52108c442881595a) + +bgpd: Remove duplicate log calls in `bgp_ls_withdraw_node()` + +`bgp_ls_withdraw_node()` called `zlog_err()` immediately followed by +`flog_err()` with identical messages on two error paths. `flog_err()` +already logs to the standard output, making the `zlog_err()` calls +redundant. Remove the duplicates. + +Signed-off-by: Carmine Scarpitta <cscarpit@cisco.com> +(cherry picked from commit d0c1911106b8304d289a3b122b940ea89606e986) + +bgpd: Demote unknown opaque message warning to debug in opaque handler + +BGP registers only for `LINK_STATE_SYNC` and `LINK_STATE_UPDATE` +opaque messages, but other daemons may send opaque messages on the +same channel for their own purposes. The `default` case in +`bgp_zebra_opaque_msg_handler()` emitted a `zlog_warn()` for every +such message, which would be noisy and gives the operator nothing to +act on. + +Demote to a debug log guarded by `BGP_DEBUG(zebra, ZEBRA)`. + +Signed-off-by: Carmine Scarpitta <cscarpit@cisco.com> +(cherry picked from commit f232a7fbd18375318022051d68e978c7b6710684) + +bgpd: Fix `edge->destination` null deref in `bgp_ls_withdraw_link()` + +`bgp_ls_withdraw_link()` dereferenced `edge->destination->node` to +read the remote AS number, but the guard only checked +`edge->attributes` — it did not verify that `edge->destination` or +`edge->destination->node` were non-NULL, leading to a potential null +dereference. + +Add the missing null checks on `edge->destination` and +`edge->destination->node`, consistent with the guard used for the +same field in `bgp_ls_originate_link()`. + +Signed-off-by: Carmine Scarpitta <cscarpit@cisco.com> +(cherry picked from commit d649b691938ab08885e8642f018930595ff09816) + +bgpd: Use `bgp_node_lookup()` in `bgp_ls_withdraw()` + +`bgp_ls_withdraw()` was calling `bgp_afi_node_get()` to locate the RIB +destination before marking the route as withdrawn. `bgp_afi_node_get()` +creates a new RIB node when none exists, so a withdraw for an NLRI that +was never installed silently created a phantom RIB entry with no path +info attached, wasting memory and polluting the table. + +Replace the call with `bgp_node_lookup()`, which is a pure read-only +lookup that returns NULL when the prefix is not present. Add an early +return with a debug-guarded log message for the not-found case so the +condition is visible under `debug bgp linkstate` without being noisy in +production. + +Signed-off-by: Carmine Scarpitta <cscarpit@cisco.com> +(cherry picked from commit bfc0419434afb230ea1c23e307071d076d94c5c7) + +bgpd: Unintern old NLRI reference in `bgp_ls_update()` + +`bgp_afi_node_get()` returns an existing RIB node when the prefix is +already present. In that case `dest->ls_nlri` already holds an +interned pointer. Unconditionally overwriting it with the new value +dropped the old reference without calling `bgp_ls_nlri_unintern()`, +leaking the previous allocation. + +Unintern the existing pointer before installing the new one so the +refcount is kept accurate in both the new-node and update paths. + +Signed-off-by: Carmine Scarpitta <cscarpit@cisco.com> +(cherry picked from commit a24ac728e9173589a11bd975ac3f00aa535555e0) + +bgpd: Clear `registered_ls_db` before calling `ls_unregister()` + +`bgp_ls_unregister()` only cleared `registered_ls_db` after a +successful `ls_unregister()` call. When `ls_unregister()` failed, the +flag was left as true, making `bgp_ls_is_registered()` report "still +registered". Any subsequent call to `bgp_ls_register()` would then +return early thinking registration was already in place, leaving BGP +permanently unable to receive link-state updates from zebra. + +Fix this by clearing `registered_ls_db` before the `ls_unregister()` +call. If the zebra unregister fails, the flag is already false so +`bgp_ls_register()` can attempt re-registration and recover. The +`false` return value is preserved to signal the zebra-side failure to +the caller. + +Signed-off-by: Carmine Scarpitta <cscarpit@cisco.com> +(cherry picked from commit 5f118f0dca9cc9ae7ceba20ac143b4e143af83b7) + +bgpd: Fix use-after-free in BGP-LS node origination + +`bgp_ls_originate_node()` could free `ls_attr` after `bgp_ls_populate_node_attr()` +failure, then continue and pass the freed pointer to `bgp_ls_update()`. + +Fix by returning immediately after `bgp_ls_attr_free(ls_attr)` on +populate failure. + +Signed-off-by: Carmine Scarpitta <cscarpit@cisco.com> +(cherry picked from commit 06036a10e40ccc3e50d23507e1679c4ecce553de) + +bgpd: Include local-node ASN in link withdraw NLRI + +A Link NLRI contains two nodes: local node and remote node. +Per RFC 9552, each node is identified by ASN, OSPF Area ID, and +IGP Router ID. + +For the remote node, `bgp_ls_withdraw_link` sets ASN, OSPF Area ID, and +IGP Router ID when generating the NLRI. + +For the local node, `bgp_ls_withdraw_link` sets only OSPF Area ID and +IGP Router ID when generating the NLRI. + +Add ASN for the local node as well, to keep symmetry with the remote +node and remain compliant with RFC 9552. + +Signed-off-by: Carmine Scarpitta <cscarpit@cisco.com> +(cherry picked from commit 46c3d1c85275cd48fbe82b79e0e8a3fd8a285c08) + +bgpd: Require valid TED objects in BGP-LS originate/withdraw APIs + +BGP-LS node/link/prefix originate and withdraw handlers are expected to +receive valid TED objects. + +Add explicit checks at the beginning of each function and return early on +invalid inputs, before any further processing. + +This makes the API contract clear, avoids NULL dereferences, and keeps the +originate/withdraw paths consistent. + +Signed-off-by: Carmine Scarpitta <cscarpit@cisco.com> +(cherry picked from commit 6e2323aabe65888274f0f2a7ad7de3c6805a2c69) + +bgpd: Fix missing present_tlvs bit for Link ID in link NLRI + +When originating/withdrawing a Link NLRI, link_remote_id is filled in +the bgp_ls_nlri structure but BGP_LS_LINK_DESC_LINK_ID_BIT is not set +in link_desc.present_tlvs. + +Fix by setting BGP_LS_LINK_DESC_LINK_ID_BIT in both bgp_ls_originate_link() +and bgp_ls_withdraw_link(). + +Signed-off-by: Carmine Scarpitta <cscarpit@cisco.com> +(cherry picked from commit 8fbdb4fa767a79c7cb3b0fb984df21f1ef8a8dfb) + +bgpd: Fix destination linkage for delayed reverse-edge updates + +An edge A->B can arrive before reverse edge B->A is present in TED. +When that happens, A->B may not have a destination yet and link +origination for that direction is deferred. + +Today, when the reverse edge arrives, the direct edge is not updated +in TED and can remain without a destination. + +As a result, when bgp_ls_process_edge tries to generate BGP-LS +NLRI, generation fails because the edge still has no destination. + +Fix this by looking up the reverse edge and completing missing +destination linkage in both directions before processing. + +This follows the same logic used in ISIS TED edge handling. + +Signed-off-by: Carmine Scarpitta <cscarpit@cisco.com> +(cherry picked from commit 350933411c82c51e857e5712f36949bf6aecec2c) + +bgpd: show BGP-LS attributes in json output + +show bgp link-state link-state NLRI json didn't show any BGP-LS +attributes. + +* Add `bgp_ls_attr_to_json` that converts `bgp_ls_attr` to json_object +* Call it from `route_vty_out_detail` and attach to output +* Small fix in `bgp_ls_attr_display`: show "Packet Loss" in % + +Signed-off-by: Kyrylo Yatsenko <hedrok@gmail.com> +(cherry picked from commit a2576fd6b0191095bf8c1821138c4d8ceb77c6f0) + +bgpd: BGP-LS: add Prefix SID (TLV 1158) + +ADD TLV 1158 Prefix SID to BGP-LS: RFC 9085 Section 2.3.1 + +Signed-off-by: Kyrylo Yatsenko <hedrok@gmail.com> +(cherry picked from commit e67b6c7a25aa229b0bda7fdeedebf9e26b07df21) + +tests: BGP-LS Prefix SID attribute + +Signed-off-by: Kyrylo Yatsenko <hedrok@gmail.com> +(cherry picked from commit 500018fb32d1a14e0ca7121bb0653e149b168eab) + +bgpd: Fix incorrect comparisons in BGP-LS *_cmp() functions + +Comparison functions in bgp_ls_nlri.c return (a - b) on unsigned +integer fields. The unsigned subtraction result is then implicitly +converted to int (the return type). When the difference exceeds +INT_MAX the sign flips, inverting the result: + + a = 0xFFFFFFFF, b = 0 + (uint32_t)a - b = 0xFFFFFFFF -> cast to int gives -1 + -> caller sees a < b, which is wrong + +These functions are expected to return a negative value when a < b, +zero when a == b, and a positive value when a > b. Currently the +top-level functions bgp_ls_nlri_cmp() and bgp_ls_attr_cmp() are +registered as hash comparison functions (DECLARE_HASH), which only +tests them for equality (== 0). All other cmp functions are called +exclusively from within those two, so in practice the sign is never +observed. However, a future caller relying on the sign to determine +ordering would silently get incorrect results. + +Replace all subtraction-based comparisons with numcmp(a, b), which +correctly implements the three-way comparator contract. + +Signed-off-by: Carmine Scarpitta <cscarpit@cisco.com> +(cherry picked from commit a462e15977c761860d03a239644bc3b4825ec41f) + +bgpd: Request initial TED sync on link-state registration + +After registering with the LS database, no initial sync is requested, +so the TED remains empty until the IGP sends unsolicited updates. +Any topology changes that occurred before registration are +permanently missed and never originated as BGP-LS NLRIs. + +Additionally, LS_MSG_EVENT_SYNC messages are not handled in the TED +processors, so any sync response from zebra is silently dropped. + +Request a sync via ls_request_sync() immediately after registration, +following the same pattern used by other TED consumers such as pathd. +Handle LS_MSG_EVENT_SYNC alongside LS_MSG_EVENT_ADD in +bgp_ls_process_vertex(), bgp_ls_process_edge(), and +bgp_ls_process_subnet(). + +Signed-off-by: Carmine Scarpitta <cscarpit@cisco.com> +(cherry picked from commit 13cc5be80825d3087873dbfe4e5b721983e30372) + +bgpd: Withdraw BGP-LS routes and reset TED on last peer deactivation + +When the last BGP-LS peer is deactivated, locally originated BGP-LS +routes are not withdrawn from the RIB, leaving stale routes on peers. +The TED is also not cleared, so the next registration re-originates +on top of stale state. + +Add bgp_ls_withdraw_ted() which removes all self-originated paths via +bgp_clear_route() and clears all TED entries. Call it in +peer_deactivate() when the last BGP-LS peer is being deactivated, +before unregistering from the LS database. + +Signed-off-by: Carmine Scarpitta <cscarpit@cisco.com> +(cherry picked from commit 906445760f177df1850bef6977b7ecbe47df8391) + +tests: Verify BGP-LS routes withdrawn on peer deactivate + +Add test_bgp_ls_peer_deactivate() to verify that deactivating the +last BGP-LS peer on r2 withdraws all locally originated routes on +r2 and clears all received routes on rr. + +Add test_bgp_ls_peer_reactivate() to verify that reactivating the +peer triggers a fresh TED sync, re-originates all BGP-LS NLRIs on +r2, and re-advertises them to rr. + +Signed-off-by: Carmine Scarpitta <cscarpit@cisco.com> +(cherry picked from commit 431b6814721723fe02c04c022d6c65d6e6656deb) + +bgpd: BGP-LS Fix bgp_ls_attr_prefix_sid_len + +Signed-off-by: Kyrylo Yatsenko <hedrok@gmail.com> +(cherry picked from commit e34b0cf424bf21b2911fe43bae17ec2f64988492) + +bgpd: BGP-LS: add SR Capabilities (TLV 1034) + +Signed-off-by: Kyrylo Yatsenko <hedrok@gmail.com> +(cherry picked from commit 349679c2d17f9d1c90b960fc25f98718e18ee113) + +tests: BGP-LS SR Capabilities (TLV 1034) + +Signed-off-by: Kyrylo Yatsenko <hedrok@gmail.com> +(cherry picked from commit 8b8c198300a4cc5a89bc087235778c3651f803e7) + +bgpd: BGP-LS: add Node MSD (TLV 266) + +Signed-off-by: Kyrylo Yatsenko <hedrok@gmail.com> +(cherry picked from commit b33f7f4cd36ef0ec8044a514bf09a527ab99a322) + +tests: BGP-LS add Node MSD (TLV 266) + +Signed-off-by: Kyrylo Yatsenko <hedrok@gmail.com> +(cherry picked from commit cfe13bb9fb9e19f051fbcfb2076c0ef4d6574a67) + +isis, lib: add isis area id to ls_node + +It is needed for BGP-LS IS-IS Area Identifier (TLV 1027) + +Signed-off-by: Kyrylo Yatsenko <hedrok@gmail.com> +(cherry picked from commit 19adc1a2f1fe180fcab37be672e31ed890b97d00) + +bgpd: BGP-LS: update IS-IS Area Identifier (TLV 1027) + +* Console output (bgp_ls_attr_display) +* JSON output (bgp_ls_attr_to_json) +* Fill in from TED (bgp_ls_populate_node_attr) + +Signed-off-by: Kyrylo Yatsenko <hedrok@gmail.com> +(cherry picked from commit 1bb63fabb913d7d9c92ec74322fefa907ec6a5bf) + +tests: BGP-LS IS-IS Area Identifier (TLV 1027) + +Signed-off-by: Kyrylo Yatsenko <hedrok@gmail.com> +(cherry picked from commit a23737163f808ab25fc0f9ac06266bb43f971c90) + +bgpd: BGP-LS: simplify bgp_ls_attr_to_json + +Simplify bgp_ls_attr_to_json using json_object_string_addf +instead of snprintfrr + json_object_string_add + +Signed-off-by: Kyrylo Yatsenko <hedrok@gmail.com> +(cherry picked from commit b4c5646d4cbb0caaae4185414a6ab30c518175ac) + +bgpd: BGP-LS: add Adjacency SID (TLV 1099) + +To properly encode/decode `Adjacency SID` protocol id must be known +as length of SID depends on flags V and L that have different bit +positions for IS-IS and OSPF. + +This commit adds `protocol_id` argument to `bgp_ls_encode_attr` +and `bgp_packet_ls_attribute`. + +During run of `bgp_ls_parse_attr` NLRI is not decoded yet, so instead of +adding complex logic of pre-parsing NLRI and getting protocol id out of +it function `bgp_ls_attr_process_with_protocol_id` is added. It is +called after parsing of attributes and after protocol id is parsed in +NLRI. + +Signed-off-by: Kyrylo Yatsenko <hedrok@gmail.com> +(cherry picked from commit 3df60175a487148f79dc2a2985001893412d85f2) + +tests: BGP-LS Adjacency SID (TLV 1099) + +Signed-off-by: Kyrylo Yatsenko <hedrok@gmail.com> +(cherry picked from commit 7461a4a3aee80bbeb7524de164481102469af7da) + +isisd: convert area_addrs from linked list to typesafe DLIST + +Convert the isis_area->area_addrs field from the legacy struct list * +to the FRR typesafe DLIST container. This change is part of the ongoing +effort to migrate IS-IS data structures to typesafe containers for +improved type safety and code clarity. + +Signed-off-by: Vincent Jardin <vjardin@free.fr> +(cherry picked from commit e62419b8940ce25233386630d7ecd1311404a575) +--- + bgpd/bgp_attr.c | 139 +- + bgpd/bgp_attr.h | 18 +- + bgpd/bgp_bmp.c | 8 +- + bgpd/bgp_debug.c | 52 + + bgpd/bgp_debug.h | 3 + + bgpd/bgp_errors.c | 6 + + bgpd/bgp_errors.h | 1 + + bgpd/bgp_ls.c | 878 +++ + bgpd/bgp_ls.h | 86 + + bgpd/bgp_ls_nlri.c | 5406 +++++++++++++++++ + bgpd/bgp_ls_nlri.h | 895 +++ + bgpd/bgp_ls_ted.c | 1403 +++++ + bgpd/bgp_ls_ted.h | 226 + + bgpd/bgp_open.c | 19 +- + bgpd/bgp_packet.c | 3 + + bgpd/bgp_route.c | 178 +- + bgpd/bgp_route.h | 10 + + bgpd/bgp_table.c | 8 + + bgpd/bgp_table.h | 2 + + bgpd/bgp_updgrp.c | 4 + + bgpd/bgp_updgrp_packet.c | 116 +- + bgpd/bgp_vty.c | 70 +- + bgpd/bgp_zebra.c | 34 +- + bgpd/bgpd.c | 76 +- + bgpd/bgpd.h | 29 +- + bgpd/rfapi/rfapi_import.c | 5 + + bgpd/rfapi/rfapi_monitor.c | 2 + + bgpd/subdir.am | 6 + + doc/user/bgp-linkstate.rst | 207 + + doc/user/bgp.rst | 2 + + isisd/isis_lsp.c | 2 +- + isisd/isis_nb_config.c | 28 +- + isisd/isis_pdu.c | 16 +- + isisd/isis_snmp.c | 6 +- + isisd/isis_te.c | 11 + + isisd/isis_tlvs.c | 22 +- + isisd/isis_tlvs.h | 17 +- + isisd/isis_zebra.c | 2 +- + isisd/isisd.c | 56 +- + isisd/isisd.h | 2 +- + lib/command.h | 1 + + lib/iana_afi.h | 10 + + lib/iso.h | 10 + + lib/link_state.c | 34 + + lib/link_state.h | 4 + + lib/prefix.c | 6 + + lib/zebra.h | 6 +- + pbrd/pbr_zebra.c | 8 + + staticd/static_vty.c | 1 + + tests/bgpd/test_peer_attr.c | 2 + + tests/isisd/test_isis_spf.c | 2 +- + tests/topotests/bgp_link_state/__init__.py | 0 + tests/topotests/bgp_link_state/r1/frr.conf | 57 + + .../topotests/bgp_link_state/r1/isis_adj.json | 18 + + .../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 | 15 + + .../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 +++ + vtysh/vtysh.c | 27 +- + zebra/connected.c | 2 + + zebra/router-id.c | 2 + + zebra/zapi_msg.c | 1 + + zebra/zebra_nhg.c | 2 + + 76 files changed, 14508 insertions(+), 142 deletions(-) + 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 + +diff --git a/bgpd/bgp_attr.c b/bgpd/bgp_attr.c +index fcfff91cd7..924e24fd19 100644 +--- a/bgpd/bgp_attr.c ++++ b/bgpd/bgp_attr.c +@@ -44,6 +44,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[] = { +@@ -992,6 +993,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; + } +@@ -1041,7 +1044,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; + } + +@@ -1291,6 +1295,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. +@@ -1504,6 +1515,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); + } + + /* Free bgp attribute and aspath. */ +@@ -3830,6 +3843,37 @@ otc_ignore: + return bgp_attr_ignore(peer, args->type); + } + ++/* BGP-LS attribute (rfc9552) */ ++static enum bgp_attr_parse_ret bgp_attr_ls(struct bgp_attr_parser_args *args) ++{ ++ struct peer *const peer = args->peer; ++ struct 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(peer->curr, args->length, ls_attr); ++ if (ret != 0) { ++ bgp_ls_attr_free(ls_attr); ++ return BGP_ATTR_PARSE_ERROR; ++ } ++ ++ 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(peer->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) +@@ -4260,6 +4304,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; +@@ -4511,6 +4558,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); + +@@ -4544,6 +4595,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"); +@@ -4598,6 +4653,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"); +@@ -4611,9 +4672,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; + } + +@@ -4717,11 +4779,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) + { + mpls_label_t restore_label; + +@@ -4748,6 +4841,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: + if (num_labels > 1) { + zlog_warn("%s: stream_put_labeled_prefix currently supports only one label, ignoring rest (num_labels=%d)", +@@ -4811,6 +4907,10 @@ size_t bgp_packet_mpattr_prefix_size(afi_t afi, safi_t safi, + case SAFI_FLOWSPEC: + size = ((struct prefix_fs *)p)->prefix.prefixlen; + break; ++ case SAFI_BGP_LS: ++ /* TODO: add explaination */ ++ size = 0; ++ break; + } + + return size; +@@ -4961,7 +5061,7 @@ bgp_size_t bgp_packet_attribute(struct bgp *bgp, struct peer *peer, struct strea + struct prefix *p, afi_t afi, safi_t safi, struct peer *from, + struct prefix_rd *prd, mpls_label_t *label, uint8_t num_labels, + bool addpath_capable, uint32_t addpath_tx_id, +- struct bgp_path_info *bpi) ++ struct bgp_path_info *bpi, struct bgp_ls_nlri *ls_nlri) + { + size_t cp; + size_t aspath_sizep; +@@ -4984,9 +5084,8 @@ bgp_size_t bgp_packet_attribute(struct bgp *bgp, struct peer *peer, struct strea + + 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); + } + +@@ -5444,6 +5543,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); +@@ -5480,12 +5583,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}; + +@@ -5500,8 +5601,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 3340822d58..88fb156bc1 100644 +--- a/bgpd/bgp_attr.h ++++ b/bgpd/bgp_attr.h +@@ -321,6 +321,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 */ +@@ -399,7 +402,8 @@ extern bgp_size_t bgp_packet_attribute(struct bgp *bgp, struct peer *peer, struc + struct prefix *p, afi_t afi, safi_t safi, struct peer *from, + struct prefix_rd *prd, mpls_label_t *label, + uint8_t num_labels, bool addpath_capable, +- uint32_t addpath_tx_id, struct bgp_path_info *bpi); ++ uint32_t addpath_tx_id, struct bgp_path_info *bpi, ++ struct bgp_ls_nlri *ls_nlri); + extern void bgp_dump_routes_attr(struct stream *s, struct bgp_path_info *bpi, + const struct prefix *p); + extern bool attrhash_cmp(const void *arg1, const void *arg2); +@@ -458,17 +462,19 @@ extern void bgp_packet_mpattr_prefix(struct stream *s, afi_t afi, safi_t safi, + const struct prefix_rd *prd, + mpls_label_t *label, uint8_t num_labels, + bool addpath_capable, +- uint32_t addpath_tx_id, struct attr *); ++ uint32_t addpath_tx_id, struct attr *, ++ 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 c095a3ea77..554766433e 100644 +--- a/bgpd/bgp_bmp.c ++++ b/bgpd/bgp_bmp.c +@@ -1118,7 +1118,7 @@ static struct stream *bmp_update(const struct prefix *p, struct prefix_rd *prd, + + /* 5: Encode all the attributes, except MP_REACH_NLRI attr. */ + total_attr_len = bgp_packet_attribute(NULL, peer, s, attr, &vecarr, NULL, afi, safi, peer, +- NULL, NULL, 0, 0, 0, NULL); ++ NULL, NULL, 0, 0, 0, NULL, NULL); + + /* 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 73b5280525..bb2fe37304 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; +@@ -2255,6 +2257,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", +@@ -2299,6 +2336,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"); + +@@ -2396,6 +2434,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); +@@ -2534,6 +2575,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"); +@@ -2691,6 +2737,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 e7d43bfacd..f16c6bcbc4 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; +@@ -139,6 +141,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 977868adc7..f084022482 100644 +--- a/bgpd/bgp_errors.c ++++ b/bgpd/bgp_errors.c +@@ -491,6 +491,12 @@ static struct log_ref ferr_bgp_err[] = { + .description = "BGP could not find the label for the specified path_info", + .suggestion = "Most likely a bug. This should never happen. Please try to build a reproducer and report.", + }, ++ { ++ .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 c15692112c..d65dbc8a52 100644 +--- a/bgpd/bgp_errors.h ++++ b/bgpd/bgp_errors.h +@@ -93,6 +93,7 @@ enum bgp_log_refs { + EC_BGP_SENDQ_STUCK_WARN, + EC_BGP_SENDQ_STUCK_PROPER, + EC_BGP_PATH_WITHOUT_LABEL, ++ EC_BGP_LS_PACKET, + }; + + extern void bgp_error_init(void); +diff --git a/bgpd/bgp_ls.c b/bgpd/bgp_ls.c +new file mode 100644 +index 0000000000..199e390bee +--- /dev/null ++++ b/bgpd/bgp_ls.c +@@ -0,0 +1,878 @@ ++// 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 (BGP_LS_TLV_CHECK(node->present_tlvs, BGP_LS_NODE_DESC_AS_BIT)) ++ json_object_int_add(json_node, "asn", node->asn); ++ ++ if (BGP_LS_TLV_CHECK(node->present_tlvs, BGP_LS_NODE_DESC_BGP_LS_ID_BIT)) ++ json_object_int_add(json_node, "bgplsId", node->bgp_ls_id); ++ ++ if (BGP_LS_TLV_CHECK(node->present_tlvs, BGP_LS_NODE_DESC_OSPF_AREA_BIT)) ++ json_object_string_addf(json_node, "ospfAreaId", "%pI4", ++ (in_addr_t *)&node->ospf_area_id); ++ ++ if (BGP_LS_TLV_CHECK(node->present_tlvs, BGP_LS_NODE_DESC_IGP_ROUTER_BIT)) { ++ 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); ++ } ++ ++ 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 (BGP_LS_TLV_CHECK(link_desc->present_tlvs, BGP_LS_LINK_DESC_LINK_ID_BIT)) { ++ json_object_int_add(json_link, "linkLocalId", link_desc->link_local_id); ++ json_object_int_add(json_link, "linkRemoteId", link_desc->link_remote_id); ++ } ++ ++ if (BGP_LS_TLV_CHECK(link_desc->present_tlvs, BGP_LS_LINK_DESC_IPV4_INTF_BIT)) ++ json_object_string_addf(json_link, "ipv4InterfaceAddress", "%pI4", ++ &link_desc->ipv4_intf_addr); ++ ++ if (BGP_LS_TLV_CHECK(link_desc->present_tlvs, BGP_LS_LINK_DESC_IPV4_NEIGH_BIT)) ++ json_object_string_addf(json_link, "ipv4NeighborAddress", "%pI4", ++ &link_desc->ipv4_neigh_addr); ++ ++ if (BGP_LS_TLV_CHECK(link_desc->present_tlvs, BGP_LS_LINK_DESC_IPV6_INTF_BIT)) ++ json_object_string_addf(json_link, "ipv6InterfaceAddress", "%pI6", ++ &link_desc->ipv6_intf_addr); ++ ++ if (BGP_LS_TLV_CHECK(link_desc->present_tlvs, BGP_LS_LINK_DESC_IPV6_NEIGH_BIT)) ++ json_object_string_addf(json_link, "ipv6NeighborAddress", "%pI6", ++ &link_desc->ipv6_neigh_addr); ++ ++ if (BGP_LS_TLV_CHECK(link_desc->present_tlvs, BGP_LS_LINK_DESC_MT_ID_BIT) && ++ 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 (BGP_LS_TLV_CHECK(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)); ++ ++ 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 (BGP_LS_TLV_CHECK(node->present_tlvs, BGP_LS_NODE_DESC_AS_BIT)) { ++ len = snprintfrr(*p, *remain, "[c%u]", node->asn); ++ *p += len; ++ *remain -= len; ++ } ++ ++ /* BGP-LS Identifier (deprecated but still used) */ ++ if (BGP_LS_TLV_CHECK(node->present_tlvs, BGP_LS_NODE_DESC_BGP_LS_ID_BIT)) { ++ 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 (BGP_LS_TLV_CHECK(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; ++ } ++ ++ 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 (BGP_LS_TLV_CHECK(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 (BGP_LS_TLV_CHECK(link_desc->present_tlvs, BGP_LS_LINK_DESC_IPV4_INTF_BIT)) { ++ len = snprintfrr(*p, *remain, "[i%pI4]", &link_desc->ipv4_intf_addr); ++ *p += len; ++ *remain -= len; ++ } ++ ++ /* IPv4 Neighbor Address (TLV 260) */ ++ if (BGP_LS_TLV_CHECK(link_desc->present_tlvs, BGP_LS_LINK_DESC_IPV4_NEIGH_BIT)) { ++ len = snprintfrr(*p, *remain, "[n%pI4]", &link_desc->ipv4_neigh_addr); ++ *p += len; ++ *remain -= len; ++ } ++ ++ /* IPv6 Interface Address (TLV 261) */ ++ if (BGP_LS_TLV_CHECK(link_desc->present_tlvs, BGP_LS_LINK_DESC_IPV6_INTF_BIT)) { ++ len = snprintfrr(*p, *remain, "[i%pI6]", &link_desc->ipv6_intf_addr); ++ *p += len; ++ *remain -= len; ++ } ++ ++ /* IPv6 Neighbor Address (TLV 262) */ ++ if (BGP_LS_TLV_CHECK(link_desc->present_tlvs, BGP_LS_LINK_DESC_IPV6_NEIGH_BIT)) { ++ 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; ++ } ++} ++ ++/* ++ * 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); ++} +diff --git a/bgpd/bgp_ls.h b/bgpd/bgp_ls.h +new file mode 100644 +index 0000000000..06078783bb +--- /dev/null ++++ b/bgpd/bgp_ls.h +@@ -0,0 +1,86 @@ ++// 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; ++}; ++ ++/* 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); ++ ++#endif /* _FRR_BGP_LS_H */ +diff --git a/bgpd/bgp_ls_nlri.c b/bgpd/bgp_ls_nlri.c +new file mode 100644 +index 0000000000..db93d2fc29 +--- /dev/null ++++ b/bgpd/bgp_ls_nlri.c +@@ -0,0 +1,5406 @@ ++// 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 (BGP_LS_TLV_CHECK(d1->present_tlvs, BGP_LS_NODE_DESC_AS_BIT)) { ++ if (d1->asn != d2->asn) ++ return numcmp(d1->asn, d2->asn); ++ } ++ ++ /* Compare BGP-LS ID if present */ ++ if (BGP_LS_TLV_CHECK(d1->present_tlvs, BGP_LS_NODE_DESC_BGP_LS_ID_BIT)) { ++ if (d1->bgp_ls_id != d2->bgp_ls_id) ++ return numcmp(d1->bgp_ls_id, d2->bgp_ls_id); ++ } ++ ++ /* Compare OSPF Area ID if present */ ++ if (BGP_LS_TLV_CHECK(d1->present_tlvs, BGP_LS_NODE_DESC_OSPF_AREA_BIT)) { ++ if (d1->ospf_area_id != d2->ospf_area_id) ++ return numcmp(d1->ospf_area_id, d2->ospf_area_id); ++ } ++ ++ /* Compare IGP Router ID if present */ ++ if (BGP_LS_TLV_CHECK(d1->present_tlvs, BGP_LS_NODE_DESC_IGP_ROUTER_BIT)) { ++ if (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; ++ } ++ ++ 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 (BGP_LS_TLV_CHECK(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 (BGP_LS_TLV_CHECK(d1->present_tlvs, BGP_LS_LINK_DESC_IPV4_INTF_BIT)) { ++ ret = IPV4_ADDR_CMP(&d1->ipv4_intf_addr, &d2->ipv4_intf_addr); ++ if (ret != 0) ++ return ret; ++ } ++ ++ /* Compare IPv4 Neighbor Address if present */ ++ if (BGP_LS_TLV_CHECK(d1->present_tlvs, BGP_LS_LINK_DESC_IPV4_NEIGH_BIT)) { ++ ret = IPV4_ADDR_CMP(&d1->ipv4_neigh_addr, &d2->ipv4_neigh_addr); ++ if (ret != 0) ++ return ret; ++ } ++ ++ /* Compare IPv6 Interface Address if present */ ++ if (BGP_LS_TLV_CHECK(d1->present_tlvs, BGP_LS_LINK_DESC_IPV6_INTF_BIT)) { ++ ret = IPV6_ADDR_CMP(&d1->ipv6_intf_addr, &d2->ipv6_intf_addr); ++ if (ret != 0) ++ return ret; ++ } ++ ++ /* Compare IPv6 Neighbor Address if present */ ++ if (BGP_LS_TLV_CHECK(d1->present_tlvs, BGP_LS_LINK_DESC_IPV6_NEIGH_BIT)) { ++ ret = IPV6_ADDR_CMP(&d1->ipv6_neigh_addr, &d2->ipv6_neigh_addr); ++ if (ret != 0) ++ return ret; ++ } ++ ++ /* Compare Multi-Topology IDs if present */ ++ if (BGP_LS_TLV_CHECK(d1->present_tlvs, BGP_LS_LINK_DESC_MT_ID_BIT)) { ++ if (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 (BGP_LS_TLV_CHECK(d1->present_tlvs, BGP_LS_PREFIX_DESC_OSPF_ROUTE_BIT)) { ++ if (d1->ospf_route_type != d2->ospf_route_type) ++ return numcmp(d1->ospf_route_type, d2->ospf_route_type); ++ } ++ ++ /* Compare Multi-Topology IDs if present */ ++ if (BGP_LS_TLV_CHECK(d1->present_tlvs, BGP_LS_PREFIX_DESC_MT_ID_BIT)) { ++ if (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; ++} ++ ++/* ++ * =========================================================================== ++ * 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 (BGP_LS_TLV_CHECK(attr1->present_tlvs, BGP_LS_ATTR_NODE_FLAGS_BIT)) { ++ if (attr1->node_flags != attr2->node_flags) ++ return numcmp(attr1->node_flags, attr2->node_flags); ++ } ++ ++ if (BGP_LS_TLV_CHECK(attr1->present_tlvs, BGP_LS_ATTR_NODE_NAME_BIT)) { ++ ret = strcmp(attr1->node_name, attr2->node_name); ++ if (ret != 0) ++ return ret; ++ } ++ ++ if (BGP_LS_TLV_CHECK(attr1->present_tlvs, BGP_LS_ATTR_ISIS_AREA_BIT)) { ++ if (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 (BGP_LS_TLV_CHECK(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 (BGP_LS_TLV_CHECK(attr1->present_tlvs, BGP_LS_ATTR_NODE_MSD_BIT)) { ++ if (attr1->msd != attr2->msd) ++ return numcmp(attr1->msd, attr2->msd); ++ } ++ ++ if (BGP_LS_TLV_CHECK(attr1->present_tlvs, BGP_LS_ATTR_IPV4_ROUTER_ID_LOCAL_BIT)) { ++ ret = IPV4_ADDR_CMP(&attr1->ipv4_router_id_local, &attr2->ipv4_router_id_local); ++ if (ret != 0) ++ return ret; ++ } ++ ++ if (BGP_LS_TLV_CHECK(attr1->present_tlvs, BGP_LS_ATTR_IPV6_ROUTER_ID_LOCAL_BIT)) { ++ ret = IPV6_ADDR_CMP(&attr1->ipv6_router_id_local, &attr2->ipv6_router_id_local); ++ if (ret != 0) ++ return ret; ++ } ++ ++ if (BGP_LS_TLV_CHECK(attr1->present_tlvs, BGP_LS_ATTR_IPV4_ROUTER_ID_REMOTE_BIT)) { ++ ret = IPV4_ADDR_CMP(&attr1->ipv4_router_id_remote, &attr2->ipv4_router_id_remote); ++ if (ret != 0) ++ return ret; ++ } ++ ++ if (BGP_LS_TLV_CHECK(attr1->present_tlvs, BGP_LS_ATTR_IPV6_ROUTER_ID_REMOTE_BIT)) { ++ ret = IPV6_ADDR_CMP(&attr1->ipv6_router_id_remote, &attr2->ipv6_router_id_remote); ++ if (ret != 0) ++ return ret; ++ } ++ ++ if (BGP_LS_TLV_CHECK(attr1->present_tlvs, BGP_LS_ATTR_ADMIN_GROUP_BIT)) { ++ if (attr1->admin_group != attr2->admin_group) ++ return numcmp(attr1->admin_group, attr2->admin_group); ++ } ++ ++ if (BGP_LS_TLV_CHECK(attr1->present_tlvs, BGP_LS_ATTR_MAX_LINK_BW_BIT)) { ++ if (attr1->max_link_bw < attr2->max_link_bw) ++ return -1; ++ if (attr1->max_link_bw > attr2->max_link_bw) ++ return 1; ++ } ++ ++ if (BGP_LS_TLV_CHECK(attr1->present_tlvs, BGP_LS_ATTR_MAX_RESV_BW_BIT)) { ++ if (attr1->max_resv_bw < attr2->max_resv_bw) ++ return -1; ++ if (attr1->max_resv_bw > attr2->max_resv_bw) ++ return 1; ++ } ++ ++ if (BGP_LS_TLV_CHECK(attr1->present_tlvs, BGP_LS_ATTR_UNRESV_BW_BIT)) { ++ 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 (BGP_LS_TLV_CHECK(attr1->present_tlvs, BGP_LS_ATTR_TE_METRIC_BIT)) { ++ if (attr1->te_metric != attr2->te_metric) ++ return numcmp(attr1->te_metric, attr2->te_metric); ++ } ++ ++ if (BGP_LS_TLV_CHECK(attr1->present_tlvs, BGP_LS_ATTR_LINK_PROTECTION_BIT)) { ++ if (attr1->link_protection != attr2->link_protection) ++ return numcmp(attr1->link_protection, attr2->link_protection); ++ } ++ ++ if (BGP_LS_TLV_CHECK(attr1->present_tlvs, BGP_LS_ATTR_MPLS_PROTOCOL_BIT)) { ++ if (attr1->mpls_protocol_mask != attr2->mpls_protocol_mask) ++ return numcmp(attr1->mpls_protocol_mask, attr2->mpls_protocol_mask); ++ } ++ ++ if (BGP_LS_TLV_CHECK(attr1->present_tlvs, BGP_LS_ATTR_IGP_METRIC_BIT)) { ++ if (attr1->igp_metric_len != attr2->igp_metric_len) ++ return numcmp(attr1->igp_metric_len, attr2->igp_metric_len); ++ if (attr1->igp_metric != attr2->igp_metric) ++ return numcmp(attr1->igp_metric, attr2->igp_metric); ++ } ++ ++ if (BGP_LS_TLV_CHECK(attr1->present_tlvs, BGP_LS_ATTR_SRLG_BIT)) { ++ if (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 (BGP_LS_TLV_CHECK(attr1->present_tlvs, BGP_LS_ATTR_LINK_NAME_BIT)) { ++ ret = strcmp(attr1->link_name, attr2->link_name); ++ if (ret != 0) ++ return ret; ++ } ++ ++ if (BGP_LS_TLV_CHECK(attr1->present_tlvs, BGP_LS_ATTR_ADJ_SID_BIT)) { ++ if (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 (BGP_LS_TLV_CHECK(attr1->present_tlvs, BGP_LS_ATTR_IGP_FLAGS_BIT)) { ++ if (attr1->igp_flags != attr2->igp_flags) ++ return numcmp(attr1->igp_flags, attr2->igp_flags); ++ } ++ ++ if (BGP_LS_TLV_CHECK(attr1->present_tlvs, BGP_LS_ATTR_ROUTE_TAG_BIT)) { ++ if (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 (BGP_LS_TLV_CHECK(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 (BGP_LS_TLV_CHECK(attr1->present_tlvs, BGP_LS_ATTR_PREFIX_METRIC_BIT)) { ++ if (attr1->prefix_metric != attr2->prefix_metric) ++ return numcmp(attr1->prefix_metric, attr2->prefix_metric); ++ } ++ ++ if (BGP_LS_TLV_CHECK(attr1->present_tlvs, BGP_LS_ATTR_OSPF_FWD_ADDR_BIT)) { ++ 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 (BGP_LS_TLV_CHECK(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 (!BGP_LS_TLV_CHECK(n->local_node.present_tlvs, BGP_LS_NODE_DESC_IGP_ROUTER_BIT)) ++ return false; ++ ++ if (!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 (!BGP_LS_TLV_CHECK(l->local_node.present_tlvs, BGP_LS_NODE_DESC_IGP_ROUTER_BIT)) ++ return false; ++ ++ /* Remote node IGP Router ID is mandatory */ ++ if (!BGP_LS_TLV_CHECK(l->remote_node.present_tlvs, BGP_LS_NODE_DESC_IGP_ROUTER_BIT)) ++ return false; ++ ++ if (!bgp_ls_igp_router_id_len_valid(l->protocol_id, ++ l->local_node.igp_router_id_len) || ++ !bgp_ls_igp_router_id_len_valid(l->protocol_id, ++ l->remote_node.igp_router_id_len)) ++ 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 (!BGP_LS_TLV_CHECK(p->local_node.present_tlvs, BGP_LS_NODE_DESC_IGP_ROUTER_BIT)) ++ return false; ++ ++ if (!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 (!BGP_LS_TLV_CHECK(p->local_node.present_tlvs, BGP_LS_NODE_DESC_IGP_ROUTER_BIT)) ++ return false; ++ ++ if (!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 (BGP_LS_TLV_CHECK(desc->present_tlvs, BGP_LS_NODE_DESC_AS_BIT)) ++ size += BGP_LS_TLV_HDR_SIZE + BGP_LS_AS_NUMBER_SIZE; ++ /* BGP-LS ID TLV */ ++ if (BGP_LS_TLV_CHECK(desc->present_tlvs, BGP_LS_NODE_DESC_BGP_LS_ID_BIT)) ++ size += BGP_LS_TLV_HDR_SIZE + BGP_LS_BGP_LS_ID_SIZE; ++ /* OSPF Area ID TLV */ ++ if (BGP_LS_TLV_CHECK(desc->present_tlvs, BGP_LS_NODE_DESC_OSPF_AREA_BIT)) ++ size += BGP_LS_TLV_HDR_SIZE + BGP_LS_OSPF_AREA_ID_SIZE; ++ /* IGP Router ID TLV */ ++ if (BGP_LS_TLV_CHECK(desc->present_tlvs, BGP_LS_NODE_DESC_IGP_ROUTER_BIT)) ++ size += BGP_LS_TLV_HDR_SIZE + desc->igp_router_id_len; ++ ++ 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 (BGP_LS_TLV_CHECK(l->link_desc.present_tlvs, BGP_LS_LINK_DESC_LINK_ID_BIT)) ++ size += BGP_LS_TLV_HDR_SIZE + BGP_LS_LINK_ID_SIZE; ++ if (BGP_LS_TLV_CHECK(l->link_desc.present_tlvs, BGP_LS_LINK_DESC_IPV4_INTF_BIT)) ++ size += BGP_LS_TLV_HDR_SIZE + BGP_LS_IPV4_ADDR_SIZE; ++ if (BGP_LS_TLV_CHECK(l->link_desc.present_tlvs, BGP_LS_LINK_DESC_IPV4_NEIGH_BIT)) ++ size += BGP_LS_TLV_HDR_SIZE + BGP_LS_IPV4_ADDR_SIZE; ++ if (BGP_LS_TLV_CHECK(l->link_desc.present_tlvs, BGP_LS_LINK_DESC_IPV6_INTF_BIT)) ++ size += BGP_LS_TLV_HDR_SIZE + BGP_LS_IPV6_ADDR_SIZE; ++ if (BGP_LS_TLV_CHECK(l->link_desc.present_tlvs, BGP_LS_LINK_DESC_IPV6_NEIGH_BIT)) ++ size += BGP_LS_TLV_HDR_SIZE + BGP_LS_IPV6_ADDR_SIZE; ++ if (BGP_LS_TLV_CHECK(l->link_desc.present_tlvs, BGP_LS_LINK_DESC_MT_ID_BIT)) ++ 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 (BGP_LS_TLV_CHECK(p->prefix_desc.present_tlvs, BGP_LS_PREFIX_DESC_MT_ID_BIT)) ++ size += BGP_LS_TLV_HDR_SIZE + ++ (p->prefix_desc.mt_id_count * BGP_LS_MT_ID_SIZE); ++ if (BGP_LS_TLV_CHECK(p->prefix_desc.present_tlvs, ++ BGP_LS_PREFIX_DESC_OSPF_ROUTE_BIT)) ++ 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; ++ ++ 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"; ++ } ++ ++ 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"; ++ } ++ ++ 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_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"; ++} ++ ++/* ++ * =========================================================================== ++ * 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 (BGP_LS_TLV_CHECK(desc->present_tlvs, BGP_LS_NODE_DESC_AS_BIT)) ++ key = jhash_1word(desc->asn, key); ++ ++ if (BGP_LS_TLV_CHECK(desc->present_tlvs, BGP_LS_NODE_DESC_BGP_LS_ID_BIT)) ++ key = jhash_1word(desc->bgp_ls_id, key); ++ ++ if (BGP_LS_TLV_CHECK(desc->present_tlvs, BGP_LS_NODE_DESC_OSPF_AREA_BIT)) ++ key = jhash_1word(desc->ospf_area_id, key); ++ ++ if (BGP_LS_TLV_CHECK(desc->present_tlvs, BGP_LS_NODE_DESC_IGP_ROUTER_BIT)) ++ key = jhash(desc->igp_router_id, desc->igp_router_id_len, 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 (BGP_LS_TLV_CHECK(link->link_desc.present_tlvs, BGP_LS_LINK_DESC_LINK_ID_BIT)) ++ key = jhash_2words(link->link_desc.link_local_id, link->link_desc.link_remote_id, ++ key); ++ ++ if (BGP_LS_TLV_CHECK(link->link_desc.present_tlvs, BGP_LS_LINK_DESC_IPV4_INTF_BIT)) ++ key = jhash_1word(link->link_desc.ipv4_intf_addr.s_addr, key); ++ ++ if (BGP_LS_TLV_CHECK(link->link_desc.present_tlvs, BGP_LS_LINK_DESC_IPV4_NEIGH_BIT)) ++ key = jhash_1word(link->link_desc.ipv4_neigh_addr.s_addr, key); ++ ++ if (BGP_LS_TLV_CHECK(link->link_desc.present_tlvs, BGP_LS_LINK_DESC_IPV6_INTF_BIT)) ++ key = jhash(&link->link_desc.ipv6_intf_addr, sizeof(struct in6_addr), key); ++ ++ if (BGP_LS_TLV_CHECK(link->link_desc.present_tlvs, BGP_LS_LINK_DESC_IPV6_NEIGH_BIT)) ++ 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 (BGP_LS_TLV_CHECK(prefix->prefix_desc.present_tlvs, BGP_LS_PREFIX_DESC_OSPF_ROUTE_BIT)) ++ key = jhash_1word(prefix->prefix_desc.ospf_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 (BGP_LS_TLV_CHECK(attr->present_tlvs, BGP_LS_ATTR_NODE_FLAGS_BIT)) ++ key = jhash_1word(attr->node_flags, key); ++ ++ if (BGP_LS_TLV_CHECK(attr->present_tlvs, BGP_LS_ATTR_NODE_NAME_BIT)) ++ key = jhash(attr->node_name, strlen(attr->node_name), key); ++ ++ if (BGP_LS_TLV_CHECK(attr->present_tlvs, BGP_LS_ATTR_ISIS_AREA_BIT)) ++ key = jhash(attr->isis_area_id, attr->isis_area_id_len, key); ++ ++ if (BGP_LS_TLV_CHECK(attr->present_tlvs, BGP_LS_ATTR_SR_CAPABILITIES_BIT)) { ++ key = jhash(&attr->srgb.flag, sizeof(attr->srgb.flag), key); ++ key = jhash(&attr->srgb.lower_bound, sizeof(attr->srgb.lower_bound), key); ++ key = jhash(&attr->srgb.range_size, sizeof(attr->srgb.range_size), key); ++ } ++ ++ if (BGP_LS_TLV_CHECK(attr->present_tlvs, BGP_LS_ATTR_NODE_MSD_BIT)) ++ key = jhash(&attr->msd, sizeof(attr->msd), key); ++ ++ if (BGP_LS_TLV_CHECK(attr->present_tlvs, BGP_LS_ATTR_IPV4_ROUTER_ID_LOCAL_BIT)) ++ key = jhash_1word(attr->ipv4_router_id_local.s_addr, key); ++ ++ if (BGP_LS_TLV_CHECK(attr->present_tlvs, BGP_LS_ATTR_IPV6_ROUTER_ID_LOCAL_BIT)) ++ key = jhash(&attr->ipv6_router_id_local, sizeof(struct in6_addr), key); ++ ++ if (BGP_LS_TLV_CHECK(attr->present_tlvs, BGP_LS_ATTR_IPV4_ROUTER_ID_REMOTE_BIT)) ++ key = jhash_1word(attr->ipv4_router_id_remote.s_addr, key); ++ ++ if (BGP_LS_TLV_CHECK(attr->present_tlvs, BGP_LS_ATTR_IPV6_ROUTER_ID_REMOTE_BIT)) ++ key = jhash(&attr->ipv6_router_id_remote, sizeof(struct in6_addr), key); ++ ++ if (BGP_LS_TLV_CHECK(attr->present_tlvs, BGP_LS_ATTR_ADMIN_GROUP_BIT)) ++ key = jhash_1word(attr->admin_group, key); ++ ++ if (BGP_LS_TLV_CHECK(attr->present_tlvs, BGP_LS_ATTR_MAX_LINK_BW_BIT)) { ++ uint32_t bw; ++ ++ memcpy(&bw, &attr->max_link_bw, sizeof(uint32_t)); ++ key = jhash_1word(bw, key); ++ } ++ ++ if (BGP_LS_TLV_CHECK(attr->present_tlvs, BGP_LS_ATTR_MAX_RESV_BW_BIT)) { ++ uint32_t bw; ++ ++ memcpy(&bw, &attr->max_resv_bw, sizeof(uint32_t)); ++ key = jhash_1word(bw, key); ++ } ++ ++ if (BGP_LS_TLV_CHECK(attr->present_tlvs, BGP_LS_ATTR_UNRESV_BW_BIT)) ++ key = jhash(attr->unreserved_bw, sizeof(attr->unreserved_bw), key); ++ ++ if (BGP_LS_TLV_CHECK(attr->present_tlvs, BGP_LS_ATTR_TE_METRIC_BIT)) ++ key = jhash_1word(attr->te_metric, key); ++ ++ if (BGP_LS_TLV_CHECK(attr->present_tlvs, BGP_LS_ATTR_LINK_PROTECTION_BIT)) ++ key = jhash_1word(attr->link_protection, key); ++ ++ if (BGP_LS_TLV_CHECK(attr->present_tlvs, BGP_LS_ATTR_MPLS_PROTOCOL_BIT)) ++ key = jhash_1word(attr->mpls_protocol_mask, key); ++ ++ if (BGP_LS_TLV_CHECK(attr->present_tlvs, BGP_LS_ATTR_IGP_METRIC_BIT)) ++ key = jhash_1word(attr->igp_metric, key); ++ ++ if (BGP_LS_TLV_CHECK(attr->present_tlvs, BGP_LS_ATTR_SRLG_BIT)) { ++ for (int i = 0; i < attr->srlg_count; i++) ++ key = jhash_1word(attr->srlg_values[i], key); ++ } ++ ++ if (BGP_LS_TLV_CHECK(attr->present_tlvs, BGP_LS_ATTR_LINK_NAME_BIT)) ++ key = jhash(attr->link_name, strlen(attr->link_name), key); ++ ++ if (BGP_LS_TLV_CHECK(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 (BGP_LS_TLV_CHECK(attr->present_tlvs, BGP_LS_ATTR_IGP_FLAGS_BIT)) ++ key = jhash_1word(attr->igp_flags, key); ++ ++ if (BGP_LS_TLV_CHECK(attr->present_tlvs, BGP_LS_ATTR_ROUTE_TAG_BIT)) { ++ for (int i = 0; i < attr->route_tag_count; i++) ++ key = jhash_1word(attr->route_tags[i], key); ++ } ++ ++ if (BGP_LS_TLV_CHECK(attr->present_tlvs, BGP_LS_ATTR_EXTENDED_TAG_BIT)) { ++ for (int i = 0; i < attr->extended_tag_count; i++) { ++ key = jhash_1word((uint32_t)(attr->extended_tags[i] >> 32), key); ++ key = jhash_1word((uint32_t)attr->extended_tags[i], key); ++ } ++ } ++ ++ if (BGP_LS_TLV_CHECK(attr->present_tlvs, BGP_LS_ATTR_PREFIX_METRIC_BIT)) ++ key = jhash_1word(attr->prefix_metric, key); ++ ++ if (BGP_LS_TLV_CHECK(attr->present_tlvs, BGP_LS_ATTR_OSPF_FWD_ADDR_BIT)) { ++ key = jhash_1word(attr->ospf_fwd_addr.s_addr, key); ++ key = jhash(&attr->ospf_fwd_addr6, sizeof(struct in6_addr), key); ++ } ++ ++ if (BGP_LS_TLV_CHECK(attr->present_tlvs, BGP_LS_ATTR_PREFIX_SID_BIT)) ++ 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 ++ * =========================================================================== ++ */ ++ ++/* ++ * Write TLV header (Type + Length) to stream ++ * ++ * Wire format (RFC 9552 Section 3.1): ++ * 0 1 2 3 ++ * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 ++ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ ++ * | Type | Length | ++ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ ++ * ++ * Returns number of bytes written, or -1 on error ++ */ ++static inline int stream_put_tlv_hdr(struct stream *s, uint16_t type, uint16_t length) ++{ ++ if (STREAM_WRITEABLE(s) < BGP_LS_TLV_HDR_SIZE) ++ return -1; ++ ++ stream_putw(s, type); ++ stream_putw(s, length); ++ return BGP_LS_TLV_HDR_SIZE; ++} ++ ++/* ++ * Put a complete TLV (Type + Length + Value) into stream ++ * Returns 0 on success, -1 on error ++ */ ++static inline int stream_put_tlv(struct stream *s, uint16_t type, uint16_t length, ++ const void *value) ++{ ++ if (stream_put_tlv_hdr(s, type, length) < 0) ++ return -1; ++ ++ if (length > 0) { ++ if (STREAM_WRITEABLE(s) < length) ++ return -1; ++ stream_put(s, value, length); ++ } ++ ++ return 0; ++} ++ ++static inline int stream_putf_tlv(struct stream *s, uint16_t type, const float value) ++{ ++ if (stream_put_tlv_hdr(s, type, 4) < 0) ++ return -1; ++ ++ if (STREAM_WRITEABLE(s) < 4) ++ return -1; ++ ++ stream_putf(s, value); ++ ++ return 0; ++} ++ ++/* ++ * Encode Node Descriptor to wire format (RFC 9552 Section 5.2.1) ++ * ++ * Wire format: ++ * 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 len_pos, sub_tlv_start; ++ int written = 0; ++ int ret; ++ ++ if (!s || !desc) ++ return -1; ++ ++ 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 */ ++ written += BGP_LS_TLV_HDR_SIZE; ++ ++ sub_tlv_start = stream_get_endp(s); ++ ++ /* AS Number (TLV 512) */ ++ if (BGP_LS_TLV_CHECK(desc->present_tlvs, BGP_LS_NODE_DESC_AS_BIT)) { ++ ret = stream_put_tlv_hdr(s, BGP_LS_TLV_AS_NUMBER, BGP_LS_AS_NUMBER_SIZE); ++ if (ret < 0) ++ return -1; ++ written += ret; ++ ++ if (STREAM_WRITEABLE(s) < BGP_LS_AS_NUMBER_SIZE) ++ return -1; ++ stream_putl(s, desc->asn); ++ written += BGP_LS_AS_NUMBER_SIZE; ++ } ++ ++ /* BGP-LS Identifier (TLV 513) - deprecated but may be present */ ++ if (BGP_LS_TLV_CHECK(desc->present_tlvs, BGP_LS_NODE_DESC_BGP_LS_ID_BIT)) { ++ ret = stream_put_tlv_hdr(s, BGP_LS_TLV_BGP_LS_ID, BGP_LS_BGP_LS_ID_SIZE); ++ if (ret < 0) ++ return -1; ++ written += ret; ++ ++ if (STREAM_WRITEABLE(s) < BGP_LS_BGP_LS_ID_SIZE) ++ return -1; ++ stream_putl(s, desc->bgp_ls_id); ++ written += BGP_LS_BGP_LS_ID_SIZE; ++ } ++ ++ /* OSPF Area ID (TLV 514) */ ++ if (BGP_LS_TLV_CHECK(desc->present_tlvs, BGP_LS_NODE_DESC_OSPF_AREA_BIT)) { ++ ret = stream_put_tlv_hdr(s, BGP_LS_TLV_OSPF_AREA_ID, BGP_LS_OSPF_AREA_ID_SIZE); ++ if (ret < 0) ++ return -1; ++ written += ret; ++ ++ if (STREAM_WRITEABLE(s) < BGP_LS_OSPF_AREA_ID_SIZE) ++ return -1; ++ stream_putl(s, desc->ospf_area_id); ++ written += BGP_LS_OSPF_AREA_ID_SIZE; ++ } ++ ++ /* IGP Router ID (TLV 515) - MANDATORY */ ++ if (BGP_LS_TLV_CHECK(desc->present_tlvs, BGP_LS_NODE_DESC_IGP_ROUTER_BIT)) { ++ ret = stream_put_tlv_hdr(s, BGP_LS_TLV_IGP_ROUTER_ID, desc->igp_router_id_len); ++ if (ret < 0) ++ return -1; ++ written += ret; ++ ++ if (STREAM_WRITEABLE(s) < desc->igp_router_id_len) ++ return -1; ++ stream_put(s, desc->igp_router_id, desc->igp_router_id_len); ++ written += desc->igp_router_id_len; ++ } ++ ++ /* Update length field */ ++ uint16_t sub_tlv_len = stream_get_endp(s) - sub_tlv_start; ++ ++ stream_putw_at(s, len_pos, sub_tlv_len); ++ ++ return written; ++} ++ ++/* ++ * 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) ++{ ++ int written = 0; ++ int ret; ++ uint16_t i; ++ ++ if (!s || !desc) ++ return -1; ++ ++ /* Link Local/Remote Identifiers (TLV 258) */ ++ if (BGP_LS_TLV_CHECK(desc->present_tlvs, BGP_LS_LINK_DESC_LINK_ID_BIT)) { ++ ret = stream_put_tlv_hdr(s, BGP_LS_TLV_LINK_ID, BGP_LS_LINK_ID_SIZE); ++ if (ret < 0) ++ return -1; ++ written += ret; ++ ++ if (STREAM_WRITEABLE(s) < BGP_LS_LINK_ID_SIZE) ++ return -1; ++ stream_putl(s, desc->link_local_id); ++ stream_putl(s, desc->link_remote_id); ++ written += BGP_LS_LINK_ID_SIZE; ++ } ++ ++ /* IPv4 Interface Address (TLV 259) */ ++ if (BGP_LS_TLV_CHECK(desc->present_tlvs, BGP_LS_LINK_DESC_IPV4_INTF_BIT)) { ++ ret = stream_put_tlv_hdr(s, BGP_LS_TLV_IPV4_INTF_ADDR, BGP_LS_IPV4_ADDR_SIZE); ++ if (ret < 0) ++ return -1; ++ written += ret; ++ ++ if (STREAM_WRITEABLE(s) < BGP_LS_IPV4_ADDR_SIZE) ++ return -1; ++ stream_put_ipv4(s, desc->ipv4_intf_addr.s_addr); ++ written += BGP_LS_IPV4_ADDR_SIZE; ++ } ++ ++ /* IPv4 Neighbor Address (TLV 260) */ ++ if (BGP_LS_TLV_CHECK(desc->present_tlvs, BGP_LS_LINK_DESC_IPV4_NEIGH_BIT)) { ++ ret = stream_put_tlv_hdr(s, BGP_LS_TLV_IPV4_NEIGH_ADDR, BGP_LS_IPV4_ADDR_SIZE); ++ if (ret < 0) ++ return -1; ++ written += ret; ++ ++ if (STREAM_WRITEABLE(s) < BGP_LS_IPV4_ADDR_SIZE) ++ return -1; ++ stream_put_ipv4(s, desc->ipv4_neigh_addr.s_addr); ++ written += BGP_LS_IPV4_ADDR_SIZE; ++ } ++ ++ /* IPv6 Interface Address (TLV 261) */ ++ if (BGP_LS_TLV_CHECK(desc->present_tlvs, BGP_LS_LINK_DESC_IPV6_INTF_BIT)) { ++ ret = stream_put_tlv_hdr(s, BGP_LS_TLV_IPV6_INTF_ADDR, BGP_LS_IPV6_ADDR_SIZE); ++ if (ret < 0) ++ return -1; ++ written += ret; ++ ++ if (STREAM_WRITEABLE(s) < BGP_LS_IPV6_ADDR_SIZE) ++ return -1; ++ stream_put(s, &desc->ipv6_intf_addr, BGP_LS_IPV6_ADDR_SIZE); ++ written += BGP_LS_IPV6_ADDR_SIZE; ++ } ++ ++ /* IPv6 Neighbor Address (TLV 262) */ ++ if (BGP_LS_TLV_CHECK(desc->present_tlvs, BGP_LS_LINK_DESC_IPV6_NEIGH_BIT)) { ++ ret = stream_put_tlv_hdr(s, BGP_LS_TLV_IPV6_NEIGH_ADDR, BGP_LS_IPV6_ADDR_SIZE); ++ if (ret < 0) ++ return -1; ++ written += ret; ++ ++ if (STREAM_WRITEABLE(s) < BGP_LS_IPV6_ADDR_SIZE) ++ return -1; ++ stream_put(s, &desc->ipv6_neigh_addr, BGP_LS_IPV6_ADDR_SIZE); ++ written += BGP_LS_IPV6_ADDR_SIZE; ++ } ++ ++ /* Multi-Topology ID (TLV 263) */ ++ if (BGP_LS_TLV_CHECK(desc->present_tlvs, BGP_LS_LINK_DESC_MT_ID_BIT) && ++ desc->mt_id_count > 0) { ++ ret = stream_put_tlv_hdr(s, BGP_LS_TLV_MT_ID, ++ desc->mt_id_count * BGP_LS_MT_ID_SIZE); ++ if (ret < 0) ++ return -1; ++ written += ret; ++ ++ if (STREAM_WRITEABLE(s) < (size_t)desc->mt_id_count * BGP_LS_MT_ID_SIZE) ++ return -1; ++ for (i = 0; i < desc->mt_id_count; i++) ++ stream_putw(s, desc->mt_id[i]); ++ written += desc->mt_id_count * BGP_LS_MT_ID_SIZE; ++ } ++ ++ /* Remote AS Number (TLV 264) */ ++ if (BGP_LS_TLV_CHECK(desc->present_tlvs, BGP_LS_LINK_DESC_REMOTE_AS_BIT)) { ++ ret = stream_put_tlv_hdr(s, BGP_LS_TLV_REMOTE_AS_NUMBER, 4); ++ if (ret < 0) ++ return -1; ++ written += ret; ++ ++ if (STREAM_WRITEABLE(s) < 4) ++ return -1; ++ stream_putl(s, desc->remote_asn); ++ written += 4; ++ } ++ ++ return written; ++} ++ ++/* ++ * 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) ++{ ++ int written = 0; ++ int ret; ++ uint16_t i; ++ uint8_t prefix_len_bytes; ++ ++ if (!s || !desc) ++ return -1; ++ ++ /* Multi-Topology ID (TLV 263) */ ++ if (BGP_LS_TLV_CHECK(desc->present_tlvs, BGP_LS_PREFIX_DESC_MT_ID_BIT) && ++ desc->mt_id_count > 0) { ++ ret = stream_put_tlv_hdr(s, BGP_LS_TLV_MT_ID, ++ desc->mt_id_count * BGP_LS_MT_ID_SIZE); ++ if (ret < 0) ++ return -1; ++ written += ret; ++ ++ if (STREAM_WRITEABLE(s) < (size_t)desc->mt_id_count * BGP_LS_MT_ID_SIZE) ++ return -1; ++ for (i = 0; i < desc->mt_id_count; i++) ++ stream_putw(s, desc->mt_id[i]); ++ written += desc->mt_id_count * BGP_LS_MT_ID_SIZE; ++ } ++ ++ /* OSPF Route Type (TLV 264) */ ++ if (BGP_LS_TLV_CHECK(desc->present_tlvs, BGP_LS_PREFIX_DESC_OSPF_ROUTE_BIT)) { ++ ret = stream_put_tlv_hdr(s, BGP_LS_TLV_OSPF_ROUTE_TYPE, ++ BGP_LS_OSPF_ROUTE_TYPE_SIZE); ++ if (ret < 0) ++ return -1; ++ written += ret; ++ ++ if (STREAM_WRITEABLE(s) < BGP_LS_OSPF_ROUTE_TYPE_SIZE) ++ return -1; ++ stream_putc(s, desc->ospf_route_type); ++ written += BGP_LS_OSPF_ROUTE_TYPE_SIZE; ++ } ++ ++ /* IP Reachability Information (TLV 265) - MANDATORY */ ++ if (desc->prefix.family == AF_INET) { ++ prefix_len_bytes = (desc->prefix.prefixlen + 7) / 8; ++ ret = stream_put_tlv_hdr(s, BGP_LS_TLV_IP_REACH_INFO, ++ BGP_LS_PREFIX_LEN_SIZE + prefix_len_bytes); ++ if (ret < 0) ++ return -1; ++ written += ret; ++ ++ if (STREAM_WRITEABLE(s) < (size_t)BGP_LS_PREFIX_LEN_SIZE + prefix_len_bytes) ++ return -1; ++ stream_putc(s, desc->prefix.prefixlen); ++ stream_put(s, &desc->prefix.u.prefix4, prefix_len_bytes); ++ written += BGP_LS_PREFIX_LEN_SIZE + prefix_len_bytes; ++ } else if (desc->prefix.family == AF_INET6) { ++ prefix_len_bytes = (desc->prefix.prefixlen + 7) / 8; ++ ret = stream_put_tlv_hdr(s, BGP_LS_TLV_IP_REACH_INFO, ++ BGP_LS_PREFIX_LEN_SIZE + prefix_len_bytes); ++ if (ret < 0) ++ return -1; ++ written += ret; ++ ++ if (STREAM_WRITEABLE(s) < (size_t)BGP_LS_PREFIX_LEN_SIZE + prefix_len_bytes) ++ return -1; ++ stream_putc(s, desc->prefix.prefixlen); ++ stream_put(s, &desc->prefix.u.prefix6, prefix_len_bytes); ++ written += BGP_LS_PREFIX_LEN_SIZE + prefix_len_bytes; ++ } ++ ++ return written; ++} ++ ++/* ++ * 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) ++{ ++ int written = 0; ++ int ret; ++ ++ if (!s || !nlri) ++ return -1; ++ ++ /* Protocol-ID (1 byte) */ ++ if (STREAM_WRITEABLE(s) < BGP_LS_PROTOCOL_ID_SIZE) ++ return -1; ++ stream_putc(s, nlri->protocol_id); ++ written += BGP_LS_PROTOCOL_ID_SIZE; ++ ++ /* Identifier (8 bytes) */ ++ if (STREAM_WRITEABLE(s) < BGP_LS_IDENTIFIER_SIZE) ++ return -1; ++ stream_putq(s, nlri->identifier); ++ written += BGP_LS_IDENTIFIER_SIZE; ++ ++ /* Local Node Descriptors */ ++ ret = bgp_ls_encode_node_descriptor(s, &nlri->local_node, BGP_LS_TLV_LOCAL_NODE_DESC); ++ if (ret < 0) ++ return -1; ++ written += ret; ++ ++ return written; ++} ++ ++/* ++ * 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) ++{ ++ int written = 0; ++ int ret; ++ ++ if (!s || !nlri) ++ return -1; ++ ++ /* Protocol-ID (1 byte) */ ++ if (STREAM_WRITEABLE(s) < BGP_LS_PROTOCOL_ID_SIZE) ++ return -1; ++ stream_putc(s, nlri->protocol_id); ++ written += BGP_LS_PROTOCOL_ID_SIZE; ++ ++ /* Identifier (8 bytes) */ ++ if (STREAM_WRITEABLE(s) < BGP_LS_IDENTIFIER_SIZE) ++ return -1; ++ stream_putq(s, nlri->identifier); ++ written += BGP_LS_IDENTIFIER_SIZE; ++ ++ /* Local Node Descriptors */ ++ ret = bgp_ls_encode_node_descriptor(s, &nlri->local_node, BGP_LS_TLV_LOCAL_NODE_DESC); ++ if (ret < 0) ++ return -1; ++ written += ret; ++ ++ /* Remote Node Descriptors */ ++ ret = bgp_ls_encode_node_descriptor(s, &nlri->remote_node, BGP_LS_TLV_REMOTE_NODE_DESC); ++ if (ret < 0) ++ return -1; ++ written += ret; ++ ++ /* Link Descriptors */ ++ ret = bgp_ls_encode_link_descriptor(s, &nlri->link_desc); ++ if (ret < 0) ++ return -1; ++ written += ret; ++ ++ return written; ++} ++ ++/* ++ * 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) ++{ ++ int written = 0; ++ int ret; ++ ++ 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; ++ ++ /* Protocol-ID (1 byte) */ ++ if (STREAM_WRITEABLE(s) < BGP_LS_PROTOCOL_ID_SIZE) ++ return -1; ++ stream_putc(s, nlri->protocol_id); ++ written += BGP_LS_PROTOCOL_ID_SIZE; ++ ++ /* Identifier (8 bytes) */ ++ if (STREAM_WRITEABLE(s) < BGP_LS_IDENTIFIER_SIZE) ++ return -1; ++ stream_putq(s, nlri->identifier); ++ written += BGP_LS_IDENTIFIER_SIZE; ++ ++ /* Local Node Descriptors */ ++ ret = bgp_ls_encode_node_descriptor(s, &nlri->local_node, BGP_LS_TLV_LOCAL_NODE_DESC); ++ if (ret < 0) ++ return -1; ++ written += ret; ++ ++ /* Prefix Descriptors */ ++ ret = bgp_ls_encode_prefix_descriptor(s, &nlri->prefix_desc); ++ if (ret < 0) ++ return -1; ++ written += ret; ++ ++ return written; ++} ++ ++/* ++ * 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 len_pos, value_start; ++ int written = 0; ++ int ret = 0; ++ ++ if (!s || !nlri) ++ return -1; ++ ++ /* Validate NLRI */ ++ if (!bgp_ls_nlri_validate(nlri)) ++ return -1; ++ ++ /* NLRI Type (2 bytes) */ ++ if (STREAM_WRITEABLE(s) < BGP_LS_NLRI_TYPE_SIZE) ++ return -1; ++ stream_putw(s, nlri->nlri_type); ++ written += BGP_LS_NLRI_TYPE_SIZE; ++ ++ /* Reserve space for NLRI Length */ ++ if (STREAM_WRITEABLE(s) < BGP_LS_NLRI_LENGTH_SIZE) ++ return -1; ++ len_pos = stream_get_endp(s); ++ stream_putw(s, 0); /* Placeholder */ ++ written += BGP_LS_NLRI_LENGTH_SIZE; ++ ++ value_start = stream_get_endp(s); ++ ++ /* 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; ++ ++ written += ret; ++ ++ /* Update NLRI Length field */ ++ uint16_t nlri_len = stream_get_endp(s) - value_start; ++ ++ stream_putw_at(s, len_pos, nlri_len); ++ ++ return written; ++} ++ ++/* ++ * 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 (BGP_LS_TLV_CHECK(attr->present_tlvs, BGP_LS_ATTR_NODE_FLAGS_BIT)) { ++ if (stream_put_tlv(s, BGP_LS_ATTR_NODE_FLAG_BITS, 1, &attr->node_flags) < 0) ++ return -1; ++ } ++ ++ /* Node Name (TLV 1026) */ ++ if (BGP_LS_TLV_CHECK(attr->present_tlvs, BGP_LS_ATTR_NODE_NAME_BIT)) { ++ if (attr->node_name) { ++ uint16_t len = strlen(attr->node_name); ++ ++ if (stream_put_tlv(s, BGP_LS_ATTR_NODE_NAME, len, attr->node_name) < 0) ++ return -1; ++ } ++ } ++ ++ /* IS-IS Area Identifier (TLV 1027) */ ++ if (BGP_LS_TLV_CHECK(attr->present_tlvs, BGP_LS_ATTR_ISIS_AREA_BIT)) { ++ if (attr->isis_area_id && attr->isis_area_id_len > 0) { ++ if (stream_put_tlv(s, BGP_LS_ATTR_ISIS_AREA_ID, attr->isis_area_id_len, ++ attr->isis_area_id) < 0) ++ return -1; ++ } ++ } ++ ++ /* SR Capabilities (TLV 1034) */ ++ if (BGP_LS_TLV_CHECK(attr->present_tlvs, BGP_LS_ATTR_SR_CAPABILITIES_BIT)) { ++ if (stream_put_tlv_hdr(s, BGP_LS_ATTR_SR_CAPABILITIES, 12) < 0) ++ return -1; ++ if (STREAM_WRITEABLE(s) < 12) ++ return -1; ++ stream_putc(s, attr->srgb.flag); ++ stream_putc(s, 0); ++ stream_put3(s, attr->srgb.range_size); ++ stream_put_tlv_hdr(s, BGP_LS_ATTR_SID_LABEL, 3); ++ stream_put3(s, attr->srgb.lower_bound); ++ } ++ ++ /* Node MSD (TLV 266) */ ++ if (BGP_LS_TLV_CHECK(attr->present_tlvs, BGP_LS_ATTR_NODE_MSD_BIT)) { ++ if (stream_put_tlv_hdr(s, BGP_LS_ATTR_NODE_MSD, 2) < 0) ++ return -1; ++ if (STREAM_WRITEABLE(s) < 2) ++ return -1; ++ stream_putc(s, BGP_LS_IGP_MSD_TYPE_BASE_MPLS); ++ stream_putc(s, attr->msd); ++ } ++ ++ /* IPv4 Router-ID of Local Node (TLV 1028) */ ++ if (BGP_LS_TLV_CHECK(attr->present_tlvs, BGP_LS_ATTR_IPV4_ROUTER_ID_LOCAL_BIT)) { ++ if (stream_put_tlv(s, BGP_LS_ATTR_IPV4_ROUTER_ID_LOCAL, 4, ++ &attr->ipv4_router_id_local) < 0) ++ return -1; ++ } ++ ++ /* IPv6 Router-ID of Local Node (TLV 1029) */ ++ if (BGP_LS_TLV_CHECK(attr->present_tlvs, BGP_LS_ATTR_IPV6_ROUTER_ID_LOCAL_BIT)) { ++ if (stream_put_tlv(s, BGP_LS_ATTR_IPV6_ROUTER_ID_LOCAL, 16, ++ &attr->ipv6_router_id_local) < 0) ++ return -1; ++ } ++ ++ /* IPv4 Router-ID of Remote Node (TLV 1030) */ ++ if (BGP_LS_TLV_CHECK(attr->present_tlvs, BGP_LS_ATTR_IPV4_ROUTER_ID_REMOTE_BIT)) { ++ if (stream_put_tlv(s, BGP_LS_ATTR_IPV4_ROUTER_ID_REMOTE, 4, ++ &attr->ipv4_router_id_remote) < 0) ++ return -1; ++ } ++ ++ /* IPv6 Router-ID of Remote Node (TLV 1031) */ ++ if (BGP_LS_TLV_CHECK(attr->present_tlvs, BGP_LS_ATTR_IPV6_ROUTER_ID_REMOTE_BIT)) { ++ if (stream_put_tlv(s, BGP_LS_ATTR_IPV6_ROUTER_ID_REMOTE, 16, ++ &attr->ipv6_router_id_remote) < 0) ++ return -1; ++ } ++ ++ /* Administrative Group (TLV 1088) */ ++ if (BGP_LS_TLV_CHECK(attr->present_tlvs, BGP_LS_ATTR_ADMIN_GROUP_BIT)) { ++ uint32_t admin_group_be = htonl(attr->admin_group); ++ ++ if (stream_put_tlv(s, BGP_LS_ATTR_ADMIN_GROUP, 4, &admin_group_be) < 0) ++ return -1; ++ } ++ ++ /* Maximum Link Bandwidth (TLV 1089) - IEEE 754 floating point */ ++ if (BGP_LS_TLV_CHECK(attr->present_tlvs, BGP_LS_ATTR_MAX_LINK_BW_BIT)) { ++ if (stream_putf_tlv(s, BGP_LS_ATTR_MAX_LINK_BW, attr->max_link_bw) < 0) ++ return -1; ++ } ++ ++ /* Maximum Reservable Bandwidth (TLV 1090) */ ++ if (BGP_LS_TLV_CHECK(attr->present_tlvs, BGP_LS_ATTR_MAX_RESV_BW_BIT)) { ++ if (stream_putf_tlv(s, BGP_LS_ATTR_MAX_RESV_BW, attr->max_resv_bw) < 0) ++ return -1; ++ } ++ ++ /* Unreserved Bandwidth (TLV 1091) - 8 priority levels */ ++ if (BGP_LS_TLV_CHECK(attr->present_tlvs, BGP_LS_ATTR_UNRESV_BW_BIT)) { ++ if (stream_put_tlv_hdr(s, BGP_LS_ATTR_UNRESV_BW, 32) < 0) ++ return -1; ++ for (int i = 0; i < BGP_LS_MAX_UNRESV_BW; i++) { ++ if (STREAM_WRITEABLE(s) < 4) ++ return -1; ++ stream_putf(s, attr->unreserved_bw[i]); ++ } ++ } ++ ++ /* TE Default Metric (TLV 1092) */ ++ if (BGP_LS_TLV_CHECK(attr->present_tlvs, BGP_LS_ATTR_TE_METRIC_BIT)) { ++ uint32_t te_metric_be = htonl(attr->te_metric); ++ ++ if (stream_put_tlv(s, BGP_LS_ATTR_TE_DEFAULT_METRIC, 4, &te_metric_be) < 0) ++ return -1; ++ } ++ ++ /* Link Protection Type (TLV 1093) */ ++ if (BGP_LS_TLV_CHECK(attr->present_tlvs, BGP_LS_ATTR_LINK_PROTECTION_BIT)) { ++ uint16_t protection_be = htons(attr->link_protection); ++ ++ if (stream_put_tlv(s, BGP_LS_ATTR_LINK_PROTECTION_TYPE, 2, &protection_be) < 0) ++ return -1; ++ } ++ ++ /* MPLS Protocol Mask (TLV 1094) */ ++ if (BGP_LS_TLV_CHECK(attr->present_tlvs, BGP_LS_ATTR_MPLS_PROTOCOL_BIT)) { ++ if (stream_put_tlv(s, BGP_LS_ATTR_MPLS_PROTOCOL_MASK, 1, ++ &attr->mpls_protocol_mask) < 0) ++ return -1; ++ } ++ ++ /* IGP Metric (TLV 1095) - Variable length 1-3 bytes */ ++ if (BGP_LS_TLV_CHECK(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_put_tlv(s, BGP_LS_ATTR_IGP_METRIC, len, metric_buf) < 0) ++ return -1; ++ } ++ } ++ ++ /* Shared Risk Link Group (TLV 1096) */ ++ if (BGP_LS_TLV_CHECK(attr->present_tlvs, BGP_LS_ATTR_SRLG_BIT)) { ++ if (attr->srlg_values && attr->srlg_count > 0) { ++ uint16_t srlg_len = attr->srlg_count * 4; ++ ++ if (stream_put_tlv_hdr(s, BGP_LS_ATTR_SRLG, srlg_len) < 0) ++ return -1; ++ for (int i = 0; i < attr->srlg_count; i++) { ++ uint32_t srlg_be = htonl(attr->srlg_values[i]); ++ ++ if (STREAM_WRITEABLE(s) < 4) ++ return -1; ++ stream_put(s, &srlg_be, 4); ++ } ++ } ++ } ++ ++ /* Link Name (TLV 1098) */ ++ if (BGP_LS_TLV_CHECK(attr->present_tlvs, BGP_LS_ATTR_LINK_NAME_BIT)) { ++ if (attr->link_name) { ++ uint16_t len = strlen(attr->link_name); ++ ++ if (stream_put_tlv(s, BGP_LS_ATTR_LINK_NAME, len, attr->link_name) < 0) ++ return -1; ++ } ++ } ++ ++ /* Adjacency SID (TLV 1099) */ ++ if (BGP_LS_TLV_CHECK(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__); ++ } else { ++ if (stream_put_tlv_hdr(s, BGP_LS_ATTR_ADJ_SID, sid_len + 4) < 0) ++ return -1; ++ if (STREAM_WRITEABLE(s) < (size_t)sid_len + 4) ++ return -1; ++ stream_putc(s, attr->adj_sid[i].flags); ++ stream_putc(s, attr->adj_sid[i].weight); ++ stream_putw(s, 0); ++ if (sid_len == 3) ++ stream_put3(s, attr->adj_sid[i].sid); ++ else ++ stream_putl(s, attr->adj_sid[i].sid); ++ } ++ } ++ ++ /* Extended Admin Group (TLV 1173) */ ++ if (BGP_LS_TLV_CHECK(attr->present_tlvs, BGP_LS_ATTR_EXT_ADMIN_GROUP_BIT)) { ++ size_t nb_words = admin_group_nb_words(&attr->ext_admin_group); ++ ++ if (nb_words > 0) { ++ uint16_t ag_len = nb_words * 4; /* Each word is 4 bytes */ ++ ++ if (stream_put_tlv_hdr(s, BGP_LS_ATTR_EXTENDED_ADMIN_GROUP, ag_len) < 0) ++ return -1; ++ ++ /* Encode each 32-bit word */ ++ for (size_t i = 0; i < nb_words; i++) { ++ uint32_t word = admin_group_get_offset(&attr->ext_admin_group, i); ++ uint32_t word_be = htonl(word); ++ ++ if (STREAM_WRITEABLE(s) < 4) ++ return -1; ++ stream_put(s, &word_be, 4); ++ } ++ } ++ } ++ ++ /* Unidirectional Link Delay (TLV 1114) */ ++ if (BGP_LS_TLV_CHECK(attr->present_tlvs, BGP_LS_ATTR_DELAY_BIT)) { ++ uint32_t delay_be = htonl(attr->delay); ++ ++ if (stream_put_tlv(s, BGP_LS_ATTR_UNIDIRECTIONAL_LINK_DELAY, 4, &delay_be) < 0) ++ return -1; ++ } ++ ++ /* Min/Max Unidirectional Link Delay (TLV 1115) */ ++ if (BGP_LS_TLV_CHECK(attr->present_tlvs, BGP_LS_ATTR_MIN_MAX_DELAY_BIT)) { ++ uint32_t min_delay_be = htonl(attr->min_delay); ++ uint32_t max_delay_be = htonl(attr->max_delay); ++ ++ if (stream_put_tlv_hdr(s, BGP_LS_ATTR_MIN_MAX_UNIDIRECTIONAL_LINK_DELAY, 8) < 0) ++ return -1; ++ ++ if (STREAM_WRITEABLE(s) < 8) ++ return -1; ++ stream_put(s, &min_delay_be, 4); ++ stream_put(s, &max_delay_be, 4); ++ } ++ ++ /* Unidirectional Delay Variation (TLV 1116) */ ++ if (BGP_LS_TLV_CHECK(attr->present_tlvs, BGP_LS_ATTR_JITTER_BIT)) { ++ uint32_t jitter_be = htonl(attr->jitter); ++ ++ if (stream_put_tlv(s, BGP_LS_ATTR_UNIDIRECTIONAL_DELAY_VARIATION, 4, &jitter_be) < ++ 0) ++ return -1; ++ } ++ ++ /* Unidirectional Packet Loss (TLV 1117) */ ++ if (BGP_LS_TLV_CHECK(attr->present_tlvs, BGP_LS_ATTR_PKT_LOSS_BIT)) { ++ uint32_t pkt_loss_be = htonl(attr->pkt_loss); ++ ++ if (stream_put_tlv(s, BGP_LS_ATTR_UNIDIRECTIONAL_LINK_LOSS, 4, &pkt_loss_be) < 0) ++ return -1; ++ } ++ ++ /* Unidirectional Residual Bandwidth (TLV 1118) */ ++ if (BGP_LS_TLV_CHECK(attr->present_tlvs, BGP_LS_ATTR_RESIDUAL_BW_BIT)) { ++ if (stream_putf_tlv(s, BGP_LS_ATTR_UNIDIRECTIONAL_RESIDUAL_BANDWIDTH, ++ attr->residual_bw) < 0) ++ return -1; ++ } ++ ++ /* Unidirectional Available Bandwidth (TLV 1119) */ ++ if (BGP_LS_TLV_CHECK(attr->present_tlvs, BGP_LS_ATTR_AVAILABLE_BW_BIT)) { ++ if (stream_putf_tlv(s, BGP_LS_ATTR_UNIDIRECTIONAL_AVAILABLE_BANDWIDTH, ++ attr->available_bw) < 0) ++ return -1; ++ } ++ ++ /* Unidirectional Utilized Bandwidth (TLV 1120) */ ++ if (BGP_LS_TLV_CHECK(attr->present_tlvs, BGP_LS_ATTR_UTILIZED_BW_BIT)) { ++ if (stream_putf_tlv(s, BGP_LS_ATTR_UNIDIRECTIONAL_UTILIZED_BANDWIDTH, ++ attr->utilized_bw) < 0) ++ return -1; ++ } ++ ++ /* IGP Flags (TLV 1152) */ ++ if (BGP_LS_TLV_CHECK(attr->present_tlvs, BGP_LS_ATTR_IGP_FLAGS_BIT)) { ++ if (stream_put_tlv(s, BGP_LS_ATTR_IGP_FLAGS, 1, &attr->igp_flags) < 0) ++ return -1; ++ } ++ ++ /* Route Tags (TLV 1153) */ ++ if (BGP_LS_TLV_CHECK(attr->present_tlvs, BGP_LS_ATTR_ROUTE_TAG_BIT)) { ++ if (attr->route_tags && attr->route_tag_count > 0) { ++ uint16_t tag_len = attr->route_tag_count * 4; ++ ++ if (stream_put_tlv_hdr(s, BGP_LS_ATTR_ROUTE_TAG, tag_len) < 0) ++ return -1; ++ for (int i = 0; i < attr->route_tag_count; i++) { ++ uint32_t tag_be = htonl(attr->route_tags[i]); ++ ++ if (STREAM_WRITEABLE(s) < 4) ++ return -1; ++ stream_put(s, &tag_be, 4); ++ } ++ } ++ } ++ ++ /* Extended Tags (TLV 1154) */ ++ if (BGP_LS_TLV_CHECK(attr->present_tlvs, BGP_LS_ATTR_EXTENDED_TAG_BIT)) { ++ if (attr->extended_tags && attr->extended_tag_count > 0) { ++ uint16_t tag_len = attr->extended_tag_count * 8; ++ ++ if (stream_put_tlv_hdr(s, BGP_LS_ATTR_EXTENDED_TAG, tag_len) < 0) ++ return -1; ++ for (int i = 0; i < attr->extended_tag_count; i++) { ++ uint64_t tag_be = htonll(attr->extended_tags[i]); ++ ++ if (STREAM_WRITEABLE(s) < 8) ++ return -1; ++ stream_put(s, &tag_be, 8); ++ } ++ } ++ } ++ ++ /* Prefix Metric (TLV 1155) */ ++ if (BGP_LS_TLV_CHECK(attr->present_tlvs, BGP_LS_ATTR_PREFIX_METRIC_BIT)) { ++ uint32_t metric_be = htonl(attr->prefix_metric); ++ ++ if (stream_put_tlv(s, BGP_LS_ATTR_PREFIX_METRIC, 4, &metric_be) < 0) ++ return -1; ++ } ++ ++ /* OSPF Forwarding Address (TLV 1156) - IPv4 or IPv6 */ ++ if (BGP_LS_TLV_CHECK(attr->present_tlvs, BGP_LS_ATTR_OSPF_FWD_ADDR_BIT)) { ++ /* Check which address family is present */ ++ if (attr->ospf_fwd_addr.s_addr != 0) { ++ /* IPv4 forwarding address */ ++ if (stream_put_tlv(s, BGP_LS_ATTR_OSPF_FWD_ADDR, 4, &attr->ospf_fwd_addr) < ++ 0) ++ return -1; ++ } else if (!IN6_IS_ADDR_UNSPECIFIED(&attr->ospf_fwd_addr6)) { ++ /* IPv6 forwarding address */ ++ if (stream_put_tlv(s, BGP_LS_ATTR_OSPF_FWD_ADDR, 16, ++ &attr->ospf_fwd_addr6) < 0) ++ return -1; ++ } ++ } ++ ++ /* Prefix SID (TLV 1158) */ ++ if (BGP_LS_TLV_CHECK(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_put_tlv_hdr(s, BGP_LS_ATTR_PREFIX_SID, 4 + sid_len) < 0) ++ return -1; ++ if (STREAM_WRITEABLE(s) < (size_t)4 + sid_len) ++ return -1; ++ 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; ++ ++ 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 (BGP_LS_TLV_CHECK(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); ++ BGP_LS_TLV_SET(desc->present_tlvs, BGP_LS_NODE_DESC_AS_BIT); ++ break; ++ ++ case BGP_LS_TLV_BGP_LS_ID: ++ if (BGP_LS_TLV_CHECK(desc->present_tlvs, BGP_LS_NODE_DESC_BGP_LS_ID_BIT)) { ++ 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); ++ BGP_LS_TLV_SET(desc->present_tlvs, BGP_LS_NODE_DESC_BGP_LS_ID_BIT); ++ break; ++ ++ case BGP_LS_TLV_OSPF_AREA_ID: ++ if (BGP_LS_TLV_CHECK(desc->present_tlvs, BGP_LS_NODE_DESC_OSPF_AREA_BIT)) { ++ 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); ++ BGP_LS_TLV_SET(desc->present_tlvs, BGP_LS_NODE_DESC_OSPF_AREA_BIT); ++ break; ++ ++ case BGP_LS_TLV_IGP_ROUTER_ID: ++ if (BGP_LS_TLV_CHECK(desc->present_tlvs, BGP_LS_NODE_DESC_IGP_ROUTER_BIT)) { ++ 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); ++ BGP_LS_TLV_SET(desc->present_tlvs, BGP_LS_NODE_DESC_IGP_ROUTER_BIT); ++ has_igp_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) { ++ flog_warn(EC_BGP_LS_PACKET, ++ "BGP-LS: Mandatory IGP 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 (BGP_LS_TLV_CHECK(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); ++ BGP_LS_TLV_SET(desc->present_tlvs, BGP_LS_LINK_DESC_LINK_ID_BIT); ++ break; ++ ++ case BGP_LS_TLV_IPV4_INTF_ADDR: ++ if (BGP_LS_TLV_CHECK(desc->present_tlvs, BGP_LS_LINK_DESC_IPV4_INTF_BIT)) { ++ 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); ++ BGP_LS_TLV_SET(desc->present_tlvs, BGP_LS_LINK_DESC_IPV4_INTF_BIT); ++ break; ++ ++ case BGP_LS_TLV_IPV4_NEIGH_ADDR: ++ if (BGP_LS_TLV_CHECK(desc->present_tlvs, BGP_LS_LINK_DESC_IPV4_NEIGH_BIT)) { ++ 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); ++ BGP_LS_TLV_SET(desc->present_tlvs, BGP_LS_LINK_DESC_IPV4_NEIGH_BIT); ++ break; ++ ++ case BGP_LS_TLV_IPV6_INTF_ADDR: ++ if (BGP_LS_TLV_CHECK(desc->present_tlvs, BGP_LS_LINK_DESC_IPV6_INTF_BIT)) { ++ 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); ++ BGP_LS_TLV_SET(desc->present_tlvs, BGP_LS_LINK_DESC_IPV6_INTF_BIT); ++ break; ++ ++ case BGP_LS_TLV_IPV6_NEIGH_ADDR: ++ if (BGP_LS_TLV_CHECK(desc->present_tlvs, BGP_LS_LINK_DESC_IPV6_NEIGH_BIT)) { ++ 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); ++ BGP_LS_TLV_SET(desc->present_tlvs, BGP_LS_LINK_DESC_IPV6_NEIGH_BIT); ++ break; ++ ++ case BGP_LS_TLV_MT_ID: ++ if (BGP_LS_TLV_CHECK(desc->present_tlvs, BGP_LS_LINK_DESC_MT_ID_BIT)) { ++ flog_warn(EC_BGP_LS_PACKET, ++ "BGP-LS: duplicate MT-ID TLV in link descriptor"); ++ goto error; ++ } ++ /* Variable length: 2*n bytes where n is number of MT-IDs */ ++ if (tlv_len % 2 != 0 || tlv_len > BGP_LS_MAX_MT_ID * 2) { ++ 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); ++ BGP_LS_TLV_SET(desc->present_tlvs, BGP_LS_LINK_DESC_MT_ID_BIT); ++ break; ++ ++ case BGP_LS_TLV_REMOTE_AS_NUMBER: ++ if (BGP_LS_TLV_CHECK(desc->present_tlvs, BGP_LS_LINK_DESC_REMOTE_AS_BIT)) { ++ 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); ++ BGP_LS_TLV_SET(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 (BGP_LS_TLV_CHECK(desc->present_tlvs, BGP_LS_PREFIX_DESC_MT_ID_BIT)) { ++ flog_warn(EC_BGP_LS_PACKET, ++ "BGP-LS: duplicate MT-ID TLV in prefix descriptor"); ++ goto error; ++ } ++ /* Variable length: 2*n bytes where n is number of MT-IDs */ ++ if (tlv_len % 2 != 0 || tlv_len > BGP_LS_MAX_MT_ID * 2) { ++ 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); ++ BGP_LS_TLV_SET(desc->present_tlvs, BGP_LS_PREFIX_DESC_MT_ID_BIT); ++ break; ++ ++ case BGP_LS_TLV_OSPF_ROUTE_TYPE: ++ if (BGP_LS_TLV_CHECK(desc->present_tlvs, ++ BGP_LS_PREFIX_DESC_OSPF_ROUTE_BIT)) { ++ 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); ++ BGP_LS_TLV_SET(desc->present_tlvs, BGP_LS_PREFIX_DESC_OSPF_ROUTE_BIT); ++ break; ++ ++ case BGP_LS_TLV_IP_REACH_INFO: ++ if (BGP_LS_TLV_CHECK(desc->present_tlvs, BGP_LS_PREFIX_DESC_IP_REACH_BIT)) { ++ 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); ++ } ++ ++ BGP_LS_TLV_SET(desc->present_tlvs, BGP_LS_PREFIX_DESC_IP_REACH_BIT); ++ has_ip_reach = true; ++ 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; ++ } ++ ++ /* 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 (2 bytes) */ ++ if (STREAM_READABLE(s) < 2) { ++ flog_warn(EC_BGP_LS_PACKET, "BGP-LS: Not enough data for NLRI type"); ++ return -1; ++ } ++ nlri_type = stream_getw(s); ++ ++ /* Read NLRI Length (2 bytes) */ ++ if (STREAM_READABLE(s) < 2) { ++ flog_warn(EC_BGP_LS_PACKET, "BGP-LS: Not enough data for NLRI length"); ++ return -1; ++ } ++ nlri_length = stream_getw(s); ++ ++ /* Check if stream has enough data for NLRI */ ++ 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 (BGP_LS_TLV_CHECK(attr->present_tlvs, BGP_LS_ATTR_NODE_FLAGS_BIT)) { ++ flog_warn(EC_BGP_UPDATE_RCV, "BGP-LS: duplicate Node Flags TLV"); ++ return -1; ++ } ++ ++ attr->node_flags = stream_getc(s); ++ BGP_LS_TLV_SET(attr->present_tlvs, BGP_LS_ATTR_NODE_FLAGS_BIT); ++ ++ /* 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 (BGP_LS_TLV_CHECK(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'; ++ BGP_LS_TLV_SET(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 (BGP_LS_TLV_CHECK(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; ++ BGP_LS_TLV_SET(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 (BGP_LS_TLV_CHECK(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); ++ BGP_LS_TLV_SET(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 (BGP_LS_TLV_CHECK(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; ++ BGP_LS_TLV_SET(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 (BGP_LS_TLV_CHECK(attr->present_tlvs, BGP_LS_ATTR_ADMIN_GROUP_BIT)) { ++ flog_warn(EC_BGP_UPDATE_RCV, "BGP-LS: duplicate Admin Group TLV"); ++ return -1; ++ } ++ ++ attr->admin_group = stream_getl(s); ++ BGP_LS_TLV_SET(attr->present_tlvs, BGP_LS_ATTR_ADMIN_GROUP_BIT); ++ ++ 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 (BGP_LS_TLV_CHECK(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); ++ BGP_LS_TLV_SET(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 (BGP_LS_TLV_CHECK(attr->present_tlvs, BGP_LS_ATTR_MAX_RESV_BW_BIT)) { ++ flog_warn(EC_BGP_UPDATE_RCV, "BGP-LS: duplicate Maximum Reservable Bandwidth TLV"); ++ return -1; ++ } ++ ++ bw_bits = stream_getl(s); ++ memcpy(&attr->max_resv_bw, &bw_bits, 4); ++ BGP_LS_TLV_SET(attr->present_tlvs, BGP_LS_ATTR_MAX_RESV_BW_BIT); ++ ++ 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 (BGP_LS_TLV_CHECK(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); ++ } ++ BGP_LS_TLV_SET(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 (BGP_LS_TLV_CHECK(attr->present_tlvs, BGP_LS_ATTR_TE_METRIC_BIT)) { ++ flog_warn(EC_BGP_UPDATE_RCV, "BGP-LS: duplicate TE Default Metric TLV"); ++ return -1; ++ } ++ ++ attr->te_metric = stream_getl(s); ++ BGP_LS_TLV_SET(attr->present_tlvs, BGP_LS_ATTR_TE_METRIC_BIT); ++ ++ 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 (BGP_LS_TLV_CHECK(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; ++ BGP_LS_TLV_SET(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 (BGP_LS_TLV_CHECK(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; ++ BGP_LS_TLV_SET(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 (BGP_LS_TLV_CHECK(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'; ++ BGP_LS_TLV_SET(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_endp(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++; ++ ++ BGP_LS_TLV_SET(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 (BGP_LS_TLV_CHECK(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); ++ ++ BGP_LS_TLV_SET(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 (BGP_LS_TLV_CHECK(attr->present_tlvs, BGP_LS_ATTR_DELAY_BIT)) { ++ flog_warn(EC_BGP_UPDATE_RCV, "BGP-LS: duplicate Unidirectional Link Delay TLV"); ++ return -1; ++ } ++ ++ attr->delay = stream_getl(s); ++ BGP_LS_TLV_SET(attr->present_tlvs, BGP_LS_ATTR_DELAY_BIT); ++ ++ 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 (BGP_LS_TLV_CHECK(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); ++ BGP_LS_TLV_SET(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 (BGP_LS_TLV_CHECK(attr->present_tlvs, BGP_LS_ATTR_JITTER_BIT)) { ++ flog_warn(EC_BGP_UPDATE_RCV, ++ "BGP-LS: duplicate Unidirectional Delay Variation TLV"); ++ return -1; ++ } ++ ++ attr->jitter = stream_getl(s); ++ BGP_LS_TLV_SET(attr->present_tlvs, BGP_LS_ATTR_JITTER_BIT); ++ ++ 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 (BGP_LS_TLV_CHECK(attr->present_tlvs, BGP_LS_ATTR_PKT_LOSS_BIT)) { ++ flog_warn(EC_BGP_UPDATE_RCV, "BGP-LS: duplicate Unidirectional Link Loss TLV"); ++ return -1; ++ } ++ ++ attr->pkt_loss = stream_getl(s); ++ BGP_LS_TLV_SET(attr->present_tlvs, BGP_LS_ATTR_PKT_LOSS_BIT); ++ ++ 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 (BGP_LS_TLV_CHECK(attr->present_tlvs, BGP_LS_ATTR_RESIDUAL_BW_BIT)) { ++ flog_warn(EC_BGP_UPDATE_RCV, "BGP-LS: duplicate Residual Bandwidth TLV"); ++ return -1; ++ } ++ ++ stream_get(&attr->residual_bw, s, 4); ++ BGP_LS_TLV_SET(attr->present_tlvs, BGP_LS_ATTR_RESIDUAL_BW_BIT); ++ ++ 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 (BGP_LS_TLV_CHECK(attr->present_tlvs, BGP_LS_ATTR_AVAILABLE_BW_BIT)) { ++ flog_warn(EC_BGP_UPDATE_RCV, "BGP-LS: duplicate Available Bandwidth TLV"); ++ return -1; ++ } ++ ++ stream_get(&attr->available_bw, s, 4); ++ BGP_LS_TLV_SET(attr->present_tlvs, BGP_LS_ATTR_AVAILABLE_BW_BIT); ++ ++ 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 (BGP_LS_TLV_CHECK(attr->present_tlvs, BGP_LS_ATTR_UTILIZED_BW_BIT)) { ++ flog_warn(EC_BGP_UPDATE_RCV, "BGP-LS: duplicate Utilized Bandwidth TLV"); ++ return -1; ++ } ++ ++ stream_get(&attr->utilized_bw, s, 4); ++ BGP_LS_TLV_SET(attr->present_tlvs, BGP_LS_ATTR_UTILIZED_BW_BIT); ++ ++ 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 (BGP_LS_TLV_CHECK(attr->present_tlvs, BGP_LS_ATTR_IGP_FLAGS_BIT)) { ++ flog_warn(EC_BGP_UPDATE_RCV, "BGP-LS: duplicate IGP Flags TLV"); ++ return -1; ++ } ++ ++ attr->igp_flags = stream_getc(s); ++ BGP_LS_TLV_SET(attr->present_tlvs, BGP_LS_ATTR_IGP_FLAGS_BIT); ++ ++ 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 (BGP_LS_TLV_CHECK(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; ++ BGP_LS_TLV_SET(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 (BGP_LS_TLV_CHECK(attr->present_tlvs, BGP_LS_ATTR_PREFIX_METRIC_BIT)) { ++ flog_warn(EC_BGP_UPDATE_RCV, "BGP-LS: duplicate Prefix Metric TLV"); ++ return -1; ++ } ++ ++ attr->prefix_metric = stream_getl(s); ++ BGP_LS_TLV_SET(attr->present_tlvs, BGP_LS_ATTR_PREFIX_METRIC_BIT); ++ ++ 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 (BGP_LS_TLV_CHECK(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); ++ } ++ ++ BGP_LS_TLV_SET(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 (BGP_LS_TLV_CHECK(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); ++ BGP_LS_TLV_SET(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 (BGP_LS_TLV_CHECK(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); ++ BGP_LS_TLV_SET(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 (BGP_LS_TLV_CHECK(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); ++ BGP_LS_TLV_SET(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 (BGP_LS_TLV_CHECK(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); ++ BGP_LS_TLV_SET(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 (BGP_LS_TLV_CHECK(attr->present_tlvs, BGP_LS_ATTR_LINK_PROTECTION_BIT)) { ++ flog_warn(EC_BGP_UPDATE_RCV, "BGP-LS: duplicate Link Protection Type TLV"); ++ return -1; ++ } ++ ++ attr->link_protection = stream_getw(s); ++ BGP_LS_TLV_SET(attr->present_tlvs, BGP_LS_ATTR_LINK_PROTECTION_BIT); ++ 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 (BGP_LS_TLV_CHECK(attr->present_tlvs, BGP_LS_ATTR_MPLS_PROTOCOL_BIT)) { ++ flog_warn(EC_BGP_UPDATE_RCV, "BGP-LS: duplicate MPLS Protocol Mask TLV"); ++ return -1; ++ } ++ ++ attr->mpls_protocol_mask = stream_getc(s); ++ BGP_LS_TLV_SET(attr->present_tlvs, BGP_LS_ATTR_MPLS_PROTOCOL_BIT); ++ 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 (BGP_LS_TLV_CHECK(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); ++ ++ BGP_LS_TLV_SET(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 (BGP_LS_TLV_CHECK(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); ++ BGP_LS_TLV_SET(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 (BGP_LS_TLV_CHECK(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])); ++ } ++ } ++ } ++ 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 (BGP_LS_TLV_CHECK(ls_attr->present_tlvs, BGP_LS_ATTR_NODE_NAME_BIT)) ++ json_object_string_add(json_ls_attr, "nodeName", ++ ls_attr->node_name ? ls_attr->node_name : "(null)"); ++ ++ if (BGP_LS_TLV_CHECK(ls_attr->present_tlvs, BGP_LS_ATTR_ISIS_AREA_BIT)) { ++ 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 (BGP_LS_TLV_CHECK(ls_attr->present_tlvs, BGP_LS_ATTR_IPV4_ROUTER_ID_LOCAL_BIT)) ++ json_object_string_addf(json_ls_attr, "routerIdLocal", "%pI4", ++ &ls_attr->ipv4_router_id_local); ++ ++ /* Local TE Router-ID (IPv6) */ ++ if (BGP_LS_TLV_CHECK(ls_attr->present_tlvs, BGP_LS_ATTR_IPV6_ROUTER_ID_LOCAL_BIT)) ++ json_object_string_addf(json_ls_attr, "routerIdLocalV6", "%pI6", ++ &ls_attr->ipv6_router_id_local); ++ ++ /* Link bandwidth */ ++ if (BGP_LS_TLV_CHECK(ls_attr->present_tlvs, BGP_LS_ATTR_MAX_LINK_BW_BIT)) ++ json_object_double_add(json_ls_attr, "maxLinkBandwidth", ls_attr->max_link_bw); ++ ++ if (BGP_LS_TLV_CHECK(ls_attr->present_tlvs, BGP_LS_ATTR_MAX_RESV_BW_BIT)) ++ json_object_double_add(json_ls_attr, "maxResvLinkBandwidth", ++ ls_attr->max_resv_bw); ++ ++ if (BGP_LS_TLV_CHECK(ls_attr->present_tlvs, BGP_LS_ATTR_UNRESV_BW_BIT)) { ++ 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 (BGP_LS_TLV_CHECK(ls_attr->present_tlvs, BGP_LS_ATTR_IGP_METRIC_BIT)) ++ json_object_int_add(json_ls_attr, "igpMetric", ls_attr->igp_metric); ++ ++ /* TE Default Metric */ ++ if (BGP_LS_TLV_CHECK(ls_attr->present_tlvs, BGP_LS_ATTR_TE_METRIC_BIT)) ++ json_object_int_add(json_ls_attr, "teMetric", ls_attr->te_metric); ++ ++ /* SR Capabilities */ ++ if (BGP_LS_TLV_CHECK(ls_attr->present_tlvs, BGP_LS_ATTR_SR_CAPABILITIES_BIT)) { ++ 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 (BGP_LS_TLV_CHECK(ls_attr->present_tlvs, BGP_LS_ATTR_NODE_MSD_BIT)) ++ json_object_int_add(json_ls_attr, "msd", ls_attr->msd); ++ ++ /* Administrative Group */ ++ if (BGP_LS_TLV_CHECK(ls_attr->present_tlvs, BGP_LS_ATTR_ADMIN_GROUP_BIT)) ++ json_object_int_add(json_ls_attr, "adminGroup", ls_attr->admin_group); ++ ++ /* Link Protection Type */ ++ if (BGP_LS_TLV_CHECK(ls_attr->present_tlvs, BGP_LS_ATTR_LINK_PROTECTION_BIT)) ++ json_object_int_add(json_ls_attr, "linkProtection", ls_attr->link_protection); ++ ++ /* MPLS Protocol Mask */ ++ if (BGP_LS_TLV_CHECK(ls_attr->present_tlvs, BGP_LS_ATTR_MPLS_PROTOCOL_BIT)) ++ json_object_int_add(json_ls_attr, "mplsProtocolMask", ++ ls_attr->mpls_protocol_mask); ++ ++ /* SRLG */ ++ if (BGP_LS_TLV_CHECK(ls_attr->present_tlvs, BGP_LS_ATTR_SRLG_BIT)) { ++ 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 (BGP_LS_TLV_CHECK(ls_attr->present_tlvs, BGP_LS_ATTR_LINK_NAME_BIT)) ++ json_object_string_add(json_ls_attr, "linkName", ls_attr->link_name); ++ ++ /* Adjacency SID */ ++ if (BGP_LS_TLV_CHECK(ls_attr->present_tlvs, BGP_LS_ATTR_ADJ_SID_BIT)) { ++ 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 (BGP_LS_TLV_CHECK(ls_attr->present_tlvs, BGP_LS_ATTR_DELAY_BIT)) ++ json_object_int_add(json_ls_attr, "delay", ls_attr->delay); ++ ++ /* Min/Max Delay */ ++ if (BGP_LS_TLV_CHECK(ls_attr->present_tlvs, BGP_LS_ATTR_MIN_MAX_DELAY_BIT)) { ++ json_object_int_add(json_ls_attr, "minDelay", ls_attr->min_delay); ++ json_object_int_add(json_ls_attr, "maxDelay", ls_attr->max_delay); ++ } ++ ++ /* Delay Variation */ ++ if (BGP_LS_TLV_CHECK(ls_attr->present_tlvs, BGP_LS_ATTR_JITTER_BIT)) ++ json_object_int_add(json_ls_attr, "jitter", ls_attr->jitter); ++ ++ /* Packet Loss */ ++ if (BGP_LS_TLV_CHECK(ls_attr->present_tlvs, BGP_LS_ATTR_PKT_LOSS_BIT)) ++ json_object_double_add(json_ls_attr, "loss", ls_attr->pkt_loss * LOSS_PRECISION); ++ ++ /* Residual Bandwidth */ ++ if (BGP_LS_TLV_CHECK(ls_attr->present_tlvs, BGP_LS_ATTR_RESIDUAL_BW_BIT)) ++ json_object_double_add(json_ls_attr, "residualBandwidth", ls_attr->residual_bw); ++ ++ /* Available Bandwidth */ ++ if (BGP_LS_TLV_CHECK(ls_attr->present_tlvs, BGP_LS_ATTR_AVAILABLE_BW_BIT)) ++ json_object_double_add(json_ls_attr, "availableBandwidth", ls_attr->available_bw); ++ ++ /* Utilized Bandwidth */ ++ if (BGP_LS_TLV_CHECK(ls_attr->present_tlvs, BGP_LS_ATTR_UTILIZED_BW_BIT)) ++ json_object_double_add(json_ls_attr, "utilizedBandwidth", ls_attr->utilized_bw); ++ ++ /* IGP Flags (for prefixes) */ ++ if (BGP_LS_TLV_CHECK(ls_attr->present_tlvs, BGP_LS_ATTR_IGP_FLAGS_BIT)) ++ json_object_string_addf(json_ls_attr, "flags", "0x%x", ls_attr->igp_flags); ++ ++ /* Route Tags */ ++ if (BGP_LS_TLV_CHECK(ls_attr->present_tlvs, BGP_LS_ATTR_ROUTE_TAG_BIT)) { ++ 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 (BGP_LS_TLV_CHECK(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 (BGP_LS_TLV_CHECK(ls_attr->present_tlvs, BGP_LS_ATTR_PREFIX_METRIC_BIT)) ++ json_object_int_add(json_ls_attr, "prefixMetric", ls_attr->prefix_metric); ++ ++ /* OSPF Forwarding Address (IPv4) */ ++ if (BGP_LS_TLV_CHECK(ls_attr->present_tlvs, BGP_LS_ATTR_OSPF_FWD_ADDR_BIT)) { ++ if (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 (BGP_LS_TLV_CHECK(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 (BGP_LS_TLV_CHECK(ls_attr->present_tlvs, BGP_LS_ATTR_NODE_NAME_BIT)) { ++ CHECK_WRAP(); ++ col += vty_out(vty, "Node Name: %s", ++ ls_attr->node_name ? ls_attr->node_name : "(null)"); ++ } ++ ++ /* IS-IS Area Identifier */ ++ if (BGP_LS_TLV_CHECK(ls_attr->present_tlvs, BGP_LS_ATTR_ISIS_AREA_BIT)) { ++ 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 (BGP_LS_TLV_CHECK(ls_attr->present_tlvs, BGP_LS_ATTR_IPV4_ROUTER_ID_LOCAL_BIT)) { ++ CHECK_WRAP(); ++ col += vty_out(vty, "Local TE Router-ID: %pI4", &ls_attr->ipv4_router_id_local); ++ } ++ ++ /* Local TE Router-ID (IPv6) */ ++ if (BGP_LS_TLV_CHECK(ls_attr->present_tlvs, BGP_LS_ATTR_IPV6_ROUTER_ID_LOCAL_BIT)) { ++ CHECK_WRAP(); ++ col += vty_out(vty, "Local TE Router-ID: %pI6", &ls_attr->ipv6_router_id_local); ++ } ++ ++ /* Link bandwidth */ ++ if (BGP_LS_TLV_CHECK(ls_attr->present_tlvs, BGP_LS_ATTR_MAX_LINK_BW_BIT)) { ++ CHECK_WRAP(); ++ col += vty_out(vty, "Maximum Link BW (kbits/sec): %.0f", ++ ls_attr->max_link_bw / 1000.0); ++ } ++ ++ if (BGP_LS_TLV_CHECK(ls_attr->present_tlvs, BGP_LS_ATTR_MAX_RESV_BW_BIT)) { ++ CHECK_WRAP(); ++ col += vty_out(vty, "Maximum Reserv Link BW (kbits/sec): %.0f", ++ ls_attr->max_resv_bw / 1000.0); ++ } ++ ++ if (BGP_LS_TLV_CHECK(ls_attr->present_tlvs, BGP_LS_ATTR_UNRESV_BW_BIT)) { ++ CHECK_WRAP(); ++ col += vty_out(vty, "Maximum Unreserv Link BW (kbits/sec):"); ++ for (int j = 0; j < 8; j++) ++ col += vty_out(vty, " %.0f", ls_attr->unreserved_bw[j] / 1000.0); ++ } ++ ++ if (BGP_LS_TLV_CHECK(ls_attr->present_tlvs, BGP_LS_ATTR_IGP_METRIC_BIT)) { ++ CHECK_WRAP(); ++ col += vty_out(vty, "metric: %u", ls_attr->igp_metric); ++ } ++ ++ /* TE Default Metric */ ++ if (BGP_LS_TLV_CHECK(ls_attr->present_tlvs, BGP_LS_ATTR_TE_METRIC_BIT)) { ++ CHECK_WRAP(); ++ col += vty_out(vty, "TE Default Metric: %u", ls_attr->te_metric); ++ } ++ ++ /* Administrative Group */ ++ if (BGP_LS_TLV_CHECK(ls_attr->present_tlvs, BGP_LS_ATTR_ADMIN_GROUP_BIT)) { ++ CHECK_WRAP(); ++ col += vty_out(vty, "Administrative Group: 0x%08x", ls_attr->admin_group); ++ } ++ ++ /* Link Protection Type */ ++ if (BGP_LS_TLV_CHECK(ls_attr->present_tlvs, BGP_LS_ATTR_LINK_PROTECTION_BIT)) { ++ CHECK_WRAP(); ++ vty_out(vty, "Link Protection Type: 0x%04x", ls_attr->link_protection); ++ } ++ ++ /* MPLS Protocol Mask */ ++ if (BGP_LS_TLV_CHECK(ls_attr->present_tlvs, BGP_LS_ATTR_MPLS_PROTOCOL_BIT)) { ++ CHECK_WRAP(); ++ col += vty_out(vty, "MPLS Protocol Mask: 0x%02x", ls_attr->mpls_protocol_mask); ++ } ++ ++ /* SR Capabilities */ ++ if (BGP_LS_TLV_CHECK(ls_attr->present_tlvs, BGP_LS_ATTR_SR_CAPABILITIES_BIT)) { ++ 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); ++ } ++ ++ /* Node MSD */ ++ if (BGP_LS_TLV_CHECK(ls_attr->present_tlvs, BGP_LS_ATTR_NODE_MSD_BIT)) { ++ CHECK_WRAP(); ++ col += vty_out(vty, "MSD: %d", ls_attr->msd); ++ } ++ ++ /* SRLG */ ++ if (BGP_LS_TLV_CHECK(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 (BGP_LS_TLV_CHECK(ls_attr->present_tlvs, BGP_LS_ATTR_LINK_NAME_BIT)) { ++ CHECK_WRAP(); ++ col += vty_out(vty, "Link Name: %s", ls_attr->link_name); ++ } ++ ++ /* Adjacency SID */ ++ if (BGP_LS_TLV_CHECK(ls_attr->present_tlvs, BGP_LS_ATTR_ADJ_SID_BIT)) { ++ 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 (BGP_LS_TLV_CHECK(ls_attr->present_tlvs, BGP_LS_ATTR_DELAY_BIT)) { ++ CHECK_WRAP(); ++ col += vty_out(vty, "Link Delay: %u us", ls_attr->delay); ++ } ++ ++ /* Min/Max Delay */ ++ if (BGP_LS_TLV_CHECK(ls_attr->present_tlvs, BGP_LS_ATTR_MIN_MAX_DELAY_BIT)) { ++ CHECK_WRAP(); ++ col += vty_out(vty, "Min Delay: %u us Max Delay: %u us", ls_attr->min_delay, ++ ls_attr->max_delay); ++ } ++ ++ /* Delay Variation */ ++ if (BGP_LS_TLV_CHECK(ls_attr->present_tlvs, BGP_LS_ATTR_JITTER_BIT)) { ++ CHECK_WRAP(); ++ col += vty_out(vty, "Delay Variation: %u us", ls_attr->jitter); ++ } ++ ++ /* Packet Loss */ ++ if (BGP_LS_TLV_CHECK(ls_attr->present_tlvs, BGP_LS_ATTR_PKT_LOSS_BIT)) { ++ CHECK_WRAP(); ++ col += vty_out(vty, "Packet Loss: %g (%%)", ++ (float)(ls_attr->pkt_loss * LOSS_PRECISION)); ++ } ++ ++ /* Residual Bandwidth */ ++ if (BGP_LS_TLV_CHECK(ls_attr->present_tlvs, BGP_LS_ATTR_RESIDUAL_BW_BIT)) { ++ CHECK_WRAP(); ++ col += vty_out(vty, "Residual BW (kbits/sec): %.0f", ls_attr->residual_bw / 1000.0); ++ } ++ ++ /* Available Bandwidth */ ++ if (BGP_LS_TLV_CHECK(ls_attr->present_tlvs, BGP_LS_ATTR_AVAILABLE_BW_BIT)) { ++ CHECK_WRAP(); ++ col += vty_out(vty, "Available BW (kbits/sec): %.0f", ++ ls_attr->available_bw / 1000.0); ++ } ++ ++ /* Utilized Bandwidth */ ++ if (BGP_LS_TLV_CHECK(ls_attr->present_tlvs, BGP_LS_ATTR_UTILIZED_BW_BIT)) { ++ CHECK_WRAP(); ++ col += vty_out(vty, "Utilized BW (kbits/sec): %.0f", ls_attr->utilized_bw / 1000.0); ++ } ++ ++ /* IGP Flags (for prefixes) */ ++ if (BGP_LS_TLV_CHECK(ls_attr->present_tlvs, BGP_LS_ATTR_IGP_FLAGS_BIT)) { ++ CHECK_WRAP(); ++ col += vty_out(vty, "IGP flags: 0x%02x", ls_attr->igp_flags); ++ } ++ ++ /* Route Tags */ ++ if (BGP_LS_TLV_CHECK(ls_attr->present_tlvs, BGP_LS_ATTR_ROUTE_TAG_BIT)) { ++ 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 (BGP_LS_TLV_CHECK(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 (BGP_LS_TLV_CHECK(ls_attr->present_tlvs, BGP_LS_ATTR_PREFIX_METRIC_BIT)) { ++ CHECK_WRAP(); ++ col += vty_out(vty, "Metric: %u", ls_attr->prefix_metric); ++ } ++ ++ /* OSPF Forwarding Address (IPv4) */ ++ if (BGP_LS_TLV_CHECK(ls_attr->present_tlvs, BGP_LS_ATTR_OSPF_FWD_ADDR_BIT)) { ++ if (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 (BGP_LS_TLV_CHECK(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) ++{ ++ char ipaddr_str[INET6_ADDRSTRLEN]; ++ const char *nlri_type_str = NULL; ++ const char *protocol_str = NULL; ++ ++ if (!nlri) ++ return; ++ ++ /* 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 (nlri->nlri_data.node.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", nlri->nlri_data.node.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 (BGP_LS_TLV_CHECK(local_node->present_tlvs, BGP_LS_NODE_DESC_AS_BIT)) ++ vty_out(vty, "\tAS Number: %u\n", local_node->asn); ++ if (BGP_LS_TLV_CHECK(local_node->present_tlvs, BGP_LS_NODE_DESC_OSPF_AREA_BIT)) ++ vty_out(vty, "\tArea ID: %pI4\n", (in_addr_t *)&local_node->ospf_area_id); ++ if (BGP_LS_TLV_CHECK(local_node->present_tlvs, BGP_LS_NODE_DESC_BGP_LS_ID_BIT)) ++ vty_out(vty, "\tBGP Identifier: %u\n", local_node->bgp_ls_id); ++ if (BGP_LS_TLV_CHECK(local_node->present_tlvs, BGP_LS_NODE_DESC_IGP_ROUTER_BIT)) { ++ if (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) { ++ inet_ntop(AF_INET6, local_node->igp_router_id, ipaddr_str, ++ sizeof(ipaddr_str)); ++ vty_out(vty, "\tRouter ID IPv6: %s\n", ipaddr_str); ++ } ++ } ++ } ++ ++ /* 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 (BGP_LS_TLV_CHECK(remote_node->present_tlvs, BGP_LS_NODE_DESC_AS_BIT)) ++ vty_out(vty, "\tAS Number: %u\n", remote_node->asn); ++ if (BGP_LS_TLV_CHECK(remote_node->present_tlvs, BGP_LS_NODE_DESC_OSPF_AREA_BIT)) ++ vty_out(vty, "\tArea ID: %pI4\n", (in_addr_t *)&remote_node->ospf_area_id); ++ if (BGP_LS_TLV_CHECK(remote_node->present_tlvs, BGP_LS_NODE_DESC_BGP_LS_ID_BIT)) ++ vty_out(vty, "\tBGP Identifier: %u\n", remote_node->bgp_ls_id); ++ if (BGP_LS_TLV_CHECK(remote_node->present_tlvs, BGP_LS_NODE_DESC_IGP_ROUTER_BIT)) { ++ if (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) { ++ inet_ntop(AF_INET6, remote_node->igp_router_id, ipaddr_str, ++ sizeof(ipaddr_str)); ++ vty_out(vty, "\tRouter ID IPv6: %s\n", ipaddr_str); ++ } ++ } ++ ++ /* Display Link Descriptor */ ++ struct bgp_ls_link_descriptor *link_desc = &nlri->nlri_data.link.link_desc; ++ ++ vty_out(vty, "Link Descriptor:\n"); ++ ++ if (BGP_LS_TLV_CHECK(link_desc->present_tlvs, BGP_LS_LINK_DESC_LINK_ID_BIT)) { ++ vty_out(vty, "\tLink ID: %u.%u\n", link_desc->link_local_id, ++ link_desc->link_remote_id); ++ } ++ ++ if (BGP_LS_TLV_CHECK(link_desc->present_tlvs, BGP_LS_LINK_DESC_IPV4_INTF_BIT)) { ++ inet_ntop(AF_INET, &link_desc->ipv4_intf_addr, ipaddr_str, ++ sizeof(ipaddr_str)); ++ vty_out(vty, "\tLocal Interface Address IPv4: %s\n", ipaddr_str); ++ } ++ if (BGP_LS_TLV_CHECK(link_desc->present_tlvs, BGP_LS_LINK_DESC_IPV4_NEIGH_BIT)) { ++ inet_ntop(AF_INET, &link_desc->ipv4_neigh_addr, ipaddr_str, ++ sizeof(ipaddr_str)); ++ vty_out(vty, "\tNeighbor Interface Address IPv4: %s\n", ipaddr_str); ++ } ++ if (BGP_LS_TLV_CHECK(link_desc->present_tlvs, BGP_LS_LINK_DESC_IPV6_INTF_BIT)) { ++ inet_ntop(AF_INET6, &link_desc->ipv6_intf_addr, ipaddr_str, ++ sizeof(ipaddr_str)); ++ vty_out(vty, "\tLocal Interface Address IPv6: %s\n", ipaddr_str); ++ } ++ if (BGP_LS_TLV_CHECK(link_desc->present_tlvs, BGP_LS_LINK_DESC_IPV6_NEIGH_BIT)) { ++ inet_ntop(AF_INET6, &link_desc->ipv6_neigh_addr, ipaddr_str, ++ sizeof(ipaddr_str)); ++ vty_out(vty, "\tNeighbor Interface Address IPv6: %s\n", ipaddr_str); ++ } ++ ++ /* Display Link Descriptor Multi-Topology info */ ++ if (BGP_LS_TLV_CHECK(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 (BGP_LS_TLV_CHECK(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); ++ } ++ ++ /* Multi-Topology */ ++ if (BGP_LS_TLV_CHECK(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..b491c78734 +--- /dev/null ++++ b/bgpd/bgp_ls_nlri.h +@@ -0,0 +1,895 @@ ++// 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 */ ++}; ++ ++/* ++ * 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 */ ++}; ++ ++/* ++ * 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-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 ++ * =========================================================================== ++ */ ++ ++/* ++ * TLV Presence Bitmask Macros ++ * Used to track which optional TLVs are present in descriptors ++ */ ++#define BGP_LS_TLV_SET(bitmap, bit) ((bitmap) |= (1ULL << (bit))) ++#define BGP_LS_TLV_CHECK(bitmap, bit) ((bitmap) & (1ULL << (bit))) ++#define BGP_LS_TLV_UNSET(bitmap, bit) ((bitmap) &= ~(1ULL << (bit))) ++#define BGP_LS_TLV_RESET(bitmap) ((bitmap) = 0) ++ ++/* Bit positions for Node Descriptor TLVs */ ++#define BGP_LS_NODE_DESC_AS_BIT 0 ++#define BGP_LS_NODE_DESC_BGP_LS_ID_BIT 1 ++#define BGP_LS_NODE_DESC_OSPF_AREA_BIT 2 ++#define BGP_LS_NODE_DESC_IGP_ROUTER_BIT 3 ++ ++/* Bit positions for Link Descriptor TLVs */ ++#define BGP_LS_LINK_DESC_LINK_ID_BIT 0 ++#define BGP_LS_LINK_DESC_IPV4_INTF_BIT 1 ++#define BGP_LS_LINK_DESC_IPV4_NEIGH_BIT 2 ++#define BGP_LS_LINK_DESC_IPV6_INTF_BIT 3 ++#define BGP_LS_LINK_DESC_IPV6_NEIGH_BIT 4 ++#define BGP_LS_LINK_DESC_MT_ID_BIT 5 ++#define BGP_LS_LINK_DESC_REMOTE_AS_BIT 6 ++ ++/* Bit positions for Prefix Descriptor TLVs */ ++#define BGP_LS_PREFIX_DESC_MT_ID_BIT 0 ++#define BGP_LS_PREFIX_DESC_OSPF_ROUTE_BIT 1 ++#define BGP_LS_PREFIX_DESC_IP_REACH_BIT 2 ++ ++/* 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 */ ++ ++/* ++ * 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 */ ++ ++/* ++ * Bit positions for attribute presence bitmasks ++ */ ++#define BGP_LS_ATTR_NODE_FLAGS_BIT 0 ++#define BGP_LS_ATTR_NODE_NAME_BIT 1 ++#define BGP_LS_ATTR_ISIS_AREA_BIT 2 ++#define BGP_LS_ATTR_SR_CAPABILITIES_BIT 3 ++#define BGP_LS_ATTR_SR_ALGORITHM_BIT 4 ++#define BGP_LS_ATTR_SR_LOCAL_BLOCK_BIT 5 ++#define BGP_LS_ATTR_NODE_MSD_BIT 6 ++#define BGP_LS_ATTR_IPV4_ROUTER_ID_LOCAL_BIT 7 ++#define BGP_LS_ATTR_IPV6_ROUTER_ID_LOCAL_BIT 8 ++#define BGP_LS_ATTR_IPV4_ROUTER_ID_REMOTE_BIT 9 ++#define BGP_LS_ATTR_IPV6_ROUTER_ID_REMOTE_BIT 10 ++#define BGP_LS_ATTR_ADMIN_GROUP_BIT 11 ++#define BGP_LS_ATTR_MAX_LINK_BW_BIT 12 ++#define BGP_LS_ATTR_MAX_RESV_BW_BIT 13 ++#define BGP_LS_ATTR_UNRESV_BW_BIT 14 ++#define BGP_LS_ATTR_TE_METRIC_BIT 15 ++#define BGP_LS_ATTR_LINK_PROTECTION_BIT 16 ++#define BGP_LS_ATTR_MPLS_PROTOCOL_BIT 17 ++#define BGP_LS_ATTR_IGP_METRIC_BIT 18 ++#define BGP_LS_ATTR_SRLG_BIT 19 ++#define BGP_LS_ATTR_LINK_NAME_BIT 20 ++#define BGP_LS_ATTR_ADJ_SID_BIT 21 ++#define BGP_LS_ATTR_LINK_MSD_BIT 22 ++#define BGP_LS_ATTR_EXT_ADMIN_GROUP_BIT 23 ++#define BGP_LS_ATTR_DELAY_BIT 24 ++#define BGP_LS_ATTR_MIN_MAX_DELAY_BIT 25 ++#define BGP_LS_ATTR_JITTER_BIT 26 ++#define BGP_LS_ATTR_PKT_LOSS_BIT 27 ++#define BGP_LS_ATTR_RESIDUAL_BW_BIT 28 ++#define BGP_LS_ATTR_AVAILABLE_BW_BIT 29 ++#define BGP_LS_ATTR_UTILIZED_BW_BIT 30 ++#define BGP_LS_ATTR_IGP_FLAGS_BIT 31 ++#define BGP_LS_ATTR_ROUTE_TAG_BIT 32 ++#define BGP_LS_ATTR_EXTENDED_TAG_BIT 33 ++#define BGP_LS_ATTR_PREFIX_METRIC_BIT 34 ++#define BGP_LS_ATTR_OSPF_FWD_ADDR_BIT 35 ++#define BGP_LS_ATTR_PREFIX_SID_BIT 36 ++#define BGP_LS_ATTR_RANGE_BIT 37 ++#define BGP_LS_ATTR_SID_LABEL_BIT 38 ++#define BGP_LS_ATTR_SRV6_LOCATOR_BIT 39 ++ ++/* ++ * 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) */ ++}; ++ ++/* ++ * 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 */ ++ 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); ++ ++/* Json conversion helpers */ ++extern const char *bgp_ls_ospf_route_type_str_json(enum bgp_ls_ospf_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..0750dd690a +--- /dev/null ++++ b/bgpd/bgp_ls_ted.c +@@ -0,0 +1,1403 @@ ++// 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; ++ attr->present_tlvs |= (1ULL << 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); ++ attr->present_tlvs |= (1ULL << 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); ++ attr->present_tlvs |= (1ULL << 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; ++ attr->present_tlvs |= (1ULL << BGP_LS_ATTR_SR_CAPABILITIES_BIT); ++ } ++ ++ /* Node MSD (TLV 266) */ ++ if (CHECK_FLAG(ls_node->flags, LS_NODE_MSD)) { ++ attr->msd = ls_node->msd; ++ attr->present_tlvs |= (1ULL << BGP_LS_ATTR_NODE_MSD_BIT); ++ } ++ ++ /* IPv4 Router-ID (TLV 1028) */ ++ if (CHECK_FLAG(ls_node->flags, LS_NODE_ROUTER_ID)) { ++ attr->ipv4_router_id_local = ls_node->router_id; ++ attr->present_tlvs |= (1ULL << BGP_LS_ATTR_IPV4_ROUTER_ID_LOCAL_BIT); ++ } ++ ++ /* IPv6 Router-ID (TLV 1029) */ ++ if (CHECK_FLAG(ls_node->flags, LS_NODE_ROUTER_ID6)) { ++ attr->ipv6_router_id_local = ls_node->router_id6; ++ attr->present_tlvs |= (1ULL << BGP_LS_ATTR_IPV6_ROUTER_ID_LOCAL_BIT); ++ } ++ ++ 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++; ++ ++ attr->present_tlvs |= (1ULL << 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; ++ attr->present_tlvs |= (1ULL << BGP_LS_ATTR_ADMIN_GROUP_BIT); ++ } ++ ++ /* Maximum Link Bandwidth (TLV 1089) */ ++ if (CHECK_FLAG(ls_attr->flags, LS_ATTR_MAX_BW)) { ++ attr->max_link_bw = ls_attr->standard.max_bw; ++ attr->present_tlvs |= (1ULL << BGP_LS_ATTR_MAX_LINK_BW_BIT); ++ } ++ ++ /* Maximum Reservable Bandwidth (TLV 1090) */ ++ if (CHECK_FLAG(ls_attr->flags, LS_ATTR_MAX_RSV_BW)) { ++ attr->max_resv_bw = ls_attr->standard.max_rsv_bw; ++ attr->present_tlvs |= (1ULL << BGP_LS_ATTR_MAX_RESV_BW_BIT); ++ } ++ ++ /* Unreserved Bandwidth (TLV 1091) */ ++ if (CHECK_FLAG(ls_attr->flags, LS_ATTR_UNRSV_BW)) { ++ for (int i = 0; i < BGP_LS_MAX_UNRESV_BW; i++) ++ attr->unreserved_bw[i] = ls_attr->standard.unrsv_bw[i]; ++ attr->present_tlvs |= (1ULL << BGP_LS_ATTR_UNRESV_BW_BIT); ++ } ++ ++ /* TE Default Metric (TLV 1092) */ ++ if (CHECK_FLAG(ls_attr->flags, LS_ATTR_TE_METRIC)) { ++ attr->te_metric = ls_attr->standard.te_metric; ++ attr->present_tlvs |= (1ULL << BGP_LS_ATTR_TE_METRIC_BIT); ++ } ++ ++ /* 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; ++ attr->present_tlvs |= (1ULL << 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]; ++ attr->present_tlvs |= (1ULL << 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); ++ attr->present_tlvs |= (1ULL << 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; ++ attr->present_tlvs |= (1ULL << BGP_LS_ATTR_IPV4_ROUTER_ID_REMOTE_BIT); ++ } ++ ++ /* Remote IPv6 Router-ID (TLV 1031) */ ++ if (CHECK_FLAG(ls_attr->flags, LS_ATTR_REMOTE_ADDR6)) { ++ attr->ipv6_router_id_remote = ls_attr->standard.remote_addr6; ++ attr->present_tlvs |= (1ULL << BGP_LS_ATTR_IPV6_ROUTER_ID_REMOTE_BIT); ++ } ++ ++ /* Extended Admin Group (TLV 1093) */ ++ if (CHECK_FLAG(ls_attr->flags, LS_ATTR_EXT_ADM_GRP)) { ++ admin_group_copy(&attr->ext_admin_group, &ls_attr->ext_admin_group); ++ attr->present_tlvs |= (1ULL << BGP_LS_ATTR_EXT_ADMIN_GROUP_BIT); ++ } ++ ++ /* Unidirectional Link Delay (TLV 1114) */ ++ if (CHECK_FLAG(ls_attr->flags, LS_ATTR_DELAY)) { ++ attr->delay = ls_attr->extended.delay; ++ attr->present_tlvs |= (1ULL << BGP_LS_ATTR_DELAY_BIT); ++ } ++ ++ /* Min/Max Unidirectional Link Delay (TLV 1115) */ ++ if (CHECK_FLAG(ls_attr->flags, LS_ATTR_MIN_MAX_DELAY)) { ++ attr->min_delay = ls_attr->extended.min_delay; ++ attr->max_delay = ls_attr->extended.max_delay; ++ attr->present_tlvs |= (1ULL << BGP_LS_ATTR_MIN_MAX_DELAY_BIT); ++ } ++ ++ /* Unidirectional Delay Variation (TLV 1116) */ ++ if (CHECK_FLAG(ls_attr->flags, LS_ATTR_JITTER)) { ++ attr->jitter = ls_attr->extended.jitter; ++ attr->present_tlvs |= (1ULL << BGP_LS_ATTR_JITTER_BIT); ++ } ++ ++ /* Unidirectional Packet Loss (TLV 1117) */ ++ if (CHECK_FLAG(ls_attr->flags, LS_ATTR_PACKET_LOSS)) { ++ attr->pkt_loss = ls_attr->extended.pkt_loss; ++ attr->present_tlvs |= (1ULL << BGP_LS_ATTR_PKT_LOSS_BIT); ++ } ++ ++ /* Unidirectional Residual Bandwidth (TLV 1118) */ ++ if (CHECK_FLAG(ls_attr->flags, LS_ATTR_RSV_BW)) { ++ attr->residual_bw = ls_attr->extended.rsv_bw; ++ attr->present_tlvs |= (1ULL << BGP_LS_ATTR_RESIDUAL_BW_BIT); ++ } ++ ++ /* Unidirectional Available Bandwidth (TLV 1119) */ ++ if (CHECK_FLAG(ls_attr->flags, LS_ATTR_AVA_BW)) { ++ attr->available_bw = ls_attr->extended.ava_bw; ++ attr->present_tlvs |= (1ULL << BGP_LS_ATTR_AVAILABLE_BW_BIT); ++ } ++ ++ /* Unidirectional Utilized Bandwidth (TLV 1120) */ ++ if (CHECK_FLAG(ls_attr->flags, LS_ATTR_USE_BW)) { ++ attr->utilized_bw = ls_attr->extended.used_bw; ++ attr->present_tlvs |= (1ULL << BGP_LS_ATTR_UTILIZED_BW_BIT); ++ } ++ ++ 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; ++ attr->present_tlvs |= (1ULL << 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; ++ attr->present_tlvs |= (1ULL << 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; ++ attr->present_tlvs |= (1ULL << BGP_LS_ATTR_EXTENDED_TAG_BIT); ++ } ++ ++ /* Prefix Metric (TLV 1155) */ ++ if (CHECK_FLAG(ls_prefix->flags, LS_PREF_METRIC)) { ++ attr->prefix_metric = ls_prefix->metric; ++ attr->present_tlvs |= (1ULL << BGP_LS_ATTR_PREFIX_METRIC_BIT); ++ } ++ ++ /* 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; ++ attr->present_tlvs |= (1ULL << 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); ++ BGP_LS_TLV_SET(nlri.nlri_data.node.local_node.present_tlvs, ++ BGP_LS_NODE_DESC_IGP_ROUTER_BIT); ++ ++ /* Set AS Number if available */ ++ if (CHECK_FLAG(vertex->node->flags, LS_NODE_AS_NUMBER)) { ++ nlri.nlri_data.node.local_node.asn = vertex->node->as_number; ++ BGP_LS_TLV_SET(nlri.nlri_data.node.local_node.present_tlvs, ++ BGP_LS_NODE_DESC_AS_BIT); ++ } ++ ++ /* Set OSPF Area ID if OSPF */ ++ if (protocol_id == BGP_LS_PROTO_OSPFV2 || protocol_id == BGP_LS_PROTO_OSPFV3) { ++ nlri.nlri_data.node.local_node.ospf_area_id = area_id; ++ BGP_LS_TLV_SET(nlri.nlri_data.node.local_node.present_tlvs, ++ BGP_LS_NODE_DESC_OSPF_AREA_BIT); ++ } ++ ++ /* 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); ++ BGP_LS_TLV_SET(nlri.nlri_data.node.local_node.present_tlvs, ++ BGP_LS_NODE_DESC_IGP_ROUTER_BIT); ++ ++ /* Set AS Number if available */ ++ if (CHECK_FLAG(vertex->node->flags, LS_NODE_AS_NUMBER)) { ++ nlri.nlri_data.node.local_node.asn = vertex->node->as_number; ++ BGP_LS_TLV_SET(nlri.nlri_data.node.local_node.present_tlvs, ++ BGP_LS_NODE_DESC_AS_BIT); ++ } ++ ++ /* Set OSPF Area ID if OSPF */ ++ if (protocol_id == BGP_LS_PROTO_OSPFV2 || protocol_id == BGP_LS_PROTO_OSPFV3) { ++ nlri.nlri_data.node.local_node.ospf_area_id = area_id; ++ BGP_LS_TLV_SET(nlri.nlri_data.node.local_node.present_tlvs, ++ BGP_LS_NODE_DESC_OSPF_AREA_BIT); ++ } ++ ++ /* 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); ++ BGP_LS_TLV_SET(nlri.nlri_data.link.local_node.present_tlvs, ++ BGP_LS_NODE_DESC_IGP_ROUTER_BIT); ++ ++ /* Set AS Number for Local Node if available */ ++ if (edge->source && edge->source->node && ++ CHECK_FLAG(edge->source->node->flags, LS_NODE_AS_NUMBER)) { ++ nlri.nlri_data.link.local_node.asn = edge->source->node->as_number; ++ BGP_LS_TLV_SET(nlri.nlri_data.link.local_node.present_tlvs, ++ BGP_LS_NODE_DESC_AS_BIT); ++ } ++ ++ /* Set Remote Node Descriptor */ ++ nlri.nlri_data.link.remote_node.igp_router_id_len = remote_router_id_len; ++ memcpy(nlri.nlri_data.link.remote_node.igp_router_id, remote_router_id, ++ remote_router_id_len); ++ BGP_LS_TLV_SET(nlri.nlri_data.link.remote_node.present_tlvs, ++ BGP_LS_NODE_DESC_IGP_ROUTER_BIT); ++ ++ /* Set AS Number for Remote Node if available */ ++ if (edge->destination && edge->destination->node && ++ CHECK_FLAG(edge->destination->node->flags, LS_NODE_AS_NUMBER)) { ++ nlri.nlri_data.link.remote_node.asn = edge->destination->node->as_number; ++ BGP_LS_TLV_SET(nlri.nlri_data.link.remote_node.present_tlvs, ++ BGP_LS_NODE_DESC_AS_BIT); ++ } ++ ++ /* Set OSPF Area ID if OSPF */ ++ if (protocol_id == BGP_LS_PROTO_OSPFV2 || protocol_id == BGP_LS_PROTO_OSPFV3) { ++ nlri.nlri_data.link.local_node.ospf_area_id = area_id; ++ BGP_LS_TLV_SET(nlri.nlri_data.link.local_node.present_tlvs, ++ BGP_LS_NODE_DESC_OSPF_AREA_BIT); ++ nlri.nlri_data.link.remote_node.ospf_area_id = area_id; ++ BGP_LS_TLV_SET(nlri.nlri_data.link.remote_node.present_tlvs, ++ BGP_LS_NODE_DESC_OSPF_AREA_BIT); ++ } ++ ++ /* Populate Link Descriptor from Link State edge attributes */ ++ /* Link Local/Remote Identifiers (TLV 258) */ ++ if (CHECK_FLAG(edge->attributes->flags, LS_ATTR_LOCAL_ID)) { ++ nlri.nlri_data.link.link_desc.link_local_id = edge->attributes->standard.local_id; ++ BGP_LS_TLV_SET(nlri.nlri_data.link.link_desc.present_tlvs, ++ BGP_LS_LINK_DESC_LINK_ID_BIT); ++ } ++ ++ if (CHECK_FLAG(edge->attributes->flags, LS_ATTR_NEIGH_ID)) { ++ nlri.nlri_data.link.link_desc.link_remote_id = edge->attributes->standard.remote_id; ++ BGP_LS_TLV_SET(nlri.nlri_data.link.link_desc.present_tlvs, ++ BGP_LS_LINK_DESC_LINK_ID_BIT); ++ } ++ ++ /* IPv4 Interface Address (TLV 259) */ ++ if (CHECK_FLAG(edge->attributes->flags, LS_ATTR_LOCAL_ADDR)) { ++ nlri.nlri_data.link.link_desc.ipv4_intf_addr = edge->attributes->standard.local; ++ BGP_LS_TLV_SET(nlri.nlri_data.link.link_desc.present_tlvs, ++ BGP_LS_LINK_DESC_IPV4_INTF_BIT); ++ } ++ ++ /* IPv4 Neighbor Address (TLV 260) */ ++ if (CHECK_FLAG(edge->attributes->flags, LS_ATTR_NEIGH_ADDR)) { ++ nlri.nlri_data.link.link_desc.ipv4_neigh_addr = edge->attributes->standard.remote; ++ BGP_LS_TLV_SET(nlri.nlri_data.link.link_desc.present_tlvs, ++ BGP_LS_LINK_DESC_IPV4_NEIGH_BIT); ++ } ++ ++ /* IPv6 Interface Address (TLV 261) */ ++ if (CHECK_FLAG(edge->attributes->flags, LS_ATTR_LOCAL_ADDR6)) { ++ nlri.nlri_data.link.link_desc.ipv6_intf_addr = edge->attributes->standard.local6; ++ BGP_LS_TLV_SET(nlri.nlri_data.link.link_desc.present_tlvs, ++ BGP_LS_LINK_DESC_IPV6_INTF_BIT); ++ } ++ ++ /* IPv6 Neighbor Address (TLV 262) */ ++ if (CHECK_FLAG(edge->attributes->flags, LS_ATTR_NEIGH_ADDR6)) { ++ nlri.nlri_data.link.link_desc.ipv6_neigh_addr = edge->attributes->standard.remote6; ++ BGP_LS_TLV_SET(nlri.nlri_data.link.link_desc.present_tlvs, ++ BGP_LS_LINK_DESC_IPV6_NEIGH_BIT); ++ } ++ ++ /* Remote AS Number (TLV 264) */ ++ if (CHECK_FLAG(edge->attributes->flags, LS_ATTR_REMOTE_AS)) { ++ nlri.nlri_data.link.link_desc.remote_asn = edge->attributes->standard.remote_as; ++ BGP_LS_TLV_SET(nlri.nlri_data.link.link_desc.present_tlvs, ++ BGP_LS_LINK_DESC_REMOTE_AS_BIT); ++ } ++ ++ /* 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); ++ BGP_LS_TLV_SET(nlri.nlri_data.link.local_node.present_tlvs, ++ BGP_LS_NODE_DESC_IGP_ROUTER_BIT); ++ ++ /* Set AS Number for Local Node if available */ ++ if (edge->source && edge->source->node && ++ CHECK_FLAG(edge->source->node->flags, LS_NODE_AS_NUMBER)) { ++ nlri.nlri_data.link.local_node.asn = edge->source->node->as_number; ++ BGP_LS_TLV_SET(nlri.nlri_data.link.local_node.present_tlvs, ++ BGP_LS_NODE_DESC_AS_BIT); ++ } ++ ++ /* Set Remote Node Descriptor */ ++ nlri.nlri_data.link.remote_node.igp_router_id_len = remote_router_id_len; ++ memcpy(nlri.nlri_data.link.remote_node.igp_router_id, remote_router_id, ++ remote_router_id_len); ++ BGP_LS_TLV_SET(nlri.nlri_data.link.remote_node.present_tlvs, ++ BGP_LS_NODE_DESC_IGP_ROUTER_BIT); ++ ++ /* Set AS Number for Remote Node if available */ ++ if (edge->destination && edge->destination->node && ++ CHECK_FLAG(edge->destination->node->flags, LS_NODE_AS_NUMBER)) { ++ nlri.nlri_data.link.remote_node.asn = edge->destination->node->as_number; ++ BGP_LS_TLV_SET(nlri.nlri_data.link.remote_node.present_tlvs, ++ BGP_LS_NODE_DESC_AS_BIT); ++ } ++ ++ /* Set OSPF Area ID if OSPF */ ++ if (protocol_id == BGP_LS_PROTO_OSPFV2 || protocol_id == BGP_LS_PROTO_OSPFV3) { ++ nlri.nlri_data.link.local_node.ospf_area_id = area_id; ++ BGP_LS_TLV_SET(nlri.nlri_data.link.local_node.present_tlvs, ++ BGP_LS_NODE_DESC_OSPF_AREA_BIT); ++ nlri.nlri_data.link.remote_node.ospf_area_id = area_id; ++ BGP_LS_TLV_SET(nlri.nlri_data.link.remote_node.present_tlvs, ++ BGP_LS_NODE_DESC_OSPF_AREA_BIT); ++ } ++ ++ /* Populate Link Descriptor from Link State edge attributes */ ++ /* Link Local/Remote Identifiers (TLV 258) */ ++ if (CHECK_FLAG(edge->attributes->flags, LS_ATTR_LOCAL_ID)) { ++ nlri.nlri_data.link.link_desc.link_local_id = edge->attributes->standard.local_id; ++ BGP_LS_TLV_SET(nlri.nlri_data.link.link_desc.present_tlvs, ++ BGP_LS_LINK_DESC_LINK_ID_BIT); ++ } ++ ++ if (CHECK_FLAG(edge->attributes->flags, LS_ATTR_NEIGH_ID)) { ++ nlri.nlri_data.link.link_desc.link_remote_id = edge->attributes->standard.remote_id; ++ BGP_LS_TLV_SET(nlri.nlri_data.link.link_desc.present_tlvs, ++ BGP_LS_LINK_DESC_LINK_ID_BIT); ++ } ++ ++ /* IPv4 Interface Address (TLV 259) */ ++ if (CHECK_FLAG(edge->attributes->flags, LS_ATTR_LOCAL_ADDR)) { ++ nlri.nlri_data.link.link_desc.ipv4_intf_addr = edge->attributes->standard.local; ++ BGP_LS_TLV_SET(nlri.nlri_data.link.link_desc.present_tlvs, ++ BGP_LS_LINK_DESC_IPV4_INTF_BIT); ++ } ++ ++ /* IPv4 Neighbor Address (TLV 260) */ ++ if (CHECK_FLAG(edge->attributes->flags, LS_ATTR_NEIGH_ADDR)) { ++ nlri.nlri_data.link.link_desc.ipv4_neigh_addr = edge->attributes->standard.remote; ++ BGP_LS_TLV_SET(nlri.nlri_data.link.link_desc.present_tlvs, ++ BGP_LS_LINK_DESC_IPV4_NEIGH_BIT); ++ } ++ ++ /* IPv6 Interface Address (TLV 261) */ ++ if (CHECK_FLAG(edge->attributes->flags, LS_ATTR_LOCAL_ADDR6)) { ++ nlri.nlri_data.link.link_desc.ipv6_intf_addr = edge->attributes->standard.local6; ++ BGP_LS_TLV_SET(nlri.nlri_data.link.link_desc.present_tlvs, ++ BGP_LS_LINK_DESC_IPV6_INTF_BIT); ++ } ++ ++ /* IPv6 Neighbor Address (TLV 262) */ ++ if (CHECK_FLAG(edge->attributes->flags, LS_ATTR_NEIGH_ADDR6)) { ++ nlri.nlri_data.link.link_desc.ipv6_neigh_addr = edge->attributes->standard.remote6; ++ BGP_LS_TLV_SET(nlri.nlri_data.link.link_desc.present_tlvs, ++ BGP_LS_LINK_DESC_IPV6_NEIGH_BIT); ++ } ++ ++ /* Remote AS Number (TLV 264) */ ++ if (CHECK_FLAG(edge->attributes->flags, LS_ATTR_REMOTE_AS)) { ++ nlri.nlri_data.link.link_desc.remote_asn = edge->attributes->standard.remote_as; ++ BGP_LS_TLV_SET(nlri.nlri_data.link.link_desc.present_tlvs, ++ BGP_LS_LINK_DESC_REMOTE_AS_BIT); ++ } ++ ++ /* 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); ++ BGP_LS_TLV_SET(nlri.nlri_data.prefix.local_node.present_tlvs, ++ BGP_LS_NODE_DESC_IGP_ROUTER_BIT); ++ ++ /* Set AS Number if available */ ++ if (subnet->vertex && subnet->vertex->node && ++ CHECK_FLAG(subnet->vertex->node->flags, LS_NODE_AS_NUMBER)) { ++ nlri.nlri_data.prefix.local_node.asn = subnet->vertex->node->as_number; ++ BGP_LS_TLV_SET(nlri.nlri_data.prefix.local_node.present_tlvs, ++ BGP_LS_NODE_DESC_AS_BIT); ++ } ++ ++ /* Set OSPF Area ID if OSPF */ ++ if (protocol_id == BGP_LS_PROTO_OSPFV2 || protocol_id == BGP_LS_PROTO_OSPFV3) { ++ nlri.nlri_data.prefix.local_node.ospf_area_id = area_id; ++ BGP_LS_TLV_SET(nlri.nlri_data.prefix.local_node.present_tlvs, ++ BGP_LS_NODE_DESC_OSPF_AREA_BIT); ++ } ++ ++ /* Set Prefix Descriptor */ ++ nlri.nlri_data.prefix.prefix_desc.prefix = *prefix; ++ apply_mask(&nlri.nlri_data.prefix.prefix_desc.prefix); ++ BGP_LS_TLV_SET(nlri.nlri_data.prefix.prefix_desc.present_tlvs, ++ BGP_LS_PREFIX_DESC_IP_REACH_BIT); ++ ++ /* 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); ++ BGP_LS_TLV_SET(nlri.nlri_data.prefix.local_node.present_tlvs, ++ BGP_LS_NODE_DESC_IGP_ROUTER_BIT); ++ ++ /* Set AS Number if available */ ++ if (subnet->vertex && subnet->vertex->node && ++ CHECK_FLAG(subnet->vertex->node->flags, LS_NODE_AS_NUMBER)) { ++ nlri.nlri_data.prefix.local_node.asn = subnet->vertex->node->as_number; ++ BGP_LS_TLV_SET(nlri.nlri_data.prefix.local_node.present_tlvs, ++ BGP_LS_NODE_DESC_AS_BIT); ++ } ++ ++ /* Set OSPF Area ID if OSPF */ ++ if (protocol_id == BGP_LS_PROTO_OSPFV2 || protocol_id == BGP_LS_PROTO_OSPFV3) { ++ nlri.nlri_data.prefix.local_node.ospf_area_id = area_id; ++ BGP_LS_TLV_SET(nlri.nlri_data.prefix.local_node.present_tlvs, ++ BGP_LS_NODE_DESC_OSPF_AREA_BIT); ++ } ++ ++ /* Set Prefix Descriptor */ ++ nlri.nlri_data.prefix.prefix_desc.prefix = *prefix; ++ BGP_LS_TLV_SET(nlri.nlri_data.prefix.prefix_desc.present_tlvs, ++ BGP_LS_PREFIX_DESC_IP_REACH_BIT); ++ ++ /* 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 7d6a0b2c24..f9e0a15bfe 100644 +--- a/bgpd/bgp_open.c ++++ b/bgpd/bgp_open.c +@@ -157,6 +157,11 @@ void bgp_capability_vty_out(struct vty *vty, struct peer *peer, bool use_json, + "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( +@@ -208,6 +213,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( +@@ -229,6 +239,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, ", +@@ -257,6 +270,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 ", +@@ -1511,7 +1527,8 @@ int bgp_open_option_parse(struct peer *peer, uint16_t length, + && !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 2157f2a02c..f41890e230 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 0db68920a0..157f5ec333 100644 +--- a/bgpd/bgp_route.c ++++ b/bgpd/bgp_route.c +@@ -64,6 +64,9 @@ + #include "bgpd/bgp_network.h" + #include "bgpd/bgp_trace.h" + #include "bgpd/bgp_rpki.h" ++#include "bgpd/bgp_bfd.h" ++#include "bgpd/bgp_ls_nlri.h" ++#include "bgpd/bgp_ls.h" + + #ifdef ENABLE_BGP_VNC + #include "bgpd/rfapi/rfapi_backend.h" +@@ -81,6 +84,10 @@ + + #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); +@@ -10256,6 +10263,18 @@ static void route_vty_out_route(struct bgp_dest *dest, const struct prefix *p, + 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); +@@ -10665,7 +10684,8 @@ void route_vty_out(struct vty *vty, const struct prefix *p, + 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(); + +@@ -11005,6 +11025,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)) { +@@ -11050,6 +11073,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)) { +@@ -11587,6 +11615,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; +@@ -11854,6 +11883,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); +@@ -12697,6 +12741,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)) +@@ -13084,6 +13137,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) +@@ -13165,6 +13220,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); +@@ -13473,6 +13536,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, +@@ -13734,6 +13824,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: +@@ -14652,6 +14743,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]", +@@ -15004,6 +15154,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; +@@ -15266,6 +15418,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; +@@ -15634,6 +15788,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, +@@ -17658,6 +17830,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); +@@ -17743,6 +17916,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 e9f9368a9a..26d69e31c1 100644 +--- a/bgpd/bgp_route.h ++++ b/bgpd/bgp_route.h +@@ -68,6 +68,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 ac84ec77a9..354ca8167d 100644 +--- a/bgpd/bgp_table.c ++++ b/bgpd/bgp_table.c +@@ -17,6 +17,7 @@ + #include "bgpd/bgp_table.h" + #include "bgp_addpath.h" + #include "bgp_trace.h" ++#include "bgp_ls.h" + + void bgp_table_lock(struct bgp_table *rt) + { +@@ -88,6 +89,13 @@ inline struct bgp_dest *bgp_dest_unlock_node(struct bgp_dest *dest) + &dest->tx_addpath, rt->afi, + rt->safi); + } ++ ++ 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 c3964433e6..4bb76705d5 100644 +--- a/bgpd/bgp_table.h ++++ b/bgpd/bgp_table.h +@@ -85,6 +85,8 @@ struct bgp_dest { + + mpls_label_t local_label; + ++ struct bgp_ls_nlri *ls_nlri; ++ + uint16_t flags; + #define BGP_NODE_PROCESS_SCHEDULED (1 << 0) + #define BGP_NODE_USER_CLEAR (1 << 1) +diff --git a/bgpd/bgp_updgrp.c b/bgpd/bgp_updgrp.c +index b680cdda27..c6c8ed62e1 100644 +--- a/bgpd/bgp_updgrp.c ++++ b/bgpd/bgp_updgrp.c +@@ -467,6 +467,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 449173d93f..315bacb2b0 100644 +--- a/bgpd/bgp_updgrp_packet.c ++++ b/bgpd/bgp_updgrp_packet.c +@@ -42,6 +42,7 @@ + #include "bgpd/bgp_mplsvpn.h" + #include "bgpd/bgp_label.h" + #include "bgpd/bgp_addpath.h" ++#include "bgpd/bgp_ls_nlri.h" + + /******************** + * PRIVATE FUNCTIONS +@@ -684,6 +685,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; +@@ -758,8 +760,7 @@ struct bpacket *subgroup_update_packet(struct update_subgroup *subgrp) + * attr. */ + total_attr_len = bgp_packet_attribute(NULL, peer, s, adv->baa->attr, + &vecarr, NULL, afi, safi, from, NULL, +- NULL, 0, 0, 0, path); +- ++ NULL, 0, 0, 0, path, dest->ls_nlri); + space_remaining = + STREAM_CONCAT_REMAIN(s, snlri, STREAM_SIZE(s)) + - BGP_MAX_PACKET_SIZE_OVERFLOW; +@@ -829,7 +830,39 @@ struct bpacket *subgroup_update_packet(struct update_subgroup *subgrp) + 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 +@@ -842,10 +875,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++; +@@ -960,6 +992,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) +@@ -1000,6 +1033,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)) + stream_put_prefix_addpath(s, dest_p, addpath_capable, +@@ -1009,6 +1089,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) { +@@ -1036,9 +1126,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++; +@@ -1168,7 +1257,7 @@ void subgroup_default_update_packet(struct update_subgroup *subgrp, + stream_putw(s, 0); + total_attr_len = bgp_packet_attribute(NULL, peer, s, attr, &vecarr, &p, afi, safi, from, + NULL, &label, num_labels, addpath_capable, +- BGP_ADDPATH_TX_ID_FOR_DEFAULT_ORIGINATE, NULL); ++ BGP_ADDPATH_TX_ID_FOR_DEFAULT_ORIGINATE, NULL, NULL); + + /* Set Total Path Attribute Length. */ + stream_putw_at(s, pos, total_attr_len); +@@ -1267,9 +1356,8 @@ void subgroup_default_withdraw_packet(struct update_subgroup *subgrp) + 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 5669baa87a..ebad07dac8 100644 +--- a/bgpd/bgp_vty.c ++++ b/bgpd/bgp_vty.c +@@ -170,6 +170,7 @@ static enum node_type bgp_node_type(afi_t afi, safi_t safi) + return BGP_VPNV4_NODE; + case SAFI_FLOWSPEC: + return BGP_FLOWSPECV4_NODE; ++ case SAFI_BGP_LS: + case SAFI_UNSPEC: + case SAFI_ENCAP: + case SAFI_EVPN: +@@ -190,6 +191,7 @@ static enum node_type bgp_node_type(afi_t afi, safi_t safi) + return BGP_VPNV6_NODE; + case SAFI_FLOWSPEC: + return BGP_FLOWSPECV6_NODE; ++ case SAFI_BGP_LS: + case SAFI_UNSPEC: + case SAFI_ENCAP: + case SAFI_EVPN: +@@ -200,6 +202,8 @@ static enum node_type bgp_node_type(afi_t afi, safi_t safi) + 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.. +@@ -241,6 +245,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"; +@@ -283,6 +290,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"; +@@ -419,6 +429,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; +@@ -451,6 +464,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; +@@ -581,6 +597,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: +@@ -601,6 +618,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: +@@ -611,6 +629,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: +@@ -622,6 +641,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"; +@@ -11211,6 +11246,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", +@@ -11450,7 +11497,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; + } +@@ -19816,6 +19864,9 @@ 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"); + +@@ -20458,6 +20509,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 +@@ -20582,6 +20636,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) +@@ -20901,6 +20963,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); +@@ -20917,6 +20980,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); +@@ -21303,6 +21367,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); +@@ -21317,6 +21382,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); +@@ -22362,6 +22428,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); +@@ -22375,6 +22442,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); +diff --git a/bgpd/bgp_zebra.c b/bgpd/bgp_zebra.c +index 1d33883e87..1b0464dbfb 100644 +--- a/bgpd/bgp_zebra.c ++++ b/bgpd/bgp_zebra.c +@@ -54,6 +54,7 @@ + #include "bgpd/bgp_trace.h" + #include "bgpd/bgp_community.h" + #include "bgpd/bgp_lcommunity.h" ++#include "bgpd/bgp_ls_ted.h" + + /* All information about zebra. */ + struct zclient *bgp_zclient = NULL; +@@ -4007,6 +4008,35 @@ static int bgp_zebra_process_srv6_locator_delete(ZAPI_CALLBACK_ARGS) + 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, +@@ -4035,9 +4065,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 0000ebd862..7e1f45e4c1 100644 +--- a/bgpd/bgpd.c ++++ b/bgpd/bgpd.c +@@ -79,6 +79,8 @@ + #include "bgpd/bgp_evpn_mh.h" + #include "bgpd/bgp_mac.h" + #include "bgpd/bgp_trace.h" ++#include "bgpd/bgp_ls.h" ++#include "bgpd/bgp_ls_ted.h" + + DEFINE_MTYPE_STATIC(BGPD, PEER_TX_SHUTDOWN_MSG, "Peer shutdown message (TX)"); + DEFINE_QOBJ_TYPE(bgp_master); +@@ -2545,6 +2547,19 @@ static int peer_activate_af(struct peer *peer, afi_t afi, safi_t safi) + 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; + } + +@@ -2609,6 +2624,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; + } + +@@ -2655,6 +2688,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; + } + +@@ -2705,6 +2743,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; + } + +@@ -3720,6 +3788,7 @@ peer_init: + bgp_evpn_vrf_es_init(bgp); + bgp_pbr_init(bgp); + bgp_srv6_init(bgp); ++ bgp_ls_init(bgp); + } + + /*initilize global GR FSM */ +@@ -4501,6 +4570,7 @@ void bgp_free(struct bgp *bgp) + + 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; +@@ -4879,7 +4949,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; +@@ -4900,7 +4971,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 c863ed2eeb..b1887d9222 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 + }; + +@@ -728,6 +729,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]; + +@@ -1773,6 +1777,8 @@ struct peer { + #define PEER_FLAG_CONFIG_DAMPENING (1ULL << 31) + #define PEER_FLAG_CONFIG_ENCAPSULATION_SRV6 (1ULL << 32) + #define PEER_FLAG_CONFIG_ENCAPSULATION_MPLS (1ULL << 33) ++#define PEER_FLAG_BGP_LS_IPV4 (1ULL << 35) ++#define PEER_FLAG_BGP_LS_IPV6 (1ULL << 36) + #define PEER_FLAG_ACCEPT_OWN (1ULL << 63) + + enum bgp_addpath_strat addpath_type[AFI_MAX][SAFI_MAX]; +@@ -2229,6 +2235,7 @@ struct bgp_nlri { + #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 +@@ -2834,6 +2841,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: +@@ -2854,6 +2862,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: +@@ -2864,11 +2873,28 @@ 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: ++ case SAFI_MPLS_VPN: ++ case SAFI_ENCAP: ++ case SAFI_FLOWSPEC: ++ case SAFI_UNSPEC: ++ case SAFI_MAX: ++ return BGP_AF_MAX; ++ } ++ break; ++ case AFI_BGP_LS: ++ switch (safi) { ++ case SAFI_BGP_LS: ++ return BGP_AF_BGP_LS; + case SAFI_UNICAST: + case SAFI_MULTICAST: + case SAFI_LABELED_UNICAST: + case SAFI_MPLS_VPN: + case SAFI_ENCAP: ++ case SAFI_EVPN: + case SAFI_FLOWSPEC: + case SAFI_UNSPEC: + case SAFI_MAX: +@@ -2921,7 +2947,8 @@ static inline int peer_group_af_configured(struct peer_group *group) + || 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 7f23df799a..c73390b629 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 c59a669b38..c04b3548a1 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 \ +@@ -135,6 +138,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..ba44a0f00f +--- /dev/null ++++ b/doc/user/bgp-linkstate.rst +@@ -0,0 +1,207 @@ ++.. _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 ++ ++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 3b8fc9fbc3..b0e880a62e 100644 +--- a/doc/user/bgp.rst ++++ b/doc/user/bgp.rst +@@ -5751,6 +5751,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_lsp.c b/isisd/isis_lsp.c +index 1a1140bc5d..9a18fc4596 100644 +--- a/isisd/isis_lsp.c ++++ b/isisd/isis_lsp.c +@@ -1075,7 +1075,7 @@ static void lsp_build(struct isis_lsp *lsp, struct isis_area *area) + + lsp_add_auth(lsp); + +- isis_tlvs_add_area_addresses(lsp->tlvs, area->area_addrs); ++ isis_tlvs_add_area_addresses(lsp->tlvs, &area->area_addrs); + + /* Protocols Supported */ + if (area->ip_circuits > 0 || area->ipv6_circuits > 0) { +diff --git a/isisd/isis_nb_config.c b/isisd/isis_nb_config.c +index 6bd3b39bc7..5deab4985d 100644 +--- a/isisd/isis_nb_config.c ++++ b/isisd/isis_nb_config.c +@@ -139,7 +139,6 @@ int isis_instance_area_address_create(struct nb_cb_create_args *args) + { + struct isis_area *area; + struct iso_address addr, *addrr = NULL, *addrp = NULL; +- struct listnode *node; + struct sysid_iter iter; + uint8_t buff[255]; + const char *net_title = yang_dnode_get_string(args->dnode, NULL); +@@ -192,11 +191,9 @@ int isis_instance_area_address_create(struct nb_cb_create_args *args) + area->isis->sysid_set = 1; + } else { + /* check that we don't already have this address */ +- for (ALL_LIST_ELEMENTS_RO(area->area_addrs, node, +- addrp)) { +- if ((addrp->addr_len + ISIS_SYS_ID_LEN +- + ISIS_NSEL_LEN) +- != (addrr->addr_len)) ++ frr_each (iso_address_list, &area->area_addrs, addrp) { ++ if ((addrp->addr_len + ISIS_SYS_ID_LEN + ISIS_NSEL_LEN) != ++ (addrr->addr_len)) + continue; + if (!memcmp(addrp->area_addr, addrr->area_addr, + addrr->addr_len)) { +@@ -208,11 +205,10 @@ int isis_instance_area_address_create(struct nb_cb_create_args *args) + + /*Forget the systemID part of the address */ + addrr->addr_len -= (ISIS_SYS_ID_LEN + ISIS_NSEL_LEN); +- assert(area->area_addrs); /* to silence scan-build sillyness */ +- listnode_add(area->area_addrs, addrr); ++ iso_address_list_add_tail(&area->area_addrs, addrr); + + /* only now we can safely generate our LSPs for this area */ +- if (listcount(area->area_addrs) > 0) { ++ if (iso_address_list_count(&area->area_addrs) > 0) { + if (area->is_type & IS_LEVEL_1) + lsp_generate(area, IS_LEVEL_1); + if (area->is_type & IS_LEVEL_2) +@@ -227,7 +223,6 @@ int isis_instance_area_address_create(struct nb_cb_create_args *args) + int isis_instance_area_address_destroy(struct nb_cb_destroy_args *args) + { + struct iso_address addr, *addrp = NULL; +- struct listnode *node; + uint8_t buff[255]; + struct isis_area *area; + const char *net_title; +@@ -243,21 +238,20 @@ int isis_instance_area_address_destroy(struct nb_cb_destroy_args *args) + memcpy(addr.area_addr, buff, (int)addr.addr_len); + area = nb_running_get_entry(args->dnode, NULL, true); + +- for (ALL_LIST_ELEMENTS_RO(area->area_addrs, node, addrp)) { +- if ((addrp->addr_len + ISIS_SYS_ID_LEN + 1) == addr.addr_len +- && !memcmp(addrp->area_addr, addr.area_addr, addr.addr_len)) ++ frr_each (iso_address_list, &area->area_addrs, addrp) { ++ if ((addrp->addr_len + ISIS_SYS_ID_LEN + 1) == addr.addr_len && ++ !memcmp(addrp->area_addr, addr.area_addr, addr.addr_len)) + break; + } + if (!addrp) + return NB_ERR_INCONSISTENCY; + +- listnode_delete(area->area_addrs, addrp); ++ iso_address_list_del(&area->area_addrs, addrp); + /* + * Last area address - reset the SystemID for this router + */ +- if (!memcmp(addrp->area_addr + addrp->addr_len, area->isis->sysid, +- ISIS_SYS_ID_LEN) && +- listcount(area->area_addrs) == 0) { ++ if (!memcmp(addrp->area_addr + addrp->addr_len, area->isis->sysid, ISIS_SYS_ID_LEN) && ++ iso_address_list_count(&area->area_addrs) == 0) { + for (ALL_LIST_ELEMENTS_RO(area->circuit_list, cnode, circuit)) + for (lvl = IS_LEVEL_1; lvl <= IS_LEVEL_2; ++lvl) { + if (circuit->u.bc.is_dr[lvl - 1]) +diff --git a/isisd/isis_pdu.c b/isisd/isis_pdu.c +index 1a3142e72a..6df061a2f8 100644 +--- a/isisd/isis_pdu.c ++++ b/isisd/isis_pdu.c +@@ -163,7 +163,7 @@ static int process_p2p_hello(struct iih_info *iih) + (iih->circuit->is_type_config == IS_LEVEL_1_AND_2) && + (iih->circ_type == IS_LEVEL_1))) { + if (!isis_tlvs_area_addresses_match(iih->tlvs, +- iih->circuit->area ++ &iih->circuit->area + ->area_addrs)) { + if (IS_DEBUG_ADJ_PACKETS) { + zlog_debug("ISIS-Adj (%s): Rcvd P2P IIH from (%s), cir type %s, cir id %u, length %u", +@@ -201,7 +201,7 @@ static int process_p2p_hello(struct iih_info *iih) + iih->calculated_type = IS_LEVEL_1_AND_2; + + if (!isis_tlvs_area_addresses_match(iih->tlvs, +- iih->circuit->area ++ &iih->circuit->area + ->area_addrs)) { + iih->calculated_type = IS_LEVEL_2; + } +@@ -277,7 +277,7 @@ static int process_p2p_hello(struct iih_info *iih) + + /* 8.2.5.2 a) a match was detected */ + if (isis_tlvs_area_addresses_match(iih->tlvs, +- iih->circuit->area->area_addrs)) { ++ &iih->circuit->area->area_addrs)) { + /* 8.2.5.2 a) 2) If the calculated type is L1 - table 5 */ + if (iih->calculated_type == IS_LEVEL_1) { + switch (iih->circ_type) { +@@ -362,7 +362,7 @@ static int process_p2p_hello(struct iih_info *iih) + } + } + /* 8.2.5.2 b) if no match was detected */ +- else if (listcount(iih->circuit->area->area_addrs) > 0) { ++ else if (iso_address_list_count(&iih->circuit->area->area_addrs) > 0) { + if (iih->calculated_type == IS_LEVEL_1) { + /* 8.2.5.2 b) 1) is_type L1 and adj is not up */ + if (adj->adj_state != ISIS_ADJ_UP) { +@@ -789,10 +789,10 @@ static int process_hello(uint8_t pdu_type, struct isis_circuit *circuit, + } + + if (!p2p_hello +- && (listcount(circuit->area->area_addrs) == 0 ++ && (iso_address_list_count(&circuit->area->area_addrs) == 0 + || (level == ISIS_LEVEL1 + && !isis_tlvs_area_addresses_match( +- iih.tlvs, circuit->area->area_addrs)))) { ++ iih.tlvs, &circuit->area->area_addrs)))) { + if (IS_DEBUG_ADJ_PACKETS) { + zlog_debug( + "ISIS-Adj (%s): Area mismatch, level %d IIH on %s", +@@ -1952,12 +1952,12 @@ int send_hello(struct isis_circuit *circuit, int level) + + isis_tlvs_add_auth(tlvs, &circuit->passwd); + +- if (!listcount(circuit->area->area_addrs)) { ++ if (!iso_address_list_count(&circuit->area->area_addrs)) { + isis_free_tlvs(tlvs); + return ISIS_WARNING; + } + +- isis_tlvs_add_area_addresses(tlvs, circuit->area->area_addrs); ++ isis_tlvs_add_area_addresses(tlvs, &circuit->area->area_addrs); + + if (circuit->circ_type == CIRCUIT_T_BROADCAST) { + isis_tlvs_add_lan_neighbors( +diff --git a/isisd/isis_snmp.c b/isisd/isis_snmp.c +index 24320e9ed9..624890d5ad 100644 +--- a/isisd/isis_snmp.c ++++ b/isisd/isis_snmp.c +@@ -839,7 +839,6 @@ static int isis_snmp_area_addr_lookup_exact(oid *oid_idx, size_t oid_idx_len, + size_t addr_len; + struct isis_area *area = NULL; + struct iso_address *addr = NULL; +- struct listnode *addr_node; + struct isis *isis = isis_lookup_by_vrfid(VRF_DEFAULT); + + if (isis == NULL) +@@ -861,7 +860,7 @@ static int isis_snmp_area_addr_lookup_exact(oid *oid_idx, size_t oid_idx_len, + return 0; + } + +- for (ALL_LIST_ELEMENTS_RO(area->area_addrs, addr_node, addr)) { ++ frr_each (iso_address_list, &area->area_addrs, addr) { + if (addr->addr_len != addr_len) + continue; + +@@ -889,7 +888,6 @@ static int isis_snmp_area_addr_lookup_next(oid *oid_idx, size_t oid_idx_len, + struct isis_area *area = NULL; + struct iso_address *found_addr = NULL; + struct iso_address *addr = NULL; +- struct listnode *addr_node; + struct isis *isis = isis_lookup_by_vrfid(VRF_DEFAULT); + + if (isis == NULL) +@@ -908,7 +906,7 @@ static int isis_snmp_area_addr_lookup_next(oid *oid_idx, size_t oid_idx_len, + if (!res) + return 0; + +- for (ALL_LIST_ELEMENTS_RO(area->area_addrs, addr_node, addr)) { ++ frr_each (iso_address_list, &area->area_addrs, addr) { + if (addr->addr_len < addr_len) + continue; + +diff --git a/isisd/isis_te.c b/isisd/isis_te.c +index cf92ae46e9..61fad6e8ee 100644 +--- a/isisd/isis_te.c ++++ b/isisd/isis_te.c +@@ -762,6 +762,17 @@ static struct ls_vertex *lsp_to_vertex(struct ls_ted *ted, struct isis_lsp *lsp) + vertex->status == NEW ? "Create" : "Found", vertex->key, + print_sys_hostname(old->adv.id.iso.sys_id)); + ++ if (iso_address_list_count(&lsp->area->area_addrs)) { ++ struct iso_address *addr = iso_address_list_first(&lsp->area->area_addrs); ++ ++ lnode.isis_area_id_len = addr->addr_len; ++ memcpy(lnode.isis_area_id, addr->area_addr, addr->addr_len); ++ SET_FLAG(lnode.flags, LS_NODE_ISIS_AREA_ID); ++ if (iso_address_list_count(&lsp->area->area_addrs) > 1) ++ zlog_warn("%s: Only one IS-IS Area ID is supported, ignoring others", ++ __func__); ++ } ++ + /* Fulfill Link State Node information */ + tlvs = lsp->tlvs; + if (tlvs) { +diff --git a/isisd/isis_tlvs.c b/isisd/isis_tlvs.c +index d29d917145..0e0d4cfd60 100644 +--- a/isisd/isis_tlvs.c ++++ b/isisd/isis_tlvs.c +@@ -7461,15 +7461,12 @@ void isis_tlvs_add_auth(struct isis_tlvs *tlvs, struct isis_passwd *passwd) + append_item(&tlvs->isis_auth, (struct isis_item *)auth); + } + +-void isis_tlvs_add_area_addresses(struct isis_tlvs *tlvs, +- struct list *addresses) ++void isis_tlvs_add_area_addresses(struct isis_tlvs *tlvs, struct iso_address_list_head *addresses) + { +- struct listnode *node; + struct iso_address *area_addr; + +- for (ALL_LIST_ELEMENTS_RO(addresses, node, area_addr)) { +- struct isis_area_address *a = +- XCALLOC(MTYPE_ISIS_TLV, sizeof(*a)); ++ frr_each (iso_address_list, addresses, area_addr) { ++ struct isis_area_address *a = XCALLOC(MTYPE_ISIS_TLV, sizeof(*a)); + + a->len = area_addr->addr_len; + memcpy(a->addr, area_addr->area_addr, ISO_ADDR_SIZE); +@@ -7660,20 +7657,17 @@ int isis_tlvs_auth_is_valid(struct isis_tlvs *tlvs, struct isis_passwd *passwd, + return ISIS_AUTH_FAILURE; + } + +-bool isis_tlvs_area_addresses_match(struct isis_tlvs *tlvs, +- struct list *addresses) ++bool isis_tlvs_area_addresses_match(struct isis_tlvs *tlvs, struct iso_address_list_head *addresses) + { + struct isis_area_address *addr_head; + + addr_head = (struct isis_area_address *)tlvs->area_addresses.head; +- for (struct isis_area_address *addr = addr_head; addr; +- addr = addr->next) { +- struct listnode *node; ++ for (struct isis_area_address *addr = addr_head; addr; addr = addr->next) { + struct iso_address *a; + +- for (ALL_LIST_ELEMENTS_RO(addresses, node, a)) { +- if (a->addr_len == addr->len +- && !memcmp(a->area_addr, addr->addr, addr->len)) ++ frr_each (iso_address_list, addresses, a) { ++ if (a->addr_len == addr->len && ++ !memcmp(a->area_addr, addr->addr, addr->len)) + return true; + } + } +diff --git a/isisd/isis_tlvs.h b/isisd/isis_tlvs.h +index 5798d318f2..f1bcde0830 100644 +--- a/isisd/isis_tlvs.h ++++ b/isisd/isis_tlvs.h +@@ -17,7 +17,7 @@ + #include "prefix.h" + #include "flex_algo.h" + #include "affinitymap.h" +- ++#include "lib/iso.h" + + #include "lib/srv6.h" + +@@ -778,14 +778,11 @@ struct list *isis_fragment_tlvs(struct isis_tlvs *tlvs, size_t size); + #endif + + void isis_tlvs_add_auth(struct isis_tlvs *tlvs, struct isis_passwd *passwd); +-void isis_tlvs_add_area_addresses(struct isis_tlvs *tlvs, +- struct list *addresses); +-void isis_tlvs_add_lan_neighbors(struct isis_tlvs *tlvs, +- struct list *neighbors); +-void isis_tlvs_set_protocols_supported(struct isis_tlvs *tlvs, +- struct nlpids *nlpids); +-void isis_tlvs_add_mt_router_info(struct isis_tlvs *tlvs, uint16_t mtid, +- bool overload, bool attached); ++void isis_tlvs_add_area_addresses(struct isis_tlvs *tlvs, struct iso_address_list_head *addresses); ++void isis_tlvs_add_lan_neighbors(struct isis_tlvs *tlvs, struct list *neighbors); ++void isis_tlvs_set_protocols_supported(struct isis_tlvs *tlvs, struct nlpids *nlpids); ++void isis_tlvs_add_mt_router_info(struct isis_tlvs *tlvs, uint16_t mtid, bool overload, ++ bool attached); + void isis_tlvs_add_ipv4_address(struct isis_tlvs *tlvs, struct in_addr *addr); + void isis_tlvs_add_ipv4_addresses(struct isis_tlvs *tlvs, + struct list *addresses); +@@ -796,7 +793,7 @@ void isis_tlvs_add_global_ipv6_addresses(struct isis_tlvs *tlvs, + int isis_tlvs_auth_is_valid(struct isis_tlvs *tlvs, struct isis_passwd *passwd, + struct stream *stream, bool is_lsp); + bool isis_tlvs_area_addresses_match(struct isis_tlvs *tlvs, +- struct list *addresses); ++ struct iso_address_list_head *addresses); + struct isis_adjacency; + void isis_tlvs_to_adj(struct isis_tlvs *tlvs, struct isis_adjacency *adj, + bool *changed); +diff --git a/isisd/isis_zebra.c b/isisd/isis_zebra.c +index 9389d6665a..2b8bc73eda 100644 +--- a/isisd/isis_zebra.c ++++ b/isisd/isis_zebra.c +@@ -68,7 +68,7 @@ static int isis_router_id_update_zebra(ZAPI_CALLBACK_ARGS) + + isis->router_id = router_id.u.prefix4.s_addr; + for (ALL_LIST_ELEMENTS_RO(isis->area_list, node, area)) +- if (listcount(area->area_addrs) > 0) ++ if (iso_address_list_count(&area->area_addrs) > 0) + lsp_regenerate_schedule(area, area->is_type, 0); + + return 0; +diff --git a/isisd/isisd.c b/isisd/isisd.c +index 78fbbe13b1..0de8793a50 100644 +--- a/isisd/isisd.c ++++ b/isisd/isisd.c +@@ -353,8 +353,7 @@ struct isis_area *isis_area_create(const char *area_tag, const char *vrf_name) + + area->circuit_list = list_new(); + area->adjacency_list = list_new(); +- area->area_addrs = list_new(); +- area->area_addrs->del = isis_area_address_delete; ++ iso_address_list_init(&area->area_addrs); + + if (!CHECK_FLAG(im->options, F_ISIS_UNIT_TEST)) + event_add_timer(master, lsp_tick, area, 1, &area->t_tick); +@@ -507,8 +506,8 @@ struct isis_area *isis_area_lookup_by_sysid(const uint8_t *sysid) + return NULL; + + for (ALL_LIST_ELEMENTS_RO(isis->area_list, node, area)) { +- if (listcount(area->area_addrs) > 0) { +- addr = listgetdata(listhead(area->area_addrs)); ++ addr = iso_address_list_first(&area->area_addrs); ++ if (addr != NULL) { + if (!memcmp(addr->area_addr + addr->addr_len, sysid, + ISIS_SYS_ID_LEN)) + return area; +@@ -593,8 +592,8 @@ void isis_area_destroy(struct isis_area *area) + if (!CHECK_FLAG(im->options, F_ISIS_UNIT_TEST)) + isis_redist_area_finish(area); + +- if (listcount(area->area_addrs) > 0) { +- addr = listgetdata(listhead(area->area_addrs)); ++ addr = iso_address_list_first(&area->area_addrs); ++ if (addr != NULL) { + if (!memcmp(addr->area_addr + addr->addr_len, area->isis->sysid, + ISIS_SYS_ID_LEN)) { + memset(area->isis->sysid, 0, ISIS_SYS_ID_LEN); +@@ -602,7 +601,9 @@ void isis_area_destroy(struct isis_area *area) + } + } + +- list_delete(&area->area_addrs); ++ while ((addr = iso_address_list_pop(&area->area_addrs))) ++ XFREE(MTYPE_ISIS_AREA_ADDR, addr); ++ iso_address_list_fini(&area->area_addrs); + + for (int i = SPF_PREFIX_PRIO_CRITICAL; i <= SPF_PREFIX_PRIO_MEDIUM; + i++) { +@@ -884,12 +885,11 @@ int area_net_title(struct vty *vty, const char *net_title) + VTY_DECLVAR_CONTEXT(isis_area, area); + struct iso_address *addr; + struct iso_address *addrp; +- struct listnode *node; + + uint8_t buff[255]; + + /* We check that we are not over the maximal number of addresses */ +- if (listcount(area->area_addrs) >= area->isis->max_area_addrs) { ++ if (iso_address_list_count(&area->area_addrs) >= area->isis->max_area_addrs) { + vty_out(vty, + "Maximum of area addresses (%d) already reached \n", + area->isis->max_area_addrs); +@@ -940,7 +940,7 @@ int area_net_title(struct vty *vty, const char *net_title) + } + + /* now we see that we don't already have this address */ +- for (ALL_LIST_ELEMENTS_RO(area->area_addrs, node, addrp)) { ++ frr_each (iso_address_list, &area->area_addrs, addrp) { + if ((addrp->addr_len + ISIS_SYS_ID_LEN + ISIS_NSEL_LEN) + != (addr->addr_len)) + continue; +@@ -956,10 +956,10 @@ int area_net_title(struct vty *vty, const char *net_title) + * Forget the systemID part of the address + */ + addr->addr_len -= (ISIS_SYS_ID_LEN + ISIS_NSEL_LEN); +- listnode_add(area->area_addrs, addr); ++ iso_address_list_add_tail(&area->area_addrs, addr); + + /* only now we can safely generate our LSPs for this area */ +- if (listcount(area->area_addrs) > 0) { ++ if (iso_address_list_count(&area->area_addrs) > 0) { + if (area->is_type & IS_LEVEL_1) + lsp_generate(area, IS_LEVEL_1); + if (area->is_type & IS_LEVEL_2) +@@ -973,7 +973,6 @@ int area_clear_net_title(struct vty *vty, const char *net_title) + { + VTY_DECLVAR_CONTEXT(isis_area, area); + struct iso_address addr, *addrp = NULL; +- struct listnode *node; + uint8_t buff[255]; + + addr.addr_len = dotformat2buff(buff, net_title); +@@ -986,7 +985,7 @@ int area_clear_net_title(struct vty *vty, const char *net_title) + + memcpy(addr.area_addr, buff, (int)addr.addr_len); + +- for (ALL_LIST_ELEMENTS_RO(area->area_addrs, node, addrp)) ++ frr_each (iso_address_list, &area->area_addrs, addrp) + if ((addrp->addr_len + ISIS_SYS_ID_LEN + 1) == addr.addr_len + && !memcmp(addrp->area_addr, addr.area_addr, addr.addr_len)) + break; +@@ -997,13 +996,13 @@ int area_clear_net_title(struct vty *vty, const char *net_title) + return CMD_ERR_NO_MATCH; + } + +- listnode_delete(area->area_addrs, addrp); ++ iso_address_list_del(&area->area_addrs, addrp); + XFREE(MTYPE_ISIS_AREA_ADDR, addrp); + + /* + * Last area address - reset the SystemID for this router + */ +- if (listcount(area->area_addrs) == 0) { ++ if (iso_address_list_count(&area->area_addrs) == 0) { + memset(area->isis->sysid, 0, ISIS_SYS_ID_LEN); + area->isis->sysid_set = 0; + if (IS_DEBUG_EVENTS) +@@ -2388,7 +2387,7 @@ static void common_isis_summary_json(struct json_object *json, + int level; + json_object *vrf_json, *areas_json, *area_json, *tx_pdu_json, *rx_pdu_json, *levels_json, + *level_json; +- struct listnode *node, *node2; ++ struct listnode *node; + struct isis_area *area; + time_t cur; + char uptime[MONOTIME_STRLEN]; +@@ -2423,10 +2422,9 @@ static void common_isis_summary_json(struct json_object *json, + : stier); + } + +- if (listcount(area->area_addrs) > 0) { ++ { + struct iso_address *area_addr; +- for (ALL_LIST_ELEMENTS_RO(area->area_addrs, node2, +- area_addr)) ++ frr_each (iso_address_list, &area->area_addrs, area_addr) + json_object_string_addf(area_json, "net", + "%pISl", area_addr); + } +@@ -2495,7 +2493,7 @@ static void common_isis_summary_json(struct json_object *json, + + static void common_isis_summary_vty(struct vty *vty, struct isis *isis) + { +- struct listnode *node, *node2; ++ struct listnode *node; + struct isis_area *area; + int level; + +@@ -2523,10 +2521,9 @@ static void common_isis_summary_vty(struct vty *vty, struct isis *isis) + vty_out(vty, " Tier: %hhu\n", tier); + } + +- if (listcount(area->area_addrs) > 0) { ++ if (iso_address_list_count(&area->area_addrs) > 0) { + struct iso_address *area_addr; +- for (ALL_LIST_ELEMENTS_RO(area->area_addrs, node2, +- area_addr)) ++ frr_each (iso_address_list, &area->area_addrs, area_addr) + vty_out(vty, " Net: %pISl\n", area_addr); + } + +@@ -3275,7 +3272,7 @@ void isis_area_is_type_set(struct isis_area *area, int is_type) + + spftree_area_init(area); + +- if (listcount(area->area_addrs) > 0) { ++ if (iso_address_list_count(&area->area_addrs) > 0) { + if (is_type & IS_LEVEL_1) + lsp_generate(area, IS_LEVEL_1); + if (is_type & IS_LEVEL_2) +@@ -3332,7 +3329,7 @@ void isis_area_overload_on_startup_set(struct isis_area *area, + + void config_end_lsp_generate(struct isis_area *area) + { +- if (listcount(area->area_addrs) > 0) { ++ if (iso_address_list_count(&area->area_addrs) > 0) { + if (CHECK_FLAG(area->is_type, IS_LEVEL_1)) + lsp_generate(area, IS_LEVEL_1); + if (CHECK_FLAG(area->is_type, IS_LEVEL_2)) +@@ -3548,7 +3545,7 @@ static int isis_config_write(struct vty *vty) + { + int write = 0; + struct isis_area *area; +- struct listnode *node, *node2, *inode; ++ struct listnode *node, *inode; + struct isis *isis; + + if (!im) { +@@ -3562,10 +3559,9 @@ static int isis_config_write(struct vty *vty) + vty_out(vty, "router " PROTO_NAME " %s\n", area->area_tag); + write++; + /* ISIS - Net */ +- if (listcount(area->area_addrs) > 0) { ++ if (iso_address_list_count(&area->area_addrs) > 0) { + struct iso_address *area_addr; +- for (ALL_LIST_ELEMENTS_RO(area->area_addrs, +- node2, area_addr)) { ++ frr_each (iso_address_list, &area->area_addrs, area_addr) { + vty_out(vty, " net %pISl\n", area_addr); + write++; + } +diff --git a/isisd/isisd.h b/isisd/isisd.h +index cd50d43a18..f43164da4d 100644 +--- a/isisd/isisd.h ++++ b/isisd/isisd.h +@@ -174,7 +174,7 @@ struct isis_area { + /* identifies the routing instance */ + char *area_tag; + /* area addresses for this area */ +- struct list *area_addrs; ++ struct iso_address_list_head area_addrs; + uint16_t max_lsp_lifetime[ISIS_LEVELS]; + char is_type; /* level-1 level-1-2 or level-2-only */ + /* are we overloaded? */ +diff --git a/lib/command.h b/lib/command.h +index d5119fd97c..ce3d86b659 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/iso.h b/lib/iso.h +index bdc9d39031..8e9df8cee6 100644 +--- a/lib/iso.h ++++ b/lib/iso.h +@@ -11,16 +11,26 @@ + #define LIB_ISO_H_ + + #include "compiler.h" ++#include "typesafe.h" + + /* len of "xx.xxxx.xxxx.xxxx.xxxx.xxxx.xxxx.xxxx.xxxx.xxxx.xx" + '\0' */ + #define ISO_ADDR_STRLEN 51 + #define ISO_ADDR_MIN 8 + #define ISO_ADDR_SIZE 20 ++ ++/* Predeclare typesafe list for isis_area->area_addrs */ ++PREDECL_DLIST(iso_address_list); ++ + struct iso_address { + uint8_t addr_len; + uint8_t area_addr[ISO_ADDR_SIZE]; ++ ++ /* Typesafe list linkage for isis_area->area_addrs */ ++ struct iso_address_list_item item; + }; + ++DECLARE_DLIST(iso_address_list, struct iso_address, item); ++ + /* len of "xxxx.xxxx.xxxx.xx-xx" + '\0' */ + #define ISO_SYSID_STRLEN 21 + +diff --git a/lib/link_state.c b/lib/link_state.c +index 3603407edb..8a9acc51ee 100644 +--- a/lib/link_state.c ++++ 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..14b8deef2d 100644 +--- a/lib/link_state.h ++++ b/lib/link_state.h +@@ -48,6 +48,7 @@ struct zclient; + /* Link State Common definitions */ + #define MAX_NAME_LENGTH 256 + #define ISO_SYS_ID_LEN 6 ++#define ISO_ADDR_SIZE 20 + + /* Type of Node */ + enum ls_node_type { +@@ -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 40277f0609..17a3129df6 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 46d3c2f660..ca137058b7 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 837a65bcb7..e7362570e4 100644 +--- a/staticd/static_vty.c ++++ b/staticd/static_vty.c +@@ -138,6 +138,7 @@ static int static_route_nb_run(struct vty *vty, struct static_route_args *args) + 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 ec2ea88282..fecaaa2d39 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/isisd/test_isis_spf.c b/tests/isisd/test_isis_spf.c +index bbafe7204e..d834a8de57 100644 +--- a/tests/isisd/test_isis_spf.c ++++ b/tests/isisd/test_isis_spf.c +@@ -261,7 +261,7 @@ static int test_run(struct vty *vty, const struct isis_topology *topology, + addr->addr_len = dotformat2buff(buff, net_title); + memcpy(addr->area_addr, buff, addr->addr_len); + addr->addr_len -= (ISIS_SYS_ID_LEN + ISIS_NSEL_LEN); +- listnode_add(area->area_addrs, addr); ++ iso_address_list_add_tail(&area->area_addrs, addr); + if (test_topology_load(topology, area, area->lspdb) != 0) { + vty_out(vty, "%% Failed to load topology\n"); + return CMD_WARNING; +diff --git a/tests/topotests/bgp_link_state/__init__.py b/tests/topotests/bgp_link_state/__init__.py +new file mode 100644 +index 0000000000..e69de29bb2 +diff --git a/tests/topotests/bgp_link_state/r1/frr.conf b/tests/topotests/bgp_link_state/r1/frr.conf +new file mode 100644 +index 0000000000..70f46ed496 +--- /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.0000.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..05fe6646b3 +--- /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.0000.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..e4212eb0d4 +--- /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.0000.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..273af2299a +--- /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.0000.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..59bf79196f +--- /dev/null ++++ b/tests/topotests/bgp_link_state/rr/bgp_ls_attrs_node4.json +@@ -0,0 +1,15 @@ ++{ ++ "paths": [ ++ { ++ "linkStateAttrs": { ++ "srCapabilities": { ++ "lowerBound": 400000, ++ "rangeSize": 4000, ++ "flags": "0xc0" ++ }, ++ "msd": 7, ++ "isisAreaId": "49.0000" ++ } ++ } ++ ] ++} +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/vtysh/vtysh.c b/vtysh/vtysh.c +index 6c0d9fefc6..dfbc492496 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; + } +@@ -5090,6 +5108,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); +@@ -5270,6 +5289,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 0a4d9220b4..9a9ec96759 100644 +--- a/zebra/connected.c ++++ b/zebra/connected.c +@@ -283,6 +283,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)); +@@ -496,6 +497,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 bd8ee28b21..b35d5bda28 100644 +--- a/zebra/zapi_msg.c ++++ b/zebra/zapi_msg.c +@@ -547,6 +547,7 @@ int zsend_redistribute_route(int cmd, struct zserv *client, const struct route_n + 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 ccb354cf86..5673d39e69 100644 +--- a/zebra/zebra_nhg.c ++++ b/zebra/zebra_nhg.c +@@ -2355,6 +2355,7 @@ static int nexthop_active(struct nexthop *nexthop, struct nhg_hash_entry *nhe, + 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__, +@@ -2398,6 +2399,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 + |
