From 6356345365e766d984f591506fb475d7935685de Mon Sep 17 00:00:00 2001 From: Yusuke Tanaka Date: Tue, 15 Dec 2020 21:02:26 +0900 Subject: [PATCH] fix: make DNS resolution async (#8743) This commit adds a new function that is an asynchronous version of `resolve_addr` using `tokio::net::lookup_host`, and accordingly, renames the synchronous version to `resolve_addr_sync`. This allows async ops to resolve hosts with non-blocking. --- runtime/ops/net.rs | 7 ++- runtime/ops/tls.rs | 5 +- runtime/resolve_addr.rs | 118 ++++++++++++++++++++++++++++++---------- 3 files changed, 97 insertions(+), 33 deletions(-) diff --git a/runtime/ops/net.rs b/runtime/ops/net.rs index 98ff83fc02..8770ef103b 100644 --- a/runtime/ops/net.rs +++ b/runtime/ops/net.rs @@ -4,6 +4,7 @@ use crate::ops::io::StreamResource; use crate::ops::io::StreamResourceHolder; use crate::permissions::Permissions; use crate::resolve_addr::resolve_addr; +use crate::resolve_addr::resolve_addr_sync; use deno_core::error::bad_resource; use deno_core::error::bad_resource_id; use deno_core::error::custom_error; @@ -205,7 +206,7 @@ async fn op_datagram_send( s.borrow::() .check_net(&args.hostname, args.port)?; } - let addr = resolve_addr(&args.hostname, args.port)?; + let addr = resolve_addr(&args.hostname, args.port).await?; poll_fn(move |cx| { let mut state = state.borrow_mut(); let resource = state @@ -272,7 +273,7 @@ async fn op_connect( .borrow::() .check_net(&args.hostname, args.port)?; } - let addr = resolve_addr(&args.hostname, args.port)?; + let addr = resolve_addr(&args.hostname, args.port).await?; let tcp_stream = TcpStream::connect(&addr).await?; let local_addr = tcp_stream.local_addr()?; let remote_addr = tcp_stream.peer_addr()?; @@ -505,7 +506,7 @@ fn op_listen( } permissions.check_net(&args.hostname, args.port)?; } - let addr = resolve_addr(&args.hostname, args.port)?; + let addr = resolve_addr_sync(&args.hostname, args.port)?; let (rid, local_addr) = if transport == "tcp" { listen_tcp(state, addr)? } else { diff --git a/runtime/ops/tls.rs b/runtime/ops/tls.rs index 37fd8f2068..b59650ab0e 100644 --- a/runtime/ops/tls.rs +++ b/runtime/ops/tls.rs @@ -3,6 +3,7 @@ use super::io::{StreamResource, StreamResourceHolder}; use crate::permissions::Permissions; use crate::resolve_addr::resolve_addr; +use crate::resolve_addr::resolve_addr_sync; use deno_core::error::bad_resource; use deno_core::error::bad_resource_id; use deno_core::error::custom_error; @@ -160,7 +161,7 @@ async fn op_connect_tls( domain.push_str("localhost"); } - let addr = resolve_addr(&args.hostname, args.port)?; + let addr = resolve_addr(&args.hostname, args.port).await?; let tcp_stream = TcpStream::connect(&addr).await?; let local_addr = tcp_stream.local_addr()?; let remote_addr = tcp_stream.peer_addr()?; @@ -334,7 +335,7 @@ fn op_listen_tls( .set_single_cert(load_certs(&cert_file)?, load_keys(&key_file)?.remove(0)) .expect("invalid key or certificate"); let tls_acceptor = TlsAcceptor::from(Arc::new(config)); - let addr = resolve_addr(&args.hostname, args.port)?; + let addr = resolve_addr_sync(&args.hostname, args.port)?; let std_listener = std::net::TcpListener::bind(&addr)?; let listener = TcpListener::from_std(std_listener)?; let local_addr = listener.local_addr()?; diff --git a/runtime/resolve_addr.rs b/runtime/resolve_addr.rs index c3dc52f8fe..d4f5008023 100644 --- a/runtime/resolve_addr.rs +++ b/runtime/resolve_addr.rs @@ -1,29 +1,45 @@ // Copyright 2018-2020 the Deno authors. All rights reserved. MIT license. use deno_core::error::AnyError; +use deno_core::error::Context; use std::net::SocketAddr; use std::net::ToSocketAddrs; +use tokio::net::lookup_host; -/// Resolve network address. Returns a future. -pub fn resolve_addr(hostname: &str, port: u16) -> Result { +/// Resolve network address *asynchronously*. +pub async fn resolve_addr( + hostname: &str, + port: u16, +) -> Result { + let addr_port_pair = make_addr_port_pair(hostname, port); + lookup_host(addr_port_pair) + .await? + .next() + .context("No resolved address found") +} + +/// Resolve network address *synchronously*. +pub fn resolve_addr_sync( + hostname: &str, + port: u16, +) -> Result { + let addr_port_pair = make_addr_port_pair(hostname, port); + addr_port_pair + .to_socket_addrs()? + .next() + .context("No resolved address found") +} + +fn make_addr_port_pair(hostname: &str, port: u16) -> (&str, u16) { // Default to localhost if given just the port. Example: ":80" - let addr: &str = if !hostname.is_empty() { - &hostname - } else { - "0.0.0.0" - }; + if hostname.is_empty() { + return ("0.0.0.0", port); + } // If this looks like an ipv6 IP address. Example: "[2001:db8::1]" // Then we remove the brackets. - let addr = if addr.starts_with('[') && addr.ends_with(']') { - let l = addr.len() - 1; - addr.get(1..l).unwrap() - } else { - addr - }; - let addr_port_pair = (addr, port); - let mut iter = addr_port_pair.to_socket_addrs()?; - Ok(iter.next().unwrap()) + let addr = hostname.trim_start_matches('[').trim_end_matches(']'); + (addr, port) } #[cfg(test)] @@ -34,39 +50,85 @@ mod tests { use std::net::SocketAddrV4; use std::net::SocketAddrV6; - #[test] - fn resolve_addr1() { + #[tokio::test] + async fn resolve_addr1() { let expected = SocketAddr::V4(SocketAddrV4::new(Ipv4Addr::new(127, 0, 0, 1), 80)); - let actual = resolve_addr("127.0.0.1", 80).unwrap(); + let actual = resolve_addr("127.0.0.1", 80).await.unwrap(); assert_eq!(actual, expected); } - #[test] - fn resolve_addr2() { + #[tokio::test] + async fn resolve_addr2() { let expected = SocketAddr::V4(SocketAddrV4::new(Ipv4Addr::new(0, 0, 0, 0), 80)); - let actual = resolve_addr("", 80).unwrap(); + let actual = resolve_addr("", 80).await.unwrap(); assert_eq!(actual, expected); } - #[test] - fn resolve_addr3() { + #[tokio::test] + async fn resolve_addr3() { let expected = SocketAddr::V4(SocketAddrV4::new(Ipv4Addr::new(192, 0, 2, 1), 25)); - let actual = resolve_addr("192.0.2.1", 25).unwrap(); + let actual = resolve_addr("192.0.2.1", 25).await.unwrap(); assert_eq!(actual, expected); } - #[test] - fn resolve_addr_ipv6() { + #[tokio::test] + async fn resolve_addr_ipv6() { let expected = SocketAddr::V6(SocketAddrV6::new( Ipv6Addr::new(0x2001, 0xdb8, 0, 0, 0, 0, 0, 1), 8080, 0, 0, )); - let actual = resolve_addr("[2001:db8::1]", 8080).unwrap(); + let actual = resolve_addr("[2001:db8::1]", 8080).await.unwrap(); assert_eq!(actual, expected); } + + #[tokio::test] + async fn resolve_addr_err() { + assert!(resolve_addr("INVALID ADDR", 1234).await.is_err()); + } + + #[test] + fn resolve_addr_sync1() { + let expected = + SocketAddr::V4(SocketAddrV4::new(Ipv4Addr::new(127, 0, 0, 1), 80)); + let actual = resolve_addr_sync("127.0.0.1", 80).unwrap(); + assert_eq!(actual, expected); + } + + #[test] + fn resolve_addr_sync2() { + let expected = + SocketAddr::V4(SocketAddrV4::new(Ipv4Addr::new(0, 0, 0, 0), 80)); + let actual = resolve_addr_sync("", 80).unwrap(); + assert_eq!(actual, expected); + } + + #[test] + fn resolve_addr_sync3() { + let expected = + SocketAddr::V4(SocketAddrV4::new(Ipv4Addr::new(192, 0, 2, 1), 25)); + let actual = resolve_addr_sync("192.0.2.1", 25).unwrap(); + assert_eq!(actual, expected); + } + + #[test] + fn resolve_addr_sync_ipv6() { + let expected = SocketAddr::V6(SocketAddrV6::new( + Ipv6Addr::new(0x2001, 0xdb8, 0, 0, 0, 0, 0, 1), + 8080, + 0, + 0, + )); + let actual = resolve_addr_sync("[2001:db8::1]", 8080).unwrap(); + assert_eq!(actual, expected); + } + + #[test] + fn resolve_addr_sync_err() { + assert!(resolve_addr_sync("INVALID ADDR", 1234).is_err()); + } }