doh_client/cmd/
remote_host.rs1#[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]); 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}