diff --git a/Cargo.lock b/Cargo.lock index 29fe91641d57df..97aa4230769532 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1560,6 +1560,7 @@ dependencies = [ "tower", "tower-http", "tower-service", + "trust-dns-resolver", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 28ef2829e0f55f..365d1fb1ef2786 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -196,6 +196,7 @@ tower = { version = "0.4.13", default-features = false, features = ["util"] } tower-http = { version = "0.6.1", features = ["decompression-br", "decompression-gzip"] } tower-lsp = { package = "deno_tower_lsp", version = "0.1.0", features = ["proposed"] } tower-service = "0.3.2" +trust-dns-resolver = { version = "0.23", features = ["tokio-runtime", "serde-config"] } twox-hash = "=1.6.3" # Upgrading past 2.4.1 may cause WPT failures url = { version = "< 2.5.0", features = ["serde", "expose_internals"] } diff --git a/cli/worker.rs b/cli/worker.rs index baacd681a16764..04df6586c92e7c 100644 --- a/cli/worker.rs +++ b/cli/worker.rs @@ -542,6 +542,7 @@ impl CliMainWorkerFactory { npm_process_state_provider: Some(shared.npm_process_state_provider()), blob_store: shared.blob_store.clone(), broadcast_channel: shared.broadcast_channel.clone(), + fetch_resolver: Default::default(), shared_array_buffer_store: Some(shared.shared_array_buffer_store.clone()), compiled_wasm_module_store: Some( shared.compiled_wasm_module_store.clone(), @@ -834,6 +835,7 @@ mod tests { node_services: Default::default(), npm_process_state_provider: Default::default(), root_cert_store_provider: Default::default(), + fetch_resolver: Default::default(), shared_array_buffer_store: Default::default(), compiled_wasm_module_store: Default::default(), v8_code_cache: Default::default(), diff --git a/ext/fetch/Cargo.toml b/ext/fetch/Cargo.toml index 93fc88ae6435b0..be0df71551dd44 100644 --- a/ext/fetch/Cargo.toml +++ b/ext/fetch/Cargo.toml @@ -40,6 +40,7 @@ tokio-util = { workspace = true, features = ["io"] } tower.workspace = true tower-http.workspace = true tower-service.workspace = true +trust-dns-resolver.workspace = true [dev-dependencies] fast-socks5.workspace = true diff --git a/ext/fetch/dns.rs b/ext/fetch/dns.rs new file mode 100644 index 00000000000000..1571ce2d8622b8 --- /dev/null +++ b/ext/fetch/dns.rs @@ -0,0 +1,116 @@ +// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. +use std::future::Future; +use std::io; +use std::net::SocketAddr; +use std::pin::Pin; +use std::task::Poll; +use std::task::{self}; +use std::vec; + +use hyper_util::client::legacy::connect::dns::GaiResolver; +use hyper_util::client::legacy::connect::dns::Name; +use tokio::task::JoinHandle; +use tower::Service; +use trust_dns_resolver::error::ResolveError; +use trust_dns_resolver::name_server::GenericConnector; +use trust_dns_resolver::name_server::TokioRuntimeProvider; +use trust_dns_resolver::AsyncResolver; + +#[derive(Clone, Debug)] +pub enum Resolver { + /// A resolver using blocking `getaddrinfo` calls in a threadpool. + Gai(GaiResolver), + /// trust_dns_resolver userspace resolver. + Trust(AsyncResolver>), +} + +impl Default for Resolver { + fn default() -> Self { + Self::gai() + } +} + +impl Resolver { + pub fn gai() -> Self { + Self::Gai(GaiResolver::new()) + } + + /// Create a [`AsyncResolver`] from system conf. + pub fn trust() -> Result { + Ok(Self::Trust( + trust_dns_resolver::AsyncResolver::tokio_from_system_conf()?, + )) + } + + pub fn trust_from_async_resolver( + resolver: AsyncResolver>, + ) -> Self { + Self::Trust(resolver) + } +} + +type SocketAddrs = vec::IntoIter; + +pub struct ResolveFut { + inner: JoinHandle>, +} + +impl Future for ResolveFut { + type Output = Result; + + fn poll( + mut self: Pin<&mut Self>, + cx: &mut task::Context<'_>, + ) -> Poll { + Pin::new(&mut self.inner).poll(cx).map(|res| match res { + Ok(Ok(addrs)) => Ok(addrs), + Ok(Err(e)) => Err(e), + Err(join_err) => { + if join_err.is_cancelled() { + Err(io::Error::new(io::ErrorKind::Interrupted, join_err)) + } else { + panic!("gai background task failed: {:?}", join_err) + } + } + }) + } +} + +impl Service for Resolver { + type Response = SocketAddrs; + type Error = io::Error; + type Future = ResolveFut; + + fn poll_ready( + &mut self, + _cx: &mut task::Context<'_>, + ) -> Poll> { + Poll::Ready(Ok(())) + } + + fn call(&mut self, name: Name) -> Self::Future { + let task = match self { + Resolver::Gai(gai_resolver) => { + let mut resolver = gai_resolver.clone(); + tokio::spawn(async move { + let result = resolver.call(name).await?; + let x: Vec<_> = result.into_iter().collect(); + let iter: SocketAddrs = x.into_iter(); + Ok(iter) + }) + } + Resolver::Trust(async_resolver) => { + let resolver = async_resolver.clone(); + tokio::spawn(async move { + let result = resolver.lookup_ip(name.as_str()).await?; + + let x: Vec<_> = + result.into_iter().map(|x| SocketAddr::new(x, 0)).collect(); + let iter: SocketAddrs = x.into_iter(); + Ok(iter) + }) + } + }; + ResolveFut { inner: task } + } +} diff --git a/ext/fetch/lib.rs b/ext/fetch/lib.rs index 7ef26431c2b091..18c4021882fef1 100644 --- a/ext/fetch/lib.rs +++ b/ext/fetch/lib.rs @@ -1,5 +1,6 @@ // Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. +pub mod dns; mod fs_fetch_handler; mod proxy; #[cfg(test)] @@ -91,6 +92,7 @@ pub struct Options { pub unsafely_ignore_certificate_errors: Option>, pub client_cert_chain_and_key: TlsKeys, pub file_fetch_handler: Rc, + pub resolver: dns::Resolver, } impl Options { @@ -114,6 +116,7 @@ impl Default for Options { unsafely_ignore_certificate_errors: None, client_cert_chain_and_key: TlsKeys::Null, file_fetch_handler: Rc::new(DefaultFileFetchHandler), + resolver: dns::Resolver::default(), } } } @@ -255,6 +258,7 @@ pub fn create_client_from_options( .map_err(HttpClientCreateError::RootCertStore)?, ca_certs: vec![], proxy: options.proxy.clone(), + resolver: options.resolver.clone(), unsafely_ignore_certificate_errors: options .unsafely_ignore_certificate_errors .clone(), @@ -835,6 +839,8 @@ pub struct CreateHttpClientArgs { proxy: Option, pool_max_idle_per_host: Option, pool_idle_timeout: Option, + #[serde(default)] + use_trust_resolver: bool, #[serde(default = "default_true")] http1: bool, #[serde(default = "default_true")] @@ -878,6 +884,13 @@ where .map_err(HttpClientCreateError::RootCertStore)?, ca_certs, proxy: args.proxy, + resolver: if args.use_trust_resolver { + dns::Resolver::trust() + .map_err(deno_core::error::AnyError::new) + .map_err(FetchError::Resource)? + } else { + dns::Resolver::default() + }, unsafely_ignore_certificate_errors: options .unsafely_ignore_certificate_errors .clone(), @@ -909,6 +922,7 @@ pub struct CreateHttpClientOptions { pub root_cert_store: Option, pub ca_certs: Vec>, pub proxy: Option, + pub resolver: dns::Resolver, pub unsafely_ignore_certificate_errors: Option>, pub client_cert_chain_and_key: Option, pub pool_max_idle_per_host: Option, @@ -923,6 +937,7 @@ impl Default for CreateHttpClientOptions { root_cert_store: None, ca_certs: vec![], proxy: None, + resolver: dns::Resolver::default(), unsafely_ignore_certificate_errors: None, client_cert_chain_and_key: None, pool_max_idle_per_host: None, @@ -976,7 +991,8 @@ pub fn create_http_client( tls_config.alpn_protocols = alpn_protocols; let tls_config = Arc::from(tls_config); - let mut http_connector = HttpConnector::new(); + let mut http_connector = + HttpConnector::new_with_resolver(options.resolver.clone()); http_connector.enforce_http(false); let user_agent = user_agent.parse::().map_err(|_| { @@ -1051,7 +1067,7 @@ pub struct Client { user_agent: HeaderValue, } -type Connector = proxy::ProxyConnector; +type Connector = proxy::ProxyConnector>; // clippy is wrong here #[allow(clippy::declare_interior_mutable_const)] diff --git a/ext/fetch/tests.rs b/ext/fetch/tests.rs index dad1b34a9e8016..ffac87c4934422 100644 --- a/ext/fetch/tests.rs +++ b/ext/fetch/tests.rs @@ -71,6 +71,7 @@ async fn run_test_client( client_cert_chain_and_key: None, pool_max_idle_per_host: None, pool_idle_timeout: None, + resolver: Default::default(), http1: true, http2: true, }, diff --git a/ext/kv/remote.rs b/ext/kv/remote.rs index 4930aacfe355c6..85d7bfa0262d2e 100644 --- a/ext/kv/remote.rs +++ b/ext/kv/remote.rs @@ -197,6 +197,7 @@ impl DatabaseHandler root_cert_store: options.root_cert_store()?, ca_certs: vec![], proxy: options.proxy.clone(), + resolver: Default::default(), unsafely_ignore_certificate_errors: options .unsafely_ignore_certificate_errors .clone(), diff --git a/ext/net/Cargo.toml b/ext/net/Cargo.toml index 5ffd1b45047b4f..cdaf3b5d0f86a8 100644 --- a/ext/net/Cargo.toml +++ b/ext/net/Cargo.toml @@ -24,4 +24,4 @@ socket2.workspace = true thiserror.workspace = true tokio.workspace = true trust-dns-proto = "0.23" -trust-dns-resolver = { version = "0.23", features = ["tokio-runtime", "serde-config"] } +trust-dns-resolver.workspace = true diff --git a/runtime/examples/extension/main.rs b/runtime/examples/extension/main.rs index 9889b28dcf073f..e9a35dc6125760 100644 --- a/runtime/examples/extension/main.rs +++ b/runtime/examples/extension/main.rs @@ -50,6 +50,7 @@ async fn main() -> Result<(), AnyError> { node_services: Default::default(), npm_process_state_provider: Default::default(), root_cert_store_provider: Default::default(), + fetch_resolver: Default::default(), shared_array_buffer_store: Default::default(), compiled_wasm_module_store: Default::default(), v8_code_cache: Default::default(), diff --git a/runtime/worker.rs b/runtime/worker.rs index b780aefc1b28b7..13f8b977d20bff 100644 --- a/runtime/worker.rs +++ b/runtime/worker.rs @@ -143,6 +143,7 @@ pub struct WorkerServiceOptions { pub npm_process_state_provider: Option, pub permissions: PermissionsContainer, pub root_cert_store_provider: Option>, + pub fetch_resolver: deno_fetch::dns::Resolver, /// The store to use for transferring SharedArrayBuffers between isolates. /// If multiple isolates should have the possibility of sharing @@ -363,6 +364,7 @@ impl MainWorker { .unsafely_ignore_certificate_errors .clone(), file_fetch_handler: Rc::new(deno_fetch::FsFetchHandler), + resolver: services.fetch_resolver, ..Default::default() }, ),