httpsrr: DoH with HTTPS, fix response handling

Fix handling of DoH response that only asks for HTTPS records.

Add test 2117 for checking that a HTTPS-RR resolve is processed,
even though the actual answer is invalid.

Closes #22372
This commit is contained in:
Stefan Eissing 2026-07-23 11:20:19 +02:00 committed by Daniel Stenberg
parent 474ebb5247
commit a954d87f0b
No known key found for this signature in database
GPG key ID: 5CC908FDB71E12C2
9 changed files with 155 additions and 44 deletions

View file

@ -219,6 +219,12 @@ struct curl_trc_feat Curl_trc_feat_dns = {
"DNS",
CURL_LOG_LVL_NONE,
};
#ifndef CURL_DISABLE_DOH
struct curl_trc_feat Curl_trc_feat_doh = {
"DoH",
CURL_LOG_LVL_NONE,
};
#endif
struct curl_trc_feat Curl_trc_feat_timer = {
"TIMER",
CURL_LOG_LVL_NONE,
@ -530,6 +536,9 @@ static struct trc_feat_def trc_feats[] = {
{ &Curl_trc_feat_read, TRC_CT_NONE },
{ &Curl_trc_feat_write, TRC_CT_NONE },
{ &Curl_trc_feat_dns, TRC_CT_NETWORK },
#ifndef CURL_DISABLE_DOH
{ &Curl_trc_feat_doh, TRC_CT_NETWORK },
#endif
{ &Curl_trc_feat_timer, TRC_CT_NETWORK },
#ifdef USE_THREADS
{ &Curl_trc_feat_threads, TRC_CT_NONE },
@ -652,10 +661,6 @@ static CURLcode trc_opt(const char *config)
trc_apply_level_by_category(TRC_CT_NETWORK, lvl);
else if(curlx_str_casecompare(&out, "proxy"))
trc_apply_level_by_category(TRC_CT_PROXY, lvl);
else if(curlx_str_casecompare(&out, "doh")) {
struct Curl_str dns = { "dns", 3 };
trc_apply_level_by_name(&dns, lvl);
}
else
trc_apply_level_by_name(&out, lvl);

View file

@ -307,6 +307,9 @@ extern struct curl_trc_feat Curl_trc_feat_multi;
extern struct curl_trc_feat Curl_trc_feat_read;
extern struct curl_trc_feat Curl_trc_feat_write;
extern struct curl_trc_feat Curl_trc_feat_dns;
#ifndef CURL_DISABLE_DOH
extern struct curl_trc_feat Curl_trc_feat_doh;
#endif
extern struct curl_trc_feat Curl_trc_feat_timer;
#ifdef USE_THREADS
extern struct curl_trc_feat Curl_trc_feat_threads;

View file

@ -217,8 +217,8 @@ static void doh_print_buf(struct Curl_easy *data,
* This looks up 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)
static void doh_probe_done(struct Curl_easy *doh,
struct Curl_easy *master, CURLcode result)
{
struct Curl_resolv_async *async = NULL;
struct doh_probes *dohp = NULL;
@ -227,13 +227,14 @@ static void doh_probe_done(struct Curl_easy *data,
doh_req = Curl_meta_get(doh, CURL_EZM_DOH_PROBE);
if(!doh_req) {
/* transfer `doh` is not a DoH probe. */
DEBUGASSERT(0);
return;
}
async = Curl_async_get(data, doh_req->resolv_id);
async = Curl_async_get(master, doh_req->resolv_id);
if(!async) {
CURL_TRC_DNS(data, "[%u] ignoring outdated DoH response",
CURL_TRC_DNS(master, "[%u] ignoring outdated DoH response",
doh_req->resolv_id);
return;
}
@ -246,7 +247,7 @@ static void doh_probe_done(struct Curl_easy *data,
/* We really should have found the slot where to store the response */
if(i >= DOH_SLOT_COUNT) {
DEBUGASSERT(0);
failf(data, "DoH: unknown sub request done");
failf(master, "DoH: unknown sub request done");
return;
}
@ -267,8 +268,8 @@ static void doh_probe_done(struct Curl_easy *data,
infof(doh, "DoH request %s", curl_easy_strerror(result));
if(!dohp->pending) {
/* DoH completed, run the transfer picking up the results */
Curl_multi_mark_dirty(data);
/* DoH completed, run master to act on results */
Curl_multi_mark_dirty(master);
}
}
@ -344,7 +345,7 @@ static CURLcode doh_probe_run(struct Curl_easy *data,
/* pass in the struct pointer via a local variable to please coverity and
the gcc typecheck helpers */
VERBOSE(doh->state.feat = &Curl_trc_feat_dns);
VERBOSE(doh->state.feat = &Curl_trc_feat_doh);
ERROR_CHECK_SETOPT(CURLOPT_URL, url);
ERROR_CHECK_SETOPT(CURLOPT_DEFAULT_PROTOCOL, "https");
ERROR_CHECK_SETOPT(CURLOPT_WRITEFUNCTION, doh_probe_write_cb);
@ -367,7 +368,7 @@ static CURLcode doh_probe_run(struct Curl_easy *data,
ERROR_CHECK_SETOPT(CURLOPT_SHARE, (CURLSH *)data->share);
if(data->set.err && data->set.err != stderr)
ERROR_CHECK_SETOPT(CURLOPT_STDERR, data->set.err);
if(Curl_trc_ft_is_verbose(data, &Curl_trc_feat_dns))
if(Curl_trc_ft_is_verbose(data, &Curl_trc_feat_doh))
ERROR_CHECK_SETOPT(CURLOPT_VERBOSE, 1L);
if(data->set.no_signal)
ERROR_CHECK_SETOPT(CURLOPT_NOSIGNAL, 1L);
@ -423,6 +424,7 @@ static CURLcode doh_probe_run(struct Curl_easy *data,
doh->state.internal = TRUE;
doh->master_mid = data->mid; /* master transfer of this one */
doh->sub_xfer_done = doh_probe_done;
result = Curl_meta_set(doh, CURL_EZM_DOH_PROBE, doh_req, doh_probe_dtor);
doh_req = NULL; /* call took ownership */
@ -479,9 +481,6 @@ CURLcode Curl_doh(struct Curl_easy *data,
dohp->host = async->hostname;
dohp->port = async->port;
/* 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 */
if(async->dns_queries & CURL_DNSQ_A) {
@ -1207,7 +1206,8 @@ CURLcode Curl_doh_take_result(struct Curl_easy *data,
if(!dohp)
return CURLE_OUT_OF_MEMORY;
if(dohp->probe_resp[DOH_SLOT_IPV4].probe_mid == UINT32_MAX &&
if(CURL_DNSQ_IS_ADDR(async->dns_queries) &&
dohp->probe_resp[DOH_SLOT_IPV4].probe_mid == UINT32_MAX &&
dohp->probe_resp[DOH_SLOT_IPV6].probe_mid == UINT32_MAX) {
failf(data, "Could not DoH-resolve: %s", dohp->host);
return async->for_proxy ?
@ -1237,7 +1237,9 @@ CURLcode Curl_doh_take_result(struct Curl_easy *data,
if(rc[slot] && (rc[slot] != DOH_DNS_NXDOMAIN))
negative = FALSE;
if(rc[slot]) {
CURL_TRC_DNS(data, "DoH: %s type %s for %s", doh_strerror(rc[slot]),
CURL_TRC_DNS(data, "[%s] [DoH] error: %s type %s for %s",
Curl_resolv_query_str(async->dns_queries),
doh_strerror(rc[slot]),
doh_type2name(p->dnstype), dohp->host);
}
} /* next slot */
@ -1247,7 +1249,7 @@ CURLcode Curl_doh_take_result(struct Curl_easy *data,
/* we have an address, of one kind or other */
struct Curl_addrinfo *ai;
if(Curl_trc_ft_is_verbose(data, &Curl_trc_feat_dns)) {
if(Curl_trc_ft_is_verbose(data, &Curl_trc_feat_doh)) {
CURL_TRC_DNS(data, "hostname: %s", dohp->host);
doh_show(data, &de);
}
@ -1283,6 +1285,7 @@ CURLcode Curl_doh_take_result(struct Curl_easy *data,
/* Now add and HTTPSRR information if we have */
struct Curl_https_rrinfo *hrr = NULL;
CURL_TRC_DNS(data, "[HTTPS] got %d records", de.numhttps_rrs);
if(de.numhttps_rrs > 0 && result == CURLE_OK) {
result = doh_resp_decode_httpsrr(data, de.https_rrs->val,
de.https_rrs->len, &hrr);
@ -1338,11 +1341,12 @@ static void doh_close(struct Curl_easy *data,
doh->probe_resp[slot].probe_mid));
continue;
}
probe_data->sub_xfer_done = NULL; /* No longer interested in result */
/* 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;
CURL_TRC_DNS(data, "[DoH] probe done");
}
}

View file

@ -98,15 +98,15 @@ void Curl_httpsrr_trace(struct Curl_easy *data,
CURLcode result;
if(!rr) {
CURL_TRC_DNS(data, "[HTTPS-RR] not available");
CURL_TRC_DNS(data, "[HTTPS] no record 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));
CURL_TRC_DNS(data, "[HTTPS] record: %s", curlx_dyn_ptr(&tmp));
else
CURL_TRC_DNS(data, "Error printing HTTPS-RR information");
CURL_TRC_DNS(data, "[HTTPS] error printing information");
curlx_dyn_free(&tmp);
}

View file

@ -2445,20 +2445,16 @@ static void handle_completed(struct Curl_multi *multi,
CURLcode result)
{
if(data->master_mid != UINT32_MAX) {
/* A sub transfer, not for msgsent to application */
struct Curl_easy *mdata;
/* A sub transfer, not for msgsent to application. Is anyone still
* interested in processing its results? */
if(data->sub_xfer_done) {
struct Curl_easy *master = Curl_multi_get_easy(multi, data->master_mid);
CURL_TRC_M(data, "sub xfer done for master %u", data->master_mid);
mdata = Curl_multi_get_easy(multi, data->master_mid);
if(mdata) {
if(mdata->sub_xfer_done)
mdata->sub_xfer_done(mdata, data, result);
CURL_TRC_M(data, "sub xfer done for master %u", data->master_mid);
if(master)
data->sub_xfer_done(data, master, result);
else
CURL_TRC_M(data, "master easy %u without sub_xfer_done callback.",
data->master_mid);
}
else {
CURL_TRC_M(data, "master easy %u already gone.", data->master_mid);
CURL_TRC_M(data, "master easy %u already gone.", data->master_mid);
}
}
else {

View file

@ -1192,10 +1192,10 @@ 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,
/* callback that gets called when the transfer `data` is done and
* `data->master_mid` is set to an existing easy handle. */
typedef void multi_sub_xfer_done_cb(struct Curl_easy *data,
struct Curl_easy *master,
CURLcode result);
/*