proxy.rs

  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}