1//! client proxy
2
3mod http_proxy;
4mod socks_proxy;
5
6use std::sync::LazyLock;
7
8use anyhow::{Context as _, Result};
9use hickory_resolver::{
10 AsyncResolver, TokioAsyncResolver,
11 config::LookupIpStrategy,
12 name_server::{GenericConnector, TokioRuntimeProvider},
13 system_conf,
14};
15use http_client::Url;
16use http_proxy::{HttpProxyType, connect_http_proxy_stream, parse_http_proxy};
17use socks_proxy::{SocksVersion, connect_socks_proxy_stream, parse_socks_proxy};
18use tokio_socks::{IntoTargetAddr, TargetAddr};
19use util::ResultExt;
20
21pub(crate) async fn connect_proxy_stream(
22 proxy: &Url,
23 rpc_host: (&str, u16),
24) -> Result<Box<dyn AsyncReadWrite>> {
25 let Some(((proxy_domain, proxy_port), proxy_type)) = parse_proxy_type(proxy).await else {
26 // If parsing the proxy URL fails, we must avoid falling back to an insecure connection.
27 // SOCKS proxies are often used in contexts where security and privacy are critical,
28 // so any fallback could expose users to significant risks.
29 anyhow::bail!("Parsing proxy url type failed");
30 };
31
32 // Connect to proxy and wrap protocol later
33 let stream = tokio::net::TcpStream::connect((proxy_domain.as_str(), proxy_port))
34 .await
35 .context("Failed to connect to proxy")?;
36
37 let proxy_stream = match proxy_type {
38 ProxyType::SocksProxy(proxy) => connect_socks_proxy_stream(stream, proxy, rpc_host).await?,
39 ProxyType::HttpProxy(proxy) => {
40 connect_http_proxy_stream(stream, proxy, rpc_host, &proxy_domain).await?
41 }
42 };
43
44 Ok(proxy_stream)
45}
46
47enum ProxyType<'t> {
48 SocksProxy(SocksVersion<'t>),
49 HttpProxy(HttpProxyType<'t>),
50}
51
52async fn parse_proxy_type(proxy: &Url) -> Option<((String, u16), ProxyType<'_>)> {
53 let scheme = proxy.scheme();
54 let proxy_type = match scheme {
55 scheme if scheme.starts_with("socks") => {
56 Some(ProxyType::SocksProxy(parse_socks_proxy(scheme, proxy)))
57 }
58 scheme if scheme.starts_with("http") => {
59 Some(ProxyType::HttpProxy(parse_http_proxy(scheme, proxy)))
60 }
61 _ => None,
62 }?;
63 let (ip, port) = {
64 let host = proxy.host()?.to_string();
65 let port = proxy.port_or_known_default()?;
66 resolve_proxy_url_if_needed((host, port)).await.log_err()?
67 };
68
69 Some(((ip, port), proxy_type))
70}
71
72static SYSTEM_DNS_RESOLVER: LazyLock<AsyncResolver<GenericConnector<TokioRuntimeProvider>>> =
73 LazyLock::new(|| {
74 let (config, mut opts) = system_conf::read_system_conf().unwrap();
75 opts.ip_strategy = LookupIpStrategy::Ipv4AndIpv6;
76 TokioAsyncResolver::tokio(config, opts)
77 });
78
79async fn resolve_proxy_url_if_needed(proxy: (String, u16)) -> Result<(String, u16)> {
80 let proxy = proxy
81 .into_target_addr()
82 .context("Failed to parse proxy addr")?;
83 match proxy {
84 TargetAddr::Domain(domain, port) => {
85 let ip = SYSTEM_DNS_RESOLVER
86 .lookup_ip(domain.as_ref())
87 .await?
88 .into_iter()
89 .next()
90 .ok_or_else(|| anyhow::anyhow!("No IP found for proxy domain {domain}"))?;
91 Ok((ip.to_string(), port))
92 }
93 TargetAddr::Ip(ip_addr) => Ok((ip_addr.ip().to_string(), ip_addr.port())),
94 }
95}
96
97pub(crate) trait AsyncReadWrite:
98 tokio::io::AsyncRead + tokio::io::AsyncWrite + Unpin + Send + 'static
99{
100}
101impl<T: tokio::io::AsyncRead + tokio::io::AsyncWrite + Unpin + Send + 'static> AsyncReadWrite
102 for T
103{
104}