doh_client/cmd/
remote_host.rs

1#[cfg(feature = "http-proxy")]
2use crate::helper::load_root_store;
3use crate::RemoteHost;
4use clap::ArgMatches;
5use std::io::Error as IoError;
6#[cfg(feature = "socks5")]
7use std::net::{IpAddr, SocketAddr};
8#[cfg(feature = "http-proxy")]
9use std::sync::Arc;
10use thiserror::Error as ThisError;
11#[cfg(feature = "http-proxy")]
12use tokio_rustls::rustls::ClientConfig;
13
14#[derive(Debug, ThisError)]
15pub enum RemoteHostError {
16    #[cfg(any(feature = "socks5", feature = "http-proxy"))]
17    #[error("Could not parse proxy scheme: {0}")]
18    ProxyScheme(String),
19    #[cfg(any(feature = "socks5", feature = "http-proxy"))]
20    #[error("Could not parse proxy credentials: {0}")]
21    ProxyCredentials(String),
22    #[error("IO Error: {0}")]
23    Io(#[from] IoError),
24    #[error("Unknown port: {0}")]
25    UnknownPort(String),
26    #[error("Unknown hostm and port: {0}")]
27    UnknownHostPort(String),
28}
29
30fn parse_host_port(host_port: &str) -> Result<(&str, u16), RemoteHostError> {
31    let host_port_vec: Vec<&str> = host_port.rsplitn(2, ':').collect();
32    if host_port_vec.len() != 2 {
33        return Err(RemoteHostError::UnknownHostPort(host_port.to_owned()));
34    }
35    let host = host_port_vec[1];
36    if let Ok(port) = host_port_vec[0].parse() {
37        Ok((host, port))
38    } else {
39        Err(RemoteHostError::UnknownPort(host.to_owned()))
40    }
41}
42
43fn get_remote_host_port(arg_matches: &ArgMatches) -> Result<(String, u16), RemoteHostError> {
44    let remote_host_port = arg_matches.get_one::<String>("remote-host").unwrap();
45    let (remote_host, remote_port) = parse_host_port(remote_host_port)?;
46    Ok((remote_host.to_owned(), remote_port))
47}
48
49fn get_direct(arg_matches: &ArgMatches) -> Result<RemoteHost, RemoteHostError> {
50    let (remote_host, remote_port) = get_remote_host_port(arg_matches)?;
51    let remote_host = RemoteHost::Direct(remote_host, remote_port);
52    Ok(remote_host)
53}
54
55#[cfg(any(feature = "socks5", feature = "http-proxy"))]
56async fn get_proxy_host_port(arg_matches: &ArgMatches) -> Result<(String, u16), RemoteHostError> {
57    let proxy_host_port = arg_matches.get_one::<String>("proxy-host").unwrap();
58    let (proxy_host, proxy_port) = parse_host_port(proxy_host_port)?;
59    Ok((proxy_host.to_owned(), proxy_port))
60}
61
62#[cfg(feature = "socks5")]
63async fn get_proxy_remote_addrs(
64    arg_matches: &ArgMatches,
65) -> Result<Vec<SocketAddr>, RemoteHostError> {
66    let remote_host = arg_matches.get_one::<String>("remote-host").unwrap();
67    let (host, port) = parse_host_port(remote_host)?;
68    match host.parse::<IpAddr>() {
69        Ok(host) => {
70            let remote_addrs = vec![SocketAddr::new(host, port)];
71            Ok(remote_addrs)
72        }
73        Err(_) => {
74            let remote_addrs = tokio::net::lookup_host(remote_host).await?;
75            let remote_addrs = remote_addrs.collect();
76            Ok(remote_addrs)
77        }
78    }
79}
80
81#[cfg(any(feature = "socks5", feature = "http-proxy"))]
82fn get_proxy_credentials(
83    arg_matches: &ArgMatches,
84) -> Result<Option<(String, String)>, RemoteHostError> {
85    if let Some(proxy_credentials) = arg_matches.get_one::<String>("proxy-credentials") {
86        let proxy_credentials_vec: Vec<&str> = proxy_credentials.splitn(2, ':').collect();
87        if proxy_credentials_vec.len() == 2 {
88            let username = proxy_credentials_vec[0].to_owned();
89            let password = proxy_credentials_vec[1].to_owned();
90            Ok(Some((username, password)))
91        } else {
92            Err(RemoteHostError::ProxyCredentials(
93                proxy_credentials.to_owned(),
94            ))
95        }
96    } else {
97        Ok(None)
98    }
99}
100
101#[cfg(feature = "http-proxy")]
102fn get_proxy_https_client_config(
103    arg_matches: &ArgMatches,
104) -> Result<ClientConfig, RemoteHostError> {
105    let https_cafile = arg_matches.get_one::<String>("proxy-https-cafile");
106    let root_store = load_root_store(https_cafile)?;
107    let mut config = ClientConfig::builder()
108        .with_root_certificates(root_store)
109        .with_no_client_auth();
110    config
111        .alpn_protocols
112        .push(vec![0x68, 0x74, 0x74, 0x70, 0x2f, 0x31, 0x2e, 0x31]); // http/1.1
113    Ok(config)
114}
115
116#[cfg(feature = "http-proxy")]
117fn get_proxy_https_domain(arg_matches: &ArgMatches) -> &String {
118    arg_matches.get_one::<String>("proxy-https-domain").unwrap()
119}
120
121#[cfg(any(feature = "socks5", feature = "http-proxy"))]
122async fn get_proxy(arg_matches: &ArgMatches) -> Result<RemoteHost, RemoteHostError> {
123    let proxy_scheme = arg_matches.get_one::<String>("proxy-scheme");
124    if let Some(proxy_scheme) = proxy_scheme {
125        let (proxy_host, proxy_port) = get_proxy_host_port(arg_matches).await?;
126        let credentials = get_proxy_credentials(arg_matches)?;
127        match proxy_scheme.as_str() {
128            #[cfg(feature = "socks5")]
129            "socks5" => {
130                let remote_addrs = get_proxy_remote_addrs(arg_matches).await?;
131                Ok(RemoteHost::Socks5(
132                    proxy_host,
133                    proxy_port,
134                    credentials,
135                    remote_addrs,
136                ))
137            }
138            #[cfg(feature = "socks5")]
139            "socks5h" => {
140                let (remote_host, remote_port) = get_remote_host_port(arg_matches)?;
141                Ok(RemoteHost::Socks5h(
142                    proxy_host,
143                    proxy_port,
144                    credentials,
145                    remote_host,
146                    remote_port,
147                ))
148            }
149            #[cfg(feature = "http-proxy")]
150            "http" => {
151                let (remote_host, remote_port) = get_remote_host_port(arg_matches)?;
152                Ok(RemoteHost::HttpProxy(
153                    proxy_host,
154                    proxy_port,
155                    credentials,
156                    remote_host,
157                    remote_port,
158                ))
159            }
160            #[cfg(feature = "http-proxy")]
161            "https" => {
162                let (remote_host, remote_port) = get_remote_host_port(arg_matches)?;
163                let https_client_config = get_proxy_https_client_config(arg_matches)?;
164                let https_client_config = Arc::new(https_client_config);
165                let https_domain = get_proxy_https_domain(arg_matches);
166                Ok(RemoteHost::HttpsProxy(
167                    proxy_host,
168                    proxy_port,
169                    credentials,
170                    remote_host,
171                    remote_port,
172                    https_client_config,
173                    https_domain.clone(),
174                ))
175            }
176            scheme => Err(RemoteHostError::ProxyScheme(scheme.to_string())),
177        }
178    } else {
179        get_direct(arg_matches)
180    }
181}
182
183#[cfg(all(not(feature = "socks5"), not(feature = "http-proxy")))]
184async fn get_proxy(_: &ArgMatches) -> Result<RemoteHost, RemoteHostError> {
185    Err(RemoteHostError::Io(IoError::new(
186        std::io::ErrorKind::Other,
187        "Feature native-certs is not enabled",
188    )))
189}
190
191pub async fn get_remote_host(arg_matches: &ArgMatches) -> Result<RemoteHost, RemoteHostError> {
192    if cfg!(any(feature = "socks5", feature = "http-proxy")) {
193        get_proxy(arg_matches).await
194    } else {
195        get_direct(arg_matches)
196    }
197}