From 2d30fd26a060e7c3de3393503fb5ba7e8f3840f8 Mon Sep 17 00:00:00 2001 From: Stefan Eissing Date: Sat, 8 Aug 2026 13:20:15 +0200 Subject: [PATCH] DoH: improvements - decode results when individual requests are done - makes happy eyeballing start asap - remove doh_resp structures as no longer needed - remove CURL_DNS_TYPE_NS, CURL_DNS_TYPE_CNAME and CURL_DNS_TYPE_DNAME from DoH - DoH: do not set PIPEWAIT and SSL OPTS when url starts with http: - mark Doh master handle as dirty after every sub-request, not only the last - Doh: start probe on AAAA before A (was the other way). cf-dns: set EXPIRE_HAPPY_EYEBALLS timer when waiting 50ms on AAAA result or progress will not be triggered in time. Add debug env var CURL_DBG_HE_AAAA_AWAIT_MS to override the default 50ms on waiting for the AAAA result to arrive. test2100: set CURL_DBG_HE_AAAA_AWAIT_MS to 60 seconds to provide enough time for slow CI runs to sent all DoH requests. closes #22514 --- docs/libcurl/libcurl-env-dbg.md | 5 + lib/vdns/cf-dns.c | 51 ++- lib/vdns/doh.c | 665 ++++++++++++++------------------ lib/vdns/doh.h | 18 +- tests/data/test2100 | 6 + tests/http/test_21_resolve.py | 23 +- tests/http/testenv/dnsd.py | 23 +- tests/server/dnsd.c | 10 +- tests/unit/unit1650.c | 12 +- 9 files changed, 382 insertions(+), 431 deletions(-) diff --git a/docs/libcurl/libcurl-env-dbg.md b/docs/libcurl/libcurl-env-dbg.md index 1387df4bad..a039cebc5c 100644 --- a/docs/libcurl/libcurl-env-dbg.md +++ b/docs/libcurl/libcurl-env-dbg.md @@ -174,6 +174,11 @@ a multi handle is destroyed. This implicitly triggers for easy handles that are run via easy_perform. The value of the environment variable gives the shutdown timeout in milliseconds. +## `CURL_DBG_HE_AAAA_AWAIT_MS` + +Overrides the time delaying a connect for AAAA resolve results to arrive +before continuing with Happy Eyeballing. + ## `CURL_DBG_RESOLV_MAX_THREADS` Overrides the maximum number of threads for resolver. diff --git a/lib/vdns/cf-dns.c b/lib/vdns/cf-dns.c index f02b37f126..51d4791ecc 100644 --- a/lib/vdns/cf-dns.c +++ b/lib/vdns/cf-dns.c @@ -34,12 +34,15 @@ #include "vdns/cf-dns.h" #include "vdns/dnscache.h" #include "vdns/httpsrr.h" +#include "curlx/strparse.h" +#define CURL_HE_AAAA_AWAIT_MS 50 struct cf_dns_ctx { struct Curl_dns_entry *dns; struct Curl_peer *peer; CURLcode resolv_result; + timediff_t he_aaaa_await_ms; uint32_t resolv_id; uint8_t dns_queries; uint8_t transport; @@ -64,6 +67,18 @@ static struct cf_dns_ctx *cf_dns_ctx_create(struct Curl_easy *data, ctx->dns_queries = dns_queries; ctx->transport = transport; ctx->for_proxy = for_proxy; + ctx->he_aaaa_await_ms = CURL_HE_AAAA_AWAIT_MS; +#ifdef DEBUGBUILD + { + const char *p = getenv("CURL_DBG_HE_AAAA_AWAIT_MS"); + if(p) { + curl_off_t l; + if(!curlx_str_number(&p, &l, UINT32_MAX)) { + ctx->he_aaaa_await_ms = (uint32_t)l; + } + } + } +#endif CURL_TRC_DNS(data, "[%s] created DNS filter for %s:%u, transport=%x", Curl_resolv_query_str(ctx->dns_queries), @@ -227,8 +242,6 @@ 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) { @@ -239,19 +252,27 @@ static bool cf_dns_ready_to_connect(struct Curl_cfilter *cf, 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; - - /* Note: if a query was never started, it is considered to have + else if(CURL_DNSQ_IS_ADDR(ctx->dns_queries)) { + timediff_t remain_ms; + /* For Happy Eyeballing, we can start on either A or AAAA resolves, + * but AAAA is preferred. We enforce a small delay for missing + * AAAA to arrive, then we let the connect continue. + * Note: if AAAA was never started (-4), it is considered to have * an answer (e.g. a negative one). */ - if(Curl_resolv_has_answers(data, ctx->resolv_id, wanted_answers)) + if(Curl_resolv_has_answers(data, ctx->resolv_id, CURL_DNSQ_AAAA)) + return TRUE; + remain_ms = ctx->he_aaaa_await_ms - + Curl_resolv_elapsed_ms(data, ctx->resolv_id); + if(remain_ms <= 0) 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; + CURL_TRC_CF(data, cf, "[%s] still waiting %" FMT_TIMEDIFF_T + "ms for AAAA result", + Curl_resolv_query_str(ctx->dns_queries), remain_ms); + Curl_expire(data, remain_ms, EXPIRE_HAPPY_EYEBALLS); + return FALSE; + } + else { + return TRUE; } #else (void)data; @@ -285,7 +306,9 @@ static CURLcode cf_dns_connect(struct Curl_cfilter *cf, if(ctx->resolv_result && ip_query) { /* failing A|AAAA resolves is a hard failure. */ - CURL_TRC_CF(data, cf, "error resolving: %d", (int)ctx->resolv_result); + CURL_TRC_CF(data, cf, "[%s] error resolving: %d", + Curl_resolv_query_str(ctx->dns_queries), + (int)ctx->resolv_result); return ctx->resolv_result; } diff --git a/lib/vdns/doh.c b/lib/vdns/doh.c index ccf763dc54..fe6f9a8907 100644 --- a/lib/vdns/doh.c +++ b/lib/vdns/doh.c @@ -44,7 +44,7 @@ static void doh_close(struct Curl_easy *data, struct Curl_resolv_async *async); #ifdef CURLVERBOSE -static const char * const errors[] = { +static const char * const doh_code_str[] = { "", "Bad label", "Out of range", @@ -59,16 +59,34 @@ static const char * const errors[] = { "No content", "Bad ID", "Name too long", - "No such name" + "No such name", + "Transport failed", + "Out Of Memory" }; static const char *doh_strerror(DOHcode code) { - if((code >= DOH_OK) && (code <= DOH_DNS_NXDOMAIN)) - return errors[code]; + if((size_t)code < CURL_ARRAYSIZE(doh_code_str)) + return doh_code_str[code]; return "bad error code"; } +static const char *doh_type2name(DNStype dnstype) +{ + switch(dnstype) { + case CURL_DNS_TYPE_A: + return "A"; + case CURL_DNS_TYPE_AAAA: + return "AAAA"; +#ifdef USE_HTTPSRR + case CURL_DNS_TYPE_HTTPS: + return "HTTPS"; +#endif + default: + return "unknown"; + } +} + #endif /* CURLVERBOSE */ /* @unittest 1655 @@ -190,89 +208,8 @@ static size_t doh_probe_write_cb(char *contents, size_t size, size_t nmemb, return realsize; } -#if defined(USE_HTTPSRR) && defined(DEBUGBUILD) && defined(CURLVERBOSE) - -/* doh_print_buf truncates if the hex string will be more than this */ -#define LOCAL_PB_HEXMAX 400 - -static void doh_print_buf(struct Curl_easy *data, - const char *prefix, - unsigned char *buf, size_t len) -{ - unsigned char hexstr[LOCAL_PB_HEXMAX]; - size_t hlen = LOCAL_PB_HEXMAX; - bool truncated = FALSE; - - if(len > (LOCAL_PB_HEXMAX / 2)) - truncated = TRUE; - Curl_hexencode(buf, len, hexstr, hlen); - if(!truncated) - infof(data, "%s: len=%d, val=%s", prefix, (int)len, hexstr); - else - infof(data, "%s: len=%d (truncated)val=%s", prefix, (int)len, hexstr); -} -#endif - -/* called from multi when a sub transfer, e.g. doh probe, is done. - * 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 *doh, - struct Curl_easy *master, CURLcode result) -{ - struct Curl_resolv_async *async = NULL; - struct doh_probes *dohp = NULL; - struct doh_request *doh_req = NULL; - int i; - - 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(master, doh_req->resolv_id); - if(!async) { - CURL_TRC_DNS(master, "[%u] ignoring outdated DoH response", - doh_req->resolv_id); - return; - } - dohp = async->doh; - - for(i = 0; i < DOH_SLOT_COUNT; ++i) { - if(dohp->probe_resp[i].probe_mid == doh->mid) - break; - } - /* We really should have found the slot where to store the response */ - if(i >= DOH_SLOT_COUNT) { - DEBUGASSERT(0); - failf(master, "DoH: unknown sub request done"); - return; - } - - async->queries_ongoing--; - infof(doh, "a DoH request is completed, %u to go", async->queries_ongoing); - dohp->probe_resp[i].result = result; - /* We expect either the meta data still to exist or the sub request - * to have already failed. */ - if(!result) { - dohp->probe_resp[i].dnstype = doh_req->dnstype; - result = curlx_dyn_addn(&dohp->probe_resp[i].body, - curlx_dyn_ptr(&doh_req->resp_body), - curlx_dyn_len(&doh_req->resp_body)); - } - Curl_meta_remove(doh, CURL_EZM_DOH_PROBE); - - if(result) - infof(doh, "DoH request %s", curl_easy_strerror(result)); - - if(!async->queries_ongoing) { - /* DoH completed, run master to act on results */ - Curl_multi_mark_dirty(master); - } -} - + struct Curl_easy *master, CURLcode result); static void doh_probe_dtor(void *key, size_t klen, void *e) { (void)key; @@ -306,6 +243,7 @@ static CURLcode doh_probe_run(struct Curl_easy *data, timediff_t timeout_ms; struct doh_request *doh_req; DOHcode d; + bool maybe_https = !curl_strnequal(url, STRCONST("http:")); *pmid = UINT32_MAX; @@ -354,8 +292,10 @@ static CURLcode doh_probe_run(struct Curl_easy *data, 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); + if(maybe_https) { + ERROR_CHECK_SETOPT(CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_2TLS); + ERROR_CHECK_SETOPT(CURLOPT_PIPEWAIT, 1L); + } #endif #ifndef DEBUGBUILD /* enforce HTTPS if not debug */ @@ -372,55 +312,57 @@ static CURLcode doh_probe_run(struct Curl_easy *data, ERROR_CHECK_SETOPT(CURLOPT_VERBOSE, 1L); if(data->set.no_signal) ERROR_CHECK_SETOPT(CURLOPT_NOSIGNAL, 1L); - - ERROR_CHECK_SETOPT(CURLOPT_SSL_VERIFYHOST, - data->set.doh_verifyhost ? 2L : 0L); - ERROR_CHECK_SETOPT(CURLOPT_SSL_VERIFYPEER, - data->set.doh_verifypeer ? 1L : 0L); - ERROR_CHECK_SETOPT(CURLOPT_SSL_VERIFYSTATUS, - data->set.doh_verifystatus ? 1L : 0L); - - /* Inherit *some* SSL options from the user's transfer. This is a - best-guess as to which options are needed for compatibility. #3661 - - Note DoH does not inherit the user's proxy server so proxy SSL settings - have no effect and are not inherited. If that changes then two new - options should be added to check doh proxy insecure separately, - CURLOPT_DOH_PROXY_SSL_VERIFYHOST and CURLOPT_DOH_PROXY_SSL_VERIFYPEER. - */ - doh->set.ssl.custom_cafile = data->set.ssl.custom_cafile; - doh->set.ssl.custom_capath = data->set.ssl.custom_capath; - doh->set.ssl.custom_cablob = data->set.ssl.custom_cablob; - if(data->set.str[STRING_SSL_CAFILE]) { - ERROR_CHECK_SETOPT(CURLOPT_CAINFO, data->set.str[STRING_SSL_CAFILE]); - } - if(data->set.blobs[BLOB_CAINFO]) { - ERROR_CHECK_SETOPT(CURLOPT_CAINFO_BLOB, data->set.blobs[BLOB_CAINFO]); - } - if(data->set.str[STRING_SSL_CAPATH]) { - ERROR_CHECK_SETOPT(CURLOPT_CAPATH, data->set.str[STRING_SSL_CAPATH]); - } - if(data->set.str[STRING_SSL_CRLFILE]) { - ERROR_CHECK_SETOPT(CURLOPT_CRLFILE, data->set.str[STRING_SSL_CRLFILE]); - } - if(data->set.ssl.certinfo) - ERROR_CHECK_SETOPT(CURLOPT_CERTINFO, 1L); - if(data->set.ssl.fsslctx) - ERROR_CHECK_SETOPT(CURLOPT_SSL_CTX_FUNCTION, data->set.ssl.fsslctx); - if(data->set.ssl.fsslctxp) - ERROR_CHECK_SETOPT(CURLOPT_SSL_CTX_DATA, data->set.ssl.fsslctxp); if(data->set.fdebug) ERROR_CHECK_SETOPT(CURLOPT_DEBUGFUNCTION, data->set.fdebug); if(data->set.debugdata) ERROR_CHECK_SETOPT(CURLOPT_DEBUGDATA, data->set.debugdata); - if(data->set.str[STRING_SSL_EC_CURVES]) { - ERROR_CHECK_SETOPT(CURLOPT_SSL_EC_CURVES, - data->set.str[STRING_SSL_EC_CURVES]); - } - (void)curl_easy_setopt(doh, CURLOPT_SSL_OPTIONS, - ((long)data->set.ssl.primary.ssl_options & - ~CURLSSLOPT_AUTO_CLIENT_CERT)); + if(maybe_https) { + ERROR_CHECK_SETOPT(CURLOPT_SSL_VERIFYHOST, + data->set.doh_verifyhost ? 2L : 0L); + ERROR_CHECK_SETOPT(CURLOPT_SSL_VERIFYPEER, + data->set.doh_verifypeer ? 1L : 0L); + ERROR_CHECK_SETOPT(CURLOPT_SSL_VERIFYSTATUS, + data->set.doh_verifystatus ? 1L : 0L); + + /* Inherit *some* SSL options from the user's transfer. This is a + best-guess as to which options are needed for compatibility. #3661 + + Note DoH does not inherit the user's proxy server so proxy SSL settings + have no effect and are not inherited. If that changes then two new + options should be added to check doh proxy insecure separately, + CURLOPT_DOH_PROXY_SSL_VERIFYHOST and CURLOPT_DOH_PROXY_SSL_VERIFYPEER. + */ + doh->set.ssl.custom_cafile = data->set.ssl.custom_cafile; + doh->set.ssl.custom_capath = data->set.ssl.custom_capath; + doh->set.ssl.custom_cablob = data->set.ssl.custom_cablob; + if(data->set.str[STRING_SSL_CAFILE]) { + ERROR_CHECK_SETOPT(CURLOPT_CAINFO, data->set.str[STRING_SSL_CAFILE]); + } + if(data->set.blobs[BLOB_CAINFO]) { + ERROR_CHECK_SETOPT(CURLOPT_CAINFO_BLOB, data->set.blobs[BLOB_CAINFO]); + } + if(data->set.str[STRING_SSL_CAPATH]) { + ERROR_CHECK_SETOPT(CURLOPT_CAPATH, data->set.str[STRING_SSL_CAPATH]); + } + if(data->set.str[STRING_SSL_CRLFILE]) { + ERROR_CHECK_SETOPT(CURLOPT_CRLFILE, data->set.str[STRING_SSL_CRLFILE]); + } + if(data->set.ssl.certinfo) + ERROR_CHECK_SETOPT(CURLOPT_CERTINFO, 1L); + if(data->set.ssl.fsslctx) + ERROR_CHECK_SETOPT(CURLOPT_SSL_CTX_FUNCTION, data->set.ssl.fsslctx); + if(data->set.ssl.fsslctxp) + ERROR_CHECK_SETOPT(CURLOPT_SSL_CTX_DATA, data->set.ssl.fsslctxp); + if(data->set.str[STRING_SSL_EC_CURVES]) { + ERROR_CHECK_SETOPT(CURLOPT_SSL_EC_CURVES, + data->set.str[STRING_SSL_EC_CURVES]); + } + + (void)curl_easy_setopt(doh, CURLOPT_SSL_OPTIONS, + ((long)data->set.ssl.primary.ssl_options & + ~CURLSSLOPT_AUTO_CLIENT_CERT)); + } doh->state.internal = TRUE; doh->master_mid = data->mid; /* master transfer of this one */ @@ -469,39 +411,56 @@ CURLcode Curl_doh(struct Curl_easy *data, Curl_doh_cleanup(data, async); } + if(!async->dns_queries) + return CURLE_BAD_FUNCTION_ARGUMENT; +#ifdef USE_HTTPSRR + if(CURL_DNSQ_IS_ADDR(async->dns_queries) && + (async->dns_queries & CURL_DNSQ_HTTPS)) { + /* Can't mix those in the same async resolve */ + DEBUGASSERT(0); + return CURLE_BAD_FUNCTION_ARGUMENT; + } +#else + if(async->dns_queries & CURL_DNSQ_HTTPS) { + DEBUGASSERT(0); + return CURLE_NOT_BUILT_IN; + } +#endif + /* start clean, consider allocating this struct on demand */ async->doh = dohp = curlx_calloc(1, sizeof(struct doh_probes)); if(!dohp) return CURLE_OUT_OF_MEMORY; for(i = 0; i < DOH_SLOT_COUNT; ++i) { - dohp->probe_resp[i].probe_mid = UINT32_MAX; - curlx_dyn_init(&dohp->probe_resp[i].body, DYN_DOH_RESPONSE); + dohp->probe_rc[i] = DOH_OK; + dohp->probe_mid[i] = UINT32_MAX; } - /* create IPv4 DoH request */ - if(async->dns_queries & CURL_DNSQ_A) { - result = doh_probe_run(data, CURL_DNS_TYPE_A, +#ifdef USE_IPV6 + /* AAAA results have preference in happy eyeballing, trigger first */ + if(async->dns_queries & CURL_DNSQ_AAAA) { + /* create IPv6 DoH request */ + result = doh_probe_run(data, CURL_DNS_TYPE_AAAA, async->peer->hostname, data->set.str[STRING_DOH], data->multi, async->id, - &dohp->probe_resp[DOH_SLOT_IPV4].probe_mid); + &dohp->probe_mid[DOH_SLOT_IPV6]); if(result) goto error; async->queries_ongoing++; } +#endif -#ifdef USE_IPV6 - if(async->dns_queries & CURL_DNSQ_AAAA) { - /* create IPv6 DoH request */ - result = doh_probe_run(data, CURL_DNS_TYPE_AAAA, + /* create IPv4 DoH request */ + if(async->dns_queries & CURL_DNSQ_A) { + result = doh_probe_run(data, CURL_DNS_TYPE_A, async->peer->hostname, data->set.str[STRING_DOH], data->multi, async->id, - &dohp->probe_resp[DOH_SLOT_IPV6].probe_mid); + &dohp->probe_mid[DOH_SLOT_IPV4]); if(result) goto error; async->queries_ongoing++; } -#endif #ifdef USE_HTTPSRR if(async->dns_queries & CURL_DNSQ_HTTPS) { @@ -516,7 +475,7 @@ CURLcode Curl_doh(struct Curl_easy *data, qname ? qname : async->peer->hostname, data->set.str[STRING_DOH], data->multi, async->id, - &dohp->probe_resp[DOH_SLOT_HTTPS_RR].probe_mid); + &dohp->probe_mid[DOH_SLOT_HTTPS_RR]); curlx_free(qname); if(result) goto error; @@ -614,58 +573,7 @@ static DOHcode doh_store_https(const unsigned char *doh, int index, } #endif -static DOHcode doh_store_cname(const unsigned char *doh, size_t dohlen, - unsigned int index, struct dohentry *d) -{ - struct dynbuf *c; - unsigned int loop = 128; /* a valid DNS name can never loop this much */ - unsigned char length; - - if(d->numcname == DOH_MAX_CNAME) - return DOH_OK; /* skip! */ - - c = &d->cname[d->numcname++]; - do { - if(index >= dohlen) - return DOH_DNS_OUT_OF_RANGE; - length = doh[index]; - if((length & 0xc0) == 0xc0) { - int newpos; - /* name pointer, get the new offset (14 bits) */ - if((index + 1) >= dohlen) - return DOH_DNS_OUT_OF_RANGE; - - /* move to the new index */ - newpos = (length & 0x3f) << 8 | doh[index + 1]; - index = (unsigned int)newpos; - continue; - } - else if(length & 0xc0) - return DOH_DNS_BAD_LABEL; /* bad input */ - else - index++; - - if(length) { - if(curlx_dyn_len(c)) { - if(curlx_dyn_addn(c, STRCONST("."))) - return DOH_OUT_OF_MEM; - } - if((index + length) > dohlen) - return DOH_DNS_BAD_LABEL; - - if(curlx_dyn_addn(c, &doh[index], length)) - return DOH_OUT_OF_MEM; - index += length; - } - } while(length && --loop); - - if(!loop) - return DOH_DNS_LABEL_LOOP; - return DOH_OK; -} - static DOHcode doh_rdata(const unsigned char *doh, - size_t dohlen, unsigned short rdlength, unsigned short type, int index, @@ -674,10 +582,7 @@ static DOHcode doh_rdata(const unsigned char *doh, /* RDATA - A (TYPE 1): 4 bytes - AAAA (TYPE 28): 16 bytes - - NS (TYPE 2): N bytes - HTTPS (TYPE 65): N bytes */ - DOHcode rc; - switch(type) { case CURL_DNS_TYPE_A: if(rdlength != 4) @@ -690,22 +595,15 @@ static DOHcode doh_rdata(const unsigned char *doh, doh_store_aaaa(doh, index, d); break; #ifdef USE_HTTPSRR - case CURL_DNS_TYPE_HTTPS: - rc = doh_store_https(doh, index, d, rdlength); + case CURL_DNS_TYPE_HTTPS: { + DOHcode rc = doh_store_https(doh, index, d, rdlength); if(rc) return rc; break; + } #endif - case CURL_DNS_TYPE_CNAME: - rc = doh_store_cname(doh, dohlen, (unsigned int)index, d); - if(rc) - return rc; - break; - case CURL_DNS_TYPE_DNAME: - /* explicit for clarity; skip; rely on synthesized CNAME */ - break; default: - /* unsupported type, skip it */ + /* unsupported type, or type we do not store, skip it */ break; } return DOH_OK; @@ -715,11 +613,8 @@ static DOHcode doh_rdata(const unsigned char *doh, UNITTEST void de_init(struct dohentry *de); UNITTEST void de_init(struct dohentry *de) { - int i; memset(de, 0, sizeof(*de)); de->ttl = INT_MAX; - for(i = 0; i < DOH_MAX_CNAME; i++) - curlx_dyn_init(&de->cname[i], DYN_DOH_CNAME); } /* TTL value cap */ @@ -781,7 +676,7 @@ UNITTEST DOHcode doh_resp_decode(const unsigned char *doh, if((type != CURL_DNS_TYPE_CNAME) && /* may be synthesized from DNAME */ (type != CURL_DNS_TYPE_DNAME) && /* if present, accept and ignore */ (type != dnstype)) - /* Not the same type as was asked for nor CNAME nor DNAME */ + /* Not the same type as was asked for, nor CNAME nor DNAME */ return DOH_DNS_UNEXPECTED_TYPE; index += 2; @@ -810,9 +705,9 @@ UNITTEST DOHcode doh_resp_decode(const unsigned char *doh, if(dohlen < (index + rdlength)) return DOH_DNS_OUT_OF_RANGE; - rc = doh_rdata(doh, dohlen, rdlength, type, (int)index, d); + rc = doh_rdata(doh, rdlength, type, (int)index, d); if(rc) - return rc; /* bad doh_rdata */ + return rc; index += rdlength; ancount--; } @@ -864,69 +759,10 @@ UNITTEST DOHcode doh_resp_decode(const unsigned char *doh, if(index != dohlen) return DOH_DNS_MALFORMAT; /* something is wrong */ -#ifdef USE_HTTPSRR - if((type != CURL_DNS_TYPE_NS) && !d->numcname && !d->numaddr && - !d->numhttps_rrs) -#else - if((type != CURL_DNS_TYPE_NS) && !d->numcname && !d->numaddr) -#endif - /* nothing stored! */ - return DOH_NO_CONTENT; - return DOH_OK; /* ok */ } -#ifdef CURLVERBOSE -static void doh_show(struct Curl_easy *data, - const struct dohentry *d) -{ - int i; - infof(data, "[DoH] TTL: %u seconds", d->ttl); - for(i = 0; i < d->numaddr; i++) { - const struct dohaddr *a = &d->addr[i]; - if(a->type == CURL_DNS_TYPE_A) { - infof(data, "[DoH] A: %u.%u.%u.%u", - a->ip.v4[0], a->ip.v4[1], - a->ip.v4[2], a->ip.v4[3]); - } - else if(a->type == CURL_DNS_TYPE_AAAA) { - int j; - char buffer[128] = "[DoH] AAAA: "; - size_t len = strlen(buffer); - char *ptr = &buffer[len]; - len = sizeof(buffer) - len; - for(j = 0; j < 16; j += 2) { - size_t l; - curl_msnprintf(ptr, len, "%s%02x%02x", j ? ":" : "", - d->addr[i].ip.v6[j], - d->addr[i].ip.v6[j + 1]); - l = strlen(ptr); - len -= l; - ptr += l; - } - infof(data, "%s", buffer); - } - } -#ifdef USE_HTTPSRR - for(i = 0; i < d->numhttps_rrs; i++) { -#if defined(DEBUGBUILD) && defined(CURLVERBOSE) - doh_print_buf(data, "DoH HTTPS", d->https_rrs[i].val, d->https_rrs[i].len); -#else - infof(data, "DoH HTTPS RR: length %d", d->https_rrs[i].len); -#endif - } -#endif /* USE_HTTPSRR */ - for(i = 0; i < d->numcname; i++) { - infof(data, "CNAME: %s", curlx_dyn_ptr(&d->cname[i])); - } -} -#else -#define doh_show(x, y) -#endif - /* - * doh2ai() - * * This function returns a pointer to the first element of a newly allocated * Curl_addrinfo struct linked list filled with the data from a set of DoH * lookups. Curl_addrinfo is meant to work like the addrinfo struct does for @@ -936,7 +772,6 @@ static void doh_show(struct Curl_easy *data, * Curl_freeaddrinfo(). For each successful call to this function there * must be an associated call later to Curl_freeaddrinfo(). */ - static CURLcode doh2ai(const struct dohentry *de, const char *hostname, int port, struct Curl_addrinfo **aip) { @@ -947,14 +782,9 @@ static CURLcode doh2ai(const struct dohentry *de, const char *hostname, #ifdef USE_IPV6 struct sockaddr_in6 *addr6; #endif + size_t hostlen = strlen(hostname) + 1; /* include null-terminator */ CURLcode result = CURLE_OK; int i; - size_t hostlen = strlen(hostname) + 1; /* include null-terminator */ - - DEBUGASSERT(de); - - if(!de->numaddr) - return CURLE_COULDNT_RESOLVE_HOST; for(i = 0; i < de->numaddr; i++) { size_t ss_size; @@ -1032,35 +862,16 @@ static CURLcode doh2ai(const struct dohentry *de, const char *hostname, return result; } -#ifdef CURLVERBOSE -static const char *doh_type2name(DNStype dnstype) -{ - switch(dnstype) { - case CURL_DNS_TYPE_A: - return "A"; - case CURL_DNS_TYPE_AAAA: - return "AAAA"; -#ifdef USE_HTTPSRR - case CURL_DNS_TYPE_HTTPS: - return "HTTPS"; -#endif - default: - return "unknown"; - } -} -#endif - /* @unittest 1655 */ UNITTEST void de_cleanup(struct dohentry *d); UNITTEST void de_cleanup(struct dohentry *d) { - int i = 0; - for(i = 0; i < d->numcname; i++) { - curlx_dyn_free(&d->cname[i]); - } #ifdef USE_HTTPSRR + int i = 0; for(i = 0; i < d->numhttps_rrs; i++) curlx_safefree(d->https_rrs[i].val); +#else + (void)d; #endif } @@ -1192,115 +1003,207 @@ err: #endif /* USE_HTTPSRR */ +/* called from multi when a sub transfer, e.g. doh probe, is done. + * Parse the response and set the results in the `async` context + * of master, using the id from the probe's CURL_EZM_DOH_PROBE + * meta data. */ +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; + struct doh_request *doh_req = NULL; + struct Curl_addrinfo **pdest_ai; + struct dohentry de; + int slot, httpcode; + + de_init(&de); + doh_req = Curl_meta_get(doh, CURL_EZM_DOH_PROBE); + if(!doh_req) { + /* transfer `doh` is not a DoH probe. */ + DEBUGASSERT(0); + goto out; + } + + async = Curl_async_get(master, doh_req->resolv_id); + if(!async) { + CURL_TRC_DNS(master, "[%u] ignoring outdated DoH response", + doh_req->resolv_id); + goto out; + } + dohp = async->doh; + + for(slot = 0; slot < DOH_SLOT_COUNT; ++slot) { + if(dohp->probe_mid[slot] == doh->mid) + break; + } + /* We really should have found the slot where to store the response */ + if(slot >= DOH_SLOT_COUNT) { + failf(master, "DoH: unknown sub request done"); + DEBUGASSERT(0); + goto out; + } + + async->queries_ongoing--; + dohp = async->doh; + httpcode = doh->info.httpcode; + switch(slot) { + case DOH_SLOT_IPV4: + async->dns_responses |= CURL_DNSQ_A; + break; +#ifdef USE_IPV6 + case DOH_SLOT_IPV6: + async->dns_responses |= CURL_DNSQ_AAAA; + break; +#endif +#ifdef USE_HTTPSRR + case DOH_SLOT_HTTPS_RR: + async->dns_responses |= CURL_DNSQ_HTTPS; + break; +#endif + default: + DEBUGASSERT(0); + break; + } + + if(result) { + dohp->probe_rc[slot] = DOH_HTTP_FAILED; + infof(doh, "[DoH] [%s] error: %s", + doh_type2name(doh_req->dnstype), curl_easy_strerror(result)); + goto out; + } + else if((httpcode < 200) || (httpcode >= 300)) { + dohp->probe_rc[slot] = DOH_HTTP_FAILED; + infof(doh, "[DoH] [%s] error: HTTP status %d", + doh_type2name(doh_req->dnstype), httpcode); + goto out; + } + + dohp->probe_rc[slot] = doh_resp_decode(curlx_dyn_uptr(&doh_req->resp_body), + curlx_dyn_len(&doh_req->resp_body), + doh_req->dnstype, &de); + if(dohp->probe_rc[slot]) { +#ifdef USE_HTTPSRR + if((dohp->probe_rc[slot] == DOH_NO_CONTENT) && + (doh_req->dnstype == CURL_DNS_TYPE_HTTPS)) { + dohp->probe_rc[slot] = DOH_DNS_NXDOMAIN; + } +#endif + infof(doh, "[DoH] [%s] error decoding response: %s", + doh_type2name(doh_req->dnstype), + doh_strerror(dohp->probe_rc[slot])); + goto out; + } + + if(doh_req->dnstype == CURL_DNS_TYPE_A) + pdest_ai = &async->ai_A; + else if(doh_req->dnstype == CURL_DNS_TYPE_AAAA) + pdest_ai = &async->ai_AAAA; + else + pdest_ai = NULL; + + if(pdest_ai && de.numaddr) { + if(*pdest_ai) { + Curl_freeaddrinfo(*pdest_ai); + *pdest_ai = NULL; + } + result = doh2ai(&de, async->peer->hostname, async->peer->port, pdest_ai); + if(result) { /* hard failure on our side, fail completely */ + infof(doh, "[DoH] [%s] error creating addrinfo: %s", + doh_type2name(doh_req->dnstype), curl_easy_strerror(result)); + dohp->probe_rc[slot] = DOH_OOM; + async->result = result; + } + } +#ifdef USE_HTTPSRR + else if((doh_req->dnstype == CURL_DNS_TYPE_HTTPS) && de.numhttps_rrs) { + CURL_TRC_DNS(doh, "[HTTPS] got %d records", de.numhttps_rrs); + result = doh_resp_decode_httpsrr(doh, de.https_rrs->val, + de.https_rrs->len, &async->httpsrr); + if(result) { + dohp->probe_rc[slot] = DOH_HTTP_FAILED; + infof(doh, "[DoH] error decoding HTTPS RR: %s", + curl_easy_strerror(result)); + goto out; + } + } +#endif /* USE_HTTPSRR */ + + /* DoH request complete, run master to act on results */ + infof(doh, "DoH request complete, %u to go", async->queries_ongoing); + +out: + Curl_multi_mark_dirty(master); + de_cleanup(&de); + Curl_meta_remove(doh, CURL_EZM_DOH_PROBE); +} + CURLcode Curl_doh_take_result(struct Curl_easy *data, struct Curl_resolv_async *async, struct Curl_dns_entry **pdns) { struct doh_probes *dohp = async->doh; CURLcode result = CURLE_OK; - struct dohentry de; *pdns = NULL; /* defaults to no response */ if(!dohp) return CURLE_OUT_OF_MEMORY; + async->negative_answer = FALSE; + if(async->result) { + result = async->result; + goto out; + } + 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) { + dohp->probe_mid[DOH_SLOT_IPV4] == UINT32_MAX && + dohp->probe_mid[DOH_SLOT_IPV6] == UINT32_MAX) { failf(data, "Could not DoH-resolve: %s", async->peer->hostname); return async->for_proxy ? CURLE_COULDNT_RESOLVE_PROXY : CURLE_COULDNT_RESOLVE_HOST; } else if(!async->queries_ongoing) { struct Curl_dns_entry *dns = NULL; - DOHcode rc[DOH_SLOT_COUNT]; bool negative = TRUE; int slot; - memset(rc, 0, sizeof(rc)); /* remove DoH handles from multi handle and close them */ doh_close(data, async); /* parse the responses, create the struct and return it! */ - de_init(&de); for(slot = 0; slot < DOH_SLOT_COUNT; slot++) { - struct doh_response *p = &dohp->probe_resp[slot]; - if(!p->dnstype) - continue; - rc[slot] = doh_resp_decode(curlx_dyn_uptr(&p->body), - curlx_dyn_len(&p->body), - p->dnstype, &de); /* Failing without an NXDOMAIN answer - a SERVFAIL-class rcode or an undecodable response - says nothing about the name. Such a failure must not be cached as a negative entry. */ - if(rc[slot] && (rc[slot] != DOH_DNS_NXDOMAIN)) + if(dohp->probe_rc[slot] && (dohp->probe_rc[slot] != DOH_DNS_NXDOMAIN)) negative = FALSE; - if(rc[slot]) { - CURL_TRC_DNS(data, "[%s] [DoH] error: %s of type %s for %s", - Curl_resolv_query_str(async->dns_queries), - doh_strerror(rc[slot]), - doh_type2name(p->dnstype), async->peer->hostname); - } } /* next slot */ - if(CURL_DNSQ_IS_ADDR(async->dns_queries)) { - if(!rc[DOH_SLOT_IPV4] || !rc[DOH_SLOT_IPV6]) { - /* we have an address, of one kind or other */ - struct Curl_addrinfo *ai; - - if(Curl_trc_ft_is_verbose(data, &Curl_trc_feat_doh)) { - CURL_TRC_DNS(data, "hostname: %s", async->peer->hostname); - doh_show(data, &de); - } - - result = doh2ai(&de, async->peer->hostname, async->peer->port, &ai); - if(result) { - /* a decoded response without any usable address, e.g. only - CNAME records, is an authoritative "no data" answer */ - if((result == CURLE_COULDNT_RESOLVE_HOST) && negative) - async->negative_answer = TRUE; - goto error; - } - - /* we got a response, create a dns entry. */ - dns = Curl_dnsc_mk_addr(data, async->dns_queries, &ai, async->peer); - if(!dns) { - result = CURLE_OUT_OF_MEMORY; - goto error; - } - } /* address processing done */ - else { - /* every query failed. Only NXDOMAIN answers for all of them - make this a negative answer, eligible for caching. */ - async->negative_answer = negative; - result = async->for_proxy ? - CURLE_COULDNT_RESOLVE_PROXY : CURLE_COULDNT_RESOLVE_HOST; + if(async->ai_A || async->ai_AAAA) { + dns = Curl_dnsc_mk_addr2( + data, async->dns_queries, &async->ai_A, &async->ai_AAAA, async->peer); + if(!dns) { + result = CURLE_OUT_OF_MEMORY; + goto out; } } - #ifdef USE_HTTPSRR - if(!dns && (async->dns_queries & CURL_DNSQ_HTTPS)) { - /* 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); - if(result) { - infof(data, "Failed to decode HTTPS RR"); - Curl_dns_entry_unlink(data, &dns); - goto error; - } - infof(data, "Some HTTPS RR to process"); - } - Curl_httpsrr_trace(data, hrr); - dns = Curl_dnsc_mk_https(data, &hrr, async->peer); + else if((async->dns_queries & CURL_DNSQ_HTTPS) && + !dohp->probe_rc[DOH_SLOT_HTTPS_RR]) { + Curl_httpsrr_trace(data, async->httpsrr); + dns = Curl_dnsc_mk_https(data, &async->httpsrr, async->peer); if(!dns) { result = CURLE_OUT_OF_MEMORY; - goto error; + goto out; } } #endif /* USE_HTTPSRR */ + else { + /* every query failed. Only NXDOMAIN answers for all of them + make this a negative answer, eligible for caching. */ + async->negative_answer = negative; + result = async->for_proxy ? + CURLE_COULDNT_RESOLVE_PROXY : CURLE_COULDNT_RESOLVE_HOST; + } /* and add the entry to the cache */ if(dns) @@ -1311,8 +1214,7 @@ CURLcode Curl_doh_take_result(struct Curl_easy *data, /* wait for pending DoH transactions to complete */ return CURLE_AGAIN; -error: - de_cleanup(&de); +out: Curl_doh_cleanup(data, async); return result; } @@ -1326,16 +1228,15 @@ static void doh_close(struct Curl_easy *data, uint32_t mid; size_t slot; for(slot = 0; slot < DOH_SLOT_COUNT; slot++) { - mid = doh->probe_resp[slot].probe_mid; + mid = doh->probe_mid[slot]; if(mid == UINT32_MAX) continue; - doh->probe_resp[slot].probe_mid = UINT32_MAX; + doh->probe_mid[slot] = UINT32_MAX; /* should have been called before data is removed from multi handle */ DEBUGASSERT(data->multi); probe_data = data->multi ? Curl_multi_get_easy(data->multi, mid) : NULL; if(!probe_data) { - DEBUGF(infof(data, "Curl_doh_close: xfer for mid=%u not found!", - doh->probe_resp[slot].probe_mid)); + DEBUGF(infof(data, "Curl_doh_close: xfer for mid=%u not found!", mid)); continue; } probe_data->sub_xfer_done = NULL; /* No longer interested in result */ @@ -1352,11 +1253,7 @@ void Curl_doh_cleanup(struct Curl_easy *data, { struct doh_probes *dohp = async->doh; if(dohp) { - int i; doh_close(data, async); - for(i = 0; i < DOH_SLOT_COUNT; ++i) { - curlx_dyn_free(&dohp->probe_resp[i].body); - } curlx_safefree(async->doh); } } diff --git a/lib/vdns/doh.h b/lib/vdns/doh.h index 1e4271796f..a6358027a7 100644 --- a/lib/vdns/doh.h +++ b/lib/vdns/doh.h @@ -45,7 +45,10 @@ typedef enum { DOH_NO_CONTENT, /* 11 */ DOH_DNS_BAD_ID, /* 12 */ DOH_DNS_NAME_TOO_LONG, /* 13 */ - DOH_DNS_NXDOMAIN /* 14 - no such name */ + DOH_DNS_NXDOMAIN, /* 14 - no such name */ + DOH_HTTP_FAILED, /* failure at the HTTP level */ + DOH_OOM, /* out of memory */ + DOH_CODE_LAST /* Not used, limit */ } DOHcode; typedef enum { @@ -98,17 +101,11 @@ struct doh_request { DNStype dnstype; }; -struct doh_response { - uint32_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]; + uint32_t probe_mid[DOH_SLOT_COUNT]; + DOHcode probe_rc[DOH_SLOT_COUNT]; }; /* @@ -123,7 +120,6 @@ CURLcode Curl_doh_take_result(struct Curl_easy *data, struct Curl_dns_entry **pdns); #define DOH_MAX_ADDR 24 -#define DOH_MAX_CNAME 4 #define DOH_MAX_HTTPS 4 struct dohaddr { @@ -150,11 +146,9 @@ struct dohhttps_rr { #endif struct dohentry { - struct dynbuf cname[DOH_MAX_CNAME]; struct dohaddr addr[DOH_MAX_ADDR]; int numaddr; unsigned int ttl; - int numcname; #ifdef USE_HTTPSRR struct dohhttps_rr https_rrs[DOH_MAX_HTTPS]; int numhttps_rrs; diff --git a/tests/data/test2100 b/tests/data/test2100 index 1bb5361e15..57dc8c6579 100644 --- a/tests/data/test2100 +++ b/tests/data/test2100 @@ -55,6 +55,12 @@ IPv6 HTTP GET using DoH (with HTTPS RR) +# Make Happy Eyeballing wait longer on AAAA results to arrive +# On slow runs (valgrind) the connect otherwise might succeed before +# all DoH requests are sent off. + +CURL_DBG_HE_AAAA_AWAIT_MS=60000 + https://foo.example.com:%HTTPSPORT/%TESTNUMBER --insecure --doh-insecure --doh-url https://%HOSTIP:%HTTPSPORT/%TESTNUMBER0001 diff --git a/tests/http/test_21_resolve.py b/tests/http/test_21_resolve.py index 685a9792ae..28634f1461 100644 --- a/tests/http/test_21_resolve.py +++ b/tests/http/test_21_resolve.py @@ -124,14 +124,14 @@ class TestResolve: r.check_stats(count=count, http_status=0, exitcode=6) assert r.duration > timedelta(milliseconds=count * delay_ms), f'{r}' - def dns_settings(self, dns_method, dnsd): + def dns_settings(self, dns_method, dnsd, path="/"): xargs = [] run_env = os.environ.copy() - run_env['CURL_DEBUG'] = 'dns,doh' + run_env['CURL_DEBUG'] = 'all' if dns_method == 'DoH': if not Env.curl_can_doh(): pytest.skip(reason="curl built without DoH") - xargs = ['--doh-insecure', '--doh-url', f'http://127.0.0.1:{dnsd.port}/'] + xargs = ['--doh-insecure', '--doh-url', f'http://127.0.0.1:{dnsd.port}{path}'] else: if not Env.curl_override_dns(): pytest.skip(reason="no DNS override") @@ -178,8 +178,6 @@ class TestResolve: # dnsd with one answer for A, delayed one for AAAA @pytest.mark.parametrize("dns_method", ["DNS", "DoH"]) def test_21_09_dnsd_a_delay(self, env: Env, httpd, dnsd, dns_method): - if dns_method == 'DoH': - pytest.skip(reason='DoH does not handle partial responses') dnsd.set_answers(addr_a=['127.0.0.1'], addr_aaaa=['[::1]'], delay_aaaa_ms=env.test_timeout * 1000) run_env, xargs = self.dns_settings(dns_method, dnsd) @@ -194,8 +192,6 @@ class TestResolve: @pytest.mark.skipif(condition=not Env.curl_has_feature('IPv6'), reason="no IPv6") @pytest.mark.parametrize("dns_method", ["DNS", "DoH"]) def test_21_10_dnsd_aaaa_delay(self, env: Env, httpd, dnsd, dns_method): - if dns_method == 'DoH': - pytest.skip(reason='DoH does not handle partial responses') dnsd.set_answers(addr_a=['127.0.0.1'], addr_aaaa=['[::1]'], delay_a_ms=env.test_timeout * 1000) run_env, xargs = self.dns_settings(dns_method, dnsd) @@ -317,6 +313,19 @@ class TestResolve: re.match(r'.* \* IPv6: fe80::1', line)] assert len(aaaa_resolves) == 1, f'{r.dump_logs()}' + # dnsd+DoH, handling HTTP response failure + def test_21_17_dnsd_http_fails(self, env: Env, httpd, dnsd): + count = 2 + dnsd.set_answers(rcode_a=2, rcode_aaaa=3) + run_env, xargs = self.dns_settings('DoH', dnsd, path='/notfound') + curl = CurlClient(env=env, run_env=run_env, force_resolv=False) + urls = [f'https://test-sf.http.curl.invalid/?id={i}' for i in range(count)] + r = curl.http_download(urls=urls, with_stats=True, extra_args=xargs) + r.check_exit_code(6) + r.check_stats(count=count, http_status=0, exitcode=6) + if env.curl_is_verbose(): + assert not [t for t in r.trace_lines if 'Negative DNS entry' in t], f'{r}' + def _clean_files(self, files): for file in files: if os.path.exists(file): diff --git a/tests/http/testenv/dnsd.py b/tests/http/testenv/dnsd.py index d80405e9e8..4411b07391 100644 --- a/tests/http/testenv/dnsd.py +++ b/tests/http/testenv/dnsd.py @@ -30,6 +30,7 @@ import time from datetime import datetime, timedelta, timezone from typing import Dict, List, Optional +from . import CurlClient from .env import Env from .ports import alloc_ports_and_do @@ -50,6 +51,7 @@ class Dnsd: self._dnsd_dir = os.path.join(env.gen_dir, self.name) self._log_dir = self._dnsd_dir self._lock_dir = os.path.join(self._dnsd_dir, 'lock') + self._tmp_dir = os.path.join(self._dnsd_dir, 'tmp') self._log_file = os.path.join(self._log_dir, 'dnsd.log') self._conf_file = os.path.join(self._log_dir, 'dnsd.cmd') self._pid_file = os.path.join(self._log_dir, 'dnsd.pid') @@ -87,12 +89,14 @@ class Dnsd: return True def stop(self, wait_dead=True): + result = True if self._process: self._process.terminate() self._process.wait(timeout=2) self._process = None + result = self.wait_dead(timeout=timedelta(seconds=Env.SERVER_TIMEOUT)) self.close_log() - return True + return result def restart(self): self.stop() @@ -100,6 +104,7 @@ class Dnsd: def initial_start(self): self._mkpath(self._lock_dir) + self._mkpath(self._tmp_dir) def startup(ports: Dict[str, int]) -> bool: self._port = ports[self._port_skey] @@ -132,10 +137,24 @@ class Dnsd: return False return self.wait_live(timeout=timedelta(seconds=Env.SERVER_TIMEOUT)) + def wait_dead(self, timeout: timedelta): + curl = CurlClient(env=self.env, run_dir=self._tmp_dir) + try_until = datetime.now(timezone.utc) + timeout + while datetime.now(timezone.utc) < try_until: + r = curl.http_get(url=f'http://127.0.0.1:{self._port}/') + if r.exit_code != 0: + return True + time.sleep(.1) + log.debug(f"Server still responding after {timeout}") + return False + def wait_live(self, timeout: timedelta): + curl = CurlClient(env=self.env, run_dir=self._tmp_dir, + timeout=timeout.total_seconds()) try_until = datetime.now(timezone.utc) + timeout while datetime.now(timezone.utc) < try_until: - if os.path.exists(self._log_file): + r = curl.http_get(url=f'http://127.0.0.1:{self._port}/') + if r.exit_code == 0: return True time.sleep(.1) log.error(f"Server still not responding after {timeout}") diff --git a/tests/server/dnsd.c b/tests/server/dnsd.c index 4c512a3701..03bfef87da 100644 --- a/tests/server/dnsd.c +++ b/tests/server/dnsd.c @@ -188,7 +188,7 @@ static const char *type2string(uint16_t qtype) * Return query (qname + type + class), type and id. */ static int store_incoming(const char *source, int query_id, - const unsigned char *data, size_t size, + const unsigned char *data, size_t datalen, unsigned char *qbuf, size_t qbuflen, size_t *qlen, uint16_t *qtype, uint16_t *idp) { @@ -200,12 +200,18 @@ static int store_incoming(const char *source, int query_id, uint16_t qd; const uint8_t *qptr; char name[256]; - size_t qsize; + size_t qsize, size; *qlen = 0; *qtype = 0; *idp = 0; + size = datalen; + if(datalen < 16) { + logmsg("query data size is too small: %ld", (long)datalen); + return -1; + } + snprintf(dumpfile, sizeof(dumpfile), "%s/dnsd.input", logdir); /* Open request dump file. */ diff --git a/tests/unit/unit1650.c b/tests/unit/unit1650.c index 1e6217176d..890027b193 100644 --- a/tests/unit/unit1650.c +++ b/tests/unit/unit1650.c @@ -106,7 +106,7 @@ static CURLcode test_unit1650(const char *arg) "\x6c\x04\x63\x75\x72\x6c\x00\x00\x05\x00\x01\xc0\x0c\x00\x05\x00" "\x01\x00\x00\x00\x37\x00\x11\x08\x61\x6e\x79\x77\x68\x65\x72\x65" "\x06\x72\x65\x61\x6c\x6c\x79\x00", 56, - CURL_DNS_TYPE_A, DOH_OK, "anywhere.really (55)"}, + CURL_DNS_TYPE_A, DOH_OK, NULL}, {DNS_FOO_EXAMPLE_COM, 49, CURL_DNS_TYPE_A, DOH_OK, "127.0.0.1 (55)"}, @@ -121,7 +121,7 @@ static CURLcode test_unit1650(const char *arg) "\x6c\x04\x63\x75\x72\x6c\x00\x00\x05\x00\x01\xc0\x0c\x00\x05\x00" "\x01\x00\x00\x00\x37\x00" "\x07\x03\x61\x6e\x79\xc0\x27\x00", 46, - CURL_DNS_TYPE_A, DOH_DNS_LABEL_LOOP, NULL}, + CURL_DNS_TYPE_A, DOH_OK, NULL}, /* packet with NSCOUNT == 1 */ {"\x00\x00\x01\x00\x00\x01\x00\x01\x00\x01\x00\x00\x04\x61\x61\x61" @@ -219,13 +219,6 @@ static CURLcode test_unit1650(const char *arg) ptr++; } } - for(u = 0; u < d.numcname; u++) { - size_t o; - curl_msnprintf(ptr, len, "%s ", curlx_dyn_ptr(&d.cname[u])); - o = strlen(ptr); - len -= o; - ptr += o; - } curl_msnprintf(ptr, len, "(%u)", d.ttl); de_cleanup(&d); if(resp[i].out && strcmp((const char *)buffer, resp[i].out)) { @@ -281,7 +274,6 @@ static CURLcode test_unit1650(const char *arg) (int)rc); abort_if(rc || strcmp((const char *)buffer, "127.0.0.1"), "bad address"); } - fail_if(d.numcname, "bad cname counter"); } #endif -- 2.47.3