bgpkit_parser/models/network/
nexthop.rs

1use std::fmt::{Debug, Display, Formatter};
2use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
3
4/// Route Distinguisher for VPN next-hops - RFC 4364, Section 4.1
5/// An 8-byte value used to distinguish VPN routes with potentially overlapping address spaces
6#[derive(PartialEq, Copy, Clone, Eq, Hash)]
7#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
8pub struct RouteDistinguisher(pub [u8; 8]);
9
10impl Debug for RouteDistinguisher {
11    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
12        write!(
13            f,
14            "RD({:02x}:{:02x}:{:02x}:{:02x}:{:02x}:{:02x}:{:02x}:{:02x})",
15            self.0[0], self.0[1], self.0[2], self.0[3], self.0[4], self.0[5], self.0[6], self.0[7]
16        )
17    }
18}
19
20impl Display for RouteDistinguisher {
21    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
22        write!(
23            f,
24            "{:02x}:{:02x}:{:02x}:{:02x}:{:02x}:{:02x}:{:02x}:{:02x}",
25            self.0[0], self.0[1], self.0[2], self.0[3], self.0[4], self.0[5], self.0[6], self.0[7]
26        )
27    }
28}
29
30/// enum that represents the type of the next hop address.
31///
32/// [NextHopAddress] is used when parsing for next hops in [Nlri](crate::models::Nlri).
33/// RFC 8950 extends this to support VPN next-hops with Route Distinguishers.
34#[derive(PartialEq, Copy, Clone, Eq, Hash)]
35#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
36pub enum NextHopAddress {
37    Ipv4(Ipv4Addr),
38    Ipv6(Ipv6Addr),
39    Ipv6LinkLocal(Ipv6Addr, Ipv6Addr),
40    /// VPN-IPv6 next hop - RFC 8950, Section 4
41    /// Contains Route Distinguisher (8 bytes) + IPv6 address (16 bytes) = 24 bytes total
42    VpnIpv6(RouteDistinguisher, Ipv6Addr),
43    /// VPN-IPv6 next hop with link-local - RFC 8950, Section 4  
44    /// Contains RD (8 bytes) + IPv6 (16 bytes) + RD (8 bytes) + IPv6 link-local (16 bytes) = 48 bytes total
45    VpnIpv6LinkLocal(RouteDistinguisher, Ipv6Addr, RouteDistinguisher, Ipv6Addr),
46}
47
48impl NextHopAddress {
49    /// Returns true if the next hop is a link local address
50    pub const fn is_link_local(&self) -> bool {
51        match self {
52            NextHopAddress::Ipv4(x) => x.is_link_local(),
53            NextHopAddress::Ipv6(x) => (x.segments()[0] & 0xffc0) == 0xfe80,
54            NextHopAddress::Ipv6LinkLocal(_, _) => true,
55            NextHopAddress::VpnIpv6(_, x) => (x.segments()[0] & 0xffc0) == 0xfe80,
56            NextHopAddress::VpnIpv6LinkLocal(_, _, _, _) => true,
57        }
58    }
59
60    /// Returns the address that this next hop points to
61    pub const fn addr(&self) -> IpAddr {
62        match self {
63            NextHopAddress::Ipv4(x) => IpAddr::V4(*x),
64            NextHopAddress::Ipv6(x) => IpAddr::V6(*x),
65            NextHopAddress::Ipv6LinkLocal(x, _) => IpAddr::V6(*x),
66            NextHopAddress::VpnIpv6(_, x) => IpAddr::V6(*x),
67            NextHopAddress::VpnIpv6LinkLocal(_, x, _, _) => IpAddr::V6(*x),
68        }
69    }
70}
71
72// Attempt to reduce the size of the debug output
73impl Debug for NextHopAddress {
74    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
75        match self {
76            NextHopAddress::Ipv4(x) => write!(f, "{x}"),
77            NextHopAddress::Ipv6(x) => write!(f, "{x}"),
78            NextHopAddress::Ipv6LinkLocal(x, y) => write!(f, "Ipv6LinkLocal({x}, {y})"),
79            NextHopAddress::VpnIpv6(rd, x) => write!(f, "VpnIpv6({rd}, {x})"),
80            NextHopAddress::VpnIpv6LinkLocal(rd1, x, rd2, y) => {
81                write!(f, "VpnIpv6LinkLocal({rd1}, {x}, {rd2}, {y})")
82            }
83        }
84    }
85}
86
87impl Display for NextHopAddress {
88    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
89        match self {
90            NextHopAddress::Ipv4(v) => write!(f, "{v}"),
91            NextHopAddress::Ipv6(v) => write!(f, "{v}"),
92            NextHopAddress::Ipv6LinkLocal(v, _) => write!(f, "{v}"),
93            NextHopAddress::VpnIpv6(_, v) => write!(f, "{v}"),
94            NextHopAddress::VpnIpv6LinkLocal(_, v, _, _) => write!(f, "{v}"),
95        }
96    }
97}
98
99impl From<IpAddr> for NextHopAddress {
100    fn from(value: IpAddr) -> Self {
101        match value {
102            IpAddr::V4(x) => NextHopAddress::Ipv4(x),
103            IpAddr::V6(x) => NextHopAddress::Ipv6(x),
104        }
105    }
106}
107
108#[cfg(test)]
109mod tests {
110    use super::*;
111    use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
112
113    #[test]
114    fn test_next_hop_address_is_link_local() {
115        let ipv4_addr = Ipv4Addr::new(169, 254, 0, 1);
116        let ipv6_addr = Ipv6Addr::new(0xfe80, 0, 0, 0, 0, 0, 0, 0);
117        let ipv6_link_local_addrs = (
118            Ipv6Addr::new(0xfe80, 0, 0, 1, 0, 0, 0, 1),
119            Ipv6Addr::new(0xfe80, 0, 0, 2, 0, 0, 0, 1),
120        );
121
122        let next_hop_ipv4 = NextHopAddress::Ipv4(ipv4_addr);
123        let next_hop_ipv6 = NextHopAddress::Ipv6(ipv6_addr);
124        let next_hop_ipv6_link_local =
125            NextHopAddress::Ipv6LinkLocal(ipv6_link_local_addrs.0, ipv6_link_local_addrs.1);
126
127        assert!(next_hop_ipv4.is_link_local());
128        assert!(next_hop_ipv6.is_link_local());
129        assert!(next_hop_ipv6_link_local.is_link_local());
130    }
131
132    #[test]
133    fn test_next_hop_address_addr() {
134        let ipv4_addr = Ipv4Addr::new(192, 0, 2, 1);
135        let ipv6_addr = Ipv6Addr::new(0x2001, 0xdb8, 0, 0, 0, 0, 0, 1);
136        let ipv6_link_local_addrs = (
137            Ipv6Addr::new(0xfe80, 0, 0, 0, 0, 0, 0, 0),
138            Ipv6Addr::new(0xfe80, 0, 0, 0, 0, 0, 0, 0),
139        );
140
141        let next_hop_ipv4 = NextHopAddress::Ipv4(ipv4_addr);
142        let next_hop_ipv6 = NextHopAddress::Ipv6(ipv6_addr);
143        let next_hop_ipv6_link_local =
144            NextHopAddress::Ipv6LinkLocal(ipv6_link_local_addrs.0, ipv6_link_local_addrs.1);
145
146        assert_eq!(next_hop_ipv4.addr(), IpAddr::V4(ipv4_addr));
147        assert_eq!(next_hop_ipv6.addr(), IpAddr::V6(ipv6_addr));
148        assert_eq!(
149            next_hop_ipv6_link_local.addr(),
150            IpAddr::V6(ipv6_link_local_addrs.0)
151        );
152    }
153
154    #[test]
155    fn test_next_hop_address_from() {
156        let ipv4_addr = IpAddr::V4(Ipv4Addr::new(192, 0, 2, 1));
157        let ipv6_addr = IpAddr::V6(Ipv6Addr::new(0x2001, 0xdb8, 0, 0, 0, 0, 0, 1));
158
159        let next_hop_ipv4 = NextHopAddress::from(ipv4_addr);
160        let next_hop_ipv6 = NextHopAddress::from(ipv6_addr);
161
162        assert_eq!(next_hop_ipv4.addr(), ipv4_addr);
163        assert_eq!(next_hop_ipv6.addr(), ipv6_addr);
164    }
165
166    #[test]
167    fn test_debug_for_next_hop_address() {
168        let ipv4_addr = Ipv4Addr::new(192, 0, 2, 1);
169        let ipv6_addr = Ipv6Addr::new(0x2001, 0xdb8, 0, 0, 0, 0, 0, 1);
170        let ipv6_link_local_addrs = (
171            Ipv6Addr::new(0xfe80, 0, 0, 0, 0, 0, 0, 0),
172            Ipv6Addr::new(0xfe80, 0, 0, 0, 0, 0, 0, 0),
173        );
174
175        let next_hop_ipv4 = NextHopAddress::Ipv4(ipv4_addr);
176        let next_hop_ipv6 = NextHopAddress::Ipv6(ipv6_addr);
177        let next_hop_ipv6_link_local =
178            NextHopAddress::Ipv6LinkLocal(ipv6_link_local_addrs.0, ipv6_link_local_addrs.1);
179
180        assert_eq!(format!("{next_hop_ipv4:?}"), "192.0.2.1");
181        assert_eq!(format!("{next_hop_ipv6:?}"), "2001:db8::1");
182        assert_eq!(
183            format!("{next_hop_ipv6_link_local:?}"),
184            "Ipv6LinkLocal(fe80::, fe80::)"
185        );
186    }
187
188    #[test]
189    fn test_display_for_next_hop_address() {
190        let ipv4_addr = Ipv4Addr::new(192, 0, 2, 1);
191        let ipv6_addr = Ipv6Addr::new(0x2001, 0xdb8, 0, 0, 0, 0, 0, 1);
192        let ipv6_link_local_addrs = (
193            Ipv6Addr::new(0xfe80, 0, 0, 0, 0, 0, 0, 0),
194            Ipv6Addr::new(0xfe80, 0, 0, 0, 0, 0, 0, 0),
195        );
196
197        let next_hop_ipv4 = NextHopAddress::Ipv4(ipv4_addr);
198        let next_hop_ipv6 = NextHopAddress::Ipv6(ipv6_addr);
199        let next_hop_ipv6_link_local =
200            NextHopAddress::Ipv6LinkLocal(ipv6_link_local_addrs.0, ipv6_link_local_addrs.1);
201
202        assert_eq!(format!("{next_hop_ipv4}"), "192.0.2.1");
203        assert_eq!(format!("{next_hop_ipv6}"), "2001:db8::1");
204        assert_eq!(format!("{next_hop_ipv6_link_local}"), "fe80::");
205    }
206
207    #[test]
208    fn test_route_distinguisher() {
209        let rd = RouteDistinguisher([0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07]);
210
211        // Test Debug format
212        assert_eq!(format!("{rd:?}"), "RD(00:01:02:03:04:05:06:07)");
213
214        // Test Display format
215        assert_eq!(format!("{rd}"), "00:01:02:03:04:05:06:07");
216    }
217
218    #[test]
219    fn test_vpn_next_hop_address() {
220        let rd = RouteDistinguisher([0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07]);
221        let ipv6_addr = Ipv6Addr::new(0x2001, 0xdb8, 0, 0, 0, 0, 0, 1);
222        let ipv6_link_local = Ipv6Addr::new(0xfe80, 0, 0, 0, 0, 0, 0, 1);
223        let rd2 = RouteDistinguisher([0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17]);
224
225        // Test VpnIpv6
226        let vpn_next_hop = NextHopAddress::VpnIpv6(rd, ipv6_addr);
227        assert_eq!(vpn_next_hop.addr(), IpAddr::V6(ipv6_addr));
228        assert!(!vpn_next_hop.is_link_local());
229        assert_eq!(format!("{vpn_next_hop}"), "2001:db8::1");
230        assert_eq!(
231            format!("{vpn_next_hop:?}"),
232            "VpnIpv6(00:01:02:03:04:05:06:07, 2001:db8::1)"
233        );
234
235        // Test VpnIpv6LinkLocal
236        let vpn_ll_next_hop = NextHopAddress::VpnIpv6LinkLocal(rd, ipv6_addr, rd2, ipv6_link_local);
237        assert_eq!(vpn_ll_next_hop.addr(), IpAddr::V6(ipv6_addr));
238        assert!(vpn_ll_next_hop.is_link_local()); // Should return true for VpnIpv6LinkLocal
239        assert_eq!(format!("{vpn_ll_next_hop}"), "2001:db8::1");
240        assert_eq!(format!("{vpn_ll_next_hop:?}"), "VpnIpv6LinkLocal(00:01:02:03:04:05:06:07, 2001:db8::1, 10:11:12:13:14:15:16:17, fe80::1)");
241
242        // Test VpnIpv6 with link-local IP (not VpnIpv6LinkLocal variant)
243        let vpn_ll_ip = NextHopAddress::VpnIpv6(rd, ipv6_link_local);
244        assert!(vpn_ll_ip.is_link_local()); // Should detect link-local from IPv6 address
245    }
246}