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