]> git.ipfire.org Git - thirdparty/curl.git/commitdiff
Happy Eyeballs: add resolution time delay
authorStefan Eissing <stefan@eissing.org>
Fri, 17 Apr 2026 08:26:25 +0000 (10:26 +0200)
committerDaniel Stenberg <daniel@haxx.se>
Mon, 20 Apr 2026 21:47:49 +0000 (23:47 +0200)
HEv3 describes conditions on when first connect attempts shall be
started.
https://www.ietf.org/archive/id/draft-ietf-happy-happyeyeballs-v3-01.html
Chapter 4.2

libcurl now waits 50ms for AAAA and HTTPS results (when requested) to
return before continuing with the connect.

Added HTTPS-RR to the "was resolved" information info message. Changed
logging of HTTPS-RR to a one-liner with RFC 9460 like formatting. This
way the user can see if/what was resolved and used in connecting.

Closes #21354

13 files changed:
lib/asyn-ares.c
lib/asyn-thrdd.c
lib/asyn.h
lib/cf-dns.c
lib/cf-dns.h
lib/cf-https-connect.c
lib/cf-ip-happy.c
lib/cfilters.c
lib/cfilters.h
lib/hostip.c
lib/hostip.h
lib/httpsrr.c
lib/httpsrr.h

index 4d14a632b429e96fa02b5df67747c47b7ef00a70..187ffbbe54feb8005417498493ce5c8cfa8cadc5 100644 (file)
@@ -563,6 +563,7 @@ static void async_ares_A_cb(void *user_data, int status, int timeouts,
 
   async->dns_responses |= CURL_DNSQ_A;
   async->queries_ongoing--;
+  async->done = !async->queries_ongoing;
   if(status == ARES_SUCCESS) {
     ares->ares_status = ARES_SUCCESS;
     ares->res_A = async_ares_node2addr(ares_ai->nodes);
@@ -585,6 +586,7 @@ static void async_ares_AAAA_cb(void *user_data, int status, int timeouts,
 
   async->dns_responses |= CURL_DNSQ_AAAA;
   async->queries_ongoing--;
+  async->done = !async->queries_ongoing;
   if(status == ARES_SUCCESS) {
     ares->ares_status = ARES_SUCCESS;
     ares->res_AAAA = async_ares_node2addr(ares_ai->nodes);
@@ -609,6 +611,7 @@ static void async_ares_rr_done(void *user_data, ares_status_t status,
   (void)timeouts;
   async->dns_responses |= CURL_DNSQ_HTTPS;
   async->queries_ongoing--;
+  async->done = !async->queries_ongoing;
   if((ARES_SUCCESS != status) || !dnsrec)
     return;
   ares->result = Curl_httpsrr_from_ares(dnsrec, &ares->hinfo);
index 79e1fd24497266ac7d0b1b5fa912ae8132200a19..f19aab0c2dc5e8f72066ef0be1910d9ca46d9efb 100644 (file)
@@ -484,7 +484,7 @@ static void async_thrdd_report_item(struct Curl_easy *data,
   int ai_family = (item->dns_queries & CURL_DNSQ_AAAA) ? AF_INET6 : AF_INET;
   CURLcode result;
 
-  if(!Curl_trc_is_verbose(data))
+  if(!CURL_TRC_DNS_is_verbose(data))
     return;
 
   curlx_dyn_init(&tmp, 1024);
@@ -500,9 +500,10 @@ static void async_thrdd_report_item(struct Curl_easy *data,
     }
   }
 
-  infof(data, "Host %s:%u resolved IPv%c: %s", item->hostname, item->port,
-        (item->dns_queries & CURL_DNSQ_AAAA) ? '6' : '4',
-        (curlx_dyn_len(&tmp) ? curlx_dyn_ptr(&tmp) : "(none)"));
+  CURL_TRC_DNS(data, "Host %s:%u resolved IPv%c: %s",
+               item->hostname, item->port,
+               (item->dns_queries & CURL_DNSQ_AAAA) ? '6' : '4',
+               (curlx_dyn_len(&tmp) ? curlx_dyn_ptr(&tmp) : "(none)"));
 out:
   curlx_dyn_free(&tmp);
 }
@@ -707,8 +708,10 @@ CURLcode Curl_async_take_result(struct Curl_easy *data,
     return CURLE_AGAIN;
 
   Curl_expire_done(data, EXPIRE_ASYNC_NAME);
-  if(async->result)
+  if(async->result) {
+    result = async->result;
     goto out;
+  }
 
   if((thrdd->res_A && thrdd->res_A->res) ||
      (thrdd->res_AAAA && thrdd->res_AAAA->res)) {
@@ -739,7 +742,6 @@ CURLcode Curl_async_take_result(struct Curl_easy *data,
   }
 
   if(dns) {
-    CURL_TRC_DNS(data, "resolving complete");
     *pdns = dns;
     dns = NULL;
   }
index 06387a85aebc4f6eb0cd6fca31c4e1895ef72c2b..8c1bd8fd6b99d41a8559af3b684d62b70899b4d0 100644 (file)
@@ -214,6 +214,8 @@ CURLcode Curl_async_pollset(struct Curl_easy *data,
 #define Curl_async_await(a, b, c)       CURLE_COULDNT_RESOLVE_HOST
 #define Curl_async_take_result(x, y, z) CURLE_COULDNT_RESOLVE_HOST
 #define Curl_async_pollset(x, y, z)     CURLE_OK
+#define Curl_async_get_https(x, y)      NULL
+#define Curl_async_knows_https(x, y)    TRUE
 #endif /* !CURLRES_ASYNCH */
 
 #if defined(CURLRES_ASYNCH) || !defined(CURL_DISABLE_DOH)
index 98784b0cf670e78037aba0eb376e8356c91563e4..54ac0eebe86e718b7c90666498e434e0153c6f41 100644 (file)
@@ -28,6 +28,7 @@
 #include "cfilters.h"
 #include "connect.h"
 #include "dnscache.h"
+#include "httpsrr.h"
 #include "curl_trc.h"
 #include "progress.h"
 #include "url.h"
@@ -138,11 +139,24 @@ static void cf_dns_report(struct Curl_cfilter *cf,
     break;
   default:
     curlx_dyn_init(&tmp, 1024);
-    infof(data, "Host %s:%d was resolved.", dns->hostname, dns->port);
+    infof(data, "Host %s:%u was resolved.", dns->hostname, dns->port);
 #ifdef CURLRES_IPV6
     cf_dns_report_addr(data, &tmp, "IPv6: ", AF_INET6, dns->addr);
 #endif
     cf_dns_report_addr(data, &tmp, "IPv4: ", AF_INET, dns->addr);
+#ifdef USE_HTTPSRR
+    if(!dns->hinfo)
+      infof(data, "HTTPS-RR: -");
+    else if(!Curl_httpsrr_applicable(data, dns->hinfo))
+      infof(data, "HTTPS-RR: not applicable");
+    else {
+      CURLcode result = Curl_httpsrr_print(&tmp, dns->hinfo);
+      if(!result)
+        infof(data, "HTTPS-RR: %s", curlx_dyn_ptr(&tmp));
+      else
+        infof(data, "Error printing HTTPS-RR information");
+    }
+#endif
     curlx_dyn_free(&tmp);
     break;
   }
@@ -205,6 +219,41 @@ static CURLcode cf_dns_start(struct Curl_cfilter *cf,
   }
 }
 
+#define CURL_HEV3_RESOLVE_DELAY_MS    50
+
+static bool cf_dns_ready_to_connect(struct Curl_cfilter *cf,
+                                    struct Curl_easy *data)
+{
+  struct cf_dns_ctx *ctx = cf->ctx;
+
+  if(ctx->resolv_result)
+    return TRUE;
+  else if(ctx->dns)
+    return TRUE;
+#ifdef USE_CURL_ASYNC
+  else {
+    /* We want AAAA answer as we prefer ipv6. If a sub-filter desires
+    * HTTPS-RR, we check for that query as well. */
+    uint8_t wanted_answers = CURL_DNSQ_AAAA;
+    if(Curl_conn_cf_wants_httpsrr(cf, data))
+      wanted_answers |= CURL_DNSQ_HTTPS;
+
+    /* Note: if a query was never started, it is considered to have
+     * an answer (e.g. a negative one). */
+    if(Curl_resolv_has_answers(data, ctx->resolv_id, wanted_answers))
+      return TRUE;
+    /* If the wanted answers are not available after a delay,
+     * we let the connect attempts start anyway. */
+    return Curl_resolv_elapsed_ms(data, ctx->resolv_id) >=
+           CURL_HEV3_RESOLVE_DELAY_MS;
+  }
+#else
+  (void)data;
+  DEBUGASSERT(0); /* We should not come here */
+  return FALSE;
+#endif /* USE_CURL_ASYNC */
+}
+
 static CURLcode cf_dns_connect(struct Curl_cfilter *cf,
                                struct Curl_easy *data,
                                bool *done)
@@ -225,9 +274,6 @@ static CURLcode cf_dns_connect(struct Curl_cfilter *cf,
   if(!ctx->dns && !ctx->resolv_result) {
     ctx->resolv_result =
       Curl_resolv_take_result(data, ctx->resolv_id, &ctx->dns);
-    if(!ctx->dns && !ctx->resolv_result)
-      CURL_TRC_CF(data, cf, "DNS resolution ongoing for %s:%u",
-                  ctx->hostname, ctx->port);
   }
 
   if(ctx->resolv_result) {
@@ -244,19 +290,23 @@ static CURLcode cf_dns_connect(struct Curl_cfilter *cf,
     cf_dns_report(cf, data, ctx->dns);
   }
 
+  if(!cf_dns_ready_to_connect(cf, data)) {
+    return CURLE_OK;
+  }
+
   if(cf->next && !cf->next->connected) {
     bool sub_done;
     CURLcode result = Curl_conn_cf_connect(cf->next, data, &sub_done);
-    CURL_TRC_CF(data, cf, "connect subfilters -> %d, done=%d",
-                result, sub_done);
     if(result || !sub_done)
       return result;
     DEBUGASSERT(sub_done);
   }
 
   /* sub filter chain is connected */
+  CURL_TRC_CF(data, cf, "connected filter chain below");
   if(ctx->complete_resolve && !ctx->dns && !ctx->resolv_result) {
     /* This filter only connects when it has resolved everything. */
+    CURL_TRC_CF(data, cf, "delay connect until resolve complete");
     return CURLE_OK;
   }
   *done = TRUE;
@@ -536,30 +586,6 @@ static const struct Curl_addrinfo *cf_dns_get_nth_ai(
   return NULL;
 }
 
-bool Curl_conn_dns_has_any_ai(struct Curl_easy *data, int sockindex)
-{
-  struct Curl_cfilter *cf = data->conn->cfilter[sockindex];
-
-  (void)data;
-  for(; cf; cf = cf->next) {
-    if(cf->cft == &Curl_cft_dns) {
-      struct cf_dns_ctx *ctx = cf->ctx;
-      if(ctx->resolv_result)
-        return FALSE;
-      else if(ctx->dns)
-        return !!ctx->dns->addr;
-      else
-#ifdef USE_IPV6
-        return Curl_resolv_get_ai(data, ctx->resolv_id, AF_INET, 0) ||
-               Curl_resolv_get_ai(data, ctx->resolv_id, AF_INET6, 0);
-#else
-        return !!Curl_resolv_get_ai(data, ctx->resolv_id, AF_INET, 0);
-#endif
-    }
-  }
-  return FALSE;
-}
-
 /* Return the addrinfo at `index` for the given `family` from the
  * first "resolve" filter underneath `cf`. If the DNS resolving is
  * not done yet or if no address for the family exists, returns NULL.
index 8e796d349b9efa1b2c8f1e07ad0a74df37a15185..3c46b1bf3dc526a4a3ab8208a38ad2224fc66e01 100644 (file)
@@ -47,10 +47,6 @@ CURLcode Curl_cf_dns_insert_after(struct Curl_cfilter *cf_at,
 
 CURLcode Curl_conn_dns_result(struct connectdata *conn, int sockindex);
 
-/* Returns TRUE if any addressinfo is available via
- * `Curl_conn_dns_get_ai()`. */
-bool Curl_conn_dns_has_any_ai(struct Curl_easy *data, int sockindex);
-
 const struct Curl_addrinfo *Curl_conn_dns_get_ai(struct Curl_easy *data,
                                                  int sockindex,
                                                  int ai_family,
index 5407dc73a69e5acaf6cd0d302f333f1592f03e82..d1d8f51076e3bff6a5b01fcf4c2b1101b37ccfac 100644 (file)
@@ -306,13 +306,8 @@ static enum alpnid cf_hc_get_httpsrr_alpn(struct Curl_cfilter *cf,
   /* Do we have HTTPS-RR information? */
   rr = Curl_conn_dns_get_https(data, cf->sockindex);
 
-  if(rr && !rr->no_def_alpn &&  /* ALPNs are defaults */
-     (!rr->target ||      /* for same host */
-      !rr->target[0] ||
-      (rr->target[0] == '.' &&
-       !rr->target[1])) &&
-     (!rr->port_set ||    /* for same port */
-      rr->port == cf->conn->remote_port)) {
+  /* We do not support `rr->no_def_alpn`. */
+  if(Curl_httpsrr_applicable(data, rr) && !rr->no_def_alpn) {
     for(i = 0; i < CURL_ARRAYSIZE(rr->alpns); ++i) {
       enum alpnid alpn_rr = (enum alpnid)rr->alpns[i];
       if(alpn_rr == not_this_one) /* don't want this one */
@@ -509,9 +504,6 @@ static CURLcode cf_hc_connect(struct Curl_cfilter *cf,
 
   switch(ctx->state) {
   case CF_HC_RESOLV:
-    /* Without any addressinfo, delay the start of balling. */
-    if(!Curl_conn_dns_has_any_ai(data, cf->sockindex))
-      return CURLE_OK;
     ctx->state = CF_HC_INIT;
     FALLTHROUGH();
 
@@ -761,7 +753,7 @@ static void cf_hc_destroy(struct Curl_cfilter *cf, struct Curl_easy *data)
 
 struct Curl_cftype Curl_cft_http_connect = {
   "HTTPS-CONNECT",
-  CF_TYPE_SETUP,
+  CF_TYPE_SETUP | CF_TYPE_HTTPSRR,
   CURL_LOG_LVL_NONE,
   cf_hc_destroy,
   cf_hc_connect,
index f246fb8e16687999335a9a0a807dd02f8e0dadd9..3064612d52339de80e751767969c9bdfb394a547 100644 (file)
@@ -814,8 +814,9 @@ static CURLcode cf_ip_happy_connect(struct Curl_cfilter *cf,
     result = Curl_conn_dns_result(cf->conn, cf->sockindex);
     if(!result)
       ctx->dns_resolved = TRUE;
-    else if(result == CURLE_AGAIN) /* not complete yet */
+    else if(result == CURLE_AGAIN) {
       result = CURLE_OK;
+    }
     else /* real error */
       goto out;
   }
index e3668af4736ee94f2a2ef04bc1f07c71f6d06962..46554141314718f0a0599e5eaf262928ae08d26b 100644 (file)
@@ -748,6 +748,17 @@ int Curl_protocol_for_transport(uint8_t transport)
   }
 }
 
+bool Curl_conn_cf_wants_httpsrr(struct Curl_cfilter *cf,
+                                struct Curl_easy *data)
+{
+  (void)data;
+  for(; cf; cf = cf->next) {
+    if(cf->cft->flags & CF_TYPE_HTTPSRR)
+      return TRUE;
+  }
+  return FALSE;
+}
+
 const char *Curl_conn_get_alpn_negotiated(struct Curl_easy *data,
                                           struct connectdata *conn)
 {
index fc77e61dfe917f1ebd7e96a4d07c6d85d8518111..ac56737ccb998a97ec49457a652b380e7be863fd 100644 (file)
@@ -201,6 +201,7 @@ typedef CURLcode Curl_cft_query(struct Curl_cfilter *cf,
  * CF_TYPE_HTTP        implement a version of the HTTP protocol
  * CF_TYPE_SETUP       filter is only needed for connection setup and
  *                     can be removed once connected
+ * CF_TYPE_HTTPSRR     filter that wants HTTPS-RR information
  */
 #define CF_TYPE_IP_CONNECT  (1 << 0)
 #define CF_TYPE_SSL         (1 << 1)
@@ -208,6 +209,7 @@ typedef CURLcode Curl_cft_query(struct Curl_cfilter *cf,
 #define CF_TYPE_PROXY       (1 << 3)
 #define CF_TYPE_HTTP        (1 << 4)
 #define CF_TYPE_SETUP       (1 << 5)
+#define CF_TYPE_HTTPSRR     (1 << 6)
 
 /* A connection filter type, e.g. specific implementation. */
 struct Curl_cftype {
@@ -354,6 +356,10 @@ int Curl_protocol_for_transport(uint8_t transport);
 const char *Curl_conn_cf_get_alpn_negotiated(struct Curl_cfilter *cf,
                                              struct Curl_easy *data);
 
+/* The filter (or one of its sub-filters) wants HTTPS-RR information. */
+bool Curl_conn_cf_wants_httpsrr(struct Curl_cfilter *cf,
+                                struct Curl_easy *data);
+
 #define CURL_CF_SSL_DEFAULT  (-1)
 #define CURL_CF_SSL_DISABLE  0
 #define CURL_CF_SSL_ENABLE   1
index f1a12bb21f04e0beb15f57864428e2706cb7eb1a..0d1095e33baa739aa2fec8369c9bae249c88cd3f 100644 (file)
@@ -431,10 +431,11 @@ static CURLcode hostip_resolv_take_result(struct Curl_easy *data,
   result = Curl_async_take_result(data, async, pdns);
 
   if(result == CURLE_AGAIN) {
-    CURL_TRC_DNS(data, "result incomplete, queries=%s, responses=%s, "
-                 "ongoing=%d", Curl_resolv_query_str(async->dns_queries),
+    CURL_TRC_DNS(data, "resolve incomplete, queries=%s, responses=%s, "
+                 "ongoing=%d for %s:%d",
+                 Curl_resolv_query_str(async->dns_queries),
                  Curl_resolv_query_str(async->dns_responses),
-                 async->queries_ongoing);
+                 async->queries_ongoing, async->hostname, async->port);
     result = CURLE_OK;
   }
   else if(result) {
@@ -442,64 +443,74 @@ static CURLcode hostip_resolv_take_result(struct Curl_easy *data,
     Curl_resolver_error(data, NULL);
   }
   else {
-    CURL_TRC_DNS(data, "result complete");
+    CURL_TRC_DNS(data, "resolve complete for %s:%u",
+                 async->hostname, async->port);
     DEBUGASSERT(*pdns);
   }
 
   return result;
 }
 
+timediff_t Curl_resolv_elapsed_ms(struct Curl_easy *data,
+                                  uint32_t resolv_id)
+{
+  struct Curl_resolv_async *async = Curl_async_get(data, resolv_id);
+  if(!async)
+    return CURL_TIMEOUT_RESOLVE_MS;
+  return curlx_ptimediff_ms(Curl_pgrs_now(data), &async->start);
+}
+
+bool Curl_resolv_has_answers(struct Curl_easy *data,
+                             uint32_t resolv_id, uint8_t dns_queries)
+{
+  struct Curl_resolv_async *async = Curl_async_get(data, resolv_id);
+  uint8_t check_queries;
+  /* a no longer existing/running resolve has all answers. */
+  if(!async || async->done)
+    return TRUE;
+  /* Relevant are only queries undertaken. Others are considered answered. */
+  check_queries = (dns_queries & async->dns_queries);
+  if((check_queries & async->dns_responses) != check_queries) {
+    return FALSE;
+  }
+  return TRUE;
+}
+
 const struct Curl_addrinfo *Curl_resolv_get_ai(struct Curl_easy *data,
                                                uint32_t resolv_id,
                                                int ai_family,
                                                unsigned int index)
 {
-#ifdef CURLRES_ASYNCH
   struct Curl_resolv_async *async = Curl_async_get(data, resolv_id);
-  if(async) {
-    if((ai_family == AF_INET) && !(async->dns_queries & CURL_DNSQ_A))
-      return NULL;
-#ifdef USE_IPV6
-    if((ai_family == AF_INET6) && !(async->dns_queries & CURL_DNSQ_AAAA))
-      return NULL;
-#endif
-    return Curl_async_get_ai(data, async, ai_family, index);
-  }
-#else
-  (void)data;
-  (void)resolv_id;
-  (void)ai_family;
   (void)index;
+  if(!async)
+    return NULL;
+  if((ai_family == AF_INET) && !(async->dns_queries & CURL_DNSQ_A))
+    return NULL;
+#ifdef USE_IPV6
+  if((ai_family == AF_INET6) && !(async->dns_queries & CURL_DNSQ_AAAA))
+    return NULL;
 #endif
-  return NULL;
+  return Curl_async_get_ai(data, async, ai_family, index);
 }
 
+
 #ifdef USE_HTTPSRR
 const struct Curl_https_rrinfo *
 Curl_resolv_get_https(struct Curl_easy *data, uint32_t resolv_id)
 {
-#ifdef CURLRES_ASYNCH
   struct Curl_resolv_async *async = Curl_async_get(data, resolv_id);
-  if(async)
-    return Curl_async_get_https(data, async);
-#else
-  (void)data;
-  (void)resolv_id;
-#endif
-  return NULL;
+  if(!async)
+    return NULL;
+  return Curl_async_get_https(data, async);
 }
 
 bool Curl_resolv_knows_https(struct Curl_easy *data, uint32_t resolv_id)
 {
-#ifdef CURLRES_ASYNCH
   struct Curl_resolv_async *async = Curl_async_get(data, resolv_id);
-  if(async)
-    return Curl_async_knows_https(data, async);
-#else
-  (void)data;
-  (void)resolv_id;
-#endif
-  return TRUE;
+  if(!async)
+    return TRUE;
+  return Curl_async_knows_https(data, async);
 }
 
 #endif /* USE_HTTPSRR */
index 96547e33b71b9fb083b8d9088d0eb4abd8773dbc..368883b8e7b754193bb4e3cefacd6edefe4ac427 100644 (file)
@@ -136,6 +136,17 @@ CURLcode Curl_resolv_take_result(struct Curl_easy *data, uint32_t resolv_id,
 
 void Curl_resolv_destroy(struct Curl_easy *data, uint32_t resolv_id);
 
+/* How much time has gone by since start of resolve.
+ * Returns CURL_TIMEOUT_RESOLVE_MS if `resolv_id` is no longer valid. */
+timediff_t Curl_resolv_elapsed_ms(struct Curl_easy *data,
+                                  uint32_t resolv_id);
+
+/* Return TRUE if `resolv_id` has answers (positive or negative) to
+ * all queries in `dns_queries`.
+ * Queries not requested are considered answered. */
+bool Curl_resolv_has_answers(struct Curl_easy *data,
+                             uint32_t resolv_id, uint8_t dns_queries);
+
 const struct Curl_addrinfo *Curl_resolv_get_ai(struct Curl_easy *data,
                                                uint32_t resolv_id,
                                                int ai_family,
@@ -150,6 +161,8 @@ bool Curl_resolv_knows_https(struct Curl_easy *data, uint32_t resolv_id);
 #define Curl_resolv_shutdown_all(x)      Curl_nop_stmt
 #define Curl_resolv_destroy_all(x)       Curl_nop_stmt
 #define Curl_resolv_take_result(x, y, z) CURLE_NOT_BUILT_IN
+#define Curl_resolv_elapsed_ms(x, y)     CURL_TIMEOUT_RESOLVE_MS
+#define Curl_resolv_has_answers(x, y, z) TRUE
 #define Curl_resolv_get_ai(x, y, z, a)   NULL
 #define Curl_resolv_get_https(x, y)      NULL
 #define Curl_resolv_knows_https(x, y)    TRUE
index 8d02d245421860614359d41217f8d4c27e3fda1a..bebd7e33b1312eca566c53e494555f1c25cfe743 100644 (file)
@@ -70,122 +70,153 @@ static CURLcode httpsrr_decode_alpn(const uint8_t *cp, size_t len,
 }
 
 #ifdef CURLVERBOSE
-static void httpsrr_report_addr(struct Curl_easy *data, int ai_family,
-                                const uint8_t *addr, size_t total_len)
+
+static CURLcode httpsrr_print_addr(struct dynbuf *dyn,
+                                   int ai_family,
+                                   const uint8_t *addr,
+                                   size_t total_len)
 {
   char buf[MAX_IPADR_LEN];
-  struct dynbuf tmp;
   size_t i, alen = (ai_family == AF_INET6) ? 16 : 4;
   const char *sep = "";
-  bool incomplete = FALSE;
-  CURLcode result;
-
-  if(!CURL_TRC_DNS_is_verbose(data))
-    return;
+  CURLcode result = CURLE_OK;
 
-  curlx_dyn_init(&tmp, 1024);
-  for(i = 0; i < (total_len / alen); ++i) {
-    if(!curlx_inet_ntop(ai_family, addr + (i * alen), buf, sizeof(buf))) {
-      CURL_TRC_DNS(data, "[HTTPS-RR] error parsing address #%zu", i);
-      goto out;
-    }
-    result = curlx_dyn_addf(&tmp, "%s%s", sep, buf);
-    if(result) {
-      incomplete = TRUE;
-      break;
-    }
-    sep = ", ";
+  for(i = 0; (i < (total_len / alen)) && !result; ++i) {
+    if(!curlx_inet_ntop(ai_family, addr + (i * alen), buf, sizeof(buf)))
+      result = curlx_dyn_add(dyn, "<error parsing address>");
+    else
+      result = curlx_dyn_addf(dyn, "%s%s", sep, buf);
+    sep = ",";
   }
-
-  CURL_TRC_DNS(data, "[HTTPS-RR] IPv%d: %s%s",
-               (ai_family == AF_INET6) ? 6 : 4,
-               curlx_dyn_len(&tmp) ? curlx_dyn_ptr(&tmp) : "(none)",
-               incomplete ? " ..." : "");
-out:
-  curlx_dyn_free(&tmp);
+  return result;
 }
 
 void Curl_httpsrr_trace(struct Curl_easy *data,
-                        struct Curl_https_rrinfo *hi)
+                        struct Curl_https_rrinfo *rr)
 {
-  if(!hi || !hi->complete) {
+  struct dynbuf tmp;
+  CURLcode result;
+
+  if(!rr || !rr->complete) {
     CURL_TRC_DNS(data, "[HTTPS-RR] not available");
     return;
   }
+  curlx_dyn_init(&tmp, 1024);
+  result = Curl_httpsrr_print(&tmp, rr);
+  if(!result)
+    CURL_TRC_DNS(data, "HTTPS-RR: %s", curlx_dyn_ptr(&tmp));
+  else
+    CURL_TRC_DNS(data, "Error printing HTTPS-RR information");
+  curlx_dyn_free(&tmp);
+}
 
-  if(hi->target)
-    CURL_TRC_DNS(data, "[HTTPS-RR] target: %s", hi->target);
-  if(hi->priority)
-    CURL_TRC_DNS(data, "[HTTPS-RR] priority: %u", hi->priority);
-  if(hi->mandatory)
-    CURL_TRC_DNS(data, "[HTTPS-RR] MANDATORY present, but not supported");
-  if(hi->alpns[0])
-    CURL_TRC_DNS(data, "[HTTPS-RR] ALPN: %u %u %u %u",
-                 hi->alpns[0], hi->alpns[1], hi->alpns[2], hi->alpns[3]);
-  if(hi->port_set)
-    CURL_TRC_DNS(data, "[HTTPS-RR] port %u", hi->port);
-  if(hi->no_def_alpn)
-    CURL_TRC_DNS(data, "[HTTPS-RR] no-def-alpn");
-  if(hi->ipv6hints_len)
-    httpsrr_report_addr(data, AF_INET6, hi->ipv6hints, hi->ipv6hints_len);
-  if(hi->ipv4hints_len)
-    httpsrr_report_addr(data, AF_INET, hi->ipv4hints, hi->ipv4hints_len);
-  if(hi->echconfiglist_len)
-    CURL_TRC_DNS(data, "[HTTPS-RR] ECH");
+CURLcode Curl_httpsrr_print(struct dynbuf *tmp,
+                            struct Curl_https_rrinfo *rr)
+{
+  CURLcode result;
+  int i;
+
+  curlx_dyn_reset(tmp);
+  result = curlx_dyn_addf(tmp, "%u %s", rr->priority,
+                          rr->target ? rr->target : ".");
+  if(!result && rr->mandatory)
+    result = curlx_dyn_add(tmp, " mandatory-keys(ignored)");
+  if(!result && rr->alpns[0]) {
+    const char *sep = "", *name;
+    result = curlx_dyn_add(tmp, " alpn=");
+    for(i = 0; !result && (i < 4); ++i) {
+      switch(rr->alpns[i]) {
+      case ALPN_h1:
+        name = "http/1.1";
+        break;
+      case ALPN_h2:
+        name = "h2";
+        break;
+      case ALPN_h3:
+        name = "h3";
+        break;
+      default:
+        name = NULL;
+      }
+      if(name) {
+        result = curlx_dyn_addf(tmp, "%s%s", sep, name);
+        sep = ",";
+      }
+    }
+  }
+  if(!result && rr->port_set) {
+    result = curlx_dyn_addf(tmp, " port=%u", rr->port);
+  }
+  if(!result && rr->no_def_alpn)
+    result = curlx_dyn_add(tmp, " no-default-alpn");
+  if(!result && rr->ipv6hints_len) {
+    result = curlx_dyn_add(tmp, " ipv6hint=");
+    if(!result)
+      result = httpsrr_print_addr(
+        tmp, AF_INET6, rr->ipv6hints, rr->ipv6hints_len);
+  }
+  if(!result && rr->ipv4hints_len) {
+    result = curlx_dyn_add(tmp, " ipv4hint=");
+    if(!result)
+      result = httpsrr_print_addr(
+        tmp, AF_INET, rr->ipv4hints, rr->ipv4hints_len);
+  }
+  if(!result && rr->echconfiglist_len)
+    result = curlx_dyn_addf(tmp, " ech=<%zu bytes>", rr->echconfiglist_len);
+
+  return result;
 }
 
-#else
-#define httpsrr_report_addr(a, b, c, d) Curl_nop_stmt
 #endif /* CURLVERBOSE */
 
-CURLcode Curl_httpsrr_set(struct Curl_https_rrinfo *hi,
+CURLcode Curl_httpsrr_set(struct Curl_https_rrinfo *rr,
                           uint16_t rrkey, const uint8_t *val, size_t vlen)
 {
   CURLcode result = CURLE_OK;
   switch(rrkey) {
   case HTTPS_RR_CODE_MANDATORY:
-    hi->mandatory = TRUE;
+    rr->mandatory = TRUE;
     break;
   case HTTPS_RR_CODE_ALPN: /* str_list */
-    result = httpsrr_decode_alpn(val, vlen, hi->alpns);
+    result = httpsrr_decode_alpn(val, vlen, rr->alpns);
     break;
   case HTTPS_RR_CODE_NO_DEF_ALPN:
     if(vlen) /* no data */
       return CURLE_BAD_FUNCTION_ARGUMENT;
-    hi->no_def_alpn = TRUE;
+    rr->no_def_alpn = TRUE;
     break;
   case HTTPS_RR_CODE_IPV4: /* addr4 list */
     if(!vlen || (vlen & 3)) /* the size must be 4-byte aligned */
       return CURLE_BAD_FUNCTION_ARGUMENT;
-    curlx_free(hi->ipv4hints);
-    hi->ipv4hints = curlx_memdup(val, vlen);
-    if(!hi->ipv4hints)
+    curlx_free(rr->ipv4hints);
+    rr->ipv4hints = curlx_memdup(val, vlen);
+    if(!rr->ipv4hints)
       return CURLE_OUT_OF_MEMORY;
-    hi->ipv4hints_len = vlen;
+    rr->ipv4hints_len = vlen;
     break;
   case HTTPS_RR_CODE_ECH:
     if(!vlen)
       return CURLE_BAD_FUNCTION_ARGUMENT;
-    curlx_free(hi->echconfiglist);
-    hi->echconfiglist = curlx_memdup(val, vlen);
-    if(!hi->echconfiglist)
+    curlx_free(rr->echconfiglist);
+    rr->echconfiglist = curlx_memdup(val, vlen);
+    if(!rr->echconfiglist)
       return CURLE_OUT_OF_MEMORY;
-    hi->echconfiglist_len = vlen;
+    rr->echconfiglist_len = vlen;
     break;
   case HTTPS_RR_CODE_IPV6: /* addr6 list */
     if(!vlen || (vlen & 15)) /* the size must be 16-byte aligned */
       return CURLE_BAD_FUNCTION_ARGUMENT;
-    curlx_free(hi->ipv6hints);
-    hi->ipv6hints = curlx_memdup(val, vlen);
-    if(!hi->ipv6hints)
+    curlx_free(rr->ipv6hints);
+    rr->ipv6hints = curlx_memdup(val, vlen);
+    if(!rr->ipv6hints)
       return CURLE_OUT_OF_MEMORY;
-    hi->ipv6hints_len = vlen;
+    rr->ipv6hints_len = vlen;
     break;
   case HTTPS_RR_CODE_PORT:
     if(vlen != 2)
       return CURLE_BAD_FUNCTION_ARGUMENT;
-    hi->port = (uint16_t)((val[0] << 8) | val[1]);
-    hi->port_set = TRUE;
+    rr->port = (uint16_t)((val[0] << 8) | val[1]);
+    rr->port_set = TRUE;
     break;
   default:
     /* unknown code */
@@ -213,6 +244,17 @@ void Curl_httpsrr_cleanup(struct Curl_https_rrinfo *rrinfo)
   rrinfo->complete = FALSE;
 }
 
+bool Curl_httpsrr_applicable(struct Curl_easy *data,
+                             const struct Curl_https_rrinfo *rr)
+{
+  if(!data->conn || !rr)
+    return FALSE;
+  return (data->conn && rr &&
+          (!rr->target || !rr->target[0] ||
+           (rr->target[0] == '.' && !rr->target[1])) &&
+          (!rr->port_set || rr->port == data->conn->remote_port));
+}
+
 #ifdef USE_ARES
 
 static CURLcode httpsrr_opt(const ares_dns_rr_t *rr,
index 68f5dad875f62bd6b333cc84f5e135a2f3e0bb55..b28e3271be6b8d02b0a82c105dab2a331b9ba869 100644 (file)
@@ -35,6 +35,7 @@
 #define MAX_HTTPSRR_ALPNS     4
 
 struct Curl_easy;
+struct dynbuf;
 
 struct Curl_https_rrinfo {
   char *rrname; /* if NULL, the same as the URL hostname */
@@ -59,7 +60,7 @@ struct Curl_https_rrinfo {
   BIT(complete); /* values have been successfully assigned */
 };
 
-CURLcode Curl_httpsrr_set(struct Curl_https_rrinfo *hi,
+CURLcode Curl_httpsrr_set(struct Curl_https_rrinfo *rr,
                           uint16_t rrkey, const uint8_t *val, size_t vlen);
 
 struct Curl_https_rrinfo *Curl_httpsrr_dup_move(
@@ -67,6 +68,11 @@ struct Curl_https_rrinfo *Curl_httpsrr_dup_move(
 
 void Curl_httpsrr_cleanup(struct Curl_https_rrinfo *rrinfo);
 
+/* TRUE if the record is applicable to the transfer and its connection. */
+bool Curl_httpsrr_applicable(struct Curl_easy *data,
+                             const struct Curl_https_rrinfo *rr);
+
+
 /*
  * Code points for DNS wire format SvcParams as per RFC 9460
  */
@@ -84,8 +90,11 @@ CURLcode Curl_httpsrr_from_ares(const ares_dns_record_t *dnsrec,
 #endif /* USE_ARES */
 
 #ifdef CURLVERBOSE
+
+CURLcode Curl_httpsrr_print(struct dynbuf *tmp,
+                            struct Curl_https_rrinfo *rr);
 void Curl_httpsrr_trace(struct Curl_easy *data,
-                        struct Curl_https_rrinfo *hi);
+                        struct Curl_https_rrinfo *rr);
 #else
 #define Curl_httpsrr_trace(a, b) Curl_nop_stmt
 #endif