@@ -7,6 +7,7 @@ use std::{
77} ;
88
99use n0_error:: e;
10+ use n0_future:: time:: { self , Duration } ;
1011use rustls:: ClientConfig ;
1112use simple_dns:: TYPE ;
1213use tracing:: { debug, trace} ;
@@ -17,6 +18,11 @@ use super::{
1718 query, system_config, transport,
1819} ;
1920
21+ /// Per-nameserver timeout. Ensures we move on to the next nameserver
22+ /// if one is unresponsive, rather than consuming the entire query budget.
23+ /// Matches hickory-resolver's default of 5 seconds.
24+ const NAMESERVER_TIMEOUT : Duration = Duration :: from_secs ( 5 ) ;
25+
2026#[ derive( Debug ) ]
2127pub ( super ) struct SimpleDnsResolver {
2228 nameservers : Vec < ( SocketAddr , DnsProtocol ) > ,
@@ -81,32 +87,47 @@ impl SimpleDnsResolver {
8187 let mut last_err = None ;
8288 for ( addr, proto) in & self . nameservers {
8389 trace ! ( %addr, ?proto, "sending DNS query" ) ;
84- let result = match proto {
85- DnsProtocol :: Udp => {
86- let resp = transport:: udp_query ( * addr, query_bytes) . await ?;
87- // Check for truncation, fallback to TCP
88- if query:: is_truncated ( & resp) {
89- debug ! ( %addr, "UDP response truncated, retrying over TCP" ) ;
90- transport:: tcp_query ( * addr, query_bytes) . await
91- } else {
92- Ok ( resp)
90+ let result = time:: timeout ( NAMESERVER_TIMEOUT , async {
91+ match proto {
92+ DnsProtocol :: Udp => {
93+ let resp = transport:: udp_query ( * addr, query_bytes) . await ?;
94+ // Check for truncation, fallback to TCP
95+ if query:: is_truncated ( & resp) {
96+ debug ! ( %addr, "UDP response truncated, retrying over TCP" ) ;
97+ transport:: tcp_query ( * addr, query_bytes) . await
98+ } else {
99+ Ok ( resp)
100+ }
101+ }
102+ DnsProtocol :: Tcp => transport:: tcp_query ( * addr, query_bytes) . await ,
103+ DnsProtocol :: Tls => {
104+ let tls_config = self . tls_config . as_ref ( ) . ok_or_else ( || {
105+ e ! ( DnsError :: Transport {
106+ source: std:: io:: Error :: new(
107+ std:: io:: ErrorKind :: InvalidInput ,
108+ "TLS config required for DNS-over-TLS" ,
109+ ) ,
110+ } )
111+ } ) ?;
112+ transport:: tls_query ( * addr, query_bytes, tls_config) . await
113+ }
114+ DnsProtocol :: Https => {
115+ let client = self . get_or_init_https_client ( ) . await ?;
116+ transport:: https_query ( * addr, query_bytes, & client) . await
93117 }
94118 }
95- DnsProtocol :: Tcp => transport:: tcp_query ( * addr, query_bytes) . await ,
96- DnsProtocol :: Tls => {
97- let tls_config = self . tls_config . as_ref ( ) . ok_or_else ( || {
98- e ! ( DnsError :: Transport {
99- source: std:: io:: Error :: new(
100- std:: io:: ErrorKind :: InvalidInput ,
101- "TLS config required for DNS-over-TLS" ,
102- ) ,
103- } )
104- } ) ?;
105- transport:: tls_query ( * addr, query_bytes, tls_config) . await
106- }
107- DnsProtocol :: Https => {
108- let client = self . get_or_init_https_client ( ) . await ?;
109- transport:: https_query ( * addr, query_bytes, & client) . await
119+ } )
120+ . await ;
121+ let result = match result {
122+ Ok ( inner) => inner,
123+ Err ( _elapsed) => {
124+ trace ! ( %addr, ?proto, "DNS query timed out" ) ;
125+ Err ( e ! ( DnsError :: Transport {
126+ source: std:: io:: Error :: new(
127+ std:: io:: ErrorKind :: TimedOut ,
128+ "nameserver query timed out" ,
129+ ) ,
130+ } ) )
110131 }
111132 } ;
112133 match result {
0 commit comments