1use crate::socket::SocketAddress;
2use hickory_proto::rr::Name;
3use serde::{Deserialize, Serialize};
4use std::net::{SocketAddr, SocketAddrV4, SocketAddrV6};
5use std::os::unix::net::SocketAddr as UnixSocketAddr;
6use std::str::FromStr;
7use thiserror::Error;
8
9#[derive(Clone, Serialize, Deserialize)]
15#[serde(try_from = "String", into = "String")]
16pub enum ResolvableSocketAddr {
17 UnixDomain(Box<UnixSocketAddr>),
18 V4(Box<SocketAddrV4>),
19 V6(Box<SocketAddrV6>),
20 Hostname {
24 host: String,
25 port: u16,
26 },
27}
28
29#[derive(Error, Debug)]
34#[error(
35 "failed to parse {candidate:?} as a resolvable socket address: \
36 not an IP socket ({socket}); \
37 not a unix socket path ({unix}); \
38 not host:port ({hostname})"
39)]
40pub struct ResolvableAddressParseError {
41 pub candidate: String,
42 pub socket: std::net::AddrParseError,
43 pub unix: std::io::Error,
44 pub hostname: HostnamePortParseError,
45}
46
47impl PartialEq for ResolvableAddressParseError {
48 fn eq(&self, other: &Self) -> bool {
49 self.to_string().eq(&other.to_string())
50 }
51}
52
53#[derive(Error, Debug)]
54pub enum HostnamePortParseError {
55 #[error("missing :port suffix")]
56 MissingPort,
57 #[error("invalid port {0:?}")]
58 InvalidPort(String),
59 #[error("invalid hostname {host:?}: {reason}")]
60 InvalidHostname { host: String, reason: String },
61}
62
63impl std::fmt::Debug for ResolvableSocketAddr {
64 fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result {
65 <Self as std::fmt::Display>::fmt(self, fmt)
66 }
67}
68
69impl std::fmt::Display for ResolvableSocketAddr {
70 fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result {
71 match self {
72 Self::UnixDomain(unix) => match unix.as_pathname() {
73 Some(path) => path.display().fmt(fmt),
74 None => write!(fmt, "<unbound unix domain>"),
75 },
76 Self::V4(a) => a.fmt(fmt),
77 Self::V6(a) => a.fmt(fmt),
78 Self::Hostname { host, port } => write!(fmt, "{host}:{port}"),
79 }
80 }
81}
82
83impl ResolvableSocketAddr {
84 pub fn unix(&self) -> Option<UnixSocketAddr> {
86 match self {
87 Self::UnixDomain(unix) => Some((**unix).clone()),
88 Self::V4(_) | Self::V6(_) | Self::Hostname { .. } => None,
89 }
90 }
91
92 pub fn port(&self) -> Option<u16> {
94 match self {
95 Self::UnixDomain(_) => None,
96 Self::V4(a) => Some(a.port()),
97 Self::V6(a) => Some(a.port()),
98 Self::Hostname { port, .. } => Some(*port),
99 }
100 }
101}
102
103impl From<ResolvableSocketAddr> for String {
104 fn from(a: ResolvableSocketAddr) -> String {
105 format!("{a}")
106 }
107}
108
109impl TryFrom<String> for ResolvableSocketAddr {
110 type Error = ResolvableAddressParseError;
111 fn try_from(s: String) -> Result<ResolvableSocketAddr, Self::Error> {
112 ResolvableSocketAddr::from_str(&s)
113 }
114}
115
116fn parse_hostname_port(s: &str) -> Result<(String, u16), HostnamePortParseError> {
117 let (host, port) = s
118 .rsplit_once(':')
119 .ok_or(HostnamePortParseError::MissingPort)?;
120 let port: u16 = port
121 .parse()
122 .map_err(|_| HostnamePortParseError::InvalidPort(port.to_string()))?;
123 if host.is_empty() {
124 return Err(HostnamePortParseError::InvalidHostname {
125 host: host.to_string(),
126 reason: "hostname is empty".to_string(),
127 });
128 }
129 Name::from_str_relaxed(host).map_err(|e| HostnamePortParseError::InvalidHostname {
130 host: host.to_string(),
131 reason: e.to_string(),
132 })?;
133 Ok((host.to_string(), port))
134}
135
136impl FromStr for ResolvableSocketAddr {
137 type Err = ResolvableAddressParseError;
138 fn from_str(s: &str) -> Result<ResolvableSocketAddr, Self::Err> {
139 match SocketAddress::from_str(s) {
140 Ok(SocketAddress::UnixDomain(p)) => Ok(ResolvableSocketAddr::UnixDomain(p)),
141 Ok(SocketAddress::V4(a)) => Ok(ResolvableSocketAddr::V4(Box::new(a))),
142 Ok(SocketAddress::V6(a)) => Ok(ResolvableSocketAddr::V6(Box::new(a))),
143 Err(socket_err) => match parse_hostname_port(s) {
144 Ok((host, port)) => Ok(ResolvableSocketAddr::Hostname { host, port }),
145 Err(hostname) => Err(ResolvableAddressParseError {
146 candidate: s.to_string(),
147 socket: socket_err.net_err,
148 unix: socket_err.unix_err,
149 hostname,
150 }),
151 },
152 }
153 }
154}
155
156impl PartialEq for ResolvableSocketAddr {
157 fn eq(&self, other: &Self) -> bool {
158 match (self, other) {
159 (Self::UnixDomain(a), Self::UnixDomain(b)) => {
160 match (a.as_pathname(), b.as_pathname()) {
161 (Some(a), Some(b)) => a.eq(b),
162 (None, None) => true,
163 _ => false,
164 }
165 }
166 (Self::V4(a), Self::V4(b)) => a.eq(b),
167 (Self::V6(a), Self::V6(b)) => a.eq(b),
168 (Self::Hostname { host: ah, port: ap }, Self::Hostname { host: bh, port: bp }) => {
169 ah == bh && ap == bp
170 }
171 _ => false,
172 }
173 }
174}
175
176impl Eq for ResolvableSocketAddr {}
177
178impl From<UnixSocketAddr> for ResolvableSocketAddr {
179 fn from(unix: UnixSocketAddr) -> Self {
180 Self::UnixDomain(unix.into())
181 }
182}
183
184impl From<tokio::net::unix::SocketAddr> for ResolvableSocketAddr {
185 fn from(unix: tokio::net::unix::SocketAddr) -> Self {
186 let unix: UnixSocketAddr = unix.into();
187 unix.into()
188 }
189}
190
191impl From<SocketAddr> for ResolvableSocketAddr {
192 fn from(a: SocketAddr) -> Self {
193 match a {
194 SocketAddr::V4(a) => Self::V4(Box::new(a)),
195 SocketAddr::V6(a) => Self::V6(Box::new(a)),
196 }
197 }
198}
199
200impl From<SocketAddrV4> for ResolvableSocketAddr {
201 fn from(a: SocketAddrV4) -> Self {
202 Self::V4(Box::new(a))
203 }
204}
205
206impl From<SocketAddrV6> for ResolvableSocketAddr {
207 fn from(a: SocketAddrV6) -> Self {
208 Self::V6(Box::new(a))
209 }
210}
211
212impl From<SocketAddress> for ResolvableSocketAddr {
213 fn from(a: SocketAddress) -> Self {
214 match a {
215 SocketAddress::UnixDomain(p) => Self::UnixDomain(p),
216 SocketAddress::V4(a) => Self::V4(Box::new(a)),
217 SocketAddress::V6(a) => Self::V6(Box::new(a)),
218 }
219 }
220}
221
222#[cfg(test)]
223mod test {
224 use super::*;
225 use std::net::{Ipv4Addr, Ipv6Addr};
226
227 #[test]
228 fn parse_literal_v4() {
229 k9::assert_equal!(
230 "10.0.0.1:25".parse::<ResolvableSocketAddr>().unwrap(),
231 ResolvableSocketAddr::V4(Box::new(SocketAddrV4::new(Ipv4Addr::new(10, 0, 0, 1), 25)))
232 );
233 k9::assert_equal!(
234 "[10.0.0.1]:25".parse::<ResolvableSocketAddr>().unwrap(),
235 ResolvableSocketAddr::V4(Box::new(SocketAddrV4::new(Ipv4Addr::new(10, 0, 0, 1), 25)))
236 );
237 }
238
239 #[test]
240 fn parse_literal_v6() {
241 k9::assert_equal!(
242 "[::1]:100".parse::<ResolvableSocketAddr>().unwrap(),
243 ResolvableSocketAddr::V6(Box::new(SocketAddrV6::new(Ipv6Addr::LOCALHOST, 100, 0, 0)))
244 );
245 }
246
247 #[test]
248 fn parse_unix() {
249 k9::assert_equal!(
250 "/some/path".parse::<ResolvableSocketAddr>().unwrap(),
251 ResolvableSocketAddr::UnixDomain(
252 UnixSocketAddr::from_pathname("/some/path").unwrap().into()
253 )
254 );
255 }
256
257 #[test]
258 fn parse_hostname() {
259 k9::assert_equal!(
260 "mx.example.com:25".parse::<ResolvableSocketAddr>().unwrap(),
261 ResolvableSocketAddr::Hostname {
262 host: "mx.example.com".to_string(),
263 port: 25
264 }
265 );
266 }
267
268 #[test]
269 fn reject_bogus_hostname() {
270 let err = "I have spaces:25"
271 .parse::<ResolvableSocketAddr>()
272 .unwrap_err();
273 k9::assert_equal!(
274 format!("{err:#}"),
275 "failed to parse \"I have spaces:25\" as a resolvable socket address: \
276 not an IP socket (invalid socket address syntax); \
277 not a unix socket path (unix domain path must be absolute); \
278 not host:port (invalid hostname \"I have spaces\": unrecognized char: )"
279 );
280 }
281
282 #[test]
283 fn reject_missing_port() {
284 let err = "mx.example.com"
285 .parse::<ResolvableSocketAddr>()
286 .unwrap_err();
287 k9::assert_equal!(
288 format!("{err:#}"),
289 "failed to parse \"mx.example.com\" as a resolvable socket address: \
290 not an IP socket (invalid socket address syntax); \
291 not a unix socket path (unix domain path must be absolute); \
292 not host:port (missing :port suffix)"
293 );
294 }
295
296 #[test]
297 fn reject_bad_port() {
298 let err = "mx.example.com:bogus"
299 .parse::<ResolvableSocketAddr>()
300 .unwrap_err();
301 k9::assert_equal!(
302 format!("{err:#}"),
303 "failed to parse \"mx.example.com:bogus\" as a resolvable socket address: \
304 not an IP socket (invalid socket address syntax); \
305 not a unix socket path (unix domain path must be absolute); \
306 not host:port (invalid port \"bogus\")"
307 );
308 }
309
310 #[test]
311 fn reject_unbracketed_v6_with_port() {
312 let err = "::1:100".parse::<ResolvableSocketAddr>().unwrap_err();
316 k9::assert_equal!(
317 format!("{err:#}"),
318 "failed to parse \"::1:100\" as a resolvable socket address: \
319 not an IP socket (invalid socket address syntax); \
320 not a unix socket path (unix domain path must be absolute); \
321 not host:port (invalid hostname \"::1\": Malformed label: ::1)"
322 );
323
324 let err = "fe80::1:100".parse::<ResolvableSocketAddr>().unwrap_err();
325 k9::assert_equal!(
326 format!("{err:#}"),
327 "failed to parse \"fe80::1:100\" as a resolvable socket address: \
328 not an IP socket (invalid socket address syntax); \
329 not a unix socket path (unix domain path must be absolute); \
330 not host:port (invalid hostname \"fe80::1\": Malformed label: fe80::1)"
331 );
332 }
333
334 #[test]
335 fn display_roundtrip() {
336 for s in &[
337 "10.0.0.1:25",
338 "[::1]:100",
339 "/some/path",
340 "mx.example.com:25",
341 ] {
342 let a: ResolvableSocketAddr = s.parse().unwrap();
343 let back: ResolvableSocketAddr = a.to_string().parse().unwrap();
344 k9::assert_equal!(a, back);
345 }
346 }
347}