diff --git a/src/http/client.rs b/src/http/client.rs index 2ec8689..f6172ae 100644 --- a/src/http/client.rs +++ b/src/http/client.rs @@ -322,8 +322,10 @@ async fn resolve_dns_for_client_inner( .ok() .flatten() .unwrap_or(Duration::from_secs(5)); - let https_records = lookup_ech_https_records(Some(dns_server), host, ech_timeout).await; - let addrs = lookup_custom_ips_with_doh_tls(cli, dns_server, host, timeout).await; + let (addrs, https_records) = tokio::join!( + lookup_custom_ips_with_doh_tls(cli, dns_server, host, timeout), + lookup_ech_https_records(Some(dns_server), host, ech_timeout), + ); (addrs?, https_records) } else if let Some(auto_http3_budget) = auto_http3_discovery { let https = spawn_auto_http3_https_records( @@ -402,8 +404,8 @@ async fn resolve_dns_for_client_inner( .ok() .flatten() .unwrap_or(Duration::from_secs(5)); - let https_records = lookup_ech_https_records(None, host, ech_timeout).await; - let socket_addrs = lookup.await; + let (socket_addrs, https_records) = + tokio::join!(lookup, lookup_ech_https_records(None, host, ech_timeout),); ( socket_addrs .map_err(|err| FetchError::Runtime(format!("lookup {host}: {err}")))? diff --git a/tests/network.rs b/tests/network.rs index 346211f..d5c8c21 100644 --- a/tests/network.rs +++ b/tests/network.rs @@ -12,9 +12,9 @@ use support::common::{ }; use support::dns::{ parse_dns_question, start_udp_dns_server, start_udp_dns_server_dropping_https, - start_udp_dns_server_with_delayed_aaaa, start_udp_dns_server_with_delayed_resolution, - start_udp_dns_server_with_hosts, start_udp_dns_server_with_https, - start_udp_dns_server_with_https_target_dropping_target, + start_udp_dns_server_with_delayed_aaaa, start_udp_dns_server_with_delayed_https_and_resolution, + start_udp_dns_server_with_delayed_resolution, start_udp_dns_server_with_hosts, + start_udp_dns_server_with_https, start_udp_dns_server_with_https_target_dropping_target, start_udp_dns_server_with_https_targets_dropping_targets, start_udp_dns_server_with_toggleable_https, start_unresponsive_udp_dns_server, }; @@ -90,6 +90,62 @@ fn custom_dns_connects_after_fast_a_without_waiting_for_slow_aaaa() { assert!(res.stderr.contains("* TCP: 127.0.0.1:")); } +#[test] +fn ech_discovery_overlaps_custom_address_resolution() { + let tls = start_tls_server(|_| TestResponse::ok("ECH DNS overlap")); + let port = Url::parse(&tls.url).unwrap().port().unwrap(); + let (dns_addr, overlapped) = start_udp_dns_server_with_delayed_https_and_resolution( + "localhost.", + Ipv4Addr::new(127, 0, 0, 1), + Duration::from_millis(250), + ); + + let res = run_fetch(&[ + "--dns-server", + &dns_addr, + "--ca-cert", + tls.ca_cert_path.to_str().unwrap(), + "--ech", + "auto", + &format!("https://localhost:{port}/ech-dns-overlap"), + ]); + + assert_exit(&res, 0); + assert_eq!(res.stdout, "ECH DNS overlap"); + assert!( + overlapped.load(Ordering::SeqCst), + "A/AAAA resolution did not overlap the delayed HTTPS lookup" + ); +} + +#[test] +fn ech_on_still_overlaps_address_resolution_before_required_failure() { + let (dns_addr, overlapped) = start_udp_dns_server_with_delayed_https_and_resolution( + "localhost.", + Ipv4Addr::new(127, 0, 0, 1), + Duration::from_millis(250), + ); + + let res = run_fetch(&[ + "--dns-server", + &dns_addr, + "--ech", + "on", + "https://localhost/ech-required-dns-overlap", + ]); + + assert_exit(&res, 1); + assert!( + res.stderr.contains("does not advertise ECH"), + "{}", + res.stderr + ); + assert!( + overlapped.load(Ordering::SeqCst), + "A/AAAA resolution did not overlap the delayed required HTTPS lookup" + ); +} + #[test] fn connect_timeout_is_shared_between_preresolved_dns_and_tls() { let tls = start_h2_tls_server_with_accept_delay( diff --git a/tests/support/dns.rs b/tests/support/dns.rs index fc7938b..d4feaf8 100644 --- a/tests/support/dns.rs +++ b/tests/support/dns.rs @@ -136,6 +136,58 @@ pub(crate) fn start_udp_dns_server_with_https_targets_dropping_targets( (addr, dropped_target_a_queries) } +pub(crate) fn start_udp_dns_server_with_delayed_https_and_resolution( + host: &'static str, + ip: Ipv4Addr, + delay: Duration, +) -> (String, Arc) { + let socket = UdpSocket::bind("127.0.0.1:0").expect("bind udp dns server"); + let addr = socket.local_addr().unwrap().to_string(); + let https_pending = Arc::new(AtomicUsize::new(0)); + let ordinary_pending = Arc::new(AtomicUsize::new(0)); + let overlapped = Arc::new(AtomicBool::new(false)); + let https_for_thread = https_pending.clone(); + let ordinary_for_thread = ordinary_pending.clone(); + let overlapped_for_thread = overlapped.clone(); + thread::spawn(move || { + let mut buf = [0_u8; 512]; + while let Ok((n, peer)) = socket.recv_from(&mut buf) { + let Some((name, qtype, question_end)) = parse_dns_question(&buf[..n]) else { + continue; + }; + let answer = + (name == host && qtype == TYPE_A).then_some((TYPE_A, ip.octets().to_vec())); + let response = dns_response(&buf[..n], question_end, answer); + let pending = if name == host && qtype == TYPE_HTTPS { + https_for_thread.fetch_add(1, Ordering::SeqCst); + if ordinary_for_thread.load(Ordering::SeqCst) > 0 { + overlapped_for_thread.store(true, Ordering::SeqCst); + } + Some(https_for_thread.clone()) + } else if name == host && matches!(qtype, TYPE_A | TYPE_AAAA) { + ordinary_for_thread.fetch_add(1, Ordering::SeqCst); + if https_for_thread.load(Ordering::SeqCst) > 0 { + overlapped_for_thread.store(true, Ordering::SeqCst); + } + Some(ordinary_for_thread.clone()) + } else { + None + }; + if let Some(pending) = pending { + let socket = socket.try_clone().expect("clone udp dns server socket"); + thread::spawn(move || { + thread::sleep(delay); + pending.fetch_sub(1, Ordering::SeqCst); + let _ = socket.send_to(&response, peer); + }); + } else { + let _ = socket.send_to(&response, peer); + } + } + }); + (addr, overlapped) +} + pub(crate) fn start_udp_dns_server_dropping_https(host: &'static str, ip: Ipv4Addr) -> String { let socket = UdpSocket::bind("127.0.0.1:0").expect("bind udp dns server"); let addr = socket.local_addr().unwrap().to_string();