From bc68455320f1be3f3a135ea1c0de24c026990286 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=A0=E5=B0=8F=E7=99=BD?= <364772080@qq.com> Date: Fri, 13 Jun 2025 15:17:49 +0800 Subject: [PATCH] =?UTF-8?q?client:=20Fix=20an=20issue=20where=20non-IP=20p?= =?UTF-8?q?roxy=20URLs=20didn=E2=80=99t=20resolve=20correctly=20(#32664)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit If the proxy URL is in the form of `example.com` instead of a raw IP address, and `example.com` isn't a well-known domain, then the default URL resolution can fail. The test setup: A Linux machine runs a CoreDNS server with a custom entry: `10.254.7.38 example.com`. On a Windows machine, if the proxy URL is set to `example.com`, the resolved address does **not** end up being `10.254.7.38`. Using `hickory_resolver` for more advanced DNS resolution fixes this issue. Release Notes: - Fixed proxy URL resolution when using custom DNS entries. --- Cargo.lock | 98 ++++++++++++++++++++++++++ Cargo.toml | 1 + crates/client/Cargo.toml | 3 + crates/client/src/proxy.rs | 50 +++++++++++-- crates/client/src/proxy/socks_proxy.rs | 10 ++- 5 files changed, 154 insertions(+), 8 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 2710f480e8..463abc3ed3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2826,6 +2826,7 @@ dependencies = [ "futures 0.3.31", "gpui", "gpui_tokio", + "hickory-resolver", "http_client", "http_client_tls", "httparse", @@ -4905,6 +4906,18 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a3d8a32ae18130a3c84dd492d4215c3d913c3b07c6b63c2eb3eb7ff1101ab7bf" +[[package]] +name = "enum-as-inner" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1e6a265c649f3f5979b601d26f1d05ada116434c87741c9493cb56218f76cbc" +dependencies = [ + "heck 0.5.0", + "proc-macro2", + "quote", + "syn 2.0.101", +] + [[package]] name = "enumflags2" version = "0.7.11" @@ -7483,6 +7496,51 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dfa686283ad6dd069f105e5ab091b04c62850d3e4cf5d67debad1933f55023df" +[[package]] +name = "hickory-proto" +version = "0.24.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92652067c9ce6f66ce53cc38d1169daa36e6e7eb7dd3b63b5103bd9d97117248" +dependencies = [ + "async-trait", + "cfg-if", + "data-encoding", + "enum-as-inner", + "futures-channel", + "futures-io", + "futures-util", + "idna", + "ipnet", + "once_cell", + "rand 0.8.5", + "thiserror 1.0.69", + "tinyvec", + "tokio", + "tracing", + "url", +] + +[[package]] +name = "hickory-resolver" +version = "0.24.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cbb117a1ca520e111743ab2f6688eddee69db4e0ea242545a604dce8a66fd22e" +dependencies = [ + "cfg-if", + "futures-util", + "hickory-proto", + "ipconfig", + "lru-cache", + "once_cell", + "parking_lot", + "rand 0.8.5", + "resolv-conf", + "smallvec", + "thiserror 1.0.69", + "tokio", + "tracing", +] + [[package]] name = "hidden-trait" version = "0.1.2" @@ -8352,6 +8410,18 @@ dependencies = [ "windows 0.58.0", ] +[[package]] +name = "ipconfig" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b58db92f96b720de98181bbbe63c831e87005ab460c1bf306eb2622b4707997f" +dependencies = [ + "socket2", + "widestring", + "windows-sys 0.48.0", + "winreg 0.50.0", +] + [[package]] name = "ipnet" version = "2.11.0" @@ -9236,6 +9306,12 @@ dependencies = [ "cc", ] +[[package]] +name = "linked-hash-map" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f" + [[package]] name = "linkify" version = "0.10.0" @@ -9496,6 +9572,15 @@ dependencies = [ "hashbrown 0.15.3", ] +[[package]] +name = "lru-cache" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "31e24f1ad8321ca0e8a1e0ac13f23cb668e6f5466c2c57319f6a5cf1cc8e3b1c" +dependencies = [ + "linked-hash-map", +] + [[package]] name = "lsp" version = "0.1.0" @@ -13327,6 +13412,7 @@ dependencies = [ "futures-core", "futures-util", "h2 0.4.9", + "hickory-resolver", "http 1.3.1", "http-body 1.0.1", "http-body-util", @@ -13383,6 +13469,12 @@ dependencies = [ "workspace-hack", ] +[[package]] +name = "resolv-conf" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95325155c684b1c89f7765e30bc1c42e4a6da51ca513615660cb8a62ef9a88e3" + [[package]] name = "resvg" version = "0.45.1" @@ -18304,6 +18396,12 @@ dependencies = [ "wasite", ] +[[package]] +name = "widestring" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd7cf3379ca1aac9eea11fba24fd7e315d621f8dfe35c8d7d2be8b793726e07d" + [[package]] name = "wiggle" version = "29.0.1" diff --git a/Cargo.toml b/Cargo.toml index 0c72e127db..9e28524708 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -524,6 +524,7 @@ reqwest = { git = "https://github.com/zed-industries/reqwest.git", rev = "951c77 "rustls-tls-native-roots", "socks", "stream", + "hickory-dns", ] } rsa = "0.9.6" runtimelib = { git = "https://github.com/ConradIrwin/runtimed", rev = "7130c804216b6914355d15d0b91ea91f6babd734", default-features = false, features = [ diff --git a/crates/client/Cargo.toml b/crates/client/Cargo.toml index dcbcecb295..416098eacf 100644 --- a/crates/client/Cargo.toml +++ b/crates/client/Cargo.toml @@ -28,6 +28,9 @@ feature_flags.workspace = true futures.workspace = true gpui.workspace = true gpui_tokio.workspace = true +# Don't update `hickory-resolver`, it has a bug that causes it to not resolve DNS queries correctly. +# See https://github.com/hickory-dns/hickory-dns/issues/3048 +hickory-resolver = { version = "0.24", features = ["tokio-runtime"] } http_client.workspace = true http_client_tls.workspace = true httparse = "1.10" diff --git a/crates/client/src/proxy.rs b/crates/client/src/proxy.rs index eb3812ca07..646586fd07 100644 --- a/crates/client/src/proxy.rs +++ b/crates/client/src/proxy.rs @@ -3,20 +3,30 @@ mod http_proxy; mod socks_proxy; +use std::sync::LazyLock; + use anyhow::{Context as _, Result}; +use hickory_resolver::{ + AsyncResolver, TokioAsyncResolver, + config::LookupIpStrategy, + name_server::{GenericConnector, TokioRuntimeProvider}, + system_conf, +}; use http_client::Url; use http_proxy::{HttpProxyType, connect_http_proxy_stream, parse_http_proxy}; use socks_proxy::{SocksVersion, connect_socks_proxy_stream, parse_socks_proxy}; +use tokio_socks::{IntoTargetAddr, TargetAddr}; +use util::ResultExt; pub(crate) async fn connect_proxy_stream( proxy: &Url, rpc_host: (&str, u16), ) -> Result> { - let Some(((proxy_domain, proxy_port), proxy_type)) = parse_proxy_type(proxy) else { + let Some(((proxy_domain, proxy_port), proxy_type)) = parse_proxy_type(proxy).await else { // If parsing the proxy URL fails, we must avoid falling back to an insecure connection. // SOCKS proxies are often used in contexts where security and privacy are critical, // so any fallback could expose users to significant risks. - anyhow::bail!("Parsing proxy url failed"); + anyhow::bail!("Parsing proxy url type failed"); }; // Connect to proxy and wrap protocol later @@ -39,10 +49,8 @@ enum ProxyType<'t> { HttpProxy(HttpProxyType<'t>), } -fn parse_proxy_type(proxy: &Url) -> Option<((String, u16), ProxyType<'_>)> { +async fn parse_proxy_type(proxy: &Url) -> Option<((String, u16), ProxyType<'_>)> { let scheme = proxy.scheme(); - let host = proxy.host()?.to_string(); - let port = proxy.port_or_known_default()?; let proxy_type = match scheme { scheme if scheme.starts_with("socks") => { Some(ProxyType::SocksProxy(parse_socks_proxy(scheme, proxy))) @@ -52,8 +60,38 @@ fn parse_proxy_type(proxy: &Url) -> Option<((String, u16), ProxyType<'_>)> { } _ => None, }?; + let (ip, port) = { + let host = proxy.host()?.to_string(); + let port = proxy.port_or_known_default()?; + resolve_proxy_url_if_needed((host, port)).await.log_err()? + }; - Some(((host, port), proxy_type)) + Some(((ip, port), proxy_type)) +} + +static SYSTEM_DNS_RESOLVER: LazyLock>> = + LazyLock::new(|| { + let (config, mut opts) = system_conf::read_system_conf().unwrap(); + opts.ip_strategy = LookupIpStrategy::Ipv4AndIpv6; + TokioAsyncResolver::tokio(config, opts) + }); + +async fn resolve_proxy_url_if_needed(proxy: (String, u16)) -> Result<(String, u16)> { + let proxy = proxy + .into_target_addr() + .context("Failed to parse proxy addr")?; + match proxy { + TargetAddr::Domain(domain, port) => { + let ip = SYSTEM_DNS_RESOLVER + .lookup_ip(domain.as_ref()) + .await? + .into_iter() + .next() + .ok_or_else(|| anyhow::anyhow!("No IP found for proxy domain {domain}"))?; + Ok((ip.to_string(), port)) + } + TargetAddr::Ip(ip_addr) => Ok((ip_addr.ip().to_string(), ip_addr.port())), + } } pub(crate) trait AsyncReadWrite: diff --git a/crates/client/src/proxy/socks_proxy.rs b/crates/client/src/proxy/socks_proxy.rs index 9ccf4906d8..faec2a30bd 100644 --- a/crates/client/src/proxy/socks_proxy.rs +++ b/crates/client/src/proxy/socks_proxy.rs @@ -1,5 +1,7 @@ //! socks proxy +use std::net::SocketAddr; + use anyhow::{Context as _, Result}; use http_client::Url; use tokio::net::TcpStream; @@ -8,6 +10,8 @@ use tokio_socks::{ tcp::{Socks4Stream, Socks5Stream}, }; +use crate::proxy::SYSTEM_DNS_RESOLVER; + use super::AsyncReadWrite; /// Identification to a Socks V4 Proxy @@ -73,12 +77,14 @@ pub(super) async fn connect_socks_proxy_stream( }; let rpc_host = match (rpc_host, local_dns) { (TargetAddr::Domain(domain, port), true) => { - let ip_addr = tokio::net::lookup_host((domain.as_ref(), port)) + let ip_addr = SYSTEM_DNS_RESOLVER + .lookup_ip(domain.as_ref()) .await .with_context(|| format!("Failed to lookup domain {}", domain))? + .into_iter() .next() .ok_or_else(|| anyhow::anyhow!("Failed to lookup domain {}", domain))?; - TargetAddr::Ip(ip_addr) + TargetAddr::Ip(SocketAddr::new(ip_addr, port)) } (rpc_host, _) => rpc_host, };