async: DoH improvements

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
This commit is contained in:
Stefan Eissing 2025-04-16 13:45:53 +02:00 committed by Daniel Stenberg
parent 8478365e29
commit 1ebd92d0fd
No known key found for this signature in database
GPG Key ID: 5CC908FDB71E12C2
14 changed files with 357 additions and 138 deletions

View 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 */

View 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 */

View 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()
*

View 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 */

203
lib/doh.c
View File

@ -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 */

View File

@ -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;

View 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);
}

View 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;
}

View 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 */

View 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,

View File

@ -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;

View File

@ -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

View 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

View File

@ -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',
);