Repository: denoland/deno
Pull request: fix(ext/fetch): support ipv6 destination host with http and socks proxy

Alternative to #31514 with proper SOCKS support and tests. Closes #31504.


Changed files:
filename: ext/fetch/proxy.rs
patch: @@ -603,12 +603,14 @@ where
 
             if is_https {
               tunnel(&mut io, &orig_dst, user_agent, auth).await?;
+              let host = orig_dst.host().unwrap();
+              let host = host
+                .strip_prefix('[')
+                .and_then(|s| s.strip_suffix(']'))
+                .unwrap_or(host);
               let tokio_io = TokioIo::new(io);
               let io = tls
-                .connect(
-                  TryFrom::try_from(orig_dst.host().unwrap().to_owned())?,
-                  tokio_io,
-                )
+                .connect(TryFrom::try_from(host.to_owned())?, tokio_io)
                 .await?;
               Ok(Proxied::HttpTunneled(Box::new(TokioIo::new(io))))
             } else {
@@ -627,6 +629,10 @@ where
               proxy_dst.port().map(|p| p.as_u16()).unwrap_or(1080),
             );
             let host = orig_dst.host().ok_or("no host in url")?;
+            let host = host
+              .strip_prefix('[')
+              .and_then(|s| s.strip_suffix(']'))
+              .unwrap_or(host);
             let port = match orig_dst.port() {
               Some(p) => p.as_u16(),
               None if is_https => 443,

filename: ext/fetch/tests.rs
patch: @@ -1,5 +1,8 @@
 // Copyright 2018-2026 the Deno authors. MIT license.
 
+use std::net::IpAddr;
+use std::net::Ipv4Addr;
+use std::net::Ipv6Addr;
 use std::net::SocketAddr;
 use std::sync::Arc;
 use std::sync::atomic::AtomicUsize;
@@ -35,15 +38,15 @@ fn test_userspace_resolver() {
 
   rt.block_on(async move {
     assert_eq!(thread_counter.load(SeqCst), 0);
-    let src_addr = create_https_server(true).await;
+    let src_addr = create_https_server(true, Ipv4Addr::LOCALHOST.into()).await;
     assert_eq!(src_addr.ip().to_string(), "127.0.0.1");
     // use `localhost` to ensure dns step happens.
     let addr = format!("localhost:{}", src_addr.port());
 
     let hickory = hickory_resolver::Resolver::builder_tokio().unwrap().build();
 
     assert_eq!(thread_counter.load(SeqCst), 0);
-    rust_test_client_with_resolver(
+    run_test_client_with_resolver(
       None,
       addr.clone(),
       "https",
@@ -52,7 +55,7 @@ fn test_userspace_resolver() {
     )
     .await;
     assert_eq!(thread_counter.load(SeqCst), 0, "userspace resolver shouldn't spawn new threads.");
-    rust_test_client_with_resolver(
+    run_test_client_with_resolver(
       None,
       addr.clone(),
       "https",
@@ -65,41 +68,62 @@ fn test_userspace_resolver() {
 }
 
 #[tokio::test]
-async fn test_https_proxy_http11() {
-  let src_addr = create_https_server(false).await;
+async fn test_http_proxy_http11_ipv4() {
+  let src_addr = create_https_server(false, Ipv4Addr::LOCALHOST.into()).await;
   let prx_addr = create_http_proxy(src_addr).await;
   run_test_client(prx_addr, src_addr, "http", http::Version::HTTP_11).await;
 }
 
 #[tokio::test]
-async fn test_https_proxy_h2() {
-  let src_addr = create_https_server(true).await;
+async fn test_http_proxy_h2_ipv4() {
+  let src_addr = create_https_server(true, Ipv4Addr::LOCALHOST.into()).await;
   let prx_addr = create_http_proxy(src_addr).await;
   run_test_client(prx_addr, src_addr, "http", http::Version::HTTP_2).await;
 }
 
 #[tokio::test]
-async fn test_https_proxy_https_h2() {
-  let src_addr = create_https_server(true).await;
+async fn test_http_proxy_h2_ipv6() {
+  let src_addr = create_https_server(true, Ipv6Addr::LOCALHOST.into()).await;
+  let prx_addr = create_http_proxy(src_addr).await;
+  run_test_client(prx_addr, src_addr, "http", http::Version::HTTP_2).await;
+}
+
+#[tokio::test]
+async fn test_https_proxy_h2_ipv4() {
+  let src_addr = create_https_server(true, Ipv4Addr::LOCALHOST.into()).await;
   let prx_addr = create_https_proxy(src_addr).await;
   run_test_client(prx_addr, src_addr, "https", http::Version::HTTP_2).await;
 }
 
 #[tokio::test]
-async fn test_socks_proxy_http11() {
-  let src_addr = create_https_server(false).await;
+async fn test_https_proxy_h2_ipv6() {
+  let src_addr = create_https_server(true, Ipv6Addr::LOCALHOST.into()).await;
+  let prx_addr = create_https_proxy(src_addr).await;
+  run_test_client(prx_addr, src_addr, "https", http::Version::HTTP_2).await;
+}
+
+#[tokio::test]
+async fn test_socks_proxy_http11_ipv4() {
+  let src_addr = create_https_server(false, Ipv4Addr::LOCALHOST.into()).await;
   let prx_addr = create_socks_proxy(src_addr).await;
   run_test_client(prx_addr, src_addr, "socks5", http::Version::HTTP_11).await;
 }
 
 #[tokio::test]
-async fn test_socks_proxy_h2() {
-  let src_addr = create_https_server(true).await;
+async fn test_socks_proxy_h2_ipv4() {
+  let src_addr = create_https_server(true, Ipv4Addr::LOCALHOST.into()).await;
+  let prx_addr = create_socks_proxy(src_addr).await;
+  run_test_client(prx_addr, src_addr, "socks5", http::Version::HTTP_2).await;
+}
+
+#[tokio::test]
+async fn test_socks_proxy_h2_ipv6() {
+  let src_addr = create_https_server(true, Ipv6Addr::LOCALHOST.into()).await;
   let prx_addr = create_socks_proxy(src_addr).await;
   run_test_client(prx_addr, src_addr, "socks5", http::Version::HTTP_2).await;
 }
 
-async fn rust_test_client_with_resolver(
+async fn run_test_client_with_resolver(
   prx_addr: Option<SocketAddr>,
   src_addr: String,
   proto: &str,
@@ -145,7 +169,7 @@ async fn run_test_client(
   proto: &str,
   ver: http::Version,
 ) {
-  rust_test_client_with_resolver(
+  run_test_client_with_resolver(
     Some(prx_addr),
     src_addr.to_string(),
     proto,
@@ -155,7 +179,7 @@ async fn run_test_client(
   .await
 }
 
-async fn create_https_server(allow_h2: bool) -> SocketAddr {
+async fn create_https_server(allow_h2: bool, bind_addr: IpAddr) -> SocketAddr {
   let _ = rustls::crypto::aws_lc_rs::default_provider().install_default();
 
   let mut tls_config = deno_tls::rustls::server::ServerConfig::builder()
@@ -170,7 +194,7 @@ async fn create_https_server(allow_h2: bool) -> SocketAddr {
   }
   tls_config.alpn_protocols.push("http/1.1".into());
   let tls_acceptor = tokio_rustls::TlsAcceptor::from(Arc::from(tls_config));
-  let src_tcp = tokio::net::TcpListener::bind("127.0.0.1:0").await.unwrap();
+  let src_tcp = tokio::net::TcpListener::bind((bind_addr, 0)).await.unwrap();
   let src_addr = src_tcp.local_addr().unwrap();
 
   tokio::spawn(async move {