1use crate::models::BgpModelsError;
2use std::fmt::{Debug, Display, Formatter};
3use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
4use std::str::FromStr;
5
6#[derive(PartialEq, Copy, Clone, Eq, Hash)]
9#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
10#[cfg_attr(feature = "ts-rs", derive(ts_rs::TS), ts(export))]
11pub struct RouteDistinguisher(pub [u8; 8]);
12
13impl Debug for RouteDistinguisher {
14 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
15 write!(
16 f,
17 "RD({:02x}:{:02x}:{:02x}:{:02x}:{:02x}:{:02x}:{:02x}:{:02x})",
18 self.0[0], self.0[1], self.0[2], self.0[3], self.0[4], self.0[5], self.0[6], self.0[7]
19 )
20 }
21}
22
23impl Display for RouteDistinguisher {
24 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
25 write!(
26 f,
27 "{:02x}:{:02x}:{:02x}:{:02x}:{:02x}:{:02x}:{:02x}:{:02x}",
28 self.0[0], self.0[1], self.0[2], self.0[3], self.0[4], self.0[5], self.0[6], self.0[7]
29 )
30 }
31}
32
33#[derive(PartialEq, Copy, Clone, Eq, Hash)]
38#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
39#[cfg_attr(feature = "ts-rs", derive(ts_rs::TS), ts(export))]
40pub enum NextHopAddress {
41 Ipv4(Ipv4Addr),
42 Ipv6(Ipv6Addr),
43 Ipv6LinkLocal(Ipv6Addr, Ipv6Addr),
44 VpnIpv6(RouteDistinguisher, Ipv6Addr),
47 VpnIpv6LinkLocal(RouteDistinguisher, Ipv6Addr, RouteDistinguisher, Ipv6Addr),
50}
51
52impl NextHopAddress {
53 pub const fn is_link_local(&self) -> bool {
55 match self {
56 NextHopAddress::Ipv4(x) => x.is_link_local(),
57 NextHopAddress::Ipv6(x) => x.is_unicast_link_local(),
58 NextHopAddress::Ipv6LinkLocal(_, _) => true,
59 NextHopAddress::VpnIpv6(_, x) => x.is_unicast_link_local(),
60 NextHopAddress::VpnIpv6LinkLocal(_, _, _, _) => true,
61 }
62 }
63
64 pub const fn addr(&self) -> IpAddr {
68 match self {
69 NextHopAddress::Ipv4(x) => IpAddr::V4(*x),
70 NextHopAddress::Ipv6(x) => IpAddr::V6(*x),
71 NextHopAddress::Ipv6LinkLocal(x, _) => IpAddr::V6(*x),
72 NextHopAddress::VpnIpv6(_, x) => IpAddr::V6(*x),
73 NextHopAddress::VpnIpv6LinkLocal(_, x, _, _) => IpAddr::V6(*x),
74 }
75 }
76
77 const fn pair(&self) -> Option<(Ipv6Addr, Ipv6Addr)> {
79 match self {
80 NextHopAddress::Ipv6LinkLocal(x, y) => Some((*x, *y)),
81 NextHopAddress::VpnIpv6LinkLocal(_, x, _, y) => Some((*x, *y)),
82 _ => None,
83 }
84 }
85
86 pub const fn global_addr(&self) -> IpAddr {
91 match self.pair() {
92 Some((first, second)) => {
93 if first.is_unicast_link_local() && !second.is_unicast_link_local() {
94 IpAddr::V6(second)
95 } else {
96 IpAddr::V6(first)
97 }
98 }
99 None => self.addr(),
100 }
101 }
102
103 pub const fn link_local_addr(&self) -> Option<Ipv6Addr> {
107 match self.pair() {
108 Some((_, second)) if second.is_unicast_link_local() => Some(second),
110 Some((first, _)) if first.is_unicast_link_local() => Some(first),
111 _ => None,
112 }
113 }
114}
115
116impl FromStr for NextHopAddress {
121 type Err = BgpModelsError;
122
123 fn from_str(s: &str) -> Result<Self, Self::Err> {
124 fn invalid(addr: &str, e: std::net::AddrParseError) -> BgpModelsError {
125 BgpModelsError::NextHopParsingError(format!("invalid address {addr:?}: {e}"))
126 }
127 fn parse_v6(addr: &str) -> Result<Ipv6Addr, BgpModelsError> {
128 let addr = addr.trim();
129 Ipv6Addr::from_str(addr).map_err(|e| invalid(addr, e))
130 }
131
132 match s.split_once(',') {
133 None => {
134 let addr = s.trim();
135 IpAddr::from_str(addr)
136 .map(NextHopAddress::from)
137 .map_err(|e| invalid(addr, e))
138 }
139 Some((_, second)) if second.contains(',') => Err(BgpModelsError::NextHopParsingError(
140 format!("more than two addresses: {s:?}"),
141 )),
142 Some((first, second)) => Ok(NextHopAddress::Ipv6LinkLocal(
143 parse_v6(first)?,
144 parse_v6(second)?,
145 )),
146 }
147 }
148}
149
150impl Debug for NextHopAddress {
152 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
153 match self {
154 NextHopAddress::Ipv4(x) => write!(f, "{x}"),
155 NextHopAddress::Ipv6(x) => write!(f, "{x}"),
156 NextHopAddress::Ipv6LinkLocal(x, y) => write!(f, "Ipv6LinkLocal({x}, {y})"),
157 NextHopAddress::VpnIpv6(rd, x) => write!(f, "VpnIpv6({rd}, {x})"),
158 NextHopAddress::VpnIpv6LinkLocal(rd1, x, rd2, y) => {
159 write!(f, "VpnIpv6LinkLocal({rd1}, {x}, {rd2}, {y})")
160 }
161 }
162 }
163}
164
165impl Display for NextHopAddress {
168 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
169 match self.pair() {
170 Some((first, second)) => write!(f, "{first},{second}"),
171 None => write!(f, "{}", self.addr()),
172 }
173 }
174}
175
176impl From<IpAddr> for NextHopAddress {
177 fn from(value: IpAddr) -> Self {
178 match value {
179 IpAddr::V4(x) => NextHopAddress::Ipv4(x),
180 IpAddr::V6(x) => NextHopAddress::Ipv6(x),
181 }
182 }
183}
184
185#[cfg(test)]
186mod tests {
187 use super::*;
188 use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
189
190 #[test]
191 fn test_next_hop_address_is_link_local() {
192 let ipv4_addr = Ipv4Addr::new(169, 254, 0, 1);
193 let ipv6_addr = Ipv6Addr::new(0xfe80, 0, 0, 0, 0, 0, 0, 0);
194 let ipv6_link_local_addrs = (
195 Ipv6Addr::new(0xfe80, 0, 0, 1, 0, 0, 0, 1),
196 Ipv6Addr::new(0xfe80, 0, 0, 2, 0, 0, 0, 1),
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!(next_hop_ipv4.is_link_local());
205 assert!(next_hop_ipv6.is_link_local());
206 assert!(next_hop_ipv6_link_local.is_link_local());
207 }
208
209 #[test]
210 fn test_next_hop_address_addr() {
211 let ipv4_addr = Ipv4Addr::new(192, 0, 2, 1);
212 let ipv6_addr = Ipv6Addr::new(0x2001, 0xdb8, 0, 0, 0, 0, 0, 1);
213 let ipv6_link_local_addrs = (
214 Ipv6Addr::new(0xfe80, 0, 0, 0, 0, 0, 0, 0),
215 Ipv6Addr::new(0xfe80, 0, 0, 0, 0, 0, 0, 0),
216 );
217
218 let next_hop_ipv4 = NextHopAddress::Ipv4(ipv4_addr);
219 let next_hop_ipv6 = NextHopAddress::Ipv6(ipv6_addr);
220 let next_hop_ipv6_link_local =
221 NextHopAddress::Ipv6LinkLocal(ipv6_link_local_addrs.0, ipv6_link_local_addrs.1);
222
223 assert_eq!(next_hop_ipv4.addr(), IpAddr::V4(ipv4_addr));
224 assert_eq!(next_hop_ipv6.addr(), IpAddr::V6(ipv6_addr));
225 assert_eq!(
226 next_hop_ipv6_link_local.addr(),
227 IpAddr::V6(ipv6_link_local_addrs.0)
228 );
229 }
230
231 #[test]
232 fn test_next_hop_address_from() {
233 let ipv4_addr = IpAddr::V4(Ipv4Addr::new(192, 0, 2, 1));
234 let ipv6_addr = IpAddr::V6(Ipv6Addr::new(0x2001, 0xdb8, 0, 0, 0, 0, 0, 1));
235
236 let next_hop_ipv4 = NextHopAddress::from(ipv4_addr);
237 let next_hop_ipv6 = NextHopAddress::from(ipv6_addr);
238
239 assert_eq!(next_hop_ipv4.addr(), ipv4_addr);
240 assert_eq!(next_hop_ipv6.addr(), ipv6_addr);
241 }
242
243 #[test]
244 fn test_debug_for_next_hop_address() {
245 let ipv4_addr = Ipv4Addr::new(192, 0, 2, 1);
246 let ipv6_addr = Ipv6Addr::new(0x2001, 0xdb8, 0, 0, 0, 0, 0, 1);
247 let ipv6_link_local_addrs = (
248 Ipv6Addr::new(0xfe80, 0, 0, 0, 0, 0, 0, 0),
249 Ipv6Addr::new(0xfe80, 0, 0, 0, 0, 0, 0, 0),
250 );
251
252 let next_hop_ipv4 = NextHopAddress::Ipv4(ipv4_addr);
253 let next_hop_ipv6 = NextHopAddress::Ipv6(ipv6_addr);
254 let next_hop_ipv6_link_local =
255 NextHopAddress::Ipv6LinkLocal(ipv6_link_local_addrs.0, ipv6_link_local_addrs.1);
256
257 assert_eq!(format!("{next_hop_ipv4:?}"), "192.0.2.1");
258 assert_eq!(format!("{next_hop_ipv6:?}"), "2001:db8::1");
259 assert_eq!(
260 format!("{next_hop_ipv6_link_local:?}"),
261 "Ipv6LinkLocal(fe80::, fe80::)"
262 );
263 }
264
265 #[test]
266 fn test_display_for_next_hop_address() {
267 let ipv4_addr = Ipv4Addr::new(192, 0, 2, 1);
268 let ipv6_addr = Ipv6Addr::new(0x2001, 0xdb8, 0, 0, 0, 0, 0, 1);
269 let ipv6_link_local_addrs = (
270 Ipv6Addr::new(0xfe80, 0, 0, 0, 0, 0, 0, 0),
271 Ipv6Addr::new(0xfe80, 0, 0, 0, 0, 0, 0, 0),
272 );
273
274 let next_hop_ipv4 = NextHopAddress::Ipv4(ipv4_addr);
275 let next_hop_ipv6 = NextHopAddress::Ipv6(ipv6_addr);
276 let next_hop_ipv6_link_local =
277 NextHopAddress::Ipv6LinkLocal(ipv6_link_local_addrs.0, ipv6_link_local_addrs.1);
278
279 assert_eq!(format!("{next_hop_ipv4}"), "192.0.2.1");
280 assert_eq!(format!("{next_hop_ipv6}"), "2001:db8::1");
281 assert_eq!(format!("{next_hop_ipv6_link_local}"), "fe80::,fe80::");
282 }
283
284 #[test]
285 fn test_next_hop_address_from_str() {
286 assert_eq!(
288 "192.0.2.1".parse::<NextHopAddress>().unwrap(),
289 NextHopAddress::Ipv4(Ipv4Addr::new(192, 0, 2, 1))
290 );
291 assert_eq!(
292 "2001:db8::1".parse::<NextHopAddress>().unwrap(),
293 NextHopAddress::Ipv6("2001:db8::1".parse().unwrap())
294 );
295 assert_eq!(
297 "fe80::1".parse::<NextHopAddress>().unwrap(),
298 NextHopAddress::Ipv6("fe80::1".parse().unwrap())
299 );
300
301 let pair = NextHopAddress::Ipv6LinkLocal(
303 "2001:db8::1".parse().unwrap(),
304 "fe80::1".parse().unwrap(),
305 );
306 assert_eq!(
307 "2001:db8::1,fe80::1".parse::<NextHopAddress>().unwrap(),
308 pair
309 );
310 assert_eq!(
311 "fe80::1,2001:db8::1".parse::<NextHopAddress>().unwrap(),
312 NextHopAddress::Ipv6LinkLocal(
313 "fe80::1".parse().unwrap(),
314 "2001:db8::1".parse().unwrap()
315 )
316 );
317 assert_eq!(
319 "2001:db8::1, fe80::1".parse::<NextHopAddress>().unwrap(),
320 pair
321 );
322
323 assert!("".parse::<NextHopAddress>().is_err());
325 assert!("not-an-address".parse::<NextHopAddress>().is_err());
326 assert!("fe80::1%eth0".parse::<NextHopAddress>().is_err());
327 assert!("2001:db8::1,fe80::1,fe80::2"
328 .parse::<NextHopAddress>()
329 .is_err());
330 assert!("2001:db8::1,".parse::<NextHopAddress>().is_err());
331 assert!("192.0.2.1,fe80::1".parse::<NextHopAddress>().is_err());
332 }
333
334 #[test]
335 fn test_next_hop_address_display_from_str_round_trip() {
336 for repr in [
337 "192.0.2.1",
338 "2001:db8::1",
339 "2001:db8::1,fe80::1",
340 "fe80::1,2001:db8::1",
341 ] {
342 assert_eq!(repr.parse::<NextHopAddress>().unwrap().to_string(), repr);
343 }
344 }
345
346 #[test]
347 fn test_next_hop_address_global_addr() {
348 let global: Ipv6Addr = "2001:db8::1".parse().unwrap();
349 let link_local: Ipv6Addr = "fe80::1".parse().unwrap();
350 let rd = RouteDistinguisher([0; 8]);
351
352 assert_eq!(
354 NextHopAddress::Ipv6(link_local).global_addr(),
355 IpAddr::V6(link_local)
356 );
357
358 assert_eq!(
360 NextHopAddress::Ipv6LinkLocal(global, link_local).global_addr(),
361 IpAddr::V6(global)
362 );
363 assert_eq!(
364 NextHopAddress::Ipv6LinkLocal(link_local, global).global_addr(),
365 IpAddr::V6(global)
366 );
367 assert_eq!(
368 NextHopAddress::VpnIpv6LinkLocal(rd, link_local, rd, global).global_addr(),
369 IpAddr::V6(global)
370 );
371
372 let second_global: Ipv6Addr = "2001:db8::2".parse().unwrap();
374 let second_link_local: Ipv6Addr = "fe80::2".parse().unwrap();
375 assert_eq!(
376 NextHopAddress::Ipv6LinkLocal(global, second_global).global_addr(),
377 IpAddr::V6(global)
378 );
379 assert_eq!(
380 NextHopAddress::Ipv6LinkLocal(link_local, second_link_local).global_addr(),
381 IpAddr::V6(link_local)
382 );
383 }
384
385 #[test]
386 fn test_next_hop_address_link_local_addr() {
387 let global: Ipv6Addr = "2001:db8::1".parse().unwrap();
388 let link_local: Ipv6Addr = "fe80::1".parse().unwrap();
389
390 assert_eq!(NextHopAddress::Ipv6(global).link_local_addr(), None);
392 assert_eq!(NextHopAddress::Ipv6(link_local).link_local_addr(), None);
393
394 assert_eq!(
396 NextHopAddress::Ipv6LinkLocal(global, link_local).link_local_addr(),
397 Some(link_local)
398 );
399 assert_eq!(
400 NextHopAddress::Ipv6LinkLocal(link_local, global).link_local_addr(),
401 Some(link_local)
402 );
403
404 let second_link_local: Ipv6Addr = "fe80::2".parse().unwrap();
406 assert_eq!(
407 NextHopAddress::Ipv6LinkLocal(link_local, second_link_local).link_local_addr(),
408 Some(second_link_local)
409 );
410 assert_eq!(
411 NextHopAddress::Ipv6LinkLocal(global, "2001:db8::2".parse().unwrap()).link_local_addr(),
412 None
413 );
414 }
415
416 #[test]
417 fn test_route_distinguisher() {
418 let rd = RouteDistinguisher([0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07]);
419
420 assert_eq!(format!("{rd:?}"), "RD(00:01:02:03:04:05:06:07)");
422
423 assert_eq!(format!("{rd}"), "00:01:02:03:04:05:06:07");
425 }
426
427 #[test]
428 fn test_vpn_next_hop_address() {
429 let rd = RouteDistinguisher([0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07]);
430 let ipv6_addr = Ipv6Addr::new(0x2001, 0xdb8, 0, 0, 0, 0, 0, 1);
431 let ipv6_link_local = Ipv6Addr::new(0xfe80, 0, 0, 0, 0, 0, 0, 1);
432 let rd2 = RouteDistinguisher([0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17]);
433
434 let vpn_next_hop = NextHopAddress::VpnIpv6(rd, ipv6_addr);
436 assert_eq!(vpn_next_hop.addr(), IpAddr::V6(ipv6_addr));
437 assert!(!vpn_next_hop.is_link_local());
438 assert_eq!(format!("{vpn_next_hop}"), "2001:db8::1");
439 assert_eq!(
440 format!("{vpn_next_hop:?}"),
441 "VpnIpv6(00:01:02:03:04:05:06:07, 2001:db8::1)"
442 );
443
444 let vpn_ll_next_hop = NextHopAddress::VpnIpv6LinkLocal(rd, ipv6_addr, rd2, ipv6_link_local);
446 assert_eq!(vpn_ll_next_hop.addr(), IpAddr::V6(ipv6_addr));
447 assert!(vpn_ll_next_hop.is_link_local()); assert_eq!(format!("{vpn_ll_next_hop}"), "2001:db8::1,fe80::1");
449 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)");
450
451 let vpn_ll_ip = NextHopAddress::VpnIpv6(rd, ipv6_link_local);
453 assert!(vpn_ll_ip.is_link_local()); }
455}