1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
//! The system resolver implementation.

use std::{io, net::SocketAddr, vec};

use dns_lookup::AddrInfoHints;
use hyper::client::connect::dns::Name;
use tokio::task::JoinHandle;

use crate::background;

/// [`System`] encapsulates logic to perform the name resolution in
/// the background using system resolution mechanisms.
///
/// Uses [`dns_lookup::getaddrinfo`] in a [`tokio::task::spawn_blocking`] to
/// perform the resolution.
#[derive(Debug, Builder, Default)]
pub struct System {
    /// The hints to give the the system resolver when performing the
    /// resolution.
    ///
    /// Passing [`None`] is not equivalent to passing [`Some`] value filled with
    /// zeroes, as underlying systems typically have some non-trivial defaults
    /// when hint is omitted.
    pub addr_info_hints: Option<AddrInfoHints>,

    /// The name of the service to resolve.
    /// If set to [`None`], the network address of the node is resolved.
    /// If set to [`Some`], the the requested service address is resolved.
    /// This can be either a descriptive name or a numeric representation
    /// suitable for use with the address family or families.
    /// If the specified address family is AF_INET,  AF_INET6, or AF_UNSPEC,
    /// the service can be specified as a string specifying a decimal port
    /// number.
    pub service: Option<String>,
}

impl background::Resolve for System {
    type Iter = vec::IntoIter<SocketAddr>;

    fn resolve(&mut self, name: Name) -> JoinHandle<io::Result<Self::Iter>> {
        let addr_info_hints = self.addr_info_hints;
        tokio::task::spawn_blocking(move || {
            debug!("resolving host={:?}", name);

            let iter = dns_lookup::getaddrinfo(Some(name.as_str()), None, addr_info_hints)?;
            let list = iter
                .map(|result| result.map(|addr_info| addr_info.sockaddr))
                .collect::<Result<Vec<_>, _>>()?;
            Ok(list.into_iter())
        })
    }
}

/// System resolver compatible with [`hyper`].
pub type Resolver = background::Resolver<System>;

#[cfg(test)]
mod tests {
    use std::{
        net::{Ipv4Addr, Ipv6Addr, SocketAddrV4, SocketAddrV6},
        str::FromStr,
    };

    use tower_service::Service;

    use crate::addr_info_hints::AddressFamily;

    use super::*;

    #[tokio::test]
    async fn test_resolve_ipv4() {
        let mut resolver = background::Resolver::new(System {
            addr_info_hints: Some(
                crate::AddrInfoHints {
                    address_family: AddressFamily::Inet,
                }
                .into(),
            ),
            service: None,
        });

        let addrs: Vec<_> = resolver
            .call(Name::from_str("localhost").unwrap())
            .await
            .unwrap()
            .collect();

        let localhost = SocketAddr::V4(SocketAddrV4::new(Ipv4Addr::new(127, 0, 0, 1), 0));
        assert_eq!(addrs, vec![localhost, localhost]);
    }

    #[tokio::test]
    async fn test_resolve_ipv6() {
        let mut resolver = background::Resolver::new(System {
            addr_info_hints: Some(
                crate::AddrInfoHints {
                    address_family: AddressFamily::Inet6,
                }
                .into(),
            ),
            service: None,
        });

        let addrs: Vec<_> = resolver
            .call(Name::from_str("localhost").unwrap())
            .await
            .unwrap()
            .collect();

        let localhost = SocketAddr::V6(SocketAddrV6::new(
            Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 1),
            0,
            0,
            0,
        ));
        assert_eq!(addrs, vec![localhost, localhost]);
    }
}