Skip to main content

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