1use std::fmt::{Debug, Display, Formatter};
2use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
3
4#[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#[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 VpnIpv6(RouteDistinguisher, Ipv6Addr),
45 VpnIpv6LinkLocal(RouteDistinguisher, Ipv6Addr, RouteDistinguisher, Ipv6Addr),
48}
49
50impl NextHopAddress {
51 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 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
74impl 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 assert_eq!(format!("{rd:?}"), "RD(00:01:02:03:04:05:06:07)");
215
216 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 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 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()); 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 let vpn_ll_ip = NextHopAddress::VpnIpv6(rd, ipv6_link_local);
246 assert!(vpn_ll_ip.is_link_local()); }
248}