]> git.ipfire.org Git - thirdparty/curl.git/commitdiff
async: DoH improvements
authorStefan Eissing <stefan@eissing.org>
Wed, 16 Apr 2025 11:45:53 +0000 (13:45 +0200)
committerDaniel Stenberg <daniel@haxx.se>
Wed, 16 Apr 2025 14:06:03 +0000 (16:06 +0200)
Adds a "meta_hash" to each easy handle for keeping special data during
operations. All meta data set needs to add its destructor callback, so
that meta data gets destroyed properly when the easy handle is cleaned
up or reset.

Add data->master_mid for "sub" transfers that belong to a "master" easy
handle. When a "sub" transfer is done, the corresponding "master" can
add a callback to be invoked. Used in DoH name resolution.

DoH: use easy meta hash to add internal structs for DoH name resolution.
One in each in each probe easy handle. When probes are done, response
data is copied from the probe to the initiating easy.

This allows DoH using transfers and their probes to be cleaned up in any
sequence correctly.

Fold DoH cleanup into the Curl_async_shutdown() and Curl_async_destroy()
functions.

Closes #16384

14 files changed:
lib/asyn-ares.c
lib/asyn-base.c
lib/asyn-thrdd.c
lib/asyn.h
lib/doh.c
lib/doh.h
lib/easy.c
lib/hostip.c
lib/multi.c
lib/request.c
lib/url.c
lib/url.h
lib/urldata.h
scripts/singleuse.pl

index 6c7922914e7e38610a9b450ad632fd3e9f034b19..2fb83eeb108c76c3219795396cfdd93a2af3e78a 100644 (file)
@@ -203,32 +203,39 @@ CURLcode Curl_async_get_impl(struct Curl_easy *data, void **impl)
   return result;
 }
 
-static void async_ares_destroy(struct Curl_easy *data);
+static void async_ares_cleanup(struct Curl_easy *data);
 
-/*
- * For asyn-ares, this is the same as abort.
- */
-void Curl_async_shutdown(struct Curl_easy *data)
+void Curl_async_ares_shutdown(struct Curl_easy *data)
 {
   struct async_ares_ctx *ares = &data->state.async.ares;
-  if(ares->channel) {
+  if(ares->channel)
     ares_cancel(ares->channel);
+  async_ares_cleanup(data);
+}
+
+void Curl_async_ares_destroy(struct Curl_easy *data)
+{
+  struct async_ares_ctx *ares = &data->state.async.ares;
+  Curl_async_ares_shutdown(data);
+  if(ares->channel) {
+    ares_destroy(ares->channel);
     ares->channel = NULL;
   }
-  async_ares_destroy(data);
 }
 
 /*
- * async_ares_destroy() cleans up async resolver data.
+ * async_ares_cleanup() cleans up async resolver data.
  */
-static void async_ares_destroy(struct Curl_easy *data)
+static void async_ares_cleanup(struct Curl_easy *data)
 {
   struct async_ares_ctx *ares = &data->state.async.ares;
   if(ares->temp_ai) {
     Curl_freeaddrinfo(ares->temp_ai);
     ares->temp_ai = NULL;
   }
-  Curl_safefree(data->state.async.hostname);
+#ifdef USE_HTTPSRR
+  Curl_httpsrr_cleanup(&ares->hinfo);
+#endif
 }
 
 /*
@@ -323,7 +330,7 @@ CURLcode Curl_async_is_resolved(struct Curl_easy *data,
     *dns = data->state.async.dns;
     CURL_TRC_DNS(data, "is_resolved() result=%d, dns=%sfound",
                  result, *dns ? "" : "not ");
-    async_ares_destroy(data);
+    async_ares_cleanup(data);
   }
   return result;
 }
@@ -780,7 +787,7 @@ CURLcode Curl_set_dns_servers(struct Curl_easy *data,
    * default.
    */
   if(!servers) {
-    Curl_async_shutdown(data);
+    Curl_async_destroy(data);
     result = async_ares_init_lazy(data);
     if(!result) {
       /* this now needs to restore the other options set to c-ares */
index 4b1063d45a1baabdcfc63704a762b6ff36b3b89c..1c07436a2f9ffbdd025374b7aeaa894546a9b359 100644 (file)
@@ -169,3 +169,37 @@ int Curl_ares_perform(ares_channel channel,
 #endif
 
 #endif /* CURLRES_ASYNCH */
+
+#ifdef USE_CURL_ASYNC
+
+#include "doh.h"
+
+void Curl_async_shutdown(struct Curl_easy *data)
+{
+#ifdef CURLRES_ARES
+  Curl_async_ares_shutdown(data);
+#endif
+#ifdef CURLRES_THREADED
+  Curl_async_thrdd_shutdown(data);
+#endif
+#ifndef CURL_DISABLE_DOH
+  Curl_doh_cleanup(data);
+#endif
+  Curl_safefree(data->state.async.hostname);
+}
+
+void Curl_async_destroy(struct Curl_easy *data)
+{
+#ifdef CURLRES_ARES
+  Curl_async_ares_destroy(data);
+#endif
+#ifdef CURLRES_THREADED
+  Curl_async_thrdd_destroy(data);
+#endif
+#ifndef CURL_DISABLE_DOH
+  Curl_doh_cleanup(data);
+#endif
+  Curl_safefree(data->state.async.hostname);
+}
+
+#endif /* USE_CURL_ASYNC */
index a6c4df2b820d95400f2197ee008f5c5a64d4c224..b11cf066d40933e5bd70b444c4f2d0c5017710fc 100644 (file)
@@ -346,8 +346,6 @@ static void async_thrdd_destroy(struct Curl_easy *data)
     wakeup_close(sock_rd);
 #endif
   }
-
-  Curl_safefree(data->state.async.hostname);
 }
 
 #ifdef USE_HTTPSRR_ARES
@@ -496,7 +494,7 @@ static CURLcode asyn_thrdd_await(struct Curl_easy *data,
  * Until we gain a way to signal the resolver threads to stop early, we must
  * simply wait for them and ignore their results.
  */
-void Curl_async_shutdown(struct Curl_easy *data)
+void Curl_async_thrdd_shutdown(struct Curl_easy *data)
 {
   struct async_thrdd_ctx *thrdd = &data->state.async.thrdd;
 
@@ -510,6 +508,11 @@ void Curl_async_shutdown(struct Curl_easy *data)
     async_thrdd_destroy(data);
 }
 
+void Curl_async_thrdd_destroy(struct Curl_easy *data)
+{
+  Curl_async_thrdd_shutdown(data);
+}
+
 /*
  * Curl_async_await()
  *
index 7bc791f3c4fc4af9aa3657c7f205a2cdf9d30d24..39de04320d0c6b0222d447dbb1560ef8a4fcdd60 100644 (file)
@@ -69,13 +69,6 @@ void Curl_async_global_cleanup(void);
  */
 CURLcode Curl_async_get_impl(struct Curl_easy *easy, void **impl);
 
-/*
- * Curl_async_shutdown().
- *
- * This frees the resources of any async resolve operation.
- */
-void Curl_async_shutdown(struct Curl_easy *data);
-
 /* Curl_async_getsock()
  *
  * This function is called from the Curl_multi_getsock() function.  'sock' is a
@@ -157,6 +150,9 @@ struct async_ares_ctx {
 #endif
 };
 
+void Curl_async_ares_shutdown(struct Curl_easy *data);
+void Curl_async_ares_destroy(struct Curl_easy *data);
+
 /*
  * Function provided by the resolver backend to set DNS servers to use.
  */
@@ -227,6 +223,9 @@ struct async_thrdd_ctx {
 #endif
 };
 
+void Curl_async_thrdd_shutdown(struct Curl_easy *data);
+void Curl_async_thrdd_destroy(struct Curl_easy *data);
+
 #endif /* CURLRES_THREADED */
 
 #ifndef CURL_DISABLE_DOH
@@ -237,7 +236,6 @@ struct doh_probes;
 
 /* convert these functions if an asynch resolver is not used */
 #define Curl_async_get_impl(x,y)    (*(y) = NULL, CURLE_OK)
-#define Curl_async_shutdown(x) Curl_nop_stmt
 #define Curl_async_is_resolved(x,y) CURLE_COULDNT_RESOLVE_HOST
 #define Curl_async_await(x,y) CURLE_COULDNT_RESOLVE_HOST
 #define Curl_async_global_init() CURLE_OK
@@ -247,7 +245,9 @@ struct doh_probes;
 
 #if defined(CURLRES_ASYNCH) || !defined(CURL_DISABLE_DOH)
 #define USE_CURL_ASYNC
+#endif
 
+#ifdef USE_CURL_ASYNC
 struct Curl_async {
 #ifdef CURLRES_ARES /*  */
   struct async_ares_ctx ares;
@@ -264,7 +264,23 @@ struct Curl_async {
   BIT(done);
 };
 
-#endif
+/*
+ * Curl_async_shutdown().
+ *
+ * This shuts down all ongoing operations.
+ */
+void Curl_async_shutdown(struct Curl_easy *data);
+
+/*
+ * Curl_async_destroy().
+ *
+ * This frees the resources of any async resolve.
+ */
+void Curl_async_destroy(struct Curl_easy *data);
+#else /* !USE_CURL_ASYNC */
+#define Curl_async_shutdown(x) Curl_nop_stmt
+#endif /* USE_CURL_ASYNC */
+
 
 /********** end of generic resolver interface functions *****************/
 #endif /* HEADER_CURL_ASYN_H */
index 5b70fffc24c1c392c9853d12165fdbb4c9f822b3..65506ea323982435167f583fdabbcbae5ffd361e 100644 (file)
--- a/lib/doh.c
+++ b/lib/doh.c
@@ -175,12 +175,15 @@ UNITTEST DOHcode doh_req_encode(const char *host,
 }
 
 static size_t
-doh_write_cb(char *contents, size_t size, size_t nmemb, void *userp)
+doh_probe_write_cb(char *contents, size_t size, size_t nmemb, void *userp)
 {
   size_t realsize = size * nmemb;
-  struct dynbuf *mem = (struct dynbuf *)userp;
+  struct Curl_easy *data = userp;
+  struct doh_request *doh_req = Curl_meta_get(data, CURL_EZM_DOH_PROBE);
+  if(!doh_req)
+    return CURL_WRITEFUNC_ERROR;
 
-  if(Curl_dyn_addn(mem, contents, realsize))
+  if(Curl_dyn_addn(&doh_req->resp_body, contents, realsize))
     return 0;
 
   return realsize;
@@ -210,22 +213,45 @@ static void doh_print_buf(struct Curl_easy *data,
 }
 #endif
 
-/* called from multi.c when this DoH transfer is complete */
-static int doh_done(struct Curl_easy *doh, CURLcode result)
+/* called from multi when a sub transfer, e.g. doh probe, is done.
+ * This looks up the the probe response at its meta CURL_EZM_DOH_PROBE
+ * and copies the response body over to the struct at the master's
+ * meta at CURL_EZM_DOH_MASTER. */
+static void doh_probe_done(struct Curl_easy *data,
+                           struct Curl_easy *doh, CURLcode result)
 {
-  struct Curl_easy *data; /* the transfer that asked for the DoH probe */
+  struct doh_probes *dohp = data->state.async.doh;
+  DEBUGASSERT(dohp);
+  if(dohp) {
+    struct doh_request *doh_req = Curl_meta_get(doh, CURL_EZM_DOH_PROBE);
+    int i;
+
+    for(i = 0; i < DOH_SLOT_COUNT; ++i) {
+      if(dohp->probe_resp[i].probe_mid == doh->mid)
+        break;
+    }
+    if(i >= DOH_SLOT_COUNT) {
+      failf(data, "unknown sub request done");
+      return;
+    }
 
-  data = Curl_multi_get_handle(doh->multi, doh->set.dohfor_mid);
-  if(!data) {
-    DEBUGF(infof(doh, "doh_done: xfer for mid=%" FMT_OFF_T
-                 " not found", doh->set.dohfor_mid));
-    DEBUGASSERT(0);
-  }
-  else {
-    struct doh_probes *dohp = data->state.async.doh;
-    /* one of the DoH request done for the 'data' transfer is now complete! */
     dohp->pending--;
     infof(doh, "a DoH request is completed, %u to go", dohp->pending);
+    dohp->probe_resp[i].result = result;
+    /* We expect either the meta data still to exist or the sub request
+     * to have already failed. */
+    DEBUGASSERT(doh_req || result);
+    if(doh_req) {
+      if(!result) {
+        dohp->probe_resp[i].dnstype = doh_req->dnstype;
+        result = Curl_dyn_addn(&dohp->probe_resp[i].body,
+                               Curl_dyn_ptr(&doh_req->resp_body),
+                               Curl_dyn_len(&doh_req->resp_body));
+        Curl_dyn_free(&doh_req->resp_body);
+      }
+      Curl_meta_clear(doh, CURL_EZM_DOH_PROBE);
+    }
+
     if(result)
       infof(doh, "DoH request %s", curl_easy_strerror(result));
 
@@ -234,7 +260,18 @@ static int doh_done(struct Curl_easy *doh, CURLcode result)
       Curl_expire(data, 0, EXPIRE_RUN_NOW);
     }
   }
-  return 0;
+}
+
+static void doh_probe_dtor(void *key, size_t klen, void *e)
+{
+  (void)key;
+  (void)klen;
+  if(e) {
+    struct doh_request *doh_req = e;
+    curl_slist_free_all(doh_req->req_hds);
+    Curl_dyn_free(&doh_req->resp_body);
+    free(e);
+  }
 }
 
 #define ERROR_CHECK_SETOPT(x,y)                         \
@@ -246,30 +283,48 @@ static int doh_done(struct Curl_easy *doh, CURLcode result)
       goto error;                                       \
   } while(0)
 
-static CURLcode doh_run_probe(struct Curl_easy *data,
-                              struct doh_probe *p, DNStype dnstype,
+static CURLcode doh_probe_run(struct Curl_easy *data,
+                              DNStype dnstype,
                               const char *host,
                               const char *url, CURLM *multi,
-                              struct curl_slist *headers)
+                              curl_off_t *pmid)
 {
   struct Curl_easy *doh = NULL;
   CURLcode result = CURLE_OK;
   timediff_t timeout_ms;
-  DOHcode d = doh_req_encode(host, dnstype, p->req_body, sizeof(p->req_body),
-                             &p->req_body_len);
+  struct doh_request *doh_req;
+  DOHcode d;
+
+  *pmid = -1;
+
+  doh_req = calloc(1, sizeof(*doh_req));
+  if(!doh_req)
+    return CURLE_OUT_OF_MEMORY;
+  doh_req->dnstype = dnstype;
+  Curl_dyn_init(&doh_req->resp_body, DYN_DOH_RESPONSE);
+
+  d = doh_req_encode(host, dnstype, doh_req->req_body,
+                     sizeof(doh_req->req_body),
+                     &doh_req->req_body_len);
   if(d) {
     failf(data, "Failed to encode DoH packet [%d]", d);
-    return CURLE_OUT_OF_MEMORY;
+    result = CURLE_OUT_OF_MEMORY;
+    goto error;
   }
 
-  p->dnstype = dnstype;
-  Curl_dyn_init(&p->resp_body, DYN_DOH_RESPONSE);
-
   timeout_ms = Curl_timeleft(data, NULL, TRUE);
   if(timeout_ms <= 0) {
     result = CURLE_OPERATION_TIMEDOUT;
     goto error;
   }
+
+  doh_req->req_hds =
+    curl_slist_append(NULL, "Content-Type: application/dns-message");
+  if(!doh_req->req_hds) {
+    result = CURLE_OUT_OF_MEMORY;
+    goto error;
+  }
+
   /* Curl_open() is the internal version of curl_easy_init() */
   result = Curl_open(&doh);
   if(result)
@@ -277,17 +332,16 @@ static CURLcode doh_run_probe(struct Curl_easy *data,
 
   /* pass in the struct pointer via a local variable to please coverity and
      the gcc typecheck helpers */
-  doh->state.internal = TRUE;
 #ifndef CURL_DISABLE_VERBOSE_STRINGS
   doh->state.feat = &Curl_trc_feat_dns;
 #endif
   ERROR_CHECK_SETOPT(CURLOPT_URL, url);
   ERROR_CHECK_SETOPT(CURLOPT_DEFAULT_PROTOCOL, "https");
-  ERROR_CHECK_SETOPT(CURLOPT_WRITEFUNCTION, doh_write_cb);
-  ERROR_CHECK_SETOPT(CURLOPT_WRITEDATA, &p->resp_body);
-  ERROR_CHECK_SETOPT(CURLOPT_POSTFIELDS, p->req_body);
-  ERROR_CHECK_SETOPT(CURLOPT_POSTFIELDSIZE, (long)p->req_body_len);
-  ERROR_CHECK_SETOPT(CURLOPT_HTTPHEADER, headers);
+  ERROR_CHECK_SETOPT(CURLOPT_WRITEFUNCTION, doh_probe_write_cb);
+  ERROR_CHECK_SETOPT(CURLOPT_WRITEDATA, doh);
+  ERROR_CHECK_SETOPT(CURLOPT_POSTFIELDS, doh_req->req_body);
+  ERROR_CHECK_SETOPT(CURLOPT_POSTFIELDSIZE, (long)doh_req->req_body_len);
+  ERROR_CHECK_SETOPT(CURLOPT_HTTPHEADER, doh_req->req_hds);
 #ifdef USE_HTTP2
   ERROR_CHECK_SETOPT(CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_2TLS);
   ERROR_CHECK_SETOPT(CURLOPT_PIPEWAIT, 1L);
@@ -374,8 +428,14 @@ static CURLcode doh_run_probe(struct Curl_easy *data,
     (void)curl_easy_setopt(doh, CURLOPT_SSL_OPTIONS, mask);
   }
 
-  doh->set.fmultidone = doh_done;
-  doh->set.dohfor_mid = data->mid; /* for which transfer this is done */
+  doh->state.internal = TRUE;
+  doh->master_mid = data->mid; /* master transfer of this one */
+
+  if(Curl_meta_set(doh, CURL_EZM_DOH_PROBE, doh_req, doh_probe_dtor)) {
+    result = CURLE_OUT_OF_MEMORY;
+    goto error;
+  }
+  doh_req = NULL;
 
   /* DoH handles must not inherit private_data. The handles may be passed to
      the user via callbacks and the user will be able to identify them as
@@ -386,12 +446,13 @@ static CURLcode doh_run_probe(struct Curl_easy *data,
   if(curl_multi_add_handle(multi, doh))
     goto error;
 
-  p->easy_mid = doh->mid;
+  *pmid = doh->mid;
   return CURLE_OK;
 
 error:
   Curl_close(&doh);
-  p->easy_mid = -1;
+  if(doh_req)
+    doh_probe_dtor(NULL, 0, doh_req);
   return result;
 }
 
@@ -407,14 +468,14 @@ struct Curl_addrinfo *Curl_doh(struct Curl_easy *data,
                                int *waitp)
 {
   CURLcode result = CURLE_OK;
-  struct doh_probes *dohp;
+  struct doh_probes *dohp = NULL;
   struct connectdata *conn = data->conn;
   size_t i;
 
-  *waitp = FALSE;
-
-  DEBUGASSERT(!data->state.async.doh);
   DEBUGASSERT(conn);
+  DEBUGASSERT(!data->state.async.doh);
+  if(data->state.async.doh)
+    Curl_doh_cleanup(data);
 
   data->state.async.done = FALSE;
   data->state.async.port = port;
@@ -424,28 +485,27 @@ struct Curl_addrinfo *Curl_doh(struct Curl_easy *data,
     return NULL;
 
   /* start clean, consider allocating this struct on demand */
-  dohp = data->state.async.doh = calloc(1, sizeof(struct doh_probes));
+  data->state.async.doh = dohp = calloc(1, sizeof(struct doh_probes));
   if(!dohp)
     return NULL;
 
   for(i = 0; i < DOH_SLOT_COUNT; ++i) {
-    dohp->probe[i].easy_mid = -1;
+    dohp->probe_resp[i].probe_mid = -1;
+    Curl_dyn_init(&dohp->probe_resp[i].body, DYN_DOH_RESPONSE);
   }
 
   conn->bits.doh = TRUE;
   dohp->host = data->state.async.hostname;
   dohp->port = data->state.async.port;
-  dohp->req_hds =
-    curl_slist_append(NULL,
-                      "Content-Type: application/dns-message");
-  if(!dohp->req_hds)
-    goto error;
+  /* We are making sub easy handles and want to be called back when
+   * one is done. */
+  data->sub_xfer_done = doh_probe_done;
 
   /* create IPv4 DoH request */
-  (void)ip_version; /* WHY not select on this for ipv4? */
-  result = doh_run_probe(data, &dohp->probe[DOH_SLOT_IPV4],
-                         DNS_TYPE_A, hostname, data->set.str[STRING_DOH],
-                         data->multi, dohp->req_hds);
+  result = doh_probe_run(data, DNS_TYPE_A,
+                         hostname, data->set.str[STRING_DOH],
+                         data->multi,
+                         &dohp->probe_resp[DOH_SLOT_IPV4].probe_mid);
   if(result)
     goto error;
   dohp->pending++;
@@ -453,9 +513,10 @@ struct Curl_addrinfo *Curl_doh(struct Curl_easy *data,
 #ifdef USE_IPV6
   if((ip_version != CURL_IPRESOLVE_V4) && Curl_ipv6works(data)) {
     /* create IPv6 DoH request */
-    result = doh_run_probe(data, &dohp->probe[DOH_SLOT_IPV6],
-                           DNS_TYPE_AAAA, hostname, data->set.str[STRING_DOH],
-                           data->multi, dohp->req_hds);
+    result = doh_probe_run(data, DNS_TYPE_AAAA,
+                           hostname, data->set.str[STRING_DOH],
+                           data->multi,
+                           &dohp->probe_resp[DOH_SLOT_IPV6].probe_mid);
     if(result)
       goto error;
     dohp->pending++;
@@ -471,10 +532,10 @@ struct Curl_addrinfo *Curl_doh(struct Curl_easy *data,
       if(!qname)
         goto error;
     }
-    result = doh_run_probe(data, &dohp->probe[DOH_SLOT_HTTPS_RR],
-                           DNS_TYPE_HTTPS,
+    result = doh_probe_run(data, DNS_TYPE_HTTPS,
                            qname ? qname : hostname, data->set.str[STRING_DOH],
-                           data->multi, dohp->req_hds);
+                           data->multi,
+                           &dohp->probe_resp[DOH_SLOT_HTTPS_RR].probe_mid);
     free(qname);
     if(result)
       goto error;
@@ -1177,8 +1238,8 @@ CURLcode Curl_doh_is_resolved(struct Curl_easy *data,
   if(!dohp)
     return CURLE_OUT_OF_MEMORY;
 
-  if(dohp->probe[DOH_SLOT_IPV4].easy_mid < 0 &&
-     dohp->probe[DOH_SLOT_IPV6].easy_mid < 0) {
+  if(dohp->probe_resp[DOH_SLOT_IPV4].probe_mid < 0 &&
+     dohp->probe_resp[DOH_SLOT_IPV6].probe_mid < 0) {
     failf(data, "Could not DoH-resolve: %s", dohp->host);
     return CONN_IS_PROXIED(data->conn) ? CURLE_COULDNT_RESOLVE_PROXY :
       CURLE_COULDNT_RESOLVE_HOST;
@@ -1197,13 +1258,12 @@ CURLcode Curl_doh_is_resolved(struct Curl_easy *data,
     /* parse the responses, create the struct and return it! */
     de_init(&de);
     for(slot = 0; slot < DOH_SLOT_COUNT; slot++) {
-      struct doh_probe *p = &dohp->probe[slot];
+      struct doh_response *p = &dohp->probe_resp[slot];
       if(!p->dnstype)
         continue;
-      rc[slot] = doh_resp_decode(Curl_dyn_uptr(&p->resp_body),
-                                 Curl_dyn_len(&p->resp_body),
+      rc[slot] = doh_resp_decode(Curl_dyn_uptr(&p->body),
+                                 Curl_dyn_len(&p->body),
                                  p->dnstype, &de);
-      Curl_dyn_free(&p->resp_body);
 #ifndef CURL_DISABLE_VERBOSE_STRINGS
       if(rc[slot]) {
         infof(data, "DoH: %s type %s for %s", doh_strerror(rc[slot]),
@@ -1277,37 +1337,38 @@ void Curl_doh_close(struct Curl_easy *data)
     curl_off_t mid;
     size_t slot;
     for(slot = 0; slot < DOH_SLOT_COUNT; slot++) {
-      mid = doh->probe[slot].easy_mid;
+      mid = doh->probe_resp[slot].probe_mid;
       if(mid < 0)
         continue;
-      doh->probe[slot].easy_mid = -1;
-      /* should have been called before data is removed from multi handle */
+      doh->probe_resp[slot].probe_mid = -1;
       DEBUGASSERT(data->multi);
       probe_data = data->multi ? Curl_multi_get_handle(data->multi, mid) :
         NULL;
       if(!probe_data) {
         DEBUGF(infof(data, "Curl_doh_close: xfer for mid=%"
                      FMT_OFF_T " not found!",
-                     doh->probe[slot].easy_mid));
+                     doh->probe_resp[slot].probe_mid));
         continue;
       }
       /* data->multi might already be reset at this time */
       curl_multi_remove_handle(data->multi, probe_data);
       Curl_close(&probe_data);
     }
+    data->sub_xfer_done = NULL;
   }
 }
 
 void Curl_doh_cleanup(struct Curl_easy *data)
 {
-  struct doh_probes *doh = data->state.async.doh;
-  if(doh) {
+  struct doh_probes *dohp = data->state.async.doh;
+  if(dohp) {
+    int i;
     Curl_doh_close(data);
-    curl_slist_free_all(doh->req_hds);
-    data->state.async.doh->req_hds = NULL;
+    for(i = 0; i < DOH_SLOT_COUNT; ++i) {
+      Curl_dyn_free(&dohp->probe_resp[i].body);
+    }
     Curl_safefree(data->state.async.doh);
   }
-  Curl_safefree(data->state.async.hostname);
 }
 
 #endif /* CURL_DISABLE_DOH */
index de709cf06d97ef79ab827561ce2eb0330531d071..ddabac8863ba230e3ba5a09d3b809a5e9edaa4a4 100644 (file)
--- a/lib/doh.h
+++ b/lib/doh.h
@@ -59,15 +59,6 @@ typedef enum {
   DNS_TYPE_HTTPS = 65
 } DNStype;
 
-/* one of these for each DoH request */
-struct doh_probe {
-  curl_off_t easy_mid; /* multi id of easy handle doing the lookup */
-  DNStype dnstype;
-  unsigned char req_body[512];
-  size_t req_body_len;
-  struct dynbuf resp_body;
-};
-
 enum doh_slot_num {
   /* Explicit values for first two symbols so as to match hard-coded
    * constants in existing code
@@ -89,9 +80,29 @@ enum doh_slot_num {
   DOH_SLOT_COUNT
 };
 
-struct doh_probes {
+#define CURL_EZM_DOH_PROBE   "ezm:doh-p"
+
+/* each DoH probe request has this
+ * as easy meta for CURL_EZM_DOH_PROBE */
+struct doh_request {
+  DNStype dnstype;
+  unsigned char req_body[512];
+  size_t req_body_len;
   struct curl_slist *req_hds;
-  struct doh_probe probe[DOH_SLOT_COUNT];
+  struct dynbuf resp_body;
+};
+
+struct doh_response {
+  curl_off_t probe_mid;
+  struct dynbuf body;
+  DNStype dnstype;
+  CURLcode result;
+};
+
+/* each transfer firing off DoH requests has this
+ * as easy meta for CURL_EZM_DOH_MASTER */
+struct doh_probes {
+  struct doh_response probe_resp[DOH_SLOT_COUNT];
   unsigned int pending; /* still outstanding probes */
   int port;
   const char *host;
index 53764925787032b885bb989f9a9b8c09b9a23496..04814544654356652a0edeeb56e7cc78dd988401 100644 (file)
@@ -938,6 +938,15 @@ static CURLcode dupset(struct Curl_easy *dst, struct Curl_easy *src)
   return result;
 }
 
+static void dupeasy_meta_freeentry(void *p)
+{
+  (void)p;
+  /* Will always be FALSE. Cannot use a 0 assert here since compilers
+   * are not in agreement if they then want a NORETURN attribute or
+   * not. *sigh* */
+  DEBUGASSERT(p == NULL);
+}
+
 /*
  * curl_easy_duphandle() is an external interface to allow duplication of a
  * given input easy handle. The returned handle will be a new working handle
@@ -957,6 +966,8 @@ CURL *curl_easy_duphandle(CURL *d)
    */
   outcurl->set.buffer_size = data->set.buffer_size;
 
+  Curl_hash_init(&outcurl->meta_hash, 23,
+                 Curl_hash_str, Curl_str_key_compare, dupeasy_meta_freeentry);
   Curl_dyn_init(&outcurl->state.headerb, CURL_MAX_HTTP_HEADER);
   Curl_netrc_init(&outcurl->state.netrc);
 
@@ -965,6 +976,7 @@ CURL *curl_easy_duphandle(CURL *d)
   outcurl->state.recent_conn_id = -1;
   outcurl->id = -1;
   outcurl->mid = -1;
+  outcurl->master_mid = -1;
 
 #ifndef CURL_DISABLE_HTTP
   Curl_llist_init(&outcurl->state.httphdrs, NULL);
@@ -1090,7 +1102,12 @@ void curl_easy_reset(CURL *d)
 {
   struct Curl_easy *data = d;
   Curl_req_hard_reset(&data->req, data);
+  Curl_hash_clean(&data->meta_hash);
 
+  /* clear all meta data */
+  Curl_meta_reset(data);
+  /* clear any async resolve data */
+  Curl_async_shutdown(data);
   /* zero out UserDefined data: */
   Curl_freeset(data);
   memset(&data->set, 0, sizeof(struct UserDefined));
@@ -1113,6 +1130,7 @@ void curl_easy_reset(CURL *d)
 #if !defined(CURL_DISABLE_HTTP) && !defined(CURL_DISABLE_DIGEST_AUTH)
   Curl_http_auth_cleanup_digest(data);
 #endif
+  data->master_mid = -1;
 }
 
 /*
@@ -1390,3 +1408,28 @@ CURLcode curl_easy_ssls_export(CURL *d,
   return CURLE_NOT_BUILT_IN;
 #endif
 }
+
+CURLcode Curl_meta_set(struct Curl_easy *data, const char *key,
+                       void *meta_data, Curl_meta_dtor *meta_dtor)
+{
+  if(!Curl_hash_add2(&data->meta_hash, CURL_UNCONST(key), strlen(key) + 1,
+                     meta_data, meta_dtor)) {
+    meta_dtor(CURL_UNCONST(key), strlen(key) + 1, meta_data);
+  }
+  return CURLE_OK;
+}
+
+void Curl_meta_clear(struct Curl_easy *data, const char *key)
+{
+  Curl_hash_delete(&data->meta_hash, CURL_UNCONST(key), strlen(key) + 1);
+}
+
+void *Curl_meta_get(struct Curl_easy *data, const char *key)
+{
+  return Curl_hash_pick(&data->meta_hash, CURL_UNCONST(key), strlen(key) + 1);
+}
+
+void Curl_meta_reset(struct Curl_easy *data)
+{
+  Curl_hash_clean(&data->meta_hash);
+}
index eb552af875ffac43f043aaa584e1c2a946098d0f..43412abb509079ab0739731b887461a3536876a6 100644 (file)
@@ -843,10 +843,12 @@ CURLcode Curl_resolv(struct Curl_easy *data,
 
   /* No luck, we need to resolve hostname. Notify user callback. */
   if(data->set.resolver_start) {
-    void *resolver;
+    void *resolver = NULL;
     int st;
+#ifdef CURLRES_ASYNCH
     if(Curl_async_get_impl(data, &resolver))
       goto error;
+#endif
     Curl_set_in_callback(data, TRUE);
     st = data->set.resolver_start(resolver, NULL,
                                   data->set.resolver_start_client);
@@ -924,6 +926,7 @@ error:
   if(dns)
     Curl_resolv_unlink(data, &dns);
   *entry = NULL;
+  Curl_async_shutdown(data);
   return CURLE_COULDNT_RESOLVE_HOST;
 }
 
@@ -1487,11 +1490,11 @@ CURLcode Curl_resolv_check(struct Curl_easy *data,
                            data->state.async.ip_version);
   if(*dns) {
     /* Tell a possibly async resolver we no longer need the results. */
+    infof(data, "Hostname '%s' was found in DNS cache",
+          data->state.async.hostname);
     Curl_async_shutdown(data);
     data->state.async.dns = *dns;
     data->state.async.done = TRUE;
-    infof(data, "Hostname '%s' was found in DNS cache",
-          data->state.async.hostname);
     return CURLE_OK;
   }
 
index c3dc3fb5e26f5f38d47583f710a2bb40f7bd93fb..6afcf7a895d360e4188575c5605d9de55200f7c1 100644 (file)
@@ -727,6 +727,7 @@ CURLMcode curl_multi_remove_handle(CURLM *m, CURL *d)
 
   data->multi = NULL; /* clear the association to this multi handle */
   data->mid = -1;
+  data->master_mid = -1;
 
   /* NOTE NOTE NOTE
      We do not touch the easy handle here! */
@@ -2507,9 +2508,24 @@ statemachine_end:
     }
 
     if(MSTATE_COMPLETED == data->mstate) {
-      if(data->set.fmultidone) {
-        /* signal via callback instead */
-        data->set.fmultidone(data, result);
+      if(data->master_mid >= 0) {
+        /* A sub transfer, not for msgsent to application */
+        struct Curl_easy *mdata;
+
+        CURL_TRC_M(data, "sub xfer done for master %" FMT_OFF_T,
+                   data->master_mid);
+        mdata = Curl_multi_get_handle(multi, data->master_mid);
+        if(mdata) {
+          if(mdata->sub_xfer_done)
+            mdata->sub_xfer_done(mdata, data, result);
+          else
+            CURL_TRC_M(data, "master easy %" FMT_OFF_T
+                       " without sub_xfer_done.", data->master_mid);
+        }
+        else {
+          CURL_TRC_M(data, "master easy %" FMT_OFF_T " already gone.",
+                     data->master_mid);
+        }
       }
       else {
         /* now fill in the Curl_message with this info */
index 513d4f2c4b13851aaef3f13666e6703a86695946..3b8f212123cab251e51bf7a3485e44cd1b3dbef6 100644 (file)
@@ -178,10 +178,6 @@ void Curl_req_free(struct SingleRequest *req, struct Curl_easy *data)
   if(req->sendbuf_init)
     Curl_bufq_free(&req->sendbuf);
   Curl_client_cleanup(data);
-
-#ifndef CURL_DISABLE_DOH
-  Curl_doh_cleanup(data);
-#endif
 }
 
 static CURLcode xfer_send(struct Curl_easy *data,
index 856d71366ecc46805c2915c58e8833db15cefbf5..efc79ccd11937b2297a162478e5f2d474c639971 100644 (file)
--- a/lib/url.c
+++ b/lib/url.c
@@ -290,7 +290,7 @@ CURLcode Curl_close(struct Curl_easy **datap)
   Curl_safefree(data->info.contenttype);
   Curl_safefree(data->info.wouldredirect);
 
-  Curl_async_shutdown(data);
+  Curl_async_destroy(data);
 
   data_priority_cleanup(data);
 
@@ -301,6 +301,7 @@ CURLcode Curl_close(struct Curl_easy **datap)
     Curl_share_unlock(data, CURL_LOCK_DATA_SHARE);
   }
 
+  Curl_hash_destroy(&data->meta_hash);
 #ifndef CURL_DISABLE_PROXY
   Curl_safefree(data->state.aptr.proxyuserpwd);
 #endif
@@ -364,10 +365,6 @@ CURLcode Curl_init_userdefined(struct Curl_easy *data)
   set->postfieldsize = -1;   /* unknown size */
   set->maxredirs = 30;       /* sensible default */
 
-#ifndef CURL_DISABLE_DOH
-  set->dohfor_mid  = -1;
-#endif
-
   set->method = HTTPREQ_GET; /* Default HTTP request */
 #ifndef CURL_DISABLE_RTSP
   set->rtspreq = RTSPREQ_OPTIONS; /* Default RTSP request */
@@ -486,6 +483,17 @@ CURLcode Curl_init_userdefined(struct Curl_easy *data)
   return result;
 }
 
+/* easy->meta_hash destructor. Should never be called as elements
+ * MUST be added with their own destructor */
+static void easy_meta_freeentry(void *p)
+{
+  (void)p;
+  /* Will always be FALSE. Cannot use a 0 assert here since compilers
+   * are not in agreement if they then want a NORETURN attribute or
+   * not. *sigh* */
+  DEBUGASSERT(p == NULL);
+}
+
 /**
  * Curl_open()
  *
@@ -509,6 +517,8 @@ CURLcode Curl_open(struct Curl_easy **curl)
 
   data->magic = CURLEASY_MAGIC_NUMBER;
 
+  Curl_hash_init(&data->meta_hash, 23,
+                 Curl_hash_str, Curl_str_key_compare, easy_meta_freeentry);
   Curl_dyn_init(&data->state.headerb, CURL_MAX_HTTP_HEADER);
   Curl_req_init(&data->req);
   Curl_initinfo(data);
@@ -527,9 +537,7 @@ CURLcode Curl_open(struct Curl_easy **curl)
   /* and not assigned an id yet */
   data->id = -1;
   data->mid = -1;
-#ifndef CURL_DISABLE_DOH
-  data->set.dohfor_mid = -1;
-#endif
+  data->master_mid = -1;
 
   data->progress.flags |= PGRS_HIDE;
   data->state.current_speed = -1; /* init to negative == impossible */
@@ -539,6 +547,7 @@ out:
     Curl_dyn_free(&data->state.headerb);
     Curl_freeset(data);
     Curl_req_free(&data->req, data);
+    Curl_hash_destroy(&data->meta_hash);
     free(data);
     data = NULL;
   }
@@ -3612,12 +3621,10 @@ static CURLcode create_conn(struct Curl_easy *data,
         connections_available = FALSE;
         break;
       case CPOOL_LIMIT_TOTAL:
-#ifndef CURL_DISABLE_DOH
-        if(data->set.dohfor_mid >= 0)
-          infof(data, "Allowing DoH to override max connection limit");
-        else
-#endif
-        {
+        if(data->master_mid >= 0)
+          CURL_TRC_M(data, "Allowing sub-requests (like DoH) to override "
+                     "max connection limit");
+        else {
           infof(data, "No connections available, total of %ld reached.",
                 data->multi->max_total_connections);
           connections_available = FALSE;
index f60460671ef2f2dc1621c7beb2fc7e43b6c107eb..aa687228abc46fe54cb66ff43ce276ab890f8ece 100644 (file)
--- a/lib/url.h
+++ b/lib/url.h
@@ -44,6 +44,20 @@ CURLcode Curl_parse_login_details(const char *login, const size_t len,
                                   char **userptr, char **passwdptr,
                                   char **optionsptr);
 
+/* Attach/Clear/Get meta data for an easy handle. Needs to provide
+ * a destructor, will be automatically called when the easy handle
+ * is reset or closed. */
+typedef void Curl_meta_dtor(void *key, size_t key_len, void *meta_data);
+
+/* Set the transfer meta data for the key. Any existing entry for that
+ * key will be destroyed.
+ * Takes ownership of `meta_data` and destroys it when the call fails. */
+CURLcode Curl_meta_set(struct Curl_easy *data, const char *key,
+                       void *meta_data, Curl_meta_dtor *meta_dtor);
+void Curl_meta_clear(struct Curl_easy *data, const char *key);
+void *Curl_meta_get(struct Curl_easy *data, const char *key);
+void Curl_meta_reset(struct Curl_easy *data);
+
 /* Get protocol handler for a URI scheme
  * @param scheme URI scheme, case-insensitive
  * @return NULL of handler not found
index ebcfeb2a7f29b2cb81bf02429f08a430a263f4a3..58cdb6023b83722c3e0421994f77f7e261e1238b 100644 (file)
@@ -1497,10 +1497,6 @@ enum dupblob {
   BLOB_LAST
 };
 
-/* callback that gets called when this easy handle is completed within a multi
-   handle. Only used for internally created transfers, like for example
-   DoH. */
-typedef int (*multidone_func)(struct Curl_easy *easy, CURLcode result);
 
 struct UserDefined {
   FILE *err;         /* the stderr user data goes here */
@@ -1656,10 +1652,6 @@ struct UserDefined {
                                                   before resolver start */
   void *resolver_start_client; /* pointer to pass to resolver start callback */
   long upkeep_interval_ms;      /* Time between calls for connection upkeep. */
-  multidone_func fmultidone;
-#ifndef CURL_DISABLE_DOH
-  curl_off_t dohfor_mid; /* this is a DoH request for that transfer */
-#endif
   CURLU *uh; /* URL handle for the current parsed URL */
 #ifndef CURL_DISABLE_HTTP
   void *trailer_data; /* pointer to pass to trailer data callback */
@@ -1818,6 +1810,12 @@ struct UserDefined {
 #define IS_MIME_POST(a) FALSE
 #endif
 
+/* callback that gets called when a sub easy (data->master_mid set) is
+   DONE. Called on the master easy. */
+typedef void multi_sub_xfer_done_cb(struct Curl_easy *master_easy,
+                                    struct Curl_easy *sub_easy,
+                                    CURLcode result);
+
 /*
  * The 'connectdata' struct MUST have all the connection oriented stuff as we
  * may have several simultaneous connections and connection structs in memory.
@@ -1843,6 +1841,8 @@ struct Curl_easy {
    * libcurl application or implicitly during `curl_easy_perform()`,
    * a unique identifier inside this one multi instance. */
   curl_off_t mid;
+  curl_off_t master_mid; /* if set, this transfer belongs to a master */
+  multi_sub_xfer_done_cb *sub_xfer_done;
 
   struct connectdata *conn;
   struct Curl_llist_node multi_queue; /* for multihandle list management */
@@ -1860,6 +1860,13 @@ struct Curl_easy {
                                     struct to which this "belongs" when used
                                     by the easy interface */
   struct Curl_share *share;    /* Share, handles global variable mutexing */
+
+  /* `meta_hash` is a general key-value store for implementations
+   * with the lifetime of the easy handle.
+   * Elements need to be added with their own destructor to be invoked when
+   * the easy handle is cleaned up (see Curl_hash_add2()).*/
+  struct Curl_hash meta_hash;
+
 #ifdef USE_LIBPSL
   struct PslCache *psl;        /* The associated PSL cache. */
 #endif
index 7732387226cc0fb38f8155e7e64b78f6d6ecbc67..13a9ff54e1ffcc14572374e8d94e799d77123e89 100755 (executable)
@@ -47,6 +47,7 @@ my %wl = (
     'Curl_creader_def_close' => 'internal api',
     'Curl_creader_def_read' => 'internal api',
     'Curl_creader_def_total_length' => 'internal api',
+    'Curl_meta_reset' => 'internal api',
     'Curl_trc_dns' => 'internal api',
 );