use std::{ future::Future, io, net::SocketAddr, pin::Pin, rc::Rc, task::{Context, Poll}, vec::IntoIter, }; use actix_rt::task::{spawn_blocking, JoinHandle}; use actix_service::{Service, ServiceFactory}; use actix_utils::future::{ok, Ready}; use futures_core::{future::LocalBoxFuture, ready}; use tracing::trace; use super::{ConnectError, ConnectInfo, Host, Resolve}; /// DNS resolver service factory. #[derive(Clone, Default)] pub struct Resolver { resolver: ResolverService, } impl Resolver { /// Constructs a new resolver factory with a custom resolver. pub fn custom(resolver: impl Resolve + 'static) -> Self { Self { resolver: ResolverService::custom(resolver), } } /// Returns a new resolver service. pub fn service(&self) -> ResolverService { self.resolver.clone() } } impl ServiceFactory> for Resolver { type Response = ConnectInfo; type Error = ConnectError; type Config = (); type Service = ResolverService; type InitError = (); type Future = Ready>; fn new_service(&self, _: ()) -> Self::Future { ok(self.resolver.clone()) } } #[derive(Clone)] enum ResolverKind { /// Built-in DNS resolver. /// /// See [`std::net::ToSocketAddrs`] trait. Default, /// Custom, user-provided DNS resolver. Custom(Rc), } impl Default for ResolverKind { fn default() -> Self { Self::Default } } /// DNS resolver service. #[derive(Clone, Default)] pub struct ResolverService { kind: ResolverKind, } impl ResolverService { /// Constructor for custom Resolve trait object and use it as resolver. pub fn custom(resolver: impl Resolve + 'static) -> Self { Self { kind: ResolverKind::Custom(Rc::new(resolver)), } } /// Resolve DNS with default resolver. fn default_lookup( req: &ConnectInfo, ) -> JoinHandle>> { // reconstruct host; concatenate hostname and port together let host = format!("{}:{}", req.hostname(), req.port()); // run blocking DNS lookup in thread pool since DNS lookups can take upwards of seconds on // some platforms if conditions are poor and OS-level cache is not populated spawn_blocking(move || std::net::ToSocketAddrs::to_socket_addrs(&host)) } } impl Service> for ResolverService { type Response = ConnectInfo; type Error = ConnectError; type Future = ResolverFut; actix_service::always_ready!(); fn call(&self, req: ConnectInfo) -> Self::Future { if req.addr.is_resolved() { // socket address(es) already resolved; return existing connection request ResolverFut::Resolved(Some(req)) } else if let Ok(ip) = req.hostname().parse() { // request hostname is valid ip address; add address to request and return let addr = SocketAddr::new(ip, req.port()); let req = req.set_addr(Some(addr)); ResolverFut::Resolved(Some(req)) } else { trace!("DNS resolver: resolving host {:?}", req.hostname()); match &self.kind { ResolverKind::Default => { let fut = Self::default_lookup(&req); ResolverFut::LookUp(fut, Some(req)) } ResolverKind::Custom(resolver) => { let resolver = Rc::clone(resolver); ResolverFut::LookupCustom(Box::pin(async move { let addrs = resolver .lookup(req.hostname(), req.port()) .await .map_err(ConnectError::Resolver)?; let req = req.set_addrs(addrs); if req.addr.is_unresolved() { Err(ConnectError::NoRecords) } else { Ok(req) } })) } } } } } /// Future for resolver service. #[doc(hidden)] pub enum ResolverFut { Resolved(Option>), LookUp( JoinHandle>>, Option>, ), LookupCustom(LocalBoxFuture<'static, Result, ConnectError>>), } impl Future for ResolverFut { type Output = Result, ConnectError>; fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { match self.get_mut() { Self::Resolved(conn) => Poll::Ready(Ok(conn .take() .expect("ResolverFuture polled after finished"))), Self::LookUp(fut, req) => { let res = match ready!(Pin::new(fut).poll(cx)) { Ok(Ok(res)) => Ok(res), Ok(Err(err)) => Err(ConnectError::Resolver(Box::new(err))), Err(err) => Err(ConnectError::Io(err.into())), }; let req = req.take().unwrap(); let addrs = res.map_err(|err| { trace!( "DNS resolver: failed to resolve host {:?} err: {:?}", req.hostname(), err ); err })?; let req = req.set_addrs(addrs); trace!( "DNS resolver: host {:?} resolved to {:?}", req.hostname(), req.addrs() ); if req.addr.is_unresolved() { Poll::Ready(Err(ConnectError::NoRecords)) } else { Poll::Ready(Ok(req)) } } Self::LookupCustom(fut) => fut.as_mut().poll(cx), } } }