Skip to main content

prettyping_rs/net/
dns.rs

1use std::net::{IpAddr, ToSocketAddrs};
2
3use thiserror::Error;
4
5use crate::config::AddressFamily;
6
7#[derive(Debug, Clone, PartialEq, Eq)]
8pub struct ResolveResult {
9    pub host: String,
10    pub addresses: Vec<IpAddr>,
11}
12
13#[derive(Debug, Error, Clone, PartialEq, Eq)]
14pub enum ResolveError {
15    #[error("host must not be empty")]
16    EmptyHost,
17    #[error("failed to resolve '{host}': {message}")]
18    LookupFailed { host: String, message: String },
19    #[error("resolved '{host}' but no address matches family {family:?}")]
20    NoAddressForFamily { host: String, family: AddressFamily },
21}
22
23pub fn resolve_once(host: &str, family: AddressFamily) -> Result<ResolveResult, ResolveError> {
24    let trimmed = host.trim();
25    if trimmed.is_empty() {
26        return Err(ResolveError::EmptyHost);
27    }
28
29    if let Ok(ip) = trimmed.parse::<IpAddr>() {
30        if matches_family(ip, family) {
31            return Ok(ResolveResult {
32                host: trimmed.to_string(),
33                addresses: vec![ip],
34            });
35        }
36        return Err(ResolveError::NoAddressForFamily {
37            host: trimmed.to_string(),
38            family,
39        });
40    }
41
42    let lookup = (trimmed, 0)
43        .to_socket_addrs()
44        .map_err(|err| ResolveError::LookupFailed {
45            host: trimmed.to_string(),
46            message: err.to_string(),
47        })?;
48
49    let mut addresses = Vec::new();
50    for socket_addr in lookup {
51        let ip = socket_addr.ip();
52        if matches_family(ip, family) && !addresses.contains(&ip) {
53            addresses.push(ip);
54        }
55    }
56
57    if addresses.is_empty() {
58        return Err(ResolveError::NoAddressForFamily {
59            host: trimmed.to_string(),
60            family,
61        });
62    }
63
64    Ok(ResolveResult {
65        host: trimmed.to_string(),
66        addresses,
67    })
68}
69
70fn matches_family(ip: IpAddr, family: AddressFamily) -> bool {
71    match family {
72        AddressFamily::Any => true,
73        AddressFamily::Ipv4 => ip.is_ipv4(),
74        AddressFamily::Ipv6 => ip.is_ipv6(),
75    }
76}
77
78#[cfg(test)]
79mod tests {
80    use super::{AddressFamily, ResolveError, resolve_once};
81
82    #[test]
83    fn literal_ipv6_is_rejected_when_ipv4_is_requested() {
84        let err = resolve_once("::1", AddressFamily::Ipv4).expect_err("must reject v6 for -4");
85        assert!(matches!(err, ResolveError::NoAddressForFamily { .. }));
86    }
87
88    #[test]
89    fn literal_ipv4_is_rejected_when_ipv6_is_requested() {
90        let err =
91            resolve_once("127.0.0.1", AddressFamily::Ipv6).expect_err("must reject v4 for -6");
92        assert!(matches!(err, ResolveError::NoAddressForFamily { .. }));
93    }
94}