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