Skip to main content

bgpkit_parser/models/bgp/attributes/
nlri.rs

1use crate::models::*;
2use ipnet::IpNet;
3use std::fmt::Debug;
4use std::net::IpAddr;
5
6/// Network Layer Reachability Information
7#[derive(Debug, PartialEq, Clone, Eq)]
8#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
9#[cfg_attr(feature = "ts-rs", derive(ts_rs::TS), ts(export))]
10pub struct Nlri {
11    pub afi: Afi,
12    pub safi: Safi,
13    pub next_hop: Option<NextHopAddress>,
14    /// Traditional IP prefixes for unicast/multicast
15    /// Also used for SAFI 4 withdrawals (RFC 8277 withdrawals carry no label semantics)
16    /// [`NetworkPrefix`] has a manual serde impl (string or `{prefix, path_id}`).
17    #[cfg_attr(
18        feature = "ts-rs",
19        ts(type = "(string | { prefix: string; path_id: number })[]")
20    )]
21    pub prefixes: Vec<NetworkPrefix>,
22    /// MPLS-labeled IP prefixes (SAFI 4 announcements only) - RFC 3107/8277
23    /// Withdrawals are routed to `prefixes`, not here
24    #[cfg_attr(feature = "ts-rs", ts(type = "Record<string, unknown>[] | null"))]
25    pub labeled_prefixes: Option<Vec<LabeledNetworkPrefix>>,
26    /// Link-State NLRI data - RFC 7752
27    #[cfg_attr(feature = "ts-rs", ts(type = "Record<string, unknown>[] | null"))]
28    pub link_state_nlris: Option<Vec<crate::models::bgp::linkstate::LinkStateNlri>>,
29    /// Flow-Spec NLRI data - RFC 8955/8956
30    #[cfg_attr(feature = "ts-rs", ts(type = "Record<string, unknown>[] | null"))]
31    pub flowspec_nlris: Option<Vec<crate::models::bgp::flowspec::FlowSpecNlri>>,
32}
33
34impl Nlri {
35    /// Returns true if this NLRI refers to the IPv4 address space.
36    pub const fn is_ipv4(&self) -> bool {
37        matches!(self.afi, Afi::Ipv4)
38    }
39
40    /// Returns true if this NLRI refers to the IPv6 address space.
41    pub const fn is_ipv6(&self) -> bool {
42        matches!(self.afi, Afi::Ipv6)
43    }
44
45    /// Returns true if this NLRI refers to Link-State information.
46    pub const fn is_link_state(&self) -> bool {
47        matches!(self.afi, Afi::LinkState)
48    }
49
50    /// Returns true if this NLRI refers to Flow-Spec information.
51    pub const fn is_flowspec(&self) -> bool {
52        matches!(self.safi, Safi::FlowSpec | Safi::FlowSpecL3Vpn)
53    }
54
55    /// Returns true if this NLRI refers to MPLS-labeled prefixes (SAFI 4).
56    pub const fn is_mpls_labeled(&self) -> bool {
57        matches!(self.safi, Safi::MplsLabel)
58    }
59
60    /// Returns true if this NLRI refers to reachable prefixes
61    pub const fn is_reachable(&self) -> bool {
62        self.next_hop.is_some()
63    }
64
65    /// Get the address of the next hop indicated by this NLRI.
66    ///
67    /// Panics if used on a unreachable NLRI message (ie. there is no next hop).
68    pub const fn next_hop_addr(&self) -> IpAddr {
69        match self.next_hop {
70            Some(next_hop) => next_hop.addr(),
71            None => panic!("unreachable NLRI"),
72        }
73    }
74
75    pub fn new_reachable(prefix: NetworkPrefix, next_hop: Option<IpAddr>) -> Nlri {
76        let next_hop = next_hop.map(NextHopAddress::from);
77        let afi = match prefix.prefix {
78            IpNet::V4(_) => Afi::Ipv4,
79            IpNet::V6(_) => Afi::Ipv6,
80        };
81        let safi = Safi::Unicast;
82        Nlri {
83            afi,
84            safi,
85            next_hop,
86            prefixes: vec![prefix],
87            labeled_prefixes: None,
88            link_state_nlris: None,
89            flowspec_nlris: None,
90        }
91    }
92
93    pub fn new_unreachable(prefix: NetworkPrefix) -> Nlri {
94        let afi = match prefix.prefix {
95            IpNet::V4(_) => Afi::Ipv4,
96            IpNet::V6(_) => Afi::Ipv6,
97        };
98        let safi = Safi::Unicast;
99        Nlri {
100            afi,
101            safi,
102            next_hop: None,
103            prefixes: vec![prefix],
104            labeled_prefixes: None,
105            link_state_nlris: None,
106            flowspec_nlris: None,
107        }
108    }
109
110    pub fn new_link_state_reachable(
111        next_hop: Option<IpAddr>,
112        safi: Safi,
113        nlri_list: Vec<crate::models::bgp::linkstate::LinkStateNlri>,
114    ) -> Nlri {
115        let next_hop = next_hop.map(NextHopAddress::from);
116        Nlri {
117            afi: Afi::LinkState,
118            safi,
119            next_hop,
120            prefixes: Vec::new(),
121            labeled_prefixes: None,
122            link_state_nlris: Some(nlri_list),
123            flowspec_nlris: None,
124        }
125    }
126
127    pub fn new_link_state_unreachable(
128        safi: Safi,
129        nlri_list: Vec<crate::models::bgp::linkstate::LinkStateNlri>,
130    ) -> Nlri {
131        Nlri {
132            afi: Afi::LinkState,
133            safi,
134            next_hop: None,
135            prefixes: Vec::new(),
136            labeled_prefixes: None,
137            link_state_nlris: Some(nlri_list),
138            flowspec_nlris: None,
139        }
140    }
141
142    /// Create a new Flow-Spec reachable NLRI
143    pub fn new_flowspec_reachable(
144        afi: Afi,
145        safi: Safi,
146        next_hop: Option<IpAddr>,
147        flowspec_nlris: Vec<crate::models::bgp::flowspec::FlowSpecNlri>,
148    ) -> Nlri {
149        let next_hop = next_hop.map(NextHopAddress::from);
150        Nlri {
151            afi,
152            safi,
153            next_hop,
154            prefixes: Vec::new(),
155            labeled_prefixes: None,
156            link_state_nlris: None,
157            flowspec_nlris: Some(flowspec_nlris),
158        }
159    }
160
161    /// Create a new Flow-Spec unreachable NLRI
162    pub fn new_flowspec_unreachable(
163        afi: Afi,
164        safi: Safi,
165        flowspec_nlris: Vec<crate::models::bgp::flowspec::FlowSpecNlri>,
166    ) -> Nlri {
167        Nlri {
168            afi,
169            safi,
170            next_hop: None,
171            prefixes: Vec::new(),
172            labeled_prefixes: None,
173            link_state_nlris: None,
174            flowspec_nlris: Some(flowspec_nlris),
175        }
176    }
177
178    /// Create a new MPLS-labeled reachable NLRI (SAFI 4) - RFC 3107/8277
179    pub fn new_labeled_reachable(
180        afi: Afi,
181        next_hop: Option<IpAddr>,
182        labeled_prefixes: Vec<LabeledNetworkPrefix>,
183    ) -> Nlri {
184        let next_hop = next_hop.map(NextHopAddress::from);
185        Nlri {
186            afi,
187            safi: Safi::MplsLabel,
188            next_hop,
189            prefixes: Vec::new(),
190            labeled_prefixes: Some(labeled_prefixes),
191            link_state_nlris: None,
192            flowspec_nlris: None,
193        }
194    }
195
196    /// Create a new MPLS-labeled unreachable NLRI (SAFI 4) - RFC 3107/8277
197    /// Note: Withdrawals use standard prefixes (no label semantics per RFC 8277 ยง2.4)
198    pub fn new_labeled_unreachable(afi: Afi, prefixes: Vec<NetworkPrefix>) -> Nlri {
199        Nlri {
200            afi,
201            safi: Safi::MplsLabel,
202            next_hop: None,
203            prefixes,
204            labeled_prefixes: None,
205            link_state_nlris: None,
206            flowspec_nlris: None,
207        }
208    }
209}
210
211/// Iterator over all prefixes in the NLRI.
212///
213/// **Note**: This iterator intentionally flattens both traditional prefixes and
214/// labeled prefixes into `IpNet`, discarding MPLS labels and path IDs. This preserves
215/// backward compatibility for users expecting only `IpNet`, but means label information
216/// is not accessible through iteration. To access labels, use `Nlri::labeled_prefixes` directly.
217impl IntoIterator for Nlri {
218    type Item = IpNet;
219    type IntoIter = std::vec::IntoIter<IpNet>;
220
221    fn into_iter(self) -> Self::IntoIter {
222        let mut result: Vec<IpNet> = self.prefixes.into_iter().map(|x| x.prefix).collect();
223
224        // Include labeled prefixes (extract the inner prefix field)
225        if let Some(labeled) = self.labeled_prefixes {
226            for labeled_prefix in labeled {
227                result.push(labeled_prefix.prefix);
228            }
229        }
230
231        result.into_iter()
232    }
233}
234
235impl<'a> IntoIterator for &'a Nlri {
236    type Item = &'a IpNet;
237    type IntoIter = std::vec::IntoIter<&'a IpNet>;
238
239    fn into_iter(self) -> Self::IntoIter {
240        let mut result: Vec<&'a IpNet> = self.prefixes.iter().map(|x| &x.prefix).collect();
241
242        // Include labeled prefixes (extract the inner prefix field)
243        if let Some(ref labeled) = self.labeled_prefixes {
244            for labeled_prefix in labeled {
245                result.push(&labeled_prefix.prefix);
246            }
247        }
248
249        result.into_iter()
250    }
251}
252
253#[derive(Debug, PartialEq, Clone, Eq)]
254#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
255pub struct MpReachableNlri {
256    afi: Afi,
257    safi: Safi,
258    next_hop: NextHopAddress,
259    prefixes: Vec<NetworkPrefix>,
260}
261
262impl MpReachableNlri {
263    pub fn new(
264        afi: Afi,
265        safi: Safi,
266        next_hop: NextHopAddress,
267        prefixes: Vec<NetworkPrefix>,
268    ) -> MpReachableNlri {
269        MpReachableNlri {
270            afi,
271            safi,
272            next_hop,
273            prefixes,
274        }
275    }
276}
277
278#[derive(Debug, PartialEq, Clone, Eq)]
279#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
280pub struct MpUnreachableNlri {
281    afi: Afi,
282    safi: Safi,
283    prefixes: Vec<NetworkPrefix>,
284}
285
286impl MpUnreachableNlri {
287    pub fn new(afi: Afi, safi: Safi, prefixes: Vec<NetworkPrefix>) -> MpUnreachableNlri {
288        MpUnreachableNlri {
289            afi,
290            safi,
291            prefixes,
292        }
293    }
294}
295
296#[cfg(test)]
297mod tests {
298    use super::*;
299    use std::str::FromStr;
300
301    #[test]
302    fn nlri_is_ipv4() {
303        let nlri = Nlri::new_reachable(
304            NetworkPrefix::from_str("10.0.2.0/24").unwrap(),
305            Some("10.0.2.1".parse().unwrap()),
306        );
307
308        assert!(nlri.is_ipv4());
309    }
310
311    #[test]
312    fn nlri_is_ipv6() {
313        let nlri = Nlri::new_unreachable(NetworkPrefix::from_str("2001:db8::/32").unwrap());
314
315        assert!(nlri.is_ipv6());
316    }
317
318    #[test]
319    fn nlri_is_reachable() {
320        let nlri = Nlri::new_reachable(
321            NetworkPrefix::from_str("10.0.2.0/24").unwrap(),
322            Some("10.0.2.1".parse().unwrap()),
323        );
324
325        assert!(nlri.is_reachable());
326    }
327
328    #[test]
329    #[should_panic]
330    fn nlri_next_hop_addr_unreachable() {
331        let nlri = Nlri::new_unreachable(NetworkPrefix::from_str("10.0.2.0/24").unwrap());
332
333        let _ = nlri.next_hop_addr();
334    }
335
336    #[test]
337    fn mp_reachable_nlri_new() {
338        let next_hop_addr = IpAddr::from_str("10.0.2.1").unwrap();
339        let nlri = MpReachableNlri::new(
340            Afi::Ipv4,
341            Safi::Unicast,
342            NextHopAddress::from(next_hop_addr),
343            vec![NetworkPrefix::from_str("10.0.2.0/24").unwrap()],
344        );
345
346        assert_eq!(nlri.afi, Afi::Ipv4);
347        assert_eq!(nlri.safi, Safi::Unicast);
348        assert_eq!(nlri.next_hop.addr(), next_hop_addr);
349        assert_eq!(nlri.prefixes.len(), 1);
350    }
351
352    #[test]
353    fn mp_unreachable_nlri_new() {
354        let nlri = MpUnreachableNlri::new(
355            Afi::Ipv4,
356            Safi::Unicast,
357            vec![NetworkPrefix::from_str("10.0.2.0/24").unwrap()],
358        );
359
360        assert_eq!(nlri.afi, Afi::Ipv4);
361        assert_eq!(nlri.safi, Safi::Unicast);
362        assert_eq!(nlri.prefixes.len(), 1);
363    }
364
365    #[test]
366    fn nlri_link_state_creation() {
367        use crate::models::bgp::linkstate::{LinkStateNlri, NodeDescriptor, ProtocolId};
368
369        let node_desc = NodeDescriptor {
370            autonomous_system: Some(65001),
371            ..Default::default()
372        };
373
374        let ls_nlri = LinkStateNlri::new_node_nlri(ProtocolId::Ospfv2, 123456, node_desc);
375        let nlri = Nlri::new_link_state_reachable(
376            Some("192.168.1.1".parse().unwrap()),
377            Safi::LinkState,
378            vec![ls_nlri],
379        );
380
381        assert!(nlri.is_link_state());
382        assert!(nlri.is_reachable());
383        assert_eq!(nlri.afi, Afi::LinkState);
384        assert_eq!(nlri.safi, Safi::LinkState);
385    }
386
387    #[test]
388    fn nlri_link_state_unreachable() {
389        use crate::models::bgp::linkstate::{LinkStateNlri, NodeDescriptor, ProtocolId};
390
391        let node_desc = NodeDescriptor::default();
392        let ls_nlri = LinkStateNlri::new_node_nlri(ProtocolId::Ospfv2, 123456, node_desc);
393        let nlri = Nlri::new_link_state_unreachable(Safi::LinkState, vec![ls_nlri]);
394
395        assert!(nlri.is_link_state());
396        assert!(!nlri.is_reachable());
397        assert_eq!(nlri.afi, Afi::LinkState);
398        assert_eq!(nlri.safi, Safi::LinkState);
399    }
400
401    #[test]
402    #[should_panic]
403    fn nlri_link_state_next_hop_addr_unreachable() {
404        use crate::models::bgp::linkstate::{LinkStateNlri, NodeDescriptor, ProtocolId};
405
406        let node_desc = NodeDescriptor::default();
407        let ls_nlri = LinkStateNlri::new_node_nlri(ProtocolId::Ospfv2, 123456, node_desc);
408        let nlri = Nlri::new_link_state_unreachable(Safi::LinkState, vec![ls_nlri]);
409
410        let _ = nlri.next_hop_addr();
411    }
412
413    #[test]
414    fn nlri_flowspec_creation() {
415        use crate::models::bgp::flowspec::{FlowSpecComponent, FlowSpecNlri};
416        use std::str::FromStr;
417
418        let component =
419            FlowSpecComponent::DestinationPrefix(NetworkPrefix::from_str("192.0.2.0/24").unwrap());
420        let flowspec_nlri = FlowSpecNlri::new(vec![component]);
421
422        let nlri = Nlri::new_flowspec_reachable(
423            Afi::Ipv4,
424            Safi::FlowSpec,
425            Some("192.0.2.1".parse().unwrap()),
426            vec![flowspec_nlri],
427        );
428
429        assert!(nlri.is_flowspec());
430        assert!(nlri.is_reachable());
431        assert!(nlri.is_ipv4());
432        assert_eq!(nlri.afi, Afi::Ipv4);
433        assert_eq!(nlri.safi, Safi::FlowSpec);
434        assert_eq!(nlri.prefixes.len(), 0); // Flow-Spec doesn't use traditional prefixes
435        assert!(nlri.flowspec_nlris.is_some());
436        assert_eq!(nlri.flowspec_nlris.as_ref().unwrap().len(), 1);
437    }
438
439    #[test]
440    fn nlri_flowspec_unreachable() {
441        use crate::models::bgp::flowspec::{FlowSpecComponent, FlowSpecNlri};
442        use std::str::FromStr;
443
444        let component =
445            FlowSpecComponent::DestinationPrefix(NetworkPrefix::from_str("2001:db8::/32").unwrap());
446        let flowspec_nlri = FlowSpecNlri::new(vec![component]);
447
448        let nlri = Nlri::new_flowspec_unreachable(Afi::Ipv6, Safi::FlowSpec, vec![flowspec_nlri]);
449
450        assert!(nlri.is_flowspec());
451        assert!(!nlri.is_reachable());
452        assert!(nlri.is_ipv6());
453        assert_eq!(nlri.afi, Afi::Ipv6);
454        assert_eq!(nlri.safi, Safi::FlowSpec);
455        assert!(nlri.flowspec_nlris.is_some());
456        assert_eq!(nlri.flowspec_nlris.as_ref().unwrap().len(), 1);
457    }
458
459    #[test]
460    fn nlri_flowspec_l3vpn() {
461        use crate::models::bgp::flowspec::{FlowSpecComponent, FlowSpecNlri, NumericOperator};
462        use std::str::FromStr;
463
464        let components = vec![
465            FlowSpecComponent::DestinationPrefix(NetworkPrefix::from_str("10.0.0.0/8").unwrap()),
466            FlowSpecComponent::IpProtocol(vec![NumericOperator::equal_to(6)]), // TCP
467        ];
468        let flowspec_nlri = FlowSpecNlri::new(components);
469
470        let nlri = Nlri::new_flowspec_reachable(
471            Afi::Ipv4,
472            Safi::FlowSpecL3Vpn,
473            None, // Flow-Spec often doesn't have next hop
474            vec![flowspec_nlri],
475        );
476
477        assert!(nlri.is_flowspec());
478        assert!(!nlri.is_reachable());
479        assert!(nlri.is_ipv4());
480        assert_eq!(nlri.safi, Safi::FlowSpecL3Vpn);
481    }
482
483    #[test]
484    #[should_panic]
485    fn nlri_flowspec_next_hop_addr_unreachable() {
486        use crate::models::bgp::flowspec::{FlowSpecComponent, FlowSpecNlri};
487        use std::str::FromStr;
488
489        let component =
490            FlowSpecComponent::DestinationPrefix(NetworkPrefix::from_str("192.0.2.0/24").unwrap());
491        let flowspec_nlri = FlowSpecNlri::new(vec![component]);
492
493        let nlri = Nlri::new_flowspec_unreachable(Afi::Ipv4, Safi::FlowSpec, vec![flowspec_nlri]);
494
495        let _ = nlri.next_hop_addr();
496    }
497}