use std::borrow::Cow;
use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr};
use std::pin::Pin;
use std::str;
use std::task::{Context, Poll};
use futures_core::Stream;
use futures_util::ready;
use futures_util::{future, stream};
use pin_project_lite::pin_project;
use tracing::trace_span;
use tracing_futures::Instrument;
use trust_dns_proto::{
error::{ProtoError, ProtoErrorKind},
op::Query,
rr::{Name, RData, RecordType},
udp::UdpClientStream,
xfer::{DnsHandle, DnsRequestOptions, DnsResponse},
};
#[cfg(feature = "tokio-dns-resolver")]
use tokio::{net::UdpSocket, runtime::Handle};
#[cfg(feature = "tokio-dns-resolver")]
use trust_dns_client::client::AsyncClient;
use crate::{Resolutions, Version};
const DEFAULT_DNS_PORT: u16 = 53;
pub const ALL: &dyn crate::Resolver<'static> = &&[
#[cfg(feature = "opendns")]
OPENDNS,
#[cfg(feature = "google")]
GOOGLE,
];
#[cfg(feature = "opendns")]
#[cfg_attr(docsrs, doc(cfg(feature = "opendns")))]
pub const OPENDNS: &dyn crate::Resolver<'static> = &&[OPENDNS_V4, OPENDNS_V6];
#[cfg(feature = "opendns")]
#[cfg_attr(docsrs, doc(cfg(feature = "opendns")))]
pub const OPENDNS_V4: &dyn crate::Resolver<'static> = &Resolver::new_static(
"myip.opendns.com",
&[
IpAddr::V4(Ipv4Addr::new(208, 67, 222, 222)),
IpAddr::V4(Ipv4Addr::new(208, 67, 220, 220)),
IpAddr::V4(Ipv4Addr::new(208, 67, 222, 220)),
IpAddr::V4(Ipv4Addr::new(208, 67, 220, 222)),
],
DEFAULT_DNS_PORT,
QueryMethod::A,
);
#[cfg(feature = "opendns")]
#[cfg_attr(docsrs, doc(cfg(feature = "opendns")))]
pub const OPENDNS_V6: &dyn crate::Resolver<'static> = &Resolver::new_static(
"myip.opendns.com",
&[
IpAddr::V6(Ipv6Addr::new(9760, 0, 3276, 0, 0, 0, 0, 2)),
IpAddr::V6(Ipv6Addr::new(9760, 0, 3277, 0, 0, 0, 0, 2)),
],
DEFAULT_DNS_PORT,
QueryMethod::AAAA,
);
#[cfg(feature = "google")]
#[cfg_attr(docsrs, doc(cfg(feature = "google")))]
pub const GOOGLE: &dyn crate::Resolver<'static> = &&[GOOGLE_V4, GOOGLE_V6];
#[cfg(feature = "google")]
#[cfg_attr(docsrs, doc(cfg(feature = "google")))]
pub const GOOGLE_V4: &dyn crate::Resolver<'static> = &Resolver::new_static(
"o-o.myaddr.l.google.com",
&[
IpAddr::V4(Ipv4Addr::new(216, 239, 32, 10)),
IpAddr::V4(Ipv4Addr::new(216, 239, 34, 10)),
IpAddr::V4(Ipv4Addr::new(216, 239, 36, 10)),
IpAddr::V4(Ipv4Addr::new(216, 239, 38, 10)),
],
DEFAULT_DNS_PORT,
QueryMethod::TXT,
);
#[cfg(feature = "google")]
#[cfg_attr(docsrs, doc(cfg(feature = "google")))]
pub const GOOGLE_V6: &dyn crate::Resolver<'static> = &Resolver::new_static(
"o-o.myaddr.l.google.com",
&[
IpAddr::V6(Ipv6Addr::new(8193, 18528, 18434, 50, 0, 0, 0, 10)),
IpAddr::V6(Ipv6Addr::new(8193, 18528, 18434, 52, 0, 0, 0, 10)),
IpAddr::V6(Ipv6Addr::new(8193, 18528, 18434, 54, 0, 0, 0, 10)),
IpAddr::V6(Ipv6Addr::new(8193, 18528, 18434, 56, 0, 0, 0, 10)),
],
DEFAULT_DNS_PORT,
QueryMethod::TXT,
);
pub type Error = ProtoError;
#[derive(Debug, Clone)]
pub struct Details {
name: Name,
server: SocketAddr,
method: QueryMethod,
}
impl Details {
#[must_use]
pub fn name(&self) -> &Name {
&self.name
}
#[must_use]
pub fn server(&self) -> SocketAddr {
self.server
}
#[must_use]
pub fn query_method(&self) -> QueryMethod {
self.method
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
#[allow(clippy::upper_case_acronyms)]
pub enum QueryMethod {
A,
AAAA,
TXT,
}
#[derive(Debug)]
pub struct Resolver<'r> {
port: u16,
name: Cow<'r, str>,
servers: Cow<'r, [IpAddr]>,
method: QueryMethod,
}
impl<'r> Resolver<'r> {
pub fn new<N, S>(name: N, servers: S, port: u16, method: QueryMethod) -> Self
where
N: Into<Cow<'r, str>>,
S: Into<Cow<'r, [IpAddr]>>,
{
Self {
port,
name: name.into(),
servers: servers.into(),
method,
}
}
}
impl Resolver<'static> {
#[must_use]
pub const fn new_static(
name: &'static str,
servers: &'static [IpAddr],
port: u16,
method: QueryMethod,
) -> Self {
Self {
port,
name: Cow::Borrowed(name),
servers: Cow::Borrowed(servers),
method,
}
}
}
impl<'r> crate::Resolver<'r> for Resolver<'r> {
fn resolve(&self, version: Version) -> Resolutions<'r> {
let port = self.port;
let method = self.method;
let name = match Name::from_ascii(self.name.as_ref()) {
Ok(name) => name,
Err(err) => return Box::pin(stream::once(future::ready(Err(crate::Error::new(err))))),
};
let mut servers: Vec<_> = self
.servers
.iter()
.copied()
.filter(|addr| version.matches(*addr))
.collect();
let first_server = match servers.pop() {
Some(server) => server,
None => return Box::pin(stream::empty()),
};
let record_type = match self.method {
QueryMethod::A => RecordType::A,
QueryMethod::AAAA => RecordType::AAAA,
QueryMethod::TXT => RecordType::TXT,
};
let span = trace_span!("dns resolver", ?version, ?method, %name, %port);
let query = Query::query(name, record_type);
let stream = resolve(first_server, port, query.clone(), method);
let resolutions = DnsResolutions {
port,
version,
query,
method,
servers,
stream,
};
Box::pin(resolutions.instrument(span))
}
}
pin_project! {
struct DnsResolutions<'r> {
port: u16,
version: Version,
query: Query,
method: QueryMethod,
servers: Vec<IpAddr>,
#[pin]
stream: Resolutions<'r>,
}
}
impl<'r> Stream for DnsResolutions<'r> {
type Item = Result<(IpAddr, crate::Details), crate::Error>;
fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
match ready!(self.as_mut().project().stream.poll_next(cx)) {
Some(o) => Poll::Ready(Some(o)),
None => self.servers.pop().map_or(Poll::Ready(None), |server| {
self.stream = resolve(server, self.port, self.query.clone(), self.method);
self.project().stream.poll_next(cx)
}),
}
}
}
#[cfg(feature = "tokio-dns-resolver")]
async fn dns_query(
server: SocketAddr,
query: Query,
query_opts: DnsRequestOptions,
) -> Result<DnsResponse, ProtoError> {
let handle = Handle::current();
let stream = UdpClientStream::<UdpSocket>::new(server);
let (mut client, bg) = AsyncClient::connect(stream).await?;
handle.spawn(bg);
client.lookup(query, query_opts).await
}
fn parse_dns_response(
mut response: DnsResponse,
method: QueryMethod,
) -> Result<IpAddr, crate::Error> {
let answer = match response.take_answers().into_iter().next() {
Some(answer) => answer,
None => return Err(crate::Error::Addr),
};
match answer.into_data() {
RData::A(addr) if method == QueryMethod::A => Ok(IpAddr::V4(addr)),
RData::AAAA(addr) if method == QueryMethod::AAAA => Ok(IpAddr::V6(addr)),
RData::TXT(txt) if method == QueryMethod::TXT => match txt.iter().next() {
Some(addr_bytes) => Ok(str::from_utf8(&addr_bytes[..])?.parse()?),
None => Err(crate::Error::Addr),
},
_ => Err(ProtoError::from(ProtoErrorKind::Message("invalid response")).into()),
}
}
fn resolve<'r>(server: IpAddr, port: u16, query: Query, method: QueryMethod) -> Resolutions<'r> {
let fut = async move {
let name = query.name().clone();
let server = SocketAddr::new(server, port);
let query_opts = DnsRequestOptions {
use_edns: true,
expects_multiple_responses: false,
};
let response = dns_query(server, query, query_opts).await?;
let addr = parse_dns_response(response, method)?;
let details = Box::new(Details {
name,
server,
method,
});
Ok((addr, crate::Details::from(details)))
};
Box::pin(stream::once(
fut.instrument(trace_span!("query server", %server)),
))
}