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
This commit is contained in:
Stefan Eissing 2026-08-08 13:20:15 +02:00 committed by Daniel Stenberg
parent 791b6883ed
commit 2d30fd26a0
No known key found for this signature in database
GPG key ID: 5CC908FDB71E12C2
9 changed files with 390 additions and 439 deletions

View file

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

View file

@ -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;
/* 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;
remain_ms = ctx->he_aaaa_await_ms -
Curl_resolv_elapsed_ms(data, ctx->resolv_id);
if(remain_ms <= 0)
return TRUE;
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;
}

View file

@ -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,40 +411,57 @@ 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;
}
#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_mid[DOH_SLOT_IPV6]);
if(result)
goto error;
async->queries_ongoing++;
}
#endif
/* 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_IPV4].probe_mid);
&dohp->probe_mid[DOH_SLOT_IPV4]);
if(result)
goto error;
async->queries_ongoing++;
}
#ifdef USE_IPV6
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_IPV6].probe_mid);
if(result)
goto error;
async->queries_ongoing++;
}
#endif
#ifdef USE_HTTPSRR
if(async->dns_queries & CURL_DNSQ_HTTPS) {
char *qname = NULL;
@ -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;
}
}
#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);
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 error;
goto out;
}
}
#ifdef USE_HTTPSRR
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 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);
}
}

View file

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

View file

@ -55,6 +55,12 @@ IPv6
<name>
HTTP GET using DoH (with HTTPS RR)
</name>
# 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.
<setenv>
CURL_DBG_HE_AAAA_AWAIT_MS=60000
</setenv>
<command>
https://foo.example.com:%HTTPSPORT/%TESTNUMBER --insecure --doh-insecure --doh-url https://%HOSTIP:%HTTPSPORT/%TESTNUMBER0001
</command>

View file

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

View file

@ -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_live(self, timeout: timedelta):
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:
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.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:
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}")

View file

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

View file

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