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