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
119
120
121
122
123
124
125
126
127
128
use std::net::IpAddr;
use std::str::FromStr;

use reqwest::{self, header, Url};
use trust_dns::op::{Message, Query};
use trust_dns::rr::{Name, RecordType};
use trust_dns::rr::RData::*;

use error::Error;

/// A DoH client
///
/// It will use the configured DoH server to send all resolve queries to.
///
/// ## Example
///
/// ```
/// let client = dnsoverhttps::Client::from_url("https://1.1.1.1/dns-query").unwrap();
/// let addr = client.resolve_host("example.com");
/// ```
pub struct Client {
    url: Url,
    client: reqwest::Client,
}

impl Client {
    /// Create a new DoH client using the given query URL.
    /// The URL's host will be resolved using the system's resolver.
    /// The host will be queried using a `POST` request using the `application/dns-message` content-type for the body.
    pub fn from_url(url: &str) -> Result<Client, Error> {
        trace!("New client with url '{}' (needs resolving)", url);
        let url = Url::from_str(url)?;

        let client = reqwest::Client::builder().build()?;

        Ok(Client {
            url: url,
            client: client,
        })
    }

    /// Create a new DoH client using the given query URL and host.
    /// This should be used to bootstrap DoH resolving without the system's resolver. The URL can
    /// contain the host's IP and the hostname is used in the HTTP request.
    /// The host will be queried using a `POST` request using the `application/dns-message` content-type for the body.
    ///
    /// ## Caution
    /// This will disable hostname verification of the TLS server certificate.
    /// The certificate is still checked for validity.
    pub fn from_url_with_hostname(url: &str, host: String) -> Result<Client, Error> {
        trace!("New client with url '{}' and host '{}'", url, host);
        let url = Url::from_str(url)?;

        let mut headers = header::HeaderMap::new();
        headers.insert(header::HOST, header::HeaderValue::from_str(&host)?);
        let client = reqwest::Client::builder()
            .danger_accept_invalid_hostnames(true)
            .default_headers(headers)
            .build()?;

        Ok(Client {
            url: url,
            client: client,
        })
    }

    /// Resolve the host specified by `host` as a list of `IpAddr`.
    ///
    /// This method queries the configured server over HTTPS for both IPv4 and IPv6 addresses.
    ///
    /// If the host cannot be found, the list will be empty.
    /// If any errors are encountered during the resolving, the error is returned.
    pub fn resolve_host(&self, host: &str) -> Result<Vec<IpAddr>, Error> {
        trace!("Resolving '{}'", host);
        let mut ipv6 = resolve_host_family(&self.client, self.url.clone(), RecordType::AAAA, host)?;
        trace!("Found {} IPv6 addresses", ipv6.len());
        let ipv4 = resolve_host_family(&self.client, self.url.clone(), RecordType::A, host)?;
        trace!("Found {} IPv4 addresses", ipv4.len());

        ipv6.extend(ipv4);
        Ok(ipv6)
    }
}

fn resolve_host_family(client: &reqwest::Client, url: Url, af: RecordType, name: &str) -> Result<Vec<IpAddr>, Error> {
    let qname = Name::from_str(name)?;
    let query = Query::query(qname, af);
    let mut msg = Message::new();
    msg.set_recursion_desired(true);
    msg.add_query(query);

    let qbuf = msg.to_vec()?;

    let wireformat = "application/dns-message";

    let mut resp = client.post(url)
        .header("content-type", wireformat)
        .header("accept", wireformat)
        .body(qbuf)
        .send()?;

    let mut body = Vec::new();
    resp.copy_to(&mut body)?;
    trace!("Got response: {:?}", body);
    let msg = Message::from_vec(&body)?;

    let results = msg.answers()
        .iter()
        .map(|answer| answer.rdata())
        .flat_map(|data| {
            match *data {
                A(ipv4) => Some(IpAddr::V4(ipv4)),
                AAAA(ipv6) => Some(IpAddr::V6(ipv6)),
                _ => None
            }
        })
        .collect();

    Ok(results)
}

impl Default for Client {
    fn default() -> Client {
        const DNS_QUERY_URL : &str = "https://1.1.1.1/dns-query";

        Client::from_url(DNS_QUERY_URL).unwrap()
    }
}