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
use crate::context::Context;
use crate::helper::load_root_store;
use crate::listen::Config as ListenConfig;
use crate::remote::{Host as RemoteHost, Session as RemoteSession};
use crate::{get_listen_config, get_remote_host, Cache, DohError, DohResult};
use clap::{value_t, ArgMatches};
use futures::lock::Mutex;
use rustls::ClientConfig;
use std::io::Result as IoResult;
use std::sync::Arc;
use tokio::net::UdpSocket;

fn create_client_config(cafile: Option<&str>) -> DohResult<ClientConfig> {
    let root_store = load_root_store(cafile)?;
    let mut config = ClientConfig::new();
    config.root_store = root_store;
    config.alpn_protocols.push(vec![104, 50]); // h2
    Ok(config)
}

/// The configuration object for the `doh-client`.
pub struct Config {
    listen_config: ListenConfig,
    remote_host: RemoteHost,
    domain: String,
    client_config: Arc<ClientConfig>,
    uri: String,
    retries: u32,
    timeout: u64,
    post: bool,
    cache_size: usize,
    cache_fallback: bool,
}

impl Config {
    /// Create a new `doh_client::Config` object.
    pub fn new(
        listen_config: ListenConfig,
        remote_host: RemoteHost,
        domain: &str,
        cafile: Option<&str>,
        path: &str,
        retries: u32,
        timeout: u64,
        post: bool,
        cache_size: usize,
        cache_fallback: bool,
    ) -> DohResult<Config> {
        let client_config = create_client_config(cafile)?;

        let uri = format!("https://{}/{}", domain, path);

        if cache_fallback && cache_size == 0 {
            return Err(DohError::CacheSize);
        }

        Ok(Config {
            listen_config,
            remote_host,
            domain: domain.to_string(),
            client_config: Arc::new(client_config),
            uri,
            retries,
            timeout,
            post,
            cache_size,
            cache_fallback,
        })
    }

    pub async fn try_from(matches: ArgMatches<'static>) -> DohResult<Config> {
        let listen_config = get_listen_config(&matches)?;
        let remote_host = get_remote_host(&matches).await?;
        let domain = matches.value_of("domain").unwrap();
        let cafile = matches.value_of("cafile");
        let path = matches.value_of("path").unwrap();
        let retries: u32 = value_t!(matches, "retries", u32).unwrap_or(3);
        let timeout: u64 = value_t!(matches, "timeout", u64).unwrap_or(2);
        let post: bool = !matches.is_present("get");
        let cache_size: usize = value_t!(matches, "cache-size", usize).unwrap_or(1024);
        let cache_fallback: bool = matches.is_present("cache-fallback");
        Config::new(
            listen_config,
            remote_host,
            domain,
            cafile,
            path,
            retries,
            timeout,
            post,
            cache_size,
            cache_fallback,
        )
    }

    pub(crate) async fn into(self) -> IoResult<(Arc<UdpSocket>, Context)> {
        let cache = if self.cache_size == 0 {
            None
        } else {
            Some(Mutex::new(Cache::new(self.cache_size)))
        };
        let cache_fallback = self.cache_fallback;
        let timeout = self.timeout;
        let socket = self.listen_config.into_socket().await?;
        let socket = Arc::new(socket);
        let remote_session = RemoteSession::new(
            self.remote_host,
            self.domain,
            self.client_config,
            self.uri,
            self.retries,
            self.post,
        );
        let context = Context::new(
            cache,
            cache_fallback,
            timeout,
            remote_session,
            socket.clone(),
        );
        Ok((socket, context))
    }
}