6868#include "progress.h"
6969
7070# if defined(CURL_STATICLIB ) && !defined(CARES_STATICLIB ) && \
71- (defined(WIN32 ) || defined(_WIN32 ) || defined( __SYMBIAN32__ ))
71+ (defined(WIN32 ) || defined(__SYMBIAN32__ ))
7272# define CARES_STATICLIB
7373# endif
7474# include <ares.h>
@@ -89,8 +89,20 @@ struct ResolverResults {
8989 int num_pending ; /* number of ares_gethostbyname() requests */
9090 Curl_addrinfo * temp_ai ; /* intermediary result while fetching c-ares parts */
9191 int last_status ;
92+ struct curltime happy_eyeballs_dns_time ; /* when this timer started, or 0 */
9293};
9394
95+ /* How long we are willing to wait for additional parallel responses after
96+ obtaining a "definitive" one.
97+
98+ This is intended to equal the c-ares default timeout. cURL always uses that
99+ default value. Unfortunately, c-ares doesn't expose its default timeout in
100+ its API, but it is officially documented as 5 seconds.
101+
102+ See query_completed_cb() for an explanation of how this is used.
103+ */
104+ #define HAPPY_EYEBALLS_DNS_TIMEOUT 5000
105+
94106/*
95107 * Curl_resolver_global_init() - the generic low-level asynchronous name
96108 * resolve API. Called from curl_global_init() to initialize global resolver
@@ -319,9 +331,9 @@ static int waitperform(struct connectdata *conn, int timeout_ms)
319331 /* move through the descriptors and ask for processing on them */
320332 for (i = 0 ; i < num ; i ++ )
321333 ares_process_fd ((ares_channel )data -> state .resolver ,
322- pfd [i ].revents & (POLLRDNORM |POLLIN )?
334+ ( pfd [i ].revents & (POLLRDNORM |POLLIN ) )?
323335 pfd [i ].fd :ARES_SOCKET_BAD ,
324- pfd [i ].revents & (POLLWRNORM |POLLOUT )?
336+ ( pfd [i ].revents & (POLLWRNORM |POLLOUT ) )?
325337 pfd [i ].fd :ARES_SOCKET_BAD );
326338 }
327339 return nfds ;
@@ -347,6 +359,29 @@ CURLcode Curl_resolver_is_resolved(struct connectdata *conn,
347359
348360 waitperform (conn , 0 );
349361
362+ /* Now that we've checked for any last minute results above, see if there are
363+ any responses still pending when the EXPIRE_HAPPY_EYEBALLS_DNS timer
364+ expires. */
365+ if (res
366+ && res -> num_pending
367+ /* This is only set to non-zero if the timer was started. */
368+ && (res -> happy_eyeballs_dns_time .tv_sec
369+ || res -> happy_eyeballs_dns_time .tv_usec )
370+ && (Curl_timediff (Curl_now (), res -> happy_eyeballs_dns_time )
371+ >= HAPPY_EYEBALLS_DNS_TIMEOUT )) {
372+ /* Remember that the EXPIRE_HAPPY_EYEBALLS_DNS timer is no longer
373+ running. */
374+ memset (
375+ & res -> happy_eyeballs_dns_time , 0 , sizeof (res -> happy_eyeballs_dns_time ));
376+
377+ /* Cancel the raw c-ares request, which will fire query_completed_cb() with
378+ ARES_ECANCELLED synchronously for all pending responses. This will
379+ leave us with res->num_pending == 0, which is perfect for the next
380+ block. */
381+ ares_cancel ((ares_channel )data -> state .resolver );
382+ DEBUGASSERT (res -> num_pending == 0 );
383+ }
384+
350385 if (res && !res -> num_pending ) {
351386 if (dns ) {
352387 (void )Curl_addrinfo_callback (conn , res -> last_status , res -> temp_ai );
@@ -455,9 +490,7 @@ CURLcode Curl_resolver_wait_resolv(struct connectdata *conn,
455490
456491 if (result )
457492 /* close the connection, since we can't return failure here without
458- cleaning up this connection properly.
459- TODO: remove this action from here, it is not a name resolver decision.
460- */
493+ cleaning up this connection properly. */
461494 connclose (conn , "c-ares resolve failed" );
462495
463496 return result ;
@@ -517,6 +550,66 @@ static void query_completed_cb(void *arg, /* (struct connectdata *) */
517550 /* A successful result overwrites any previous error */
518551 if (res -> last_status != ARES_SUCCESS )
519552 res -> last_status = status ;
553+
554+ /* If there are responses still pending, we presume they must be the
555+ complementary IPv4 or IPv6 lookups that we started in parallel in
556+ Curl_resolver_getaddrinfo() (for Happy Eyeballs). If we've got a
557+ "definitive" response from one of a set of parallel queries, we need to
558+ think about how long we're willing to wait for more responses. */
559+ if (res -> num_pending
560+ /* Only these c-ares status values count as "definitive" for these
561+ purposes. For example, ARES_ENODATA is what we expect when there is
562+ no IPv6 entry for a domain name, and that's not a reason to get more
563+ aggressive in our timeouts for the other response. Other errors are
564+ either a result of bad input (which should affect all parallel
565+ requests), local or network conditions, non-definitive server
566+ responses, or us cancelling the request. */
567+ && (status == ARES_SUCCESS || status == ARES_ENOTFOUND )) {
568+ /* Right now, there can only be up to two parallel queries, so don't
569+ bother handling any other cases. */
570+ DEBUGASSERT (res -> num_pending == 1 );
571+
572+ /* It's possible that one of these parallel queries could succeed
573+ quickly, but the other could always fail or timeout (when we're
574+ talking to a pool of DNS servers that can only successfully resolve
575+ IPv4 address, for example).
576+
577+ It's also possible that the other request could always just take
578+ longer because it needs more time or only the second DNS server can
579+ fulfill it successfully. But, to align with the philosophy of Happy
580+ Eyeballs, we don't want to wait _too_ long or users will think
581+ requests are slow when IPv6 lookups don't actually work (but IPv4 ones
582+ do).
583+
584+ So, now that we have a usable answer (some IPv4 addresses, some IPv6
585+ addresses, or "no such domain"), we start a timeout for the remaining
586+ pending responses. Even though it is typical that this resolved
587+ request came back quickly, that needn't be the case. It might be that
588+ this completing request didn't get a result from the first DNS server
589+ or even the first round of the whole DNS server pool. So it could
590+ already be quite some time after we issued the DNS queries in the
591+ first place. Without modifying c-ares, we can't know exactly where in
592+ its retry cycle we are. We could guess based on how much time has
593+ gone by, but it doesn't really matter. Happy Eyeballs tells us that,
594+ given usable information in hand, we simply don't want to wait "too
595+ much longer" after we get a result.
596+
597+ We simply wait an additional amount of time equal to the default
598+ c-ares query timeout. That is enough time for a typical parallel
599+ response to arrive without being "too long". Even on a network
600+ where one of the two types of queries is failing or timing out
601+ constantly, this will usually mean we wait a total of the default
602+ c-ares timeout (5 seconds) plus the round trip time for the successful
603+ request, which seems bearable. The downside is that c-ares might race
604+ with us to issue one more retry just before we give up, but it seems
605+ better to "waste" that request instead of trying to guess the perfect
606+ timeout to prevent it. After all, we don't even know where in the
607+ c-ares retry cycle each request is.
608+ */
609+ res -> happy_eyeballs_dns_time = Curl_now ();
610+ Curl_expire (
611+ conn -> data , HAPPY_EYEBALLS_DNS_TIMEOUT , EXPIRE_HAPPY_EYEBALLS_DNS );
612+ }
520613 }
521614}
522615
0 commit comments