mirror of
https://github.com/curl/curl.git
synced 2026-08-24 20:33:42 +03:00
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:
parent
791b6883ed
commit
2d30fd26a0
9 changed files with 390 additions and 439 deletions
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
|||
679
lib/vdns/doh.c
679
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,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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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):
|
||||
|
|
|
|||
|
|
@ -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}")
|
||||
|
|
|
|||
|
|
@ -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. */
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue