@@ -10,9 +10,11 @@ use bytes::Bytes;
1010use data_encoding:: BASE64URL ;
1111use http_body_util:: Empty ;
1212use hyper:: { Request , upgrade:: Parts } ;
13+ use hyper_util:: rt:: TokioIo ;
1314use n0_error:: e;
14- use n0_future:: { task, time} ;
15+ use n0_future:: { IterExt , StreamExt , task, time} ;
1516use rustls:: client:: Resumption ;
17+ use tokio:: net:: TcpStream ;
1618use tracing:: error;
1719
1820use super :: {
@@ -111,61 +113,26 @@ impl MaybeTlsStreamBuilder {
111113 let stream = self . dial_url_proxy ( proxy. clone ( ) , tls_connector) . await ?;
112114 Ok ( ProxyStream :: Proxied ( stream) )
113115 } else {
114- let stream = self . dial_url_direct ( ) . await ?;
116+ let stream =
117+ dial_happy_eyeballs ( & self . dns_resolver , & self . url , self . prefer_ipv6 ) . await ?;
115118 Ok ( ProxyStream :: Raw ( stream) )
116119 }
117120 }
118121
119- async fn dial_url_direct ( & self ) -> Result < tokio:: net:: TcpStream , DialError > {
120- use tokio:: net:: TcpStream ;
121- let dst_ip = self
122- . dns_resolver
123- . resolve_host ( & self . url , self . prefer_ipv6 , DNS_TIMEOUT )
124- . await ?;
125-
126- let port = url_port ( & self . url ) . ok_or_else ( || e ! ( DialError :: InvalidTargetPort ) ) ?;
127- let addr = SocketAddr :: new ( dst_ip, port) ;
128-
129- trace ! ( "connecting to {}" , addr) ;
130- let tcp_stream = time:: timeout ( DIAL_ENDPOINT_TIMEOUT , async move {
131- TcpStream :: connect ( addr) . await
132- } )
133- . await
134- . map_err ( |err| e ! ( DialError :: Timeout , err) ) ??;
135-
136- tcp_stream. set_nodelay ( true ) ?;
137-
138- Ok ( tcp_stream)
139- }
140-
141122 async fn dial_url_proxy (
142123 & self ,
143124 proxy_url : Url ,
144125 tls_connector : & tokio_rustls:: TlsConnector ,
145126 ) -> Result < util:: Chain < std:: io:: Cursor < Bytes > , MaybeTlsStream < tokio:: net:: TcpStream > > , DialError >
146127 {
147- use hyper_util:: rt:: TokioIo ;
148- use tokio:: net:: TcpStream ;
149128 debug ! ( %self . url, %proxy_url, "dial url via proxy" ) ;
150129
151- // Resolve proxy DNS
152- let proxy_ip = self
153- . dns_resolver
154- . resolve_host ( & proxy_url, self . prefer_ipv6 , DNS_TIMEOUT )
155- . await ?;
156-
157- let proxy_port =
158- url_port ( & proxy_url) . ok_or_else ( || e ! ( DialError :: ProxyInvalidTargetPort ) ) ?;
159- let proxy_addr = SocketAddr :: new ( proxy_ip, proxy_port) ;
160-
161- debug ! ( %proxy_addr, "connecting to proxy" ) ;
162-
163- let tcp_stream = time:: timeout ( DIAL_ENDPOINT_TIMEOUT , async move {
164- TcpStream :: connect ( proxy_addr) . await
165- } )
166- . await ??;
167-
168- tcp_stream. set_nodelay ( true ) ?;
130+ let tcp_stream = dial_happy_eyeballs ( & self . dns_resolver , & proxy_url, self . prefer_ipv6 )
131+ . await
132+ . map_err ( |err| match err {
133+ DialError :: InvalidTargetPort { meta } => DialError :: ProxyInvalidTargetPort { meta } ,
134+ err @ _ => err,
135+ } ) ?;
169136
170137 // Setup TLS if necessary
171138 let io = if proxy_url. scheme ( ) == "http" {
@@ -254,6 +221,56 @@ impl MaybeTlsStreamBuilder {
254221 }
255222}
256223
224+ /// Resolves `url` and races a TCP connection across the resolved addresses.
225+ ///
226+ /// The host is resolved to all of its addresses in preference order (IPv6 first
227+ /// when `prefer_ipv6`, otherwise IPv4). Each successive dial is started
228+ /// [`DIAL_STAGGER_DELAY`] after the previous one and capped at
229+ /// [`DIAL_ENDPOINT_TIMEOUT`], so a slow or unreachable preferred address
230+ /// does not hold back the rest for the full timeout. The first connection to succeed
231+ /// is returned. The remaining attempts are cancelled.
232+ async fn dial_happy_eyeballs (
233+ dns_resolver : & DnsResolver ,
234+ url : & Url ,
235+ prefer_ipv6 : bool ,
236+ ) -> Result < TcpStream , DialError > {
237+ let port = url_port ( url) . ok_or_else ( || e ! ( DialError :: InvalidTargetPort ) ) ?;
238+ let addrs = dns_resolver
239+ . resolve_host_all ( url, prefer_ipv6, DNS_TIMEOUT )
240+ . await ?;
241+ let mut dials = addrs
242+ . into_iter ( )
243+ . enumerate ( )
244+ . map ( |( i, ip) | {
245+ let addr = SocketAddr :: new ( ip, port) ;
246+ async move {
247+ time:: sleep ( DIAL_STAGGER_DELAY * i as u32 ) . await ;
248+ trace ! ( "connecting to {}" , addr) ;
249+ let tcp_stream = time:: timeout ( DIAL_ENDPOINT_TIMEOUT , TcpStream :: connect ( addr) )
250+ . await
251+ . map_err ( |err| e ! ( DialError :: Timeout , err) )
252+ . and_then ( |res| res. map_err ( |err| e ! ( DialError :: Io , err) ) )
253+ . inspect_err ( |err| {
254+ debug ! ( %addr, "failed to connect to relay at {addr}: {err:#}" ) ;
255+ } ) ?;
256+ tcp_stream. set_nodelay ( true ) ?;
257+ Ok ( tcp_stream)
258+ }
259+ } )
260+ . into_unordered_stream ( ) ;
261+
262+ let mut last_err = None ;
263+ while let Some ( res) = dials. next ( ) . await {
264+ match res {
265+ Ok ( tcp_stream) => return Ok ( tcp_stream) ,
266+ Err ( err) => last_err = Some ( err) ,
267+ }
268+ }
269+
270+ // If we haven't returned yet, last_err is always set unless `addrs` was empty.
271+ Err ( last_err. unwrap_or_else ( || e ! ( DnsError :: NoResponse ) . into ( ) ) )
272+ }
273+
257274fn url_port ( url : & Url ) -> Option < u16 > {
258275 if let Some ( port) = url. port ( ) {
259276 return Some ( port) ;
0 commit comments