use std::fmt;
use std::io;
use std::net::{IpAddr, SocketAddr};
use std::str::FromStr;
use serde::{Deserialize, Serialize};
const DEFAULT_DNS_PORT: u16 = 53;
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Nameserver {
Addr(SocketAddr),
Host {
host: String,
port: u16,
},
}
impl Nameserver {
pub async fn resolve(&self) -> io::Result<SocketAddr> {
match self {
Self::Addr(sa) => Ok(*sa),
Self::Host { host, port } => tokio::net::lookup_host((host.as_str(), *port))
.await?
.next()
.ok_or_else(|| {
io::Error::new(
io::ErrorKind::NotFound,
format!("no addresses resolved for {host}:{port}"),
)
}),
}
}
}
impl fmt::Display for Nameserver {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Addr(sa) => write!(f, "{sa}"),
Self::Host { host, port } => write!(f, "{host}:{port}"),
}
}
}
#[derive(Debug, thiserror::Error)]
#[error("invalid nameserver {0:?}; expected IP, IP:PORT, HOST, or HOST:PORT")]
pub struct ParseNameserverError(pub String);
impl FromStr for Nameserver {
type Err = ParseNameserverError;
fn from_str(input: &str) -> Result<Self, Self::Err> {
let s = input.trim();
if s.is_empty() {
return Err(ParseNameserverError(input.to_owned()));
}
if let Ok(sa) = s.parse::<SocketAddr>() {
return Ok(Self::Addr(sa));
}
if let Ok(ip) = s.parse::<IpAddr>() {
return Ok(Self::Addr(SocketAddr::new(ip, DEFAULT_DNS_PORT)));
}
if let Some((host, port)) = s.rsplit_once(':')
&& !host.is_empty()
&& !host.contains(':')
&& host.parse::<IpAddr>().is_err()
&& let Ok(port) = port.parse::<u16>()
{
return Ok(Self::Host {
host: host.to_owned(),
port,
});
}
if !s.contains(char::is_whitespace) && !s.contains(':') {
return Ok(Self::Host {
host: s.to_owned(),
port: DEFAULT_DNS_PORT,
});
}
Err(ParseNameserverError(input.to_owned()))
}
}
impl Serialize for Nameserver {
fn serialize<S: serde::Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
s.collect_str(self)
}
}
impl<'de> Deserialize<'de> for Nameserver {
fn deserialize<D: serde::Deserializer<'de>>(d: D) -> Result<Self, D::Error> {
let s = String::deserialize(d)?;
s.parse().map_err(serde::de::Error::custom)
}
}
impl From<SocketAddr> for Nameserver {
fn from(sa: SocketAddr) -> Self {
Self::Addr(sa)
}
}
impl From<IpAddr> for Nameserver {
fn from(ip: IpAddr) -> Self {
Self::Addr(SocketAddr::new(ip, DEFAULT_DNS_PORT))
}
}
#[cfg(test)]
mod tests {
use super::*;
fn addr(s: &str) -> Nameserver {
Nameserver::Addr(s.parse().unwrap())
}
fn host(host: &str, port: u16) -> Nameserver {
Nameserver::Host {
host: host.to_owned(),
port,
}
}
#[test]
fn parses_ipv4_bare() {
assert_eq!("1.1.1.1".parse::<Nameserver>().unwrap(), addr("1.1.1.1:53"));
}
#[test]
fn parses_ipv4_with_port() {
assert_eq!(
"8.8.8.8:5353".parse::<Nameserver>().unwrap(),
addr("8.8.8.8:5353")
);
}
#[test]
fn parses_ipv6_bare() {
assert_eq!(
"2606:4700:4700::1111".parse::<Nameserver>().unwrap(),
addr("[2606:4700:4700::1111]:53")
);
}
#[test]
fn parses_ipv6_bracketed_with_port() {
assert_eq!(
"[2606:4700:4700::1111]:53".parse::<Nameserver>().unwrap(),
addr("[2606:4700:4700::1111]:53")
);
}
#[test]
fn parses_hostname_bare() {
assert_eq!(
"dns.google".parse::<Nameserver>().unwrap(),
host("dns.google", 53)
);
}
#[test]
fn parses_hostname_with_port() {
assert_eq!(
"dns.google:53".parse::<Nameserver>().unwrap(),
host("dns.google", 53)
);
assert_eq!(
"my-dns.corp.internal:5353".parse::<Nameserver>().unwrap(),
host("my-dns.corp.internal", 5353)
);
}
#[test]
fn trims_whitespace() {
assert_eq!(
" 1.1.1.1 ".parse::<Nameserver>().unwrap(),
addr("1.1.1.1:53")
);
}
#[test]
fn rejects_empty() {
assert!("".parse::<Nameserver>().is_err());
assert!(" ".parse::<Nameserver>().is_err());
}
#[test]
fn rejects_embedded_whitespace() {
assert!("dns google".parse::<Nameserver>().is_err());
}
#[test]
fn rejects_bad_port() {
assert!("dns.google:notaport".parse::<Nameserver>().is_err());
assert!("1.1.1.1:99999".parse::<Nameserver>().is_err());
}
#[test]
fn display_roundtrip() {
for s in ["1.1.1.1:53", "[2606:4700:4700::1111]:53", "dns.google:53"] {
let ns: Nameserver = s.parse().unwrap();
assert_eq!(ns.to_string(), s);
}
}
#[test]
fn display_feeds_back_into_parse() {
for s in ["1.1.1.1", "dns.google", "dns.google:53"] {
let ns: Nameserver = s.parse().unwrap();
let reparsed: Nameserver = ns.to_string().parse().unwrap();
assert_eq!(ns, reparsed);
}
}
}