- Rename `Curl_resolv_unlink()` to `Curl_dns_entry_unlink()`.
- Change `Curl_dnscache_get()` to return CURLcode result. Returns
now `CURLE_COULDNT_RESOLVE_HOST` for "negative" cache entries.
- Add `Curl_dnscache_add_negative()` to put a "negative" entry
into the cache.
Closes #20864
The qname for the DoH query is modified if the port number is not 443, as
defined in the SVCB specification.
-When the DoH process has worked, `Curl_doh_is_resolved()` now also returns
+When the DoH process has worked, `Curl_doh_take_result()` now also returns
the relevant HTTPS RR value data in the `Curl_dns_entry` structure.
That is later accessed when the TLS session is being established, if ECH is
enabled (from `lib/vtls/openssl.c` as described above).
cw-out.c \
cw-pause.c \
dict.c \
+ dnscache.c \
doh.c \
dynhds.c \
easy.c \
cw-out.h \
cw-pause.h \
dict.h \
+ dnscache.h \
doh.h \
dynhds.h \
easy_lock.h \
* allocates memory also.
*/
-struct Curl_addrinfo *Curl_ipv4_resolve_r(const char *hostname, int port)
+struct Curl_addrinfo *Curl_ipv4_resolve_r(const char *hostname, uint16_t port)
{
struct Curl_addrinfo *ai = NULL;
struct hostent *h;
}
}
-static CURLcode async_ares_init(struct Curl_easy *data)
+static CURLcode async_ares_init(struct Curl_easy *data,
+ struct Curl_resolv_async *async)
{
- struct async_ares_ctx *ares = &data->state.async.ares;
+ struct async_ares_ctx *ares = &async->ares;
int status;
struct ares_options options;
int optmask = ARES_OPT_SOCK_STATE_CB;
return rc;
}
-static CURLcode async_ares_init_lazy(struct Curl_easy *data)
+static CURLcode async_ares_init_lazy(struct Curl_easy *data,
+ struct Curl_resolv_async *async)
{
- struct async_ares_ctx *ares = &data->state.async.ares;
+ struct async_ares_ctx *ares = &async->ares;
if(!ares->channel)
- return async_ares_init(data);
+ return async_ares_init(data, async);
return CURLE_OK;
}
-CURLcode Curl_async_get_impl(struct Curl_easy *data, void **impl)
+CURLcode Curl_async_get_impl(struct Curl_easy *data,
+ struct Curl_resolv_async *async,
+ void **impl)
{
- struct async_ares_ctx *ares = &data->state.async.ares;
+ struct async_ares_ctx *ares = &async->ares;
CURLcode result = CURLE_OK;
if(!ares->channel) {
- result = async_ares_init(data);
+ result = async_ares_init(data, async);
}
*impl = ares->channel;
return result;
/*
* async_ares_cleanup() cleans up async resolver data.
*/
-static void async_ares_cleanup(struct Curl_easy *data)
+static void async_ares_cleanup(struct Curl_resolv_async *async)
{
- struct async_ares_ctx *ares = &data->state.async.ares;
+ struct async_ares_ctx *ares = &async->ares;
if(ares->temp_ai) {
Curl_freeaddrinfo(ares->temp_ai);
ares->temp_ai = NULL;
#endif
}
-void Curl_async_ares_shutdown(struct Curl_easy *data)
+void Curl_async_ares_shutdown(struct Curl_easy *data,
+ struct Curl_resolv_async *async)
{
/* c-ares has a method to "cancel" operations on a channel, but
* as reported in #18216, this does not totally reset the channel
* and ares may get stuck.
* We need to destroy the channel and on demand create a new
* one to avoid that. */
- Curl_async_ares_destroy(data);
+ Curl_async_ares_destroy(data, async);
}
-void Curl_async_ares_destroy(struct Curl_easy *data)
+void Curl_async_ares_destroy(struct Curl_easy *data,
+ struct Curl_resolv_async *async)
{
- struct async_ares_ctx *ares = &data->state.async.ares;
+ struct async_ares_ctx *ares = &async->ares;
+ (void)data;
if(ares->channel) {
ares_destroy(ares->channel);
ares->channel = NULL;
}
- async_ares_cleanup(data);
+ async_ares_cleanup(async);
}
/*
CURLcode Curl_async_pollset(struct Curl_easy *data, struct easy_pollset *ps)
{
- struct async_ares_ctx *ares = &data->state.async.ares;
- if(ares->channel)
+ struct Curl_resolv_async *async = data->state.async;
+ struct async_ares_ctx *ares = async ? &async->ares : NULL;
+ if(ares && ares->channel)
return Curl_ares_pollset(data, ares->channel, ps);
return CURLE_OK;
}
/*
- * Curl_async_is_resolved() is called repeatedly to check if a previous
+ * Curl_async_take_result() is called repeatedly to check if a previous
* name resolve request has completed. It should also make sure to time-out if
* the operation seems to take too long.
*
* Returns normal CURLcode errors.
*/
-CURLcode Curl_async_is_resolved(struct Curl_easy *data,
- struct Curl_dns_entry **dns)
+CURLcode Curl_async_take_result(struct Curl_easy *data,
+ struct Curl_resolv_async *async,
+ struct Curl_dns_entry **pdns)
{
- struct async_ares_ctx *ares = &data->state.async.ares;
+ struct async_ares_ctx *ares = &async->ares;
CURLcode result = CURLE_OK;
- DEBUGASSERT(dns);
- *dns = NULL;
-
- if(data->state.async.done) {
- *dns = data->state.async.dns;
- return ares->result;
- }
+ DEBUGASSERT(pdns);
+ *pdns = NULL;
+ if(!ares)
+ return CURLE_FAILED_INIT;
if(Curl_ares_perform(ares->channel, 0) < 0) {
result = CURLE_UNRECOVERABLE_POLL;
if(!ares->num_pending) {
/* all c-ares operations done, what is the result to report? */
- Curl_resolv_unlink(data, &data->state.async.dns);
- data->state.async.done = TRUE;
result = ares->result;
if(ares->ares_status == ARES_SUCCESS && !result) {
- data->state.async.dns =
+ struct Curl_dns_entry *dns =
Curl_dnscache_mk_entry(data, &ares->temp_ai,
- data->state.async.hostname, 0,
- data->state.async.port, FALSE);
- if(!data->state.async.dns) {
+ async->hostname, async->port,
+ async->ip_version);
+ if(!dns) {
result = CURLE_OUT_OF_MEMORY;
goto out;
}
if(!lhrr)
result = CURLE_OUT_OF_MEMORY;
else
- data->state.async.dns->hinfo = lhrr;
+ dns->hinfo = lhrr;
}
#endif
- if(!result)
- result = Curl_dnscache_add(data, data->state.async.dns);
+ if(!result) {
+ result = Curl_dnscache_add(data, dns);
+ *pdns = dns;
+ }
}
/* if we have not found anything, report the proper
* CURLE_COULDNT_RESOLVE_* code */
- if(!result && !data->state.async.dns) {
+ if(!result && !*pdns) {
const char *msg = NULL;
if(ares->ares_status != ARES_SUCCESS)
msg = ares_strerror(ares->ares_status);
result = Curl_resolver_error(data, msg);
}
- if(result)
- Curl_resolv_unlink(data, &data->state.async.dns);
- *dns = data->state.async.dns;
CURL_TRC_DNS(data, "is_resolved() result=%d, dns=%sfound",
- result, *dns ? "" : "not ");
- async_ares_cleanup(data);
+ result, *pdns ? "" : "not ");
+ async_ares_cleanup(async);
}
+ else
+ return CURLE_AGAIN;
out:
ares->result = result;
* CURLE_OPERATION_TIMEDOUT if a time-out occurred, or other errors.
*/
CURLcode Curl_async_await(struct Curl_easy *data,
- struct Curl_dns_entry **dns)
+ struct Curl_resolv_async *async,
+ struct Curl_dns_entry **pdns)
{
- struct async_ares_ctx *ares = &data->state.async.ares;
+ struct async_ares_ctx *ares = &async->ares;
struct curltime start = *Curl_pgrs_now(data);
CURLcode result = CURLE_OK;
- DEBUGASSERT(dns);
- *dns = NULL; /* clear on entry */
+ DEBUGASSERT(pdns);
+ *pdns = NULL; /* clear on entry */
/* Wait for the name resolve query to complete or time out. */
while(!result) {
break;
}
- result = Curl_async_is_resolved(data, dns);
- if(result || data->state.async.done)
+ result = Curl_async_take_result(data, async, pdns);
+ if(result == CURLE_AGAIN)
+ result = CURLE_OK;
+ else if(result || *pdns)
break;
if(Curl_pgrsUpdate(data)) {
}
}
- /* Operation complete, if the lookup was successful we now have the entry
- in the cache. */
- data->state.async.done = TRUE;
- *dns = data->state.async.dns;
-
if(result)
ares_cancel(ares->channel);
return result;
struct hostent *hostent)
{
struct Curl_easy *data = (struct Curl_easy *)user_data;
- struct async_ares_ctx *ares = &data->state.async.ares;
+ struct Curl_resolv_async *async = data->state.async;
+ struct async_ares_ctx *ares = async ? &async->ares : NULL;
(void)timeouts; /* ignored */
+ if(!ares)
+ return;
if(ARES_EDESTRUCTION == status)
return;
if(ARES_SUCCESS == status) {
ares->ares_status = status; /* one success overrules any error */
- async_addr_concat(&ares->temp_ai,
- Curl_he2ai(hostent, data->state.async.port));
+ async_addr_concat(&ares->temp_ai, Curl_he2ai(hostent, async->port));
}
else if(ares->ares_status != ARES_SUCCESS) {
/* no success so far, remember last error */
struct ares_addrinfo *ares_ai)
{
struct Curl_easy *data = (struct Curl_easy *)user_data;
- struct async_ares_ctx *ares = &data->state.async.ares;
+ struct Curl_resolv_async *async = data->state.async;
+ struct async_ares_ctx *ares = async ? &async->ares : NULL;
+
(void)timeouts;
+ if(!ares)
+ return;
if(ares->ares_status != ARES_SUCCESS) /* do not overwrite success */
ares->ares_status = status;
if(status == ARES_SUCCESS) {
const ares_dns_record_t *dnsrec)
{
struct Curl_easy *data = user_data;
- struct async_ares_ctx *ares = &data->state.async.ares;
+ struct Curl_resolv_async *async = data->state.async;
+ struct async_ares_ctx *ares = async ? &async->ares : NULL;
+ if(!ares)
+ return;
(void)timeouts;
--ares->num_pending;
CURL_TRC_DNS(data, "ares: httpsrr done, status=%d, pending=%d, "
*
* Starts a name resolve for the given hostname and port number.
*/
-CURLcode Curl_async_getaddrinfo(struct Curl_easy *data, const char *hostname,
- int port, int ip_version)
+CURLcode Curl_async_getaddrinfo(struct Curl_easy *data,
+ struct Curl_resolv_async *async)
{
- struct async_ares_ctx *ares = &data->state.async.ares;
+ struct async_ares_ctx *ares = &async->ares;
#ifdef USE_HTTPSRR
char *rrname = NULL;
#endif
- if(async_ares_init_lazy(data))
+ if(async_ares_init_lazy(data, async))
return CURLE_FAILED_INIT;
- data->state.async.done = FALSE; /* not done */
- data->state.async.dns = NULL; /* clear */
- data->state.async.port = port;
- data->state.async.ip_version = ip_version;
- data->state.async.hostname = curlx_strdup(hostname);
- if(!data->state.async.hostname)
- return CURLE_OUT_OF_MEMORY;
#ifdef USE_HTTPSRR
- if(port != 443) {
- rrname = curl_maprintf("_%d._https.%s", port, hostname);
+ if(async->port != 443) {
+ rrname = curl_maprintf("_%d._https.%s", async->port, async->hostname);
if(!rrname)
return CURLE_OUT_OF_MEMORY;
}
int pf = PF_INET;
memset(&hints, 0, sizeof(hints));
#ifdef CURLRES_IPV6
- if((ip_version != CURL_IPRESOLVE_V4) && Curl_ipv6works(data)) {
+ if((async->ip_version != CURL_IPRESOLVE_V4) && Curl_ipv6works(data)) {
/* The stack seems to be IPv6-enabled */
- if(ip_version == CURL_IPRESOLVE_V6)
+ if(async->ip_version == CURL_IPRESOLVE_V6)
pf = PF_INET6;
else
pf = PF_UNSPEC;
* accordingly to save a call to getservbyname in inside C-Ares
*/
hints.ai_flags = ARES_AI_NUMERICSERV;
- curl_msnprintf(service, sizeof(service), "%d", port);
+ curl_msnprintf(service, sizeof(service), "%d", async->port);
ares->num_pending = 1;
- ares_getaddrinfo(ares->channel, data->state.async.hostname,
+ ares_getaddrinfo(ares->channel, async->hostname,
service, &hints, async_ares_addrinfo_cb, data);
}
#else
#if ARES_VERSION >= 0x010601 /* IPv6 supported since 1.6.1 */
- if((ip_version != CURL_IPRESOLVE_V4) && Curl_ipv6works(data)) {
+ if((async->ip_version != CURL_IPRESOLVE_V4) && Curl_ipv6works(data)) {
/* The stack seems to be IPv6-enabled */
/* areschannel is already setup in the Curl_open() function */
CURL_TRC_DNS(data, "asyn-ares: fire off query for A");
- ares_gethostbyname(ares->channel, data->state.async.hostname, PF_INET,
+ ares_gethostbyname(ares->channel, async->hostname, PF_INET,
async_ares_hostbyname_cb, data);
CURL_TRC_DNS(data, "asyn-ares: fire off query for AAAA");
ares->num_pending = 2;
- ares_gethostbyname(ares->channel, data->state.async.hostname, PF_INET6,
+ ares_gethostbyname(ares->channel, async->hostname, PF_INET6,
async_ares_hostbyname_cb, data);
}
else
/* areschannel is already setup in the Curl_open() function */
CURL_TRC_DNS(data, "asyn-ares: fire off query for A");
ares->num_pending = 1;
- ares_gethostbyname(ares->channel, data->state.async.hostname, PF_INET,
+ ares_gethostbyname(ares->channel, async->hostname, PF_INET,
async_ares_hostbyname_cb, data);
}
#endif
#ifdef USE_HTTPSRR
{
CURL_TRC_DNS(data, "asyn-ares: fire off query for HTTPSRR: %s",
- rrname ? rrname : data->state.async.hostname);
+ rrname ? rrname : async->hostname);
memset(&ares->hinfo, 0, sizeof(ares->hinfo));
ares->hinfo.port = -1;
ares->hinfo.rrname = rrname;
ares->num_pending++; /* one more */
ares_query_dnsrec(ares->channel,
- rrname ? rrname : data->state.async.hostname,
+ rrname ? rrname : async->hostname,
ARES_CLASS_IN, ARES_REC_TYPE_HTTPS,
async_ares_rr_done, data, NULL);
}
static CURLcode async_ares_set_dns_servers(struct Curl_easy *data,
bool reset_on_null)
{
- struct async_ares_ctx *ares = &data->state.async.ares;
+ struct Curl_resolv_async *async = data->state.async;
+ struct async_ares_ctx *ares = async ? &async->ares : NULL;
CURLcode result = CURLE_NOT_BUILT_IN;
const char *servers = data->set.str[STRING_DNS_SERVERS];
int ares_result = ARES_SUCCESS;
#ifdef HAVE_CARES_SERVERS_CSV
/* if channel is not there, this is a parameter check */
- if(ares->channel)
+ if(ares && ares->channel)
#ifdef HAVE_CARES_PORTS_CSV
ares_result = ares_set_servers_ports_csv(ares->channel, servers);
#else
CURLcode Curl_async_ares_set_dns_interface(struct Curl_easy *data)
{
#ifdef HAVE_CARES_LOCAL_DEV
- struct async_ares_ctx *ares = &data->state.async.ares;
+ struct Curl_resolv_async *async = data->state.async;
+ struct async_ares_ctx *ares = async ? &async->ares : NULL;
const char *interf = data->set.str[STRING_DNS_INTERFACE];
if(!interf)
interf = "";
/* if channel is not there, this is a parameter check */
- if(ares->channel)
+ if(ares && ares->channel)
ares_set_local_dev(ares->channel, interf);
return CURLE_OK;
CURLcode Curl_async_ares_set_dns_local_ip4(struct Curl_easy *data)
{
#ifdef HAVE_CARES_SET_LOCAL
- struct async_ares_ctx *ares = &data->state.async.ares;
+ struct Curl_resolv_async *async = data->state.async;
+ struct async_ares_ctx *ares = async ? &async->ares : NULL;
struct in_addr a4;
const char *local_ip4 = data->set.str[STRING_DNS_LOCAL_IP4];
}
/* if channel is not there yet, this is a parameter check */
- if(ares->channel)
+ if(ares && ares->channel)
ares_set_local_ip4(ares->channel, ntohl(a4.s_addr));
return CURLE_OK;
CURLcode Curl_async_ares_set_dns_local_ip6(struct Curl_easy *data)
{
#if defined(HAVE_CARES_SET_LOCAL) && defined(USE_IPV6)
- struct async_ares_ctx *ares = &data->state.async.ares;
+ struct Curl_resolv_async *async = data->state.async;
+ struct async_ares_ctx *ares = async ? &async->ares : NULL;
unsigned char a6[INET6_ADDRSTRLEN];
const char *local_ip6 = data->set.str[STRING_DNS_LOCAL_IP6];
}
/* if channel is not there, this is a parameter check */
- if(ares->channel)
+ if(ares && ares->channel)
ares_set_local_ip6(ares->channel, a6);
return CURLE_OK;
#endif
#include "urldata.h"
+#include "curl_trc.h"
#include "hostip.h"
#include "multiif.h"
#include "select.h"
void Curl_async_shutdown(struct Curl_easy *data)
{
+ if(data->state.async) {
+ CURL_TRC_DNS(data, "shutdown async");
#ifdef CURLRES_ARES
- Curl_async_ares_shutdown(data);
+ Curl_async_ares_shutdown(data, data->state.async);
#endif
#ifdef CURLRES_THREADED
- Curl_async_thrdd_shutdown(data);
+ Curl_async_thrdd_shutdown(data, data->state.async);
#endif
#ifndef CURL_DISABLE_DOH
- Curl_doh_cleanup(data);
+ Curl_doh_cleanup(data, data->state.async);
#endif
- Curl_safefree(data->state.async.hostname);
+ }
}
void Curl_async_destroy(struct Curl_easy *data)
{
+ if(data->state.async) {
+ CURL_TRC_DNS(data, "destroy async");
#ifdef CURLRES_ARES
- Curl_async_ares_destroy(data);
+ Curl_async_ares_destroy(data, data->state.async);
#endif
#ifdef CURLRES_THREADED
- Curl_async_thrdd_destroy(data);
+ Curl_async_thrdd_destroy(data, data->state.async);
#endif
#ifndef CURL_DISABLE_DOH
- Curl_doh_cleanup(data);
+ Curl_doh_cleanup(data, data->state.async);
#endif
- Curl_safefree(data->state.async.hostname);
+ Curl_safefree(data->state.async);
+ }
}
#endif /* USE_CURL_ASYNC */
#endif
}
-static void async_thrdd_destroy(struct Curl_easy *data);
-static void async_thrdd_shutdown(struct Curl_easy *data);
-
-CURLcode Curl_async_get_impl(struct Curl_easy *data, void **impl)
+CURLcode Curl_async_get_impl(struct Curl_easy *data,
+ struct Curl_resolv_async *async,
+ void **impl)
{
(void)data;
+ (void)async;
*impl = NULL;
return CURLE_OK;
}
/* Initialize context for threaded resolver */
static struct async_thrdd_addr_ctx *
addr_ctx_create(struct Curl_easy *data,
- const char *hostname, int port,
+ struct Curl_resolv_async *async,
const struct addrinfo *hints)
{
struct async_thrdd_addr_ctx *addr_ctx = curlx_calloc(1, sizeof(*addr_ctx));
return NULL;
addr_ctx->thread_hnd = curl_thread_t_null;
- addr_ctx->port = port;
+ addr_ctx->port = async->port;
addr_ctx->ref_count = 1;
#ifdef HAVE_GETADDRINFO
/* Copying hostname string because original can be destroyed by parent
* thread during gethostbyname execution.
*/
- addr_ctx->hostname = curlx_strdup(hostname);
+ addr_ctx->hostname = curlx_strdup(async->hostname);
if(!addr_ctx->hostname)
goto err_exit;
/*
* async_thrdd_destroy() cleans up async resolver data and thread handle.
*/
-static void async_thrdd_destroy(struct Curl_easy *data)
+static void async_thrdd_destroy(struct Curl_easy *data,
+ struct Curl_resolv_async *async)
{
- struct async_thrdd_ctx *thrdd = &data->state.async.thrdd;
+ struct async_thrdd_ctx *thrdd = &async->thrdd;
struct async_thrdd_addr_ctx *addr = thrdd->addr;
#ifdef USE_HTTPSRR_ARES
const ares_dns_record_t *dnsrec)
{
struct Curl_easy *data = user_data;
- struct async_thrdd_ctx *thrdd = &data->state.async.thrdd;
+ struct async_thrdd_ctx *thrdd = &data->state.async->thrdd;
(void)timeouts;
thrdd->rr.done = TRUE;
static CURLcode async_rr_start(struct Curl_easy *data, int port)
{
- struct async_thrdd_ctx *thrdd = &data->state.async.thrdd;
+ struct async_thrdd_ctx *thrdd = &data->state.async->thrdd;
int status;
char *rrname = NULL;
* Returns FALSE in case of failure, otherwise TRUE.
*/
static bool async_thrdd_init(struct Curl_easy *data,
- const char *hostname, int port, int ip_version,
+ struct Curl_resolv_async *async,
const struct addrinfo *hints)
{
- struct async_thrdd_ctx *thrdd = &data->state.async.thrdd;
+ struct async_thrdd_ctx *thrdd = &async->thrdd;
struct async_thrdd_addr_ctx *addr_ctx;
/* !checksrc! disable ERRNOVAR 1 */
int err = ENOMEM;
- if(thrdd->addr
-#ifdef USE_HTTPSRR_ARES
- || thrdd->rr.channel
-#endif
- ) {
- CURL_TRC_DNS(data, "starting new resolve, with previous not cleaned up");
- async_thrdd_destroy(data);
- DEBUGASSERT(!thrdd->addr);
+ DEBUGASSERT(!thrdd->addr);
#ifdef USE_HTTPSRR_ARES
- DEBUGASSERT(!thrdd->rr.channel);
+ DEBUGASSERT(!thrdd->rr.channel);
#endif
- }
- data->state.async.dns = NULL;
- data->state.async.done = FALSE;
- data->state.async.port = port;
- data->state.async.ip_version = ip_version;
- curlx_free(data->state.async.hostname);
- data->state.async.hostname = curlx_strdup(hostname);
- if(!data->state.async.hostname)
- goto err_exit;
-
- addr_ctx = addr_ctx_create(data, hostname, port, hints);
+ addr_ctx = addr_ctx_create(data, async, hints);
if(!addr_ctx)
goto err_exit;
thrdd->addr = addr_ctx;
}
#ifdef USE_HTTPSRR_ARES
- if(async_rr_start(data, port))
+ if(async_rr_start(data, async->port))
infof(data, "Failed HTTPS RR operation");
#endif
- CURL_TRC_DNS(data, "resolve thread started for of %s:%d", hostname, port);
+ CURL_TRC_DNS(data, "resolve thread started for of %s:%d",
+ async->hostname, async->port);
return TRUE;
err_exit:
CURL_TRC_DNS(data, "resolve thread failed init: %d", err);
- async_thrdd_destroy(data);
+ async_thrdd_destroy(data, async);
errno = err;
return FALSE;
}
-static void async_thrdd_shutdown(struct Curl_easy *data)
+static void async_thrdd_shutdown(struct Curl_easy *data,
+ struct Curl_resolv_async *async)
{
- struct async_thrdd_ctx *thrdd = &data->state.async.thrdd;
+ struct async_thrdd_ctx *thrdd = &async->thrdd;
struct async_thrdd_addr_ctx *addr_ctx = thrdd->addr;
curl_bit done;
* 'entry' may be NULL and then no data is returned
*/
static CURLcode asyn_thrdd_await(struct Curl_easy *data,
- struct async_thrdd_addr_ctx *addr_ctx,
+ struct Curl_resolv_async *async,
struct Curl_dns_entry **entry)
{
+ struct async_thrdd_addr_ctx *addr_ctx = async->thrdd.addr;
CURLcode result = CURLE_OK;
- if(addr_ctx->thread_hnd != curl_thread_t_null) {
+ if(addr_ctx && (addr_ctx->thread_hnd != curl_thread_t_null)) {
/* not interested in result? cancel, if still running... */
if(!entry)
- async_thrdd_shutdown(data);
+ async_thrdd_shutdown(data, async);
if(addr_ctx->thread_hnd != curl_thread_t_null) {
CURL_TRC_DNS(data, "resolve, wait for thread to finish");
}
}
- if(entry)
- result = Curl_async_is_resolved(data, entry);
+ if(entry) {
+ result = Curl_async_take_result(data, async, entry);
+ if(result == CURLE_AGAIN)
+ result = CURLE_OK;
+ }
}
- data->state.async.done = TRUE;
- if(entry)
- *entry = data->state.async.dns;
-
return result;
}
* Until we gain a way to signal the resolver threads to stop early, we must
* wait for them and ignore their results.
*/
-void Curl_async_thrdd_shutdown(struct Curl_easy *data)
+void Curl_async_thrdd_shutdown(struct Curl_easy *data,
+ struct Curl_resolv_async *async)
{
- async_thrdd_shutdown(data);
+ async_thrdd_shutdown(data, async);
}
-void Curl_async_thrdd_destroy(struct Curl_easy *data)
+void Curl_async_thrdd_destroy(struct Curl_easy *data,
+ struct Curl_resolv_async *async)
{
- struct async_thrdd_ctx *thrdd = &data->state.async.thrdd;
-
- if(thrdd->addr && !data->set.quick_exit) {
- (void)asyn_thrdd_await(data, thrdd->addr, NULL);
+ if(!data->set.quick_exit) {
+ (void)asyn_thrdd_await(data, async, NULL);
}
- async_thrdd_destroy(data);
+ async_thrdd_destroy(data, async);
}
/*
* This is the version for resolves-in-a-thread.
*/
CURLcode Curl_async_await(struct Curl_easy *data,
- struct Curl_dns_entry **dns)
+ struct Curl_resolv_async *async,
+ struct Curl_dns_entry **entry)
{
- struct async_thrdd_ctx *thrdd = &data->state.async.thrdd;
- if(thrdd->addr)
- return asyn_thrdd_await(data, thrdd->addr, dns);
- return CURLE_FAILED_INIT;
+ return asyn_thrdd_await(data, async, entry);
}
/*
- * Curl_async_is_resolved() is called repeatedly to check if a previous
+ * Curl_async_take_result() is called repeatedly to check if a previous
* name resolve request has completed. It should also make sure to time-out if
* the operation seems to take too long.
*/
-CURLcode Curl_async_is_resolved(struct Curl_easy *data,
- struct Curl_dns_entry **dns)
+CURLcode Curl_async_take_result(struct Curl_easy *data,
+ struct Curl_resolv_async *async,
+ struct Curl_dns_entry **pdns)
{
- struct async_thrdd_ctx *thrdd = &data->state.async.thrdd;
+ struct async_thrdd_ctx *thrdd = &async->thrdd;
curl_bit done = FALSE;
- DEBUGASSERT(dns);
- *dns = NULL;
-
- if(data->state.async.done) {
- *dns = data->state.async.dns;
- CURL_TRC_DNS(data, "threaded: is_resolved(), already done, dns=%sfound",
- *dns ? "" : "not ");
- return CURLE_OK;
- }
+ DEBUGASSERT(pdns);
+ *pdns = NULL;
#ifdef USE_HTTPSRR_ARES
/* best effort, ignore errors */
if(done) {
CURLcode result = CURLE_OK;
- data->state.async.done = TRUE;
- Curl_resolv_unlink(data, &data->state.async.dns);
Curl_expire_done(data, EXPIRE_ASYNC_NAME);
if(thrdd->addr->res) {
- data->state.async.dns =
+ struct Curl_dns_entry *dns =
Curl_dnscache_mk_entry(data, &thrdd->addr->res,
- data->state.async.hostname, 0,
- data->state.async.port, FALSE);
- if(!data->state.async.dns)
+ async->hostname, async->port,
+ async->ip_version);
+ if(!dns)
result = CURLE_OUT_OF_MEMORY;
#ifdef USE_HTTPSRR_ARES
if(!lhrr)
result = CURLE_OUT_OF_MEMORY;
else
- data->state.async.dns->hinfo = lhrr;
+ dns->hinfo = lhrr;
}
}
#endif
- if(!result && data->state.async.dns)
- result = Curl_dnscache_add(data, data->state.async.dns);
+ if(!result && dns) {
+ result = Curl_dnscache_add(data, dns);
+ *pdns = dns;
+ }
}
- if(!result && !data->state.async.dns)
+ if(!result && !*pdns)
result = Curl_resolver_error(data, NULL);
- if(result)
- Curl_resolv_unlink(data, &data->state.async.dns);
- *dns = data->state.async.dns;
CURL_TRC_DNS(data, "is_resolved() result=%d, dns=%sfound",
- result, *dns ? "" : "not ");
- async_thrdd_shutdown(data);
+ result, *pdns ? "" : "not ");
+ async_thrdd_shutdown(data, async);
return result;
}
else {
thrdd->addr->interval_end = elapsed + thrdd->addr->poll_interval;
Curl_expire(data, thrdd->addr->poll_interval, EXPIRE_ASYNC_NAME);
- return CURLE_OK;
+ return CURLE_AGAIN;
}
}
CURLcode Curl_async_pollset(struct Curl_easy *data, struct easy_pollset *ps)
{
- struct async_thrdd_ctx *thrdd = &data->state.async.thrdd;
+ struct async_thrdd_ctx *thrdd = &data->state.async->thrdd;
CURLcode result = CURLE_OK;
curl_bit thrd_done;
/*
* Curl_async_getaddrinfo() - for platforms without getaddrinfo
*/
-CURLcode Curl_async_getaddrinfo(struct Curl_easy *data, const char *hostname,
- int port, int ip_version)
+CURLcode Curl_async_getaddrinfo(struct Curl_easy *data,
+ struct Curl_resolv_async *async)
{
(void)ip_version;
/* fire up a new resolver thread! */
- if(async_thrdd_init(data, hostname, port, ip_version, NULL)) {
+ if(async_thrdd_init(data, async, NULL)) {
return CURLE_OK;
}
/*
* Curl_async_getaddrinfo() - for getaddrinfo
*/
-CURLcode Curl_async_getaddrinfo(struct Curl_easy *data, const char *hostname,
- int port, int ip_version)
+CURLcode Curl_async_getaddrinfo(struct Curl_easy *data,
+ struct Curl_resolv_async *async)
{
struct addrinfo hints;
int pf = PF_INET;
- CURL_TRC_DNS(data, "init threaded resolve of %s:%d", hostname, port);
+ CURL_TRC_DNS(data, "init threaded resolve of %s:%d",
+ async->hostname, async->port);
#ifdef CURLRES_IPV6
- if((ip_version != CURL_IPRESOLVE_V4) && Curl_ipv6works(data)) {
+ if((async->ip_version != CURL_IPRESOLVE_V4) && Curl_ipv6works(data)) {
/* The stack seems to be IPv6-enabled */
- if(ip_version == CURL_IPRESOLVE_V6)
+ if(async->ip_version == CURL_IPRESOLVE_V6)
pf = PF_INET6;
else
pf = PF_UNSPEC;
}
-#else
- (void)ip_version;
#endif /* CURLRES_IPV6 */
memset(&hints, 0, sizeof(hints));
SOCK_STREAM : SOCK_DGRAM;
/* fire up a new resolver thread! */
- if(async_thrdd_init(data, hostname, port, ip_version, &hints))
+ if(async_thrdd_init(data, async, &hints))
return CURLE_OK;
failf(data, "getaddrinfo() thread failed to start");
struct Curl_easy;
struct Curl_dns_entry;
+struct Curl_resolv_async;
#ifdef CURLRES_ASYNCH
* Get the resolver implementation instance (c-ares channel) or NULL
* for passing to application callback.
*/
-CURLcode Curl_async_get_impl(struct Curl_easy *data, void **impl);
+CURLcode Curl_async_get_impl(struct Curl_easy *easy,
+ struct Curl_resolv_async *async,
+ void **impl);
/* Curl_async_pollset()
*
CURLcode Curl_async_pollset(struct Curl_easy *data, struct easy_pollset *ps);
/*
- * Curl_async_is_resolved()
- *
- * Called repeatedly to check if a previous name resolve request has
- * completed. It should also make sure to time-out if the operation seems to
- * take too long.
- *
- * Returns normal CURLcode errors.
+ * Take the result of an async resolve operation.
+ * Returns CURLE_OK with `*pdns` != NULL, CURLE_AGAIN while still
+ * ongoing or an error code for a failed resolve.
*/
-CURLcode Curl_async_is_resolved(struct Curl_easy *data,
- struct Curl_dns_entry **dns);
+CURLcode Curl_async_take_result(struct Curl_easy *data,
+ struct Curl_resolv_async *async,
+ struct Curl_dns_entry **pdns);
/*
* Curl_async_await()
* CURLE_OPERATION_TIMEDOUT if a time-out occurred, or other errors.
*/
CURLcode Curl_async_await(struct Curl_easy *data,
- struct Curl_dns_entry **dns);
+ struct Curl_resolv_async *async,
+ struct Curl_dns_entry **pdns);
/*
* Curl_async_getaddrinfo() - when using this resolver
* Each resolver backend must of course make sure to return data in the
* correct format to comply with this.
*/
-CURLcode Curl_async_getaddrinfo(struct Curl_easy *data, const char *hostname,
- int port, int ip_version);
+CURLcode Curl_async_getaddrinfo(struct Curl_easy *data,
+ struct Curl_resolv_async *async);
#ifdef USE_ARES
/* common functions for c-ares and threaded resolver with HTTPSRR */
#endif
};
-void Curl_async_ares_shutdown(struct Curl_easy *data);
-void Curl_async_ares_destroy(struct Curl_easy *data);
+void Curl_async_ares_shutdown(struct Curl_easy *data,
+ struct Curl_resolv_async *async);
+void Curl_async_ares_destroy(struct Curl_easy *data,
+ struct Curl_resolv_async *async);
/* Set the DNS server to use by ares, from `data` settings. */
CURLcode Curl_async_ares_set_dns_servers(struct Curl_easy *data);
#endif
};
-void Curl_async_thrdd_shutdown(struct Curl_easy *data);
-void Curl_async_thrdd_destroy(struct Curl_easy *data);
+void Curl_async_thrdd_shutdown(struct Curl_easy *data,
+ struct Curl_resolv_async *async);
+void Curl_async_thrdd_destroy(struct Curl_easy *data,
+ struct Curl_resolv_async *async);
#endif /* CURLRES_THREADED */
#else /* CURLRES_ASYNCH */
/* convert these functions if an asynch resolver is not used */
-#define Curl_async_get_impl(x, y) (*(y) = NULL, CURLE_OK)
-#define Curl_async_is_resolved(x, y) CURLE_COULDNT_RESOLVE_HOST
-#define Curl_async_await(x, y) CURLE_COULDNT_RESOLVE_HOST
-#define Curl_async_global_init() CURLE_OK
-#define Curl_async_global_cleanup() Curl_nop_stmt
+#define Curl_async_get_impl(x, y, z) (*(z) = NULL, CURLE_OK)
+#define Curl_async_take_result(x, y, z) CURLE_COULDNT_RESOLVE_HOST
+#define Curl_async_await(x, y, z) CURLE_COULDNT_RESOLVE_HOST
+#define Curl_async_global_init() CURLE_OK
+#define Curl_async_global_cleanup() Curl_nop_stmt
#endif /* !CURLRES_ASYNCH */
#endif
#ifdef USE_CURL_ASYNC
-struct Curl_async {
+struct Curl_resolv_async {
#ifdef CURLRES_ARES
struct async_ares_ctx ares;
#elif defined(CURLRES_THREADED)
#ifndef CURL_DISABLE_DOH
struct doh_probes *doh; /* DoH specific data for this request */
#endif
- struct Curl_dns_entry *dns; /* result of resolving on success */
- char *hostname; /* copy of the params resolv started with */
- int port;
- int ip_version;
- BIT(done);
+ uint32_t id; /* unique id per easy handle of the resolve operation */
+ /* what is being resolved */
+ uint16_t port;
+ uint8_t ip_version;
+ char hostname[1];
};
/*
* of the connection. The resolve functions should really be changed
* to take a type parameter instead.
*/
- int ip_version = (af == AF_INET) ?
- CURL_IPRESOLVE_V4 : CURL_IPRESOLVE_WHATEVER;
+ uint8_t ip_version = (af == AF_INET) ?
+ CURL_IPRESOLVE_V4 : CURL_IPRESOLVE_WHATEVER;
#ifdef USE_IPV6
if(af == AF_INET6)
ip_version = CURL_IPRESOLVE_V6;
Curl_printable_address(h->addr, myhost, sizeof(myhost));
infof(data, "Name '%s' family %i resolved to '%s' family %i",
host, af, myhost, h_af);
- Curl_resolv_unlink(data, &h); /* this will NULL, potential free h */
+ Curl_dns_entry_unlink(data, &h); /* this will NULL, potential free h */
if(af != h_af) {
/* bad IP version combo, signal the caller to try another address
family if available */
DEBUGASSERT(conn->scheme);
DEBUGASSERT(dns);
- Curl_resolv_unlink(data, &data->state.dns[sockindex]);
+ Curl_dns_entry_unlink(data, &data->state.dns[sockindex]);
data->state.dns[sockindex] = dns;
#ifndef CURL_DISABLE_HTTP
DEBUGASSERT(conn->cfilter[sockindex]);
out:
if(result)
- Curl_resolv_unlink(data, &data->state.dns[sockindex]);
+ Curl_dns_entry_unlink(data, &data->state.dns[sockindex]);
return result;
}
* Given an IPv4 or IPv6 dotted string address, this converts it to a proper
* allocated Curl_addrinfo struct and returns it.
*/
-CURLcode Curl_str2addr(const char *dotted, int port,
+CURLcode Curl_str2addr(const char *dotted, uint16_t port,
struct Curl_addrinfo **addrp)
{
struct in_addr in;
#endif
bool Curl_is_ipaddr(const char *address);
-CURLcode Curl_str2addr(const char *dotted, int port,
+CURLcode Curl_str2addr(const char *dotted, uint16_t port,
struct Curl_addrinfo **addrp);
#ifdef USE_UNIX_SOCKETS
--- /dev/null
+/***************************************************************************
+ * _ _ ____ _
+ * Project ___| | | | _ \| |
+ * / __| | | | |_) | |
+ * | (__| |_| | _ <| |___
+ * \___|\___/|_| \_\_____|
+ *
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
+ *
+ * This software is licensed as described in the file COPYING, which
+ * you should have received as part of this distribution. The terms
+ * are also available at https://curl.se/docs/copyright.html.
+ *
+ * You may opt to use, copy, modify, merge, publish, distribute and/or sell
+ * copies of the Software, and permit persons to whom the Software is
+ * furnished to do so, under the terms of the COPYING file.
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+ * KIND, either express or implied.
+ *
+ * SPDX-License-Identifier: curl
+ *
+ ***************************************************************************/
+#include "curl_setup.h"
+
+#ifdef HAVE_NETINET_IN_H
+#include <netinet/in.h>
+#endif
+#ifdef HAVE_NETINET_IN6_H
+#include <netinet/in6.h>
+#endif
+#ifdef HAVE_NETDB_H
+#include <netdb.h>
+#endif
+#ifdef HAVE_ARPA_INET_H
+#include <arpa/inet.h>
+#endif
+#ifdef __VMS
+#include <in.h>
+#include <inet.h>
+#endif
+
+#include "urldata.h"
+#include "curl_addrinfo.h"
+#include "curl_share.h"
+#include "curl_trc.h"
+#include "dnscache.h"
+#include "hash.h"
+#include "httpsrr.h"
+#include "progress.h"
+#include "rand.h"
+#include "strcase.h"
+#include "curlx/inet_ntop.h"
+#include "curlx/inet_pton.h"
+#include "curlx/strcopy.h"
+#include "curlx/strparse.h"
+
+#define MAX_HOSTCACHE_LEN (255 + 7) /* max FQDN + colon + port number + zero */
+
+#define MAX_DNS_CACHE_SIZE 29999
+
+#ifdef CURLVERBOSE
+static const char *dnscache_ipv_str(uint8_t ip_version)
+{
+ switch(ip_version) {
+ case CURL_IPRESOLVE_WHATEVER:
+ return "A+AAAA";
+ case CURL_IPRESOLVE_V4:
+ return "A";
+#ifdef PF_INET6
+ case CURL_IPRESOLVE_V6:
+ return "AAAA";
+#endif
+ default:
+ DEBUGASSERT(0);
+ return "???";
+ }
+}
+#endif
+
+static void dnscache_entry_free(struct Curl_dns_entry *dns)
+{
+ Curl_freeaddrinfo(dns->addr);
+#ifdef USE_HTTPSRR
+ if(dns->hinfo) {
+ Curl_httpsrr_cleanup(dns->hinfo);
+ curlx_free(dns->hinfo);
+ }
+#endif
+ curlx_free(dns);
+}
+
+/*
+ * Create a hostcache id string for the provided host + port, to be used by
+ * the DNS caching. Without alloc. Return length of the id string.
+ */
+static size_t create_dnscache_id(const char *name,
+ size_t nlen, /* 0 or actual name length */
+ uint16_t port, char *ptr, size_t buflen)
+{
+ size_t len = nlen ? nlen : strlen(name);
+ DEBUGASSERT(buflen >= MAX_HOSTCACHE_LEN);
+ if(len > (buflen - 7))
+ len = buflen - 7;
+ /* store and lower case the name */
+ Curl_strntolower(ptr, name, len);
+ return curl_msnprintf(&ptr[len], 7, ":%u", port) + len;
+}
+
+struct dnscache_prune_data {
+ struct curltime now;
+ timediff_t oldest_ms; /* oldest time in cache not pruned. */
+ timediff_t max_age_ms;
+};
+
+/*
+ * This function is set as a callback to be called for every entry in the DNS
+ * cache when we want to prune old unused entries.
+ *
+ * Returning non-zero means remove the entry, return 0 to keep it in the
+ * cache.
+ */
+static int dnscache_entry_is_stale(void *datap, void *hc)
+{
+ struct dnscache_prune_data *prune = (struct dnscache_prune_data *)datap;
+ struct Curl_dns_entry *dns = (struct Curl_dns_entry *)hc;
+
+ if(dns->timestamp.tv_sec || dns->timestamp.tv_usec) {
+ /* get age in milliseconds */
+ timediff_t age = curlx_ptimediff_ms(&prune->now, &dns->timestamp);
+ if(!dns->addr)
+ age *= 2; /* negative entries age twice as fast */
+ if(age >= prune->max_age_ms)
+ return TRUE;
+ if(age > prune->oldest_ms)
+ prune->oldest_ms = age;
+ }
+ return FALSE;
+}
+
+/*
+ * Prune the DNS cache. This assumes that a lock has already been taken.
+ * Returns the 'age' of the oldest still kept entry - in milliseconds.
+ */
+static timediff_t dnscache_prune(struct Curl_hash *hostcache,
+ timediff_t cache_timeout_ms,
+ struct curltime now)
+{
+ struct dnscache_prune_data user;
+
+ user.max_age_ms = cache_timeout_ms;
+ user.now = now;
+ user.oldest_ms = 0;
+
+ Curl_hash_clean_with_criterium(hostcache,
+ (void *)&user,
+ dnscache_entry_is_stale);
+
+ return user.oldest_ms;
+}
+
+static struct Curl_dnscache *dnscache_get(struct Curl_easy *data)
+{
+ if(data->share && data->share->specifier & (1 << CURL_LOCK_DATA_DNS))
+ return &data->share->dnscache;
+ if(data->multi)
+ return &data->multi->dnscache;
+ return NULL;
+}
+
+static void dnscache_lock(struct Curl_easy *data,
+ struct Curl_dnscache *dnscache)
+{
+ if(data->share && dnscache == &data->share->dnscache)
+ Curl_share_lock(data, CURL_LOCK_DATA_DNS, CURL_LOCK_ACCESS_SINGLE);
+}
+
+static void dnscache_unlock(struct Curl_easy *data,
+ struct Curl_dnscache *dnscache)
+{
+ if(data->share && dnscache == &data->share->dnscache)
+ Curl_share_unlock(data, CURL_LOCK_DATA_DNS);
+}
+
+/*
+ * Library-wide function for pruning the DNS cache. This function takes and
+ * returns the appropriate locks.
+ */
+void Curl_dnscache_prune(struct Curl_easy *data)
+{
+ struct Curl_dnscache *dnscache = dnscache_get(data);
+ /* the timeout may be set -1 (forever) */
+ timediff_t timeout_ms = data->set.dns_cache_timeout_ms;
+
+ if(!dnscache || (timeout_ms == -1))
+ /* NULL hostcache means we cannot do it */
+ return;
+
+ dnscache_lock(data, dnscache);
+
+ do {
+ /* Remove outdated and unused entries from the hostcache */
+ timediff_t oldest_ms =
+ dnscache_prune(&dnscache->entries, timeout_ms, *Curl_pgrs_now(data));
+
+ if(Curl_hash_count(&dnscache->entries) > MAX_DNS_CACHE_SIZE)
+ /* prune the ones over half this age */
+ timeout_ms = oldest_ms / 2;
+ else
+ break;
+
+ /* if the cache size is still too big, use the oldest age as new prune
+ limit */
+ } while(timeout_ms);
+
+ dnscache_unlock(data, dnscache);
+}
+
+void Curl_dnscache_clear(struct Curl_easy *data)
+{
+ struct Curl_dnscache *dnscache = dnscache_get(data);
+ if(dnscache) {
+ dnscache_lock(data, dnscache);
+ Curl_hash_clean(&dnscache->entries);
+ dnscache_unlock(data, dnscache);
+ }
+}
+
+/* lookup address, returns entry if found and not stale */
+static CURLcode fetch_addr(struct Curl_easy *data,
+ struct Curl_dnscache *dnscache,
+ const char *hostname,
+ uint16_t port,
+ uint8_t ip_version,
+ struct Curl_dns_entry **pdns)
+{
+ struct Curl_dns_entry *dns = NULL;
+ char entry_id[MAX_HOSTCACHE_LEN];
+ size_t entry_len;
+ CURLcode result = CURLE_OK;
+
+ *pdns = NULL;
+ if(!dnscache)
+ return CURLE_OK;
+
+ /* Create an entry id, based upon the hostname and port */
+ entry_len = create_dnscache_id(hostname, 0, port,
+ entry_id, sizeof(entry_id));
+
+ /* See if it is already in our dns cache */
+ dns = Curl_hash_pick(&dnscache->entries, entry_id, entry_len + 1);
+
+ /* No entry found in cache, check if we might have a wildcard entry */
+ if(!dns && data->state.wildcard_resolve) {
+ entry_len = create_dnscache_id("*", 1, port, entry_id, sizeof(entry_id));
+
+ /* See if it is already in our dns cache */
+ dns = Curl_hash_pick(&dnscache->entries, entry_id, entry_len + 1);
+ }
+
+ if(dns && (data->set.dns_cache_timeout_ms != -1)) {
+ /* See whether the returned entry is stale. Done before we release lock */
+ struct dnscache_prune_data user;
+
+ user.now = *Curl_pgrs_now(data);
+ user.max_age_ms = data->set.dns_cache_timeout_ms;
+ user.oldest_ms = 0;
+
+ if(dnscache_entry_is_stale(&user, dns)) {
+ infof(data, "Hostname in DNS cache was stale, zapped");
+ dns = NULL; /* the memory deallocation is being handled by the hash */
+ Curl_hash_delete(&dnscache->entries, entry_id, entry_len + 1);
+ }
+ }
+
+ if(dns && dns->ip_version != ip_version) {
+ switch(dns->ip_version) {
+ case CURL_IPRESOLVE_WHATEVER: {
+ /* Do we have addresses that match the requested ip version? */
+ int pf = PF_INET;
+ bool found = FALSE;
+ struct Curl_addrinfo *addr = dns->addr;
+
+#ifdef PF_INET6
+ if(ip_version == CURL_IPRESOLVE_V6)
+ pf = PF_INET6;
+#endif
+
+ while(addr) {
+ if(addr->ai_family == pf) {
+ found = TRUE;
+ break;
+ }
+ addr = addr->ai_next;
+ }
+
+ if(!found) {
+ /* We assume that CURL_IPRESOLVE_WHATEVER means we tried to
+ * get addresses for all supported types, but there are none
+ * for the ip version we need. This is a negative resolve. */
+ CURL_TRC_DNS(data, "cache entry does not have type=%s addresses",
+ dnscache_ipv_str(ip_version));
+ dns = NULL;
+ result = CURLE_COULDNT_RESOLVE_HOST;
+ }
+ break;
+ }
+ default:
+ /* different families, we return NULL + OK, so a new resolve
+ * attempt may get started. */
+ dns = NULL;
+ break;
+ }
+ }
+
+ if(dns && !dns->addr) { /* negative entry */
+ dns = NULL;
+ result = CURLE_COULDNT_RESOLVE_HOST;
+ }
+ *pdns = dns;
+ return result;
+}
+
+/*
+ * Curl_dnscache_get() fetches a 'Curl_dns_entry' already in the DNS cache.
+ *
+ * Curl_resolv() checks initially and multi_runsingle() checks each time
+ * it discovers the handle in the state WAITRESOLVE whether the hostname
+ * has already been resolved and the address has already been stored in
+ * the DNS cache. This short circuits waiting for a lot of pending
+ * lookups for the same hostname requested by different handles.
+ *
+ * Returns the Curl_dns_entry entry pointer or NULL if not in the cache.
+ *
+ * The returned data *MUST* be "released" with Curl_dns_entry_unlink() after
+ * use, or we will leak memory!
+ */
+CURLcode Curl_dnscache_get(struct Curl_easy *data,
+ const char *hostname,
+ uint16_t port,
+ uint8_t ip_version,
+ struct Curl_dns_entry **pentry)
+{
+ struct Curl_dnscache *dnscache = dnscache_get(data);
+ struct Curl_dns_entry *dns = NULL;
+ CURLcode result = CURLE_OK;
+
+ dnscache_lock(data, dnscache);
+ result = fetch_addr(data, dnscache, hostname, port, ip_version, &dns);
+ if(!result && dns)
+ dns->refcount++; /* we pass out a reference */
+ else if(result) {
+ DEBUGASSERT(!dns);
+ dns = NULL;
+ }
+ dnscache_unlock(data, dnscache);
+
+ *pentry = dns;
+ return result;
+}
+
+#ifndef CURL_DISABLE_SHUFFLE_DNS
+/*
+ * Return # of addresses in a Curl_addrinfo struct
+ */
+static int num_addresses(const struct Curl_addrinfo *addr)
+{
+ int i = 0;
+ while(addr) {
+ addr = addr->ai_next;
+ i++;
+ }
+ return i;
+}
+
+UNITTEST CURLcode Curl_shuffle_addr(struct Curl_easy *data,
+ struct Curl_addrinfo **addr);
+/*
+ * Curl_shuffle_addr() shuffles the order of addresses in a 'Curl_addrinfo'
+ * struct by re-linking its linked list.
+ *
+ * The addr argument should be the address of a pointer to the head node of a
+ * `Curl_addrinfo` list and it will be modified to point to the new head after
+ * shuffling.
+ *
+ * Not declared static only to make it easy to use in a unit test!
+ *
+ * @unittest: 1608
+ */
+UNITTEST CURLcode Curl_shuffle_addr(struct Curl_easy *data,
+ struct Curl_addrinfo **addr)
+{
+ CURLcode result = CURLE_OK;
+ const int num_addrs = num_addresses(*addr);
+
+ if(num_addrs > 1) {
+ struct Curl_addrinfo **nodes;
+ CURL_TRC_DNS(data, "Shuffling %i addresses", num_addrs);
+
+ nodes = curlx_malloc(num_addrs * sizeof(*nodes));
+ if(nodes) {
+ int i;
+ unsigned int *rnd;
+ const size_t rnd_size = num_addrs * sizeof(*rnd);
+
+ /* build a plain array of Curl_addrinfo pointers */
+ nodes[0] = *addr;
+ for(i = 1; i < num_addrs; i++) {
+ nodes[i] = nodes[i - 1]->ai_next;
+ }
+
+ rnd = curlx_malloc(rnd_size);
+ if(rnd) {
+ /* Fisher-Yates shuffle */
+ if(Curl_rand(data, (unsigned char *)rnd, rnd_size) == CURLE_OK) {
+ struct Curl_addrinfo *swap_tmp;
+ for(i = num_addrs - 1; i > 0; i--) {
+ swap_tmp = nodes[rnd[i] % (unsigned int)(i + 1)];
+ nodes[rnd[i] % (unsigned int)(i + 1)] = nodes[i];
+ nodes[i] = swap_tmp;
+ }
+
+ /* relink list in the new order */
+ for(i = 1; i < num_addrs; i++) {
+ nodes[i - 1]->ai_next = nodes[i];
+ }
+
+ nodes[num_addrs - 1]->ai_next = NULL;
+ *addr = nodes[0];
+ }
+ curlx_free(rnd);
+ }
+ else
+ result = CURLE_OUT_OF_MEMORY;
+ curlx_free(nodes);
+ }
+ else
+ result = CURLE_OUT_OF_MEMORY;
+ }
+ return result;
+}
+#endif
+
+static struct Curl_dns_entry *
+dnscache_entry_create(struct Curl_easy *data,
+ struct Curl_addrinfo **paddr,
+ const char *hostname,
+ size_t hostlen, /* length or zero */
+ uint16_t port,
+ uint8_t ip_version,
+ bool permanent)
+{
+ struct Curl_dns_entry *dns = NULL;
+
+#ifndef CURL_DISABLE_SHUFFLE_DNS
+ /* shuffle addresses if requested */
+ if(data->set.dns_shuffle_addresses && paddr) {
+ CURLcode result = Curl_shuffle_addr(data, paddr);
+ if(result)
+ goto out;
+ }
+#else
+ (void)data;
+#endif
+ if(!hostlen)
+ hostlen = strlen(hostname);
+
+ /* Create a new cache entry */
+ dns = curlx_calloc(1, sizeof(struct Curl_dns_entry) + hostlen);
+ if(!dns)
+ goto out;
+
+ dns->refcount = 1; /* the cache has the first reference */
+ dns->addr = paddr ? *paddr : NULL; /* this is the address(es) */
+ if(permanent) {
+ dns->timestamp.tv_sec = 0; /* an entry that never goes stale */
+ dns->timestamp.tv_usec = 0; /* an entry that never goes stale */
+ }
+ else {
+ dns->timestamp = *Curl_pgrs_now(data);
+ }
+ dns->port = port;
+ dns->ip_version = ip_version;
+ if(hostlen)
+ memcpy(dns->hostname, hostname, hostlen);
+
+out:
+ if(paddr) {
+ if(!dns)
+ Curl_freeaddrinfo(*paddr);
+ *paddr = NULL;
+ }
+ return dns;
+}
+
+struct Curl_dns_entry *
+Curl_dnscache_mk_entry(struct Curl_easy *data,
+ struct Curl_addrinfo **paddr,
+ const char *hostname,
+ uint16_t port,
+ uint8_t ip_version)
+{
+ return dnscache_entry_create(data, paddr, hostname, 0,
+ port, ip_version, FALSE);
+}
+
+static struct Curl_dns_entry *
+dnscache_add_addr(struct Curl_easy *data,
+ struct Curl_dnscache *dnscache,
+ struct Curl_addrinfo **paddr,
+ const char *hostname,
+ size_t hlen, /* length or zero */
+ uint16_t port,
+ uint8_t ip_version,
+ bool permanent)
+{
+ char entry_id[MAX_HOSTCACHE_LEN];
+ size_t entry_len;
+ struct Curl_dns_entry *dns;
+ struct Curl_dns_entry *dns2;
+
+ dns = dnscache_entry_create(data, paddr, hostname, hlen, port,
+ ip_version, permanent);
+ if(!dns)
+ return NULL;
+
+ /* Create an entry id, based upon the hostname and port */
+ entry_len = create_dnscache_id(hostname, hlen, port,
+ entry_id, sizeof(entry_id));
+
+ /* Store the resolved data in our DNS cache. */
+ dns2 = Curl_hash_add(&dnscache->entries, entry_id, entry_len + 1,
+ (void *)dns);
+ if(!dns2) {
+ dnscache_entry_free(dns);
+ return NULL;
+ }
+
+ dns = dns2;
+ dns->refcount++; /* mark entry as in-use */
+ return dns;
+}
+
+CURLcode Curl_dnscache_add(struct Curl_easy *data,
+ struct Curl_dns_entry *entry)
+{
+ struct Curl_dnscache *dnscache = dnscache_get(data);
+ char id[MAX_HOSTCACHE_LEN];
+ size_t idlen;
+
+ if(!dnscache)
+ return CURLE_FAILED_INIT;
+ /* Create an entry id, based upon the hostname and port */
+ idlen = create_dnscache_id(entry->hostname, 0, entry->port, id, sizeof(id));
+
+ /* Store the resolved data in our DNS cache and up ref count */
+ dnscache_lock(data, dnscache);
+ if(!Curl_hash_add(&dnscache->entries, id, idlen + 1, (void *)entry)) {
+ dnscache_unlock(data, dnscache);
+ return CURLE_OUT_OF_MEMORY;
+ }
+ entry->refcount++;
+ dnscache_unlock(data, dnscache);
+ return CURLE_OK;
+}
+
+CURLcode Curl_dnscache_add_negative(struct Curl_easy *data,
+ const char *host,
+ uint16_t port,
+ uint8_t ip_version)
+{
+ struct Curl_dnscache *dnscache = dnscache_get(data);
+ struct Curl_dns_entry *dns;
+ DEBUGASSERT(dnscache);
+ if(!dnscache)
+ return CURLE_FAILED_INIT;
+
+ /* put this new host in the cache */
+ dns = dnscache_add_addr(data, dnscache, NULL, host, 0,
+ port, ip_version, FALSE);
+ if(dns) {
+ /* release the returned reference; the cache itself will keep the
+ * entry alive: */
+ dns->refcount--;
+ CURL_TRC_DNS(data, "cache negative name resolve for %s:%d type=%s",
+ host, port, dnscache_ipv_str(ip_version));
+ return CURLE_OK;
+ }
+ return CURLE_OUT_OF_MEMORY;
+}
+
+/*
+ * Curl_dns_entry_unlink() releases a reference to the given cached DNS entry.
+ * When the reference count reaches 0, the entry is destroyed. It is important
+ * that only one unlink is made for each Curl_resolv() call.
+ *
+ * May be called with 'data' == NULL for global cache.
+ */
+void Curl_dns_entry_unlink(struct Curl_easy *data,
+ struct Curl_dns_entry **pdns)
+{
+ if(*pdns) {
+ struct Curl_dnscache *dnscache = dnscache_get(data);
+ struct Curl_dns_entry *dns = *pdns;
+ *pdns = NULL;
+ dnscache_lock(data, dnscache);
+ dns->refcount--;
+ if(dns->refcount == 0)
+ dnscache_entry_free(dns);
+ dnscache_unlock(data, dnscache);
+ }
+}
+
+static void dnscache_entry_dtor(void *entry)
+{
+ struct Curl_dns_entry *dns = (struct Curl_dns_entry *)entry;
+ DEBUGASSERT(dns && (dns->refcount > 0));
+ dns->refcount--;
+ if(dns->refcount == 0)
+ dnscache_entry_free(dns);
+}
+
+/*
+ * Curl_dnscache_init() inits a new DNS cache.
+ */
+void Curl_dnscache_init(struct Curl_dnscache *dns, size_t size)
+{
+ Curl_hash_init(&dns->entries, size, Curl_hash_str, curlx_str_key_compare,
+ dnscache_entry_dtor);
+}
+
+void Curl_dnscache_destroy(struct Curl_dnscache *dns)
+{
+ Curl_hash_destroy(&dns->entries);
+}
+
+CURLcode Curl_loadhostpairs(struct Curl_easy *data)
+{
+ struct Curl_dnscache *dnscache = dnscache_get(data);
+ struct curl_slist *hostp;
+
+ if(!dnscache)
+ return CURLE_FAILED_INIT;
+
+ /* Default is no wildcard found */
+ data->state.wildcard_resolve = FALSE;
+
+ for(hostp = data->state.resolve; hostp; hostp = hostp->next) {
+ char entry_id[MAX_HOSTCACHE_LEN];
+ const char *host = hostp->data;
+ struct Curl_str source;
+ if(!host)
+ continue;
+ if(*host == '-') {
+ curl_off_t num = 0;
+ size_t entry_len;
+ host++;
+ if(!curlx_str_single(&host, '[')) {
+ if(curlx_str_until(&host, &source, MAX_IPADR_LEN, ']') ||
+ curlx_str_single(&host, ']') ||
+ curlx_str_single(&host, ':'))
+ continue;
+ }
+ else {
+ if(curlx_str_until(&host, &source, 4096, ':') ||
+ curlx_str_single(&host, ':')) {
+ continue;
+ }
+ }
+
+ if(!curlx_str_number(&host, &num, 0xffff)) {
+ /* Create an entry id, based upon the hostname and port */
+ entry_len = create_dnscache_id(curlx_str(&source),
+ curlx_strlen(&source), (uint16_t)num,
+ entry_id, sizeof(entry_id));
+ dnscache_lock(data, dnscache);
+ /* delete entry, ignore if it did not exist */
+ Curl_hash_delete(&dnscache->entries, entry_id, entry_len + 1);
+ dnscache_unlock(data, dnscache);
+ }
+ }
+ else {
+ struct Curl_dns_entry *dns;
+ struct Curl_addrinfo *head = NULL, *tail = NULL;
+ size_t entry_len;
+ char address[64];
+ curl_off_t tmpofft = 0;
+ uint16_t port = 0;
+ bool permanent = TRUE;
+ bool error = TRUE;
+ VERBOSE(const char *addresses = NULL);
+
+ if(*host == '+') {
+ host++;
+ permanent = FALSE;
+ }
+ if(!curlx_str_single(&host, '[')) {
+ if(curlx_str_until(&host, &source, MAX_IPADR_LEN, ']') ||
+ curlx_str_single(&host, ']'))
+ continue;
+ }
+ else {
+ if(curlx_str_until(&host, &source, 4096, ':'))
+ continue;
+ }
+ if(curlx_str_single(&host, ':') ||
+ curlx_str_number(&host, &tmpofft, 0xffff) ||
+ curlx_str_single(&host, ':'))
+ goto err;
+ port = (uint16_t)tmpofft;
+
+ VERBOSE(addresses = host);
+
+ /* start the address section */
+ while(*host) {
+ struct Curl_str target;
+ struct Curl_addrinfo *ai;
+ CURLcode result;
+
+ if(!curlx_str_single(&host, '[')) {
+ if(curlx_str_until(&host, &target, MAX_IPADR_LEN, ']') ||
+ curlx_str_single(&host, ']'))
+ goto err;
+ }
+ else {
+ if(curlx_str_until(&host, &target, 4096, ',')) {
+ if(curlx_str_single(&host, ','))
+ goto err;
+ /* survive nothing but a comma */
+ continue;
+ }
+ }
+#ifndef USE_IPV6
+ if(memchr(curlx_str(&target), ':', curlx_strlen(&target))) {
+ infof(data, "Ignoring resolve address '%.*s', missing IPv6 support.",
+ (int)curlx_strlen(&target), curlx_str(&target));
+ if(curlx_str_single(&host, ','))
+ goto err;
+ continue;
+ }
+#endif
+
+ if(curlx_strlen(&target) >= sizeof(address))
+ goto err;
+
+ memcpy(address, curlx_str(&target), curlx_strlen(&target));
+ address[curlx_strlen(&target)] = '\0';
+
+ result = Curl_str2addr(address, port, &ai);
+ if(result) {
+ infof(data, "Resolve address '%s' found illegal", address);
+ goto err;
+ }
+
+ if(tail) {
+ tail->ai_next = ai;
+ tail = tail->ai_next;
+ }
+ else {
+ head = tail = ai;
+ }
+ if(curlx_str_single(&host, ','))
+ break;
+ }
+
+ if(!head)
+ goto err;
+
+ error = FALSE;
+err:
+ if(error) {
+ failf(data, "Could not parse CURLOPT_RESOLVE entry '%s'", hostp->data);
+ Curl_freeaddrinfo(head);
+ return CURLE_SETOPT_OPTION_SYNTAX;
+ }
+
+ /* Create an entry id, based upon the hostname and port */
+ entry_len = create_dnscache_id(curlx_str(&source), curlx_strlen(&source),
+ port, entry_id, sizeof(entry_id));
+
+ dnscache_lock(data, dnscache);
+
+ /* See if it is already in our dns cache */
+ dns = Curl_hash_pick(&dnscache->entries, entry_id, entry_len + 1);
+
+ if(dns) {
+ infof(data, "RESOLVE %.*s:%u - old addresses discarded",
+ (int)curlx_strlen(&source),
+ curlx_str(&source), port);
+ /* delete old entry, there are two reasons for this
+ 1. old entry may have different addresses.
+ 2. even if entry with correct addresses is already in the cache,
+ but if it is close to expire, then by the time next http
+ request is made, it can get expired and pruned because old
+ entry is not necessarily marked as permanent.
+ 3. when adding a non-permanent entry, we want it to remove and
+ replace an existing permanent entry.
+ 4. when adding a non-permanent entry, we want it to get a "fresh"
+ timeout that starts _now_. */
+
+ Curl_hash_delete(&dnscache->entries, entry_id, entry_len + 1);
+ }
+
+ /* put this new host in the cache */
+ dns = dnscache_add_addr(data, dnscache, &head, curlx_str(&source),
+ curlx_strlen(&source), port,
+ CURL_IPRESOLVE_WHATEVER, permanent);
+ if(dns)
+ /* release the returned reference; the cache itself will keep the
+ * entry alive: */
+ dns->refcount--;
+
+ dnscache_unlock(data, dnscache);
+
+ if(!dns)
+ return CURLE_OUT_OF_MEMORY;
+
+ infof(data, "Added %.*s:%u:%s to DNS cache%s",
+ (int)curlx_strlen(&source), curlx_str(&source), port, addresses,
+ permanent ? "" : " (non-permanent)");
+
+ /* Wildcard hostname */
+ if(curlx_str_casecompare(&source, "*")) {
+ infof(data, "RESOLVE *:%u using wildcard", port);
+ data->state.wildcard_resolve = TRUE;
+ }
+ }
+ }
+ data->state.resolve = NULL; /* dealt with now */
+
+ return CURLE_OK;
+}
--- /dev/null
+#ifndef HEADER_CURL_DNSCACHE_H
+#define HEADER_CURL_DNSCACHE_H
+/***************************************************************************
+ * _ _ ____ _
+ * Project ___| | | | _ \| |
+ * / __| | | | |_) | |
+ * | (__| |_| | _ <| |___
+ * \___|\___/|_| \_\_____|
+ *
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
+ *
+ * This software is licensed as described in the file COPYING, which
+ * you should have received as part of this distribution. The terms
+ * are also available at https://curl.se/docs/copyright.html.
+ *
+ * You may opt to use, copy, modify, merge, publish, distribute and/or sell
+ * copies of the Software, and permit persons to whom the Software is
+ * furnished to do so, under the terms of the COPYING file.
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+ * KIND, either express or implied.
+ *
+ * SPDX-License-Identifier: curl
+ *
+ ***************************************************************************/
+#include "curl_setup.h"
+
+#include "hash.h"
+
+struct addrinfo;
+struct hostent;
+struct Curl_easy;
+struct connectdata;
+struct easy_pollset;
+struct Curl_https_rrinfo;
+struct Curl_multi;
+
+struct Curl_dns_entry {
+ struct Curl_addrinfo *addr;
+#ifdef USE_HTTPSRR
+ struct Curl_https_rrinfo *hinfo;
+#endif
+ /* timestamp == 0 -- permanent CURLOPT_RESOLVE entry (does not time out) */
+ struct curltime timestamp;
+ /* reference counter, entry is freed on reaching 0 */
+ uint32_t refcount;
+ /* hostname port number that resolved to addr. */
+ uint16_t port;
+ uint8_t ip_version;
+ /* hostname that resolved to addr. may be NULL (Unix domain sockets). */
+ char hostname[1];
+};
+
+/*
+ * Create a `Curl_dns_entry` with a reference count of 1.
+ * Use `Curl_dns_entry_unlink()` to release your hold on it.
+ *
+ * The call takes ownership of `addr`, even in case of failure, and always
+ * clears `*paddr`. It makes a copy of `hostname`.
+ *
+ * Returns entry or NULL on OOM.
+ */
+struct Curl_dns_entry *
+Curl_dnscache_mk_entry(struct Curl_easy *data,
+ struct Curl_addrinfo **paddr,
+ const char *hostname,
+ uint16_t port,
+ uint8_t ip_version);
+
+/* unlink a dns entry, frees all resources if it was the last reference.
+ * Always clears `*pdns`` */
+void Curl_dns_entry_unlink(struct Curl_easy *data,
+ struct Curl_dns_entry **pdns);
+
+
+struct Curl_dnscache {
+ struct Curl_hash entries;
+};
+
+/* init a new dns cache */
+void Curl_dnscache_init(struct Curl_dnscache *dns, size_t hashsize);
+
+void Curl_dnscache_destroy(struct Curl_dnscache *dns);
+
+/* prune old entries from the DNS cache */
+void Curl_dnscache_prune(struct Curl_easy *data);
+
+/* clear the DNS cache */
+void Curl_dnscache_clear(struct Curl_easy *data);
+
+/*
+ * Curl_dnscache_get() fetches a 'Curl_dns_entry' already in the DNS cache.
+ *
+ * Returns the Curl_dns_entry entry pointer or NULL if not in the cache.
+ *
+ * The returned data *MUST* be "released" with Curl_dns_entry_unlink() after
+ * use, or we will leak memory!
+ * Returns CURLE_OK or CURLE_COULDNT_RESOLVE_HOST when a negative
+ * entry was in the cache.
+ */
+CURLcode Curl_dnscache_get(struct Curl_easy *data,
+ const char *hostname,
+ uint16_t port,
+ uint8_t ip_version,
+ struct Curl_dns_entry **pentry);
+
+/*
+ * Curl_dnscache_addr() adds `entry` to the cache, increasing its
+ * reference count on success.
+ */
+CURLcode Curl_dnscache_add(struct Curl_easy *data,
+ struct Curl_dns_entry *entry);
+
+/* Store a "negative" entry for host:port, e.g. remember that
+ * it could not be resolved. */
+CURLcode Curl_dnscache_add_negative(struct Curl_easy *data,
+ const char *host,
+ uint16_t port,
+ uint8_t ip_version);
+
+/*
+ * Populate the cache with specified entries from CURLOPT_RESOLVE.
+ */
+CURLcode Curl_loadhostpairs(struct Curl_easy *data);
+
+#endif /* HEADER_CURL_DNSCACHE_H */
static void doh_probe_done(struct Curl_easy *data,
struct Curl_easy *doh, CURLcode result)
{
- struct doh_probes *dohp = data->state.async.doh;
- DEBUGASSERT(dohp);
- if(dohp) {
- struct doh_request *doh_req = Curl_meta_get(doh, CURL_EZM_DOH_PROBE);
- int i;
+ struct Curl_resolv_async *async = data->state.async;
+ struct doh_probes *dohp = async ? async->doh : NULL;
+ struct doh_request *doh_req = NULL;
+ int i;
- for(i = 0; i < DOH_SLOT_COUNT; ++i) {
- if(dohp->probe_resp[i].probe_mid == doh->mid)
- break;
- }
- if(i >= DOH_SLOT_COUNT) {
- failf(data, "unknown sub request done");
- return;
- }
+ if(!dohp) {
+ DEBUGASSERT(0);
+ return;
+ }
- dohp->pending--;
- infof(doh, "a DoH request is completed, %u to go", dohp->pending);
- dohp->probe_resp[i].result = result;
- /* We expect either the meta data still to exist or the sub request
- * to have already failed. */
- DEBUGASSERT(doh_req || result);
- if(doh_req) {
- if(!result) {
- dohp->probe_resp[i].dnstype = doh_req->dnstype;
- result = curlx_dyn_addn(&dohp->probe_resp[i].body,
- curlx_dyn_ptr(&doh_req->resp_body),
- curlx_dyn_len(&doh_req->resp_body));
- curlx_dyn_free(&doh_req->resp_body);
- }
- Curl_meta_remove(doh, CURL_EZM_DOH_PROBE);
- }
+ doh_req = Curl_meta_get(doh, CURL_EZM_DOH_PROBE);
+ /* A DoH response may arrive for a resolve operation already cancelled. */
+ if(doh_req && (doh_req->async_id != async->id)) {
+ CURL_TRC_DNS(data, "ignoring DoH response from a previous resolve");
+ return;
+ }
- if(result)
- infof(doh, "DoH request %s", curl_easy_strerror(result));
+ 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(data, "DoH: unknown sub request done");
+ return;
+ }
- if(!dohp->pending) {
- /* DoH completed, run the transfer picking up the results */
- Curl_multi_mark_dirty(data);
+ dohp->pending--;
+ infof(doh, "a DoH request is completed, %u to go", dohp->pending);
+ dohp->probe_resp[i].result = result;
+ /* We expect either the meta data still to exist or the sub request
+ * to have already failed. */
+ DEBUGASSERT(doh_req || result);
+ if(doh_req) {
+ if(!result) {
+ dohp->probe_resp[i].dnstype = doh_req->dnstype;
+ result = curlx_dyn_addn(&dohp->probe_resp[i].body,
+ curlx_dyn_ptr(&doh_req->resp_body),
+ curlx_dyn_len(&doh_req->resp_body));
+ curlx_dyn_free(&doh_req->resp_body);
}
+ Curl_meta_remove(doh, CURL_EZM_DOH_PROBE);
+ }
+
+ if(result)
+ infof(doh, "DoH request %s", curl_easy_strerror(result));
+
+ if(!dohp->pending) {
+ /* DoH completed, run the transfer picking up the results */
+ Curl_multi_mark_dirty(data);
}
}
DNStype dnstype,
const char *host,
const char *url, CURLM *multi,
+ uint32_t async_id,
uint32_t *pmid)
{
struct Curl_easy *doh = NULL;
doh_req = curlx_calloc(1, sizeof(*doh_req));
if(!doh_req)
return CURLE_OUT_OF_MEMORY;
+ doh_req->async_id = async_id;
doh_req->dnstype = dnstype;
curlx_dyn_init(&doh_req->resp_body, DYN_DOH_RESPONSE);
* a 'Curl_addrinfo *' with the address information.
*/
-CURLcode Curl_doh(struct Curl_easy *data, const char *hostname,
- int port, int ip_version)
+CURLcode Curl_doh(struct Curl_easy *data,
+ struct Curl_resolv_async *async)
{
CURLcode result = CURLE_OK;
struct doh_probes *dohp = NULL;
size_t i;
DEBUGASSERT(conn);
- DEBUGASSERT(!data->state.async.doh);
- DEBUGASSERT(hostname && hostname[0]);
- if(data->state.async.doh)
- Curl_doh_cleanup(data);
-
- data->state.async.done = FALSE;
- data->state.async.port = port;
- data->state.async.ip_version = ip_version;
- curlx_free(data->state.async.hostname);
- data->state.async.hostname = curlx_strdup(hostname);
- if(!data->state.async.hostname)
- return CURLE_OUT_OF_MEMORY;
+ DEBUGASSERT(!async->doh);
+ DEBUGASSERT(async->hostname[0]);
+ if(async->doh)
+ Curl_doh_cleanup(data, async);
/* start clean, consider allocating this struct on demand */
- data->state.async.doh = dohp = curlx_calloc(1, sizeof(struct doh_probes));
+ async->doh = dohp = curlx_calloc(1, sizeof(struct doh_probes));
if(!dohp)
return CURLE_OUT_OF_MEMORY;
}
conn->bits.doh = TRUE;
- dohp->host = data->state.async.hostname;
- dohp->port = data->state.async.port;
+ dohp->host = async->hostname;
+ dohp->port = async->port;
/* We are making sub easy handles and want to be called back when
* one is done. */
data->sub_xfer_done = doh_probe_done;
/* create IPv4 DoH request */
result = doh_probe_run(data, CURL_DNS_TYPE_A,
- hostname, data->set.str[STRING_DOH],
- data->multi,
+ async->hostname, data->set.str[STRING_DOH],
+ data->multi, async->id,
&dohp->probe_resp[DOH_SLOT_IPV4].probe_mid);
if(result)
goto error;
dohp->pending++;
#ifdef USE_IPV6
- if((ip_version != CURL_IPRESOLVE_V4) && Curl_ipv6works(data)) {
+ if((async->ip_version != CURL_IPRESOLVE_V4) && Curl_ipv6works(data)) {
/* create IPv6 DoH request */
result = doh_probe_run(data, CURL_DNS_TYPE_AAAA,
- hostname, data->set.str[STRING_DOH],
- data->multi,
+ async->hostname, data->set.str[STRING_DOH],
+ data->multi, async->id,
&dohp->probe_resp[DOH_SLOT_IPV6].probe_mid);
if(result)
goto error;
if(conn->scheme->protocol & PROTO_FAMILY_HTTP) {
/* Only use HTTPS RR for HTTP(S) transfers */
char *qname = NULL;
- if(port != PORT_HTTPS) {
- qname = curl_maprintf("_%d._https.%s", port, hostname);
+ if(async->port != PORT_HTTPS) {
+ qname = curl_maprintf("_%d._https.%s", async->port, async->hostname);
if(!qname)
goto error;
}
result = doh_probe_run(data, CURL_DNS_TYPE_HTTPS,
- qname ? qname : hostname, data->set.str[STRING_DOH],
- data->multi,
+ qname ? qname : async->hostname,
+ data->set.str[STRING_DOH], data->multi,
+ async->id,
&dohp->probe_resp[DOH_SLOT_HTTPS_RR].probe_mid);
curlx_free(qname);
if(result)
return CURLE_OK;
error:
- Curl_doh_cleanup(data);
+ Curl_doh_cleanup(data, async);
return result;
}
# endif
#endif
-CURLcode Curl_doh_is_resolved(struct Curl_easy *data,
- struct Curl_dns_entry **dnsp)
+CURLcode Curl_doh_take_result(struct Curl_easy *data,
+ struct Curl_dns_entry **pdns)
{
+ struct Curl_resolv_async *async = data->state.async;
+ struct doh_probes *dohp = async ? async->doh : NULL;
CURLcode result = CURLE_OK;
- struct doh_probes *dohp = data->state.async.doh;
struct dohentry de;
- *dnsp = NULL; /* defaults to no response */
+
+ *pdns = NULL; /* defaults to no response */
if(!dohp)
return CURLE_OUT_OF_MEMORY;
DOHcode rc[DOH_SLOT_COUNT];
int slot;
- /* Clear any result the might still be there */
- Curl_resolv_unlink(data, &data->state.async.dns);
-
memset(rc, 0, sizeof(rc));
/* remove DoH handles from multi handle and close them */
Curl_doh_close(data);
}
} /* next slot */
- result = CURLE_COULDNT_RESOLVE_HOST; /* until we know better */
if(!rc[DOH_SLOT_IPV4] || !rc[DOH_SLOT_IPV6]) {
/* we have an address, of one kind or other */
struct Curl_dns_entry *dns;
goto error;
/* we got a response, create a dns entry. */
- dns = Curl_dnscache_mk_entry(data, &ai, dohp->host, 0,
- dohp->port, FALSE);
- if(dns) {
- /* Now add and HTTPSRR information if we have */
+ dns = Curl_dnscache_mk_entry(data, &ai, dohp->host, dohp->port,
+ async->ip_version);
+ if(!dns) {
+ result = CURLE_OUT_OF_MEMORY;
+ goto error;
+ }
+
+ /* Now add and HTTPSRR information if we have */
#ifdef USE_HTTPSRR
- if(de.numhttps_rrs > 0 && result == CURLE_OK) {
- struct Curl_https_rrinfo *hrr = NULL;
- 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_resolv_unlink(data, &dns);
- goto error;
- }
- infof(data, "Some HTTPS RR to process");
+ if(de.numhttps_rrs > 0 && result == CURLE_OK) {
+ struct Curl_https_rrinfo *hrr = NULL;
+ 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");
#if defined(DEBUGBUILD) && defined(CURLVERBOSE)
- doh_print_httpsrr(data, hrr);
+ doh_print_httpsrr(data, hrr);
#endif
- dns->hinfo = hrr;
- }
-#endif /* USE_HTTPSRR */
- /* and add the entry to the cache */
- data->state.async.dns = dns;
- result = Curl_dnscache_add(data, dns);
- *dnsp = data->state.async.dns;
+ dns->hinfo = hrr;
}
+#endif /* USE_HTTPSRR */
+ /* and add the entry to the cache */
+ result = Curl_dnscache_add(data, dns);
+ *pdns = dns;
} /* address processing done */
+ else {
+ result = CONN_IS_PROXIED(data->conn) ? CURLE_COULDNT_RESOLVE_PROXY :
+ CURLE_COULDNT_RESOLVE_HOST;
+ }
- /* All done */
- data->state.async.done = TRUE;
} /* !dohp->pending */
else
/* wait for pending DoH transactions to complete */
- return CURLE_OK;
+ return CURLE_AGAIN;
error:
de_cleanup(&de);
- Curl_doh_cleanup(data);
+ Curl_doh_cleanup(data, async);
return result;
}
void Curl_doh_close(struct Curl_easy *data)
{
- struct doh_probes *doh = data->state.async.doh;
+ struct Curl_resolv_async *async = data->state.async;
+ struct doh_probes *doh = async ? async->doh : NULL;
if(doh && data->multi) {
struct Curl_easy *probe_data;
uint32_t mid;
}
}
-void Curl_doh_cleanup(struct Curl_easy *data)
+void Curl_doh_cleanup(struct Curl_easy *data,
+ struct Curl_resolv_async *async)
{
- struct doh_probes *dohp = data->state.async.doh;
+ struct doh_probes *dohp = async->doh;
if(dohp) {
int i;
Curl_doh_close(data);
for(i = 0; i < DOH_SLOT_COUNT; ++i) {
curlx_dyn_free(&dohp->probe_resp[i].body);
}
- Curl_safefree(data->state.async.doh);
+ Curl_safefree(async->doh);
}
}
/* enums outside of the #ifdef to make the types work in unitprotos.h even on
builds without DoH support */
+struct Curl_resolv_async;
+
typedef enum {
DOH_OK,
DOH_DNS_BAD_LABEL, /* 1 */
struct curl_slist *req_hds;
struct dynbuf resp_body;
size_t req_body_len;
+ uint32_t async_id; /* transfer specific id of the resolve operation */
DNStype dnstype;
};
struct doh_probes {
struct doh_response probe_resp[DOH_SLOT_COUNT];
unsigned int pending; /* still outstanding probes */
- int port;
+ uint16_t port;
const char *host;
};
* name and returns a 'Curl_addrinfo *' with the address information.
*/
-CURLcode Curl_doh(struct Curl_easy *data, const char *hostname,
- int port, int ip_version);
+CURLcode Curl_doh(struct Curl_easy *data,
+ struct Curl_resolv_async *async);
-CURLcode Curl_doh_is_resolved(struct Curl_easy *data,
- struct Curl_dns_entry **dnsp);
+CURLcode Curl_doh_take_result(struct Curl_easy *data,
+ struct Curl_dns_entry **dns);
#define DOH_MAX_ADDR 24
#define DOH_MAX_CNAME 4
};
void Curl_doh_close(struct Curl_easy *data);
-void Curl_doh_cleanup(struct Curl_easy *data);
+void Curl_doh_cleanup(struct Curl_easy *data,
+ struct Curl_resolv_async *async);
+#define Curl_doh_wanted(d) (!!(d)->set.doh)
+
#else /* CURL_DISABLE_DOH */
-#define Curl_doh(a, b, c, d, e) NULL
-#define Curl_doh_is_resolved(x, y) CURLE_COULDNT_RESOLVE_HOST
+#define Curl_doh(a, b) NULL
+#define Curl_doh_take_result(x, y) CURLE_COULDNT_RESOLVE_HOST
+#define Curl_doh_wanted(d) FALSE
#endif /* !CURL_DISABLE_DOH */
#endif /* HEADER_CURL_DOH_H */
Curl_meta_reset(data);
/* clear any resolve data */
Curl_async_shutdown(data);
- Curl_resolv_unlink(data, &data->state.dns[0]);
- Curl_resolv_unlink(data, &data->state.dns[1]);
+ Curl_dns_entry_unlink(data, &data->state.dns[0]);
+ Curl_dns_entry_unlink(data, &data->state.dns[1]);
/* zero out UserDefined data: */
Curl_freeset(data);
memset(&data->set, 0, sizeof(struct UserDefined));
/* cleanup */
if(dns_entry)
- Curl_resolv_unlink(data, &dns_entry);
+ Curl_dns_entry_unlink(data, &dns_entry);
if(result) {
ftp_state(data, ftpc, FTP_STOP);
}
#include "urldata.h"
#include "curl_addrinfo.h"
#include "curl_trc.h"
-#include "connect.h"
+#include "dnscache.h"
#include "hostip.h"
-#include "hash.h"
#include "httpsrr.h"
-#include "rand.h"
-#include "curl_share.h"
#include "url.h"
-#include "curlx/inet_ntop.h"
-#include "curlx/inet_pton.h"
#include "multiif.h"
-#include "doh.h"
#include "progress.h"
+#include "doh.h"
#include "select.h"
#include "strcase.h"
#include "easy_lock.h"
+#include "curlx/inet_ntop.h"
+#include "curlx/inet_pton.h"
#include "curlx/strcopy.h"
#include "curlx/strparse.h"
a = dns->addr;
infof(data, "Host %s:%d was resolved.",
- (dns->hostname[0] ? dns->hostname : "(none)"), dns->hostport);
+ (dns->hostname[0] ? dns->hostname : "(none)"), dns->port);
curlx_dyn_init(&out[0], 1024);
#ifdef CURLRES_IPV6
#define show_resolve_info(x, y) Curl_nop_stmt
#endif
-static void dnscache_entry_free(struct Curl_dns_entry *dns)
-{
- Curl_freeaddrinfo(dns->addr);
-#ifdef USE_HTTPSRR
- if(dns->hinfo) {
- Curl_httpsrr_cleanup(dns->hinfo);
- curlx_free(dns->hinfo);
- }
-#endif
- curlx_free(dns);
-}
-
/*
* Curl_printable_address() stores a printable version of the 1st address
* given in the 'ai' argument. The result will be stored in the buf that is
}
}
-/*
- * Create a hostcache id string for the provided host + port, to be used by
- * the DNS caching. Without alloc. Return length of the id string.
- */
-static size_t create_dnscache_id(const char *name,
- size_t nlen, /* 0 or actual name length */
- int port, char *ptr, size_t buflen)
-{
- size_t len = nlen ? nlen : strlen(name);
- DEBUGASSERT(buflen >= MAX_HOSTCACHE_LEN);
- if(len > (buflen - 7))
- len = buflen - 7;
- /* store and lower case the name */
- Curl_strntolower(ptr, name, len);
- return curl_msnprintf(&ptr[len], 7, ":%u", port) + len;
-}
-
-struct dnscache_prune_data {
- struct curltime now;
- timediff_t oldest_ms; /* oldest time in cache not pruned. */
- timediff_t max_age_ms;
-};
-
-/*
- * This function is set as a callback to be called for every entry in the DNS
- * cache when we want to prune old unused entries.
- *
- * Returning non-zero means remove the entry, return 0 to keep it in the
- * cache.
- */
-static int dnscache_entry_is_stale(void *datap, void *hc)
-{
- struct dnscache_prune_data *prune = (struct dnscache_prune_data *)datap;
- struct Curl_dns_entry *dns = (struct Curl_dns_entry *)hc;
-
- if(dns->timestamp.tv_sec || dns->timestamp.tv_usec) {
- /* get age in milliseconds */
- timediff_t age = curlx_ptimediff_ms(&prune->now, &dns->timestamp);
- if(!dns->addr)
- age *= 2; /* negative entries age twice as fast */
- if(age >= prune->max_age_ms)
- return TRUE;
- if(age > prune->oldest_ms)
- prune->oldest_ms = age;
- }
- return FALSE;
-}
-
-/*
- * Prune the DNS cache. This assumes that a lock has already been taken.
- * Returns the 'age' of the oldest still kept entry - in milliseconds.
- */
-static timediff_t dnscache_prune(struct Curl_hash *hostcache,
- timediff_t cache_timeout_ms,
- struct curltime now)
-{
- struct dnscache_prune_data user;
-
- user.max_age_ms = cache_timeout_ms;
- user.now = now;
- user.oldest_ms = 0;
-
- Curl_hash_clean_with_criterium(hostcache,
- (void *)&user,
- dnscache_entry_is_stale);
-
- return user.oldest_ms;
-}
-
-static struct Curl_dnscache *dnscache_get(struct Curl_easy *data)
-{
- if(data->share && data->share->specifier & (1 << CURL_LOCK_DATA_DNS))
- return &data->share->dnscache;
- if(data->multi)
- return &data->multi->dnscache;
- return NULL;
-}
-
-static void dnscache_lock(struct Curl_easy *data,
- struct Curl_dnscache *dnscache)
-{
- if(data->share && dnscache == &data->share->dnscache)
- Curl_share_lock(data, CURL_LOCK_DATA_DNS, CURL_LOCK_ACCESS_SINGLE);
-}
-
-static void dnscache_unlock(struct Curl_easy *data,
- struct Curl_dnscache *dnscache)
-{
- if(data->share && dnscache == &data->share->dnscache)
- Curl_share_unlock(data, CURL_LOCK_DATA_DNS);
-}
-
-/*
- * Library-wide function for pruning the DNS cache. This function takes and
- * returns the appropriate locks.
- */
-void Curl_dnscache_prune(struct Curl_easy *data)
-{
- struct Curl_dnscache *dnscache = dnscache_get(data);
- /* the timeout may be set -1 (forever) */
- timediff_t timeout_ms = data->set.dns_cache_timeout_ms;
-
- if(!dnscache || (timeout_ms == -1))
- /* NULL hostcache means we cannot do it */
- return;
-
- dnscache_lock(data, dnscache);
-
- do {
- /* Remove outdated and unused entries from the hostcache */
- timediff_t oldest_ms =
- dnscache_prune(&dnscache->entries, timeout_ms, *Curl_pgrs_now(data));
-
- if(Curl_hash_count(&dnscache->entries) > MAX_DNS_CACHE_SIZE)
- /* prune the ones over half this age */
- timeout_ms = oldest_ms / 2;
- else
- break;
-
- /* if the cache size is still too big, use the oldest age as new prune
- limit */
- } while(timeout_ms);
-
- dnscache_unlock(data, dnscache);
-}
-
-void Curl_dnscache_clear(struct Curl_easy *data)
-{
- struct Curl_dnscache *dnscache = dnscache_get(data);
- if(dnscache) {
- dnscache_lock(data, dnscache);
- Curl_hash_clean(&dnscache->entries);
- dnscache_unlock(data, dnscache);
- }
-}
-
#ifdef USE_ALARM_TIMEOUT
/* Beware this is a global and unique instance. This is used to store the
return address that we can jump back to from inside a signal handler. This
static curl_simple_lock curl_jmpenv_lock;
#endif
-/* lookup address, returns entry if found and not stale */
-static struct Curl_dns_entry *fetch_addr(struct Curl_easy *data,
- struct Curl_dnscache *dnscache,
- const char *hostname,
- int port,
- int ip_version)
-{
- struct Curl_dns_entry *dns = NULL;
- char entry_id[MAX_HOSTCACHE_LEN];
- size_t entry_len;
-
- if(!dnscache)
- return NULL;
-
- /* Create an entry id, based upon the hostname and port */
- entry_len = create_dnscache_id(hostname, 0, port,
- entry_id, sizeof(entry_id));
-
- /* See if it is already in our dns cache */
- dns = Curl_hash_pick(&dnscache->entries, entry_id, entry_len + 1);
-
- /* No entry found in cache, check if we might have a wildcard entry */
- if(!dns && data->state.wildcard_resolve) {
- entry_len = create_dnscache_id("*", 1, port, entry_id, sizeof(entry_id));
-
- /* See if it is already in our dns cache */
- dns = Curl_hash_pick(&dnscache->entries, entry_id, entry_len + 1);
- }
-
- if(dns && (data->set.dns_cache_timeout_ms != -1)) {
- /* See whether the returned entry is stale. Done before we release lock */
- struct dnscache_prune_data user;
-
- user.now = *Curl_pgrs_now(data);
- user.max_age_ms = data->set.dns_cache_timeout_ms;
- user.oldest_ms = 0;
-
- if(dnscache_entry_is_stale(&user, dns)) {
- infof(data, "Hostname in DNS cache was stale, zapped");
- dns = NULL; /* the memory deallocation is being handled by the hash */
- Curl_hash_delete(&dnscache->entries, entry_id, entry_len + 1);
- }
- }
-
- /* See if the returned entry matches the required resolve mode */
- if(dns && ip_version != CURL_IPRESOLVE_WHATEVER) {
- int pf = PF_INET;
- bool found = FALSE;
- struct Curl_addrinfo *addr = dns->addr;
-
-#ifdef PF_INET6
- if(ip_version == CURL_IPRESOLVE_V6)
- pf = PF_INET6;
-#endif
-
- while(addr) {
- if(addr->ai_family == pf) {
- found = TRUE;
- break;
- }
- addr = addr->ai_next;
- }
-
- if(!found) {
- infof(data, "Hostname in DNS cache does not have needed family, zapped");
- dns = NULL; /* the memory deallocation is being handled by the hash */
- Curl_hash_delete(&dnscache->entries, entry_id, entry_len + 1);
- }
- }
- return dns;
-}
-
-/*
- * Curl_dnscache_get() fetches a 'Curl_dns_entry' already in the DNS cache.
- *
- * Curl_resolv() checks initially and multi_runsingle() checks each time
- * it discovers the handle in the state WAITRESOLVE whether the hostname
- * has already been resolved and the address has already been stored in
- * the DNS cache. This short circuits waiting for a lot of pending
- * lookups for the same hostname requested by different handles.
- *
- * Returns the Curl_dns_entry entry pointer or NULL if not in the cache.
- *
- * The returned data *MUST* be "released" with Curl_resolv_unlink() after
- * use, or we will leak memory!
- */
-struct Curl_dns_entry *Curl_dnscache_get(struct Curl_easy *data,
- const char *hostname,
- int port,
- int ip_version)
-{
- struct Curl_dnscache *dnscache = dnscache_get(data);
- struct Curl_dns_entry *dns = NULL;
-
- dnscache_lock(data, dnscache);
-
- dns = fetch_addr(data, dnscache, hostname, port, ip_version);
- if(dns)
- dns->refcount++; /* we use it! */
-
- dnscache_unlock(data, dnscache);
-
- return dns;
-}
-
-#ifndef CURL_DISABLE_SHUFFLE_DNS
-/*
- * Return # of addresses in a Curl_addrinfo struct
- */
-static int num_addresses(const struct Curl_addrinfo *addr)
-{
- int i = 0;
- while(addr) {
- addr = addr->ai_next;
- i++;
- }
- return i;
-}
-
-UNITTEST CURLcode Curl_shuffle_addr(struct Curl_easy *data,
- struct Curl_addrinfo **addr);
-/*
- * Curl_shuffle_addr() shuffles the order of addresses in a 'Curl_addrinfo'
- * struct by re-linking its linked list.
- *
- * The addr argument should be the address of a pointer to the head node of a
- * `Curl_addrinfo` list and it will be modified to point to the new head after
- * shuffling.
- *
- * Not declared static only to make it easy to use in a unit test!
- *
- * @unittest: 1608
- */
-UNITTEST CURLcode Curl_shuffle_addr(struct Curl_easy *data,
- struct Curl_addrinfo **addr)
-{
- CURLcode result = CURLE_OK;
- const int num_addrs = num_addresses(*addr);
-
- if(num_addrs > 1) {
- struct Curl_addrinfo **nodes;
- infof(data, "Shuffling %i addresses", num_addrs);
-
- nodes = curlx_malloc(num_addrs * sizeof(*nodes));
- if(nodes) {
- int i;
- unsigned int *rnd;
- const size_t rnd_size = num_addrs * sizeof(*rnd);
-
- /* build a plain array of Curl_addrinfo pointers */
- nodes[0] = *addr;
- for(i = 1; i < num_addrs; i++) {
- nodes[i] = nodes[i - 1]->ai_next;
- }
-
- rnd = curlx_malloc(rnd_size);
- if(rnd) {
- /* Fisher-Yates shuffle */
- if(Curl_rand(data, (unsigned char *)rnd, rnd_size) == CURLE_OK) {
- struct Curl_addrinfo *swap_tmp;
- for(i = num_addrs - 1; i > 0; i--) {
- swap_tmp = nodes[rnd[i] % (unsigned int)(i + 1)];
- nodes[rnd[i] % (unsigned int)(i + 1)] = nodes[i];
- nodes[i] = swap_tmp;
- }
-
- /* relink list in the new order */
- for(i = 1; i < num_addrs; i++) {
- nodes[i - 1]->ai_next = nodes[i];
- }
-
- nodes[num_addrs - 1]->ai_next = NULL;
- *addr = nodes[0];
- }
- curlx_free(rnd);
- }
- else
- result = CURLE_OUT_OF_MEMORY;
- curlx_free(nodes);
- }
- else
- result = CURLE_OUT_OF_MEMORY;
- }
- return result;
-}
-#endif
-
-struct Curl_dns_entry *
-Curl_dnscache_mk_entry(struct Curl_easy *data,
- struct Curl_addrinfo **paddr,
- const char *hostname,
- size_t hostlen, /* length or zero */
- int port,
- bool permanent)
-{
- struct Curl_dns_entry *dns = NULL;
-
-#ifndef CURL_DISABLE_SHUFFLE_DNS
- /* shuffle addresses if requested */
- if(data->set.dns_shuffle_addresses && paddr) {
- CURLcode result = Curl_shuffle_addr(data, paddr);
- if(result)
- goto out;
- }
-#else
- (void)data;
-#endif
- if(!hostlen)
- hostlen = strlen(hostname);
-
- /* Create a new cache entry */
- dns = curlx_calloc(1, sizeof(struct Curl_dns_entry) + hostlen);
- if(!dns)
- goto out;
-
- dns->refcount = 1; /* the cache has the first reference */
- dns->addr = paddr ? *paddr : NULL; /* this is the address(es) */
- if(permanent) {
- dns->timestamp.tv_sec = 0; /* an entry that never goes stale */
- dns->timestamp.tv_usec = 0; /* an entry that never goes stale */
- }
- else {
- dns->timestamp = *Curl_pgrs_now(data);
- }
- dns->hostport = port;
- if(hostlen)
- memcpy(dns->hostname, hostname, hostlen);
-
-out:
- if(paddr) {
- if(!dns)
- Curl_freeaddrinfo(*paddr);
- *paddr = NULL;
- }
- return dns;
-}
-
-static struct Curl_dns_entry *
-dnscache_add_addr(struct Curl_easy *data,
- struct Curl_dnscache *dnscache,
- struct Curl_addrinfo **paddr,
- const char *hostname,
- size_t hlen, /* length or zero */
- int port,
- bool permanent)
-{
- char entry_id[MAX_HOSTCACHE_LEN];
- size_t entry_len;
- struct Curl_dns_entry *dns;
- struct Curl_dns_entry *dns2;
-
- dns = Curl_dnscache_mk_entry(data, paddr, hostname, hlen, port, permanent);
- if(!dns)
- return NULL;
-
- /* Create an entry id, based upon the hostname and port */
- entry_len = create_dnscache_id(hostname, hlen, port,
- entry_id, sizeof(entry_id));
-
- /* Store the resolved data in our DNS cache. */
- dns2 = Curl_hash_add(&dnscache->entries, entry_id, entry_len + 1,
- (void *)dns);
- if(!dns2) {
- dnscache_entry_free(dns);
- return NULL;
- }
-
- dns = dns2;
- dns->refcount++; /* mark entry as in-use */
- return dns;
-}
-
-CURLcode Curl_dnscache_add(struct Curl_easy *data,
- struct Curl_dns_entry *entry)
-{
- struct Curl_dnscache *dnscache = dnscache_get(data);
- char id[MAX_HOSTCACHE_LEN];
- size_t idlen;
-
- if(!dnscache)
- return CURLE_FAILED_INIT;
- /* Create an entry id, based upon the hostname and port */
- idlen = create_dnscache_id(entry->hostname, 0, entry->hostport,
- id, sizeof(id));
-
- /* Store the resolved data in our DNS cache and up ref count */
- dnscache_lock(data, dnscache);
- if(!Curl_hash_add(&dnscache->entries, id, idlen + 1, (void *)entry)) {
- dnscache_unlock(data, dnscache);
- return CURLE_OUT_OF_MEMORY;
- }
- entry->refcount++;
- dnscache_unlock(data, dnscache);
- return CURLE_OK;
-}
-
#ifdef USE_IPV6
/* return a static IPv6 ::1 for the name */
-static struct Curl_addrinfo *get_localhost6(int port, const char *name)
+static struct Curl_addrinfo *get_localhost6(uint16_t port, const char *name)
{
struct Curl_addrinfo *ca;
const size_t ss_size = sizeof(struct sockaddr_in6);
#endif
/* return a static IPv4 127.0.0.1 for the given name */
-static struct Curl_addrinfo *get_localhost(int port, const char *name)
+static struct Curl_addrinfo *get_localhost(uint16_t port, const char *name)
{
struct Curl_addrinfo *ca;
struct Curl_addrinfo *ca6;
return TRUE;
}
-static CURLcode store_negative_resolve(struct Curl_easy *data,
- const char *host,
- int port)
-{
- struct Curl_dnscache *dnscache = dnscache_get(data);
- struct Curl_dns_entry *dns;
- DEBUGASSERT(dnscache);
- if(!dnscache)
- return CURLE_FAILED_INIT;
+#ifdef USE_CURL_ASYNC
+static CURLcode hostip_async_new(struct Curl_easy *data,
+ const char *hostname,
+ uint16_t port,
+ uint8_t ip_version)
+{
+ struct Curl_resolv_async *async;
+ size_t hostlen = strlen(hostname);
+
+ DEBUGASSERT(!data->state.async);
+ /* struct size already includes the NUL for hostname */
+ async = curlx_calloc(1, sizeof(*async) + hostlen);
+ if(!async)
+ return CURLE_OUT_OF_MEMORY;
- /* put this new host in the cache */
- dns = dnscache_add_addr(data, dnscache, NULL, host, 0, port, FALSE);
- if(dns) {
- /* release the returned reference; the cache itself will keep the
- * entry alive: */
- dns->refcount--;
- infof(data, "Store negative name resolve for %s:%d", host, port);
- return CURLE_OK;
- }
- return CURLE_OUT_OF_MEMORY;
+ /* Even if this wraps (unlikely), it will be in time so far apart
+ * that it does not matter for all practical purposes. */
+ async->id = data->state.next_async_id++;
+ async->port = port;
+ async->ip_version = ip_version;
+ if(hostlen)
+ memcpy(async->hostname, hostname, hostlen);
+
+ data->state.async = async;
+ return CURLE_OK;
}
+#endif
-/*
- * Curl_resolv() is the main name resolve function within libcurl. It resolves
- * a name and returns a pointer to the entry in the 'entry' argument. This
- * function might return immediately if we are using asynch resolves. See the
- * return codes.
- *
- * The cache entry we return will get its 'inuse' counter increased when this
- * function is used. You MUST call Curl_resolv_unlink() later (when you are
- * done using this struct) to decrease the reference counter again.
- *
- * Return codes:
- * CURLE_OK = success, *entry set to non-NULL
- * CURLE_AGAIN = resolving in progress, *entry == NULL
- * CURLE_COULDNT_RESOLVE_HOST = error, *entry == NULL
- * CURLE_OPERATION_TIMEDOUT = timeout expired, *entry == NULL
- */
-CURLcode Curl_resolv(struct Curl_easy *data,
- const char *hostname,
- int port,
- int ip_version,
- bool allowDOH,
- struct Curl_dns_entry **entry)
+static CURLcode hostip_resolv(struct Curl_easy *data,
+ const char *hostname,
+ uint16_t port,
+ uint8_t ip_version,
+ bool allowDOH,
+ struct Curl_dns_entry **entry)
{
- struct Curl_dnscache *dnscache = dnscache_get(data);
struct Curl_dns_entry *dns = NULL;
struct Curl_addrinfo *addr = NULL;
bool respwait = FALSE;
size_t hostname_len;
- CURLcode result = CURLE_COULDNT_RESOLVE_HOST;
+ CURLcode r2, result = CURLE_COULDNT_RESOLVE_HOST;
*entry = NULL;
+#ifdef USE_CURL_ASYNC
+ if(data->state.async)
+ Curl_async_destroy(data);
+#endif
+
#ifndef CURL_DISABLE_DOH
data->conn->bits.doh = FALSE; /* default is not */
#else
(void)allowDOH;
#endif
- DEBUGASSERT(dnscache);
- if(!dnscache) {
- result = CURLE_BAD_FUNCTION_ARGUMENT;
- goto error;
- }
/* We should intentionally error and not resolve .onion TLDs */
hostname_len = strlen(hostname);
goto error;
}
+#ifdef DEBUGBUILD
+ CURL_TRC_DNS(data, "hostip_resolv(%s:%u, ip=%x)",
+ hostname, port, ip_version);
+ if((ip_version == CURL_IPRESOLVE_V6) &&
+ getenv("CURL_DBG_RESOLV_FAIL_IPV6")) {
+ infof(data, "DEBUG fail ipv6 resolve");
+ result = Curl_resolver_error(data, NULL);
+ goto error;
+ }
+#endif
/* Let's check our DNS cache first */
- dnscache_lock(data, dnscache);
- dns = fetch_addr(data, dnscache, hostname, port, ip_version);
- if(dns)
- dns->refcount++; /* we pass out the reference. */
- dnscache_unlock(data, dnscache);
+ r2 = Curl_dnscache_get(data, hostname, port, ip_version, &dns);
if(dns) {
infof(data, "Hostname %s was found in DNS cache", hostname);
result = CURLE_OK;
goto out;
}
+ else if(r2) {
+ DEBUGASSERT(!dns);
+ infof(data, "Negative DNS entry");
+ result = Curl_resolver_error(data, NULL);
+ goto error;
+ }
/* No luck, we need to resolve hostname. Notify user callback. */
if(data->set.resolver_start) {
void *resolver = NULL;
int st;
#ifdef CURLRES_ASYNCH
- result = Curl_async_get_impl(data, &resolver);
+ if(!data->state.async) {
+ result = hostip_async_new(data, hostname, port, ip_version);
+ if(result)
+ goto error;
+ }
+ result = Curl_async_get_impl(data, data->state.async, &resolver);
if(result)
goto error;
#endif
}
#ifndef CURL_DISABLE_DOH
else if(!Curl_is_ipaddr(hostname) && allowDOH && data->set.doh) {
- result = Curl_doh(data, hostname, port, ip_version);
+ if(!data->state.async) {
+ result = hostip_async_new(data, hostname, port, ip_version);
+ if(result)
+ goto error;
+ }
+ result = Curl_doh(data, data->state.async);
respwait = TRUE;
}
#endif
}
#ifdef CURLRES_ASYNCH
- result = Curl_async_getaddrinfo(data, hostname, port, ip_version);
+ if(!data->state.async) {
+ result = hostip_async_new(data, hostname, port, ip_version);
+ if(result)
+ goto error;
+ }
+ result = Curl_async_getaddrinfo(data, data->state.async);
respwait = TRUE;
#else
respwait = FALSE; /* no async waiting here */
if(result)
;
else if(dns) {
- if(!dns->addr) {
- infof(data, "Negative DNS entry");
- dns->refcount--;
- return CURLE_COULDNT_RESOLVE_HOST;
- }
*entry = dns;
return CURLE_OK;
}
else if(addr) {
/* we got a response, create a dns entry, add to cache, return */
- dns = Curl_dnscache_mk_entry(data, &addr, hostname, 0, port, FALSE);
+ dns = Curl_dnscache_mk_entry(data, &addr, hostname, port, ip_version);
if(!dns || Curl_dnscache_add(data, dns)) {
/* this is OOM or similar, do not store such negative resolves */
result = CURLE_OUT_OF_MEMORY;
}
else if(respwait) {
#ifdef USE_CURL_ASYNC
- if(!Curl_resolv_check(data, &dns)) {
+ result = Curl_resolv_take_result(data, &dns);
+ if(!result) {
*entry = dns;
return dns ? CURLE_OK : CURLE_AGAIN;
}
}
error:
if(dns)
- Curl_resolv_unlink(data, &dns);
+ Curl_dns_entry_unlink(data, &dns);
Curl_async_shutdown(data);
if(result == CURLE_COULDNT_RESOLVE_HOST)
- store_negative_resolve(data, hostname, port);
+ Curl_dnscache_add_negative(data, hostname, port, ip_version);
DEBUGASSERT(result);
return result;
}
CURLcode Curl_resolv_blocking(struct Curl_easy *data,
const char *hostname,
- int port,
- int ip_version,
- struct Curl_dns_entry **entry)
+ uint16_t port,
+ uint8_t ip_version,
+ struct Curl_dns_entry **pdns)
{
CURLcode result;
DEBUGASSERT(hostname && *hostname);
- *entry = NULL;
- result = Curl_resolv(data, hostname, port, ip_version, FALSE, entry);
+ *pdns = NULL;
+ /* We cannot do a blocking resolve using DoH currently */
+ result = hostip_resolv(data, hostname, port, ip_version, FALSE, pdns);
switch(result) {
case CURLE_OK:
- DEBUGASSERT(*entry);
+ DEBUGASSERT(*pdns);
return CURLE_OK;
+#ifdef USE_CURL_ASYNC
case CURLE_AGAIN:
- DEBUGASSERT(!*entry);
- result = Curl_async_await(data, entry);
- if(result || !*entry) {
- /* close the connection, since we cannot return failure here without
- cleaning up this connection properly. */
- connclose(data->conn, "async resolve failed");
- }
- return result;
+ DEBUGASSERT(!*pdns);
+ if(!data->state.async)
+ return CURLE_FAILED_INIT;
+ return Curl_async_await(data, data->state.async, pdns);
+#endif
default:
return result;
}
}
#endif /* USE_ALARM_TIMEOUT */
-/*
- * Curl_resolv_timeout() is the same as Curl_resolv() but specifies a
- * timeout. This function might return immediately if we are using asynch
- * resolves. See the return codes.
- *
- * The cache entry we return will get its 'inuse' counter increased when this
- * function is used. You MUST call Curl_resolv_unlink() later (when you are
- * done using this struct) to decrease the reference counter again.
- *
- * If built with a synchronous resolver and use of signals is not
- * disabled by the application, then a nonzero timeout will cause a
- * timeout after the specified number of milliseconds. Otherwise, timeout
- * is ignored.
- *
- * Return codes:
- * CURLE_OK = success, *entry set to non-NULL
- * CURLE_AGAIN = resolving in progress, *entry == NULL
- * CURLE_COULDNT_RESOLVE_HOST = error, *entry == NULL
- * CURLE_OPERATION_TIMEDOUT = timeout expired, *entry == NULL
- */
+#ifdef USE_ALARM_TIMEOUT
-CURLcode Curl_resolv_timeout(struct Curl_easy *data,
- const char *hostname,
- int port,
- int ip_version,
- struct Curl_dns_entry **entry,
- timediff_t timeoutms)
+static CURLcode resolv_alarm_timeout(struct Curl_easy *data,
+ const char *hostname,
+ uint16_t port,
+ uint8_t ip_version,
+ timediff_t timeoutms,
+ struct Curl_dns_entry **entry)
{
-#ifdef USE_ALARM_TIMEOUT
#ifdef HAVE_SIGACTION
struct sigaction keep_sigact; /* store the old struct here */
volatile bool keep_copysig = FALSE; /* whether old sigact has been saved */
#endif /* HAVE_SIGACTION */
volatile long timeout;
volatile unsigned int prev_alarm = 0;
-#endif /* USE_ALARM_TIMEOUT */
CURLcode result;
DEBUGASSERT(hostname && *hostname);
- *entry = NULL;
-
- if(timeoutms < 0)
- /* got an already expired timeout */
- return CURLE_OPERATION_TIMEDOUT;
-
-#ifdef USE_ALARM_TIMEOUT
- if(data->set.no_signal)
- /* Ignore the timeout when signals are disabled */
- timeout = 0;
- else
- timeout = (timeoutms > LONG_MAX) ? LONG_MAX : (long)timeoutms;
-
- if(!timeout
+ DEBUGASSERT(timeoutms > 0);
+ DEBUGASSERT(data->set.no_signal);
#ifndef CURL_DISABLE_DOH
- || data->set.doh
+ DEBUGASSERT(!data->set.doh);
#endif
- )
- /* USE_ALARM_TIMEOUT defined, but no timeout actually requested or resolve
- done using DoH */
- return Curl_resolv(data, hostname, port, ip_version, TRUE, entry);
+ *entry = NULL;
+ timeout = (timeoutms > LONG_MAX) ? LONG_MAX : (long)timeoutms;
if(timeout < 1000) {
/* The alarm() function only provides integer second resolution, so if
we want to wait less than one second we must bail out already now. */
prev_alarm = alarm(curlx_sltoui(timeout / 1000L));
}
-#else /* !USE_ALARM_TIMEOUT */
-#ifndef CURLRES_ASYNCH
- if(timeoutms)
- infof(data, "timeout on name lookup is not supported");
-#else
- (void)timeoutms;
-#endif
-#endif /* USE_ALARM_TIMEOUT */
-
/* Perform the actual name resolution. This might be interrupted by an
- * alarm if it takes too long.
- */
- result = Curl_resolv(data, hostname, port, ip_version, TRUE, entry);
+ * alarm if it takes too long. */
+ result = hostip_resolv(data, hostname, port, ip_version, TRUE, entry);
-#ifdef USE_ALARM_TIMEOUT
clean_up:
-
if(!prev_alarm)
/* deactivate a possibly active alarm before uninstalling the handler */
alarm(0);
else
alarm((unsigned int)alarm_set);
}
-#endif /* USE_ALARM_TIMEOUT */
return result;
}
-/*
- * Curl_resolv_unlink() releases a reference to the given cached DNS entry.
- * When the reference count reaches 0, the entry is destroyed. It is important
- * that only one unlink is made for each Curl_resolv() call.
- *
- * May be called with 'data' == NULL for global cache.
- */
-void Curl_resolv_unlink(struct Curl_easy *data, struct Curl_dns_entry **pdns)
-{
- if(*pdns) {
- struct Curl_dnscache *dnscache = dnscache_get(data);
- struct Curl_dns_entry *dns = *pdns;
- *pdns = NULL;
- dnscache_lock(data, dnscache);
- dns->refcount--;
- if(dns->refcount == 0)
- dnscache_entry_free(dns);
- dnscache_unlock(data, dnscache);
- }
-}
-
-static void dnscache_entry_dtor(void *entry)
-{
- struct Curl_dns_entry *dns = (struct Curl_dns_entry *)entry;
- DEBUGASSERT(dns && (dns->refcount > 0));
- dns->refcount--;
- if(dns->refcount == 0)
- dnscache_entry_free(dns);
-}
+#endif /* USE_ALARM_TIMEOUT */
/*
- * Curl_dnscache_init() inits a new DNS cache.
+ * Curl_resolv() is the main name resolve function within libcurl. It resolves
+ * a name and returns a pointer to the entry in the 'entry' argument. This
+ * function might return immediately if we are using asynch resolves. See the
+ * return codes.
+ *
+ * The cache entry we return will get its 'inuse' counter increased when this
+ * function is used. You MUST call Curl_dns_entry_unlink() later (when you are
+ * done using this struct) to decrease the reference counter again.
+ *
+ * If built with a synchronous resolver and use of signals is not
+ * disabled by the application, then a nonzero timeout will cause a
+ * timeout after the specified number of milliseconds. Otherwise, timeout
+ * is ignored.
+ *
+ * Return codes:
+ * CURLE_OK = success, *entry set to non-NULL
+ * CURLE_AGAIN = resolving in progress, *entry == NULL
+ * CURLE_COULDNT_RESOLVE_HOST = error, *entry == NULL
+ * CURLE_OPERATION_TIMEDOUT = timeout expired, *entry == NULL
*/
-void Curl_dnscache_init(struct Curl_dnscache *dns, size_t size)
-{
- Curl_hash_init(&dns->entries, size, Curl_hash_str, curlx_str_key_compare,
- dnscache_entry_dtor);
-}
-
-void Curl_dnscache_destroy(struct Curl_dnscache *dns)
-{
- Curl_hash_destroy(&dns->entries);
-}
-
-CURLcode Curl_loadhostpairs(struct Curl_easy *data)
+CURLcode Curl_resolv(struct Curl_easy *data,
+ const char *hostname,
+ uint16_t port,
+ uint8_t ip_version,
+ timediff_t timeoutms,
+ struct Curl_dns_entry **entry)
{
- struct Curl_dnscache *dnscache = dnscache_get(data);
- struct curl_slist *hostp;
+ DEBUGASSERT(hostname && *hostname);
+ *entry = NULL;
- if(!dnscache)
- return CURLE_FAILED_INIT;
+ if(timeoutms < 0)
+ /* got an already expired timeout */
+ return CURLE_OPERATION_TIMEDOUT;
- /* Default is no wildcard found */
- data->state.wildcard_resolve = FALSE;
-
- for(hostp = data->state.resolve; hostp; hostp = hostp->next) {
- char entry_id[MAX_HOSTCACHE_LEN];
- const char *host = hostp->data;
- struct Curl_str source;
- if(!host)
- continue;
- if(*host == '-') {
- curl_off_t num = 0;
- size_t entry_len;
- host++;
- if(!curlx_str_single(&host, '[')) {
- if(curlx_str_until(&host, &source, MAX_IPADR_LEN, ']') ||
- curlx_str_single(&host, ']') ||
- curlx_str_single(&host, ':'))
- continue;
- }
- else {
- if(curlx_str_until(&host, &source, 4096, ':') ||
- curlx_str_single(&host, ':')) {
- continue;
- }
- }
+#ifdef USE_ALARM_TIMEOUT
+ if(timeoutms && !data->set.no_signal) {
+ /* Cannot use ALARM when signals are disabled */
+ timeoutms = 0;
+ }
+ if(timeoutms && !Curl_doh_wanted(data)) {
+ return resolv_alarm_timeout(data, hostname, port, ip_version,
+ timeoutms, entry);
+ }
+#endif /* !USE_ALARM_TIMEOUT */
- if(!curlx_str_number(&host, &num, 0xffff)) {
- /* Create an entry id, based upon the hostname and port */
- entry_len = create_dnscache_id(curlx_str(&source),
- curlx_strlen(&source), (int)num,
- entry_id, sizeof(entry_id));
- dnscache_lock(data, dnscache);
- /* delete entry, ignore if it did not exist */
- Curl_hash_delete(&dnscache->entries, entry_id, entry_len + 1);
- dnscache_unlock(data, dnscache);
- }
- }
- else {
- struct Curl_dns_entry *dns;
- struct Curl_addrinfo *head = NULL, *tail = NULL;
- size_t entry_len;
- char address[64];
- curl_off_t port = 0;
- bool permanent = TRUE;
- bool error = TRUE;
- VERBOSE(const char *addresses = NULL);
-
- if(*host == '+') {
- host++;
- permanent = FALSE;
- }
- if(!curlx_str_single(&host, '[')) {
- if(curlx_str_until(&host, &source, MAX_IPADR_LEN, ']') ||
- curlx_str_single(&host, ']'))
- continue;
- }
- else {
- if(curlx_str_until(&host, &source, 4096, ':'))
- continue;
- }
- if(curlx_str_single(&host, ':') ||
- curlx_str_number(&host, &port, 0xffff) ||
- curlx_str_single(&host, ':'))
- goto err;
-
- VERBOSE(addresses = host);
-
- /* start the address section */
- while(*host) {
- struct Curl_str target;
- struct Curl_addrinfo *ai;
- CURLcode result;
-
- if(!curlx_str_single(&host, '[')) {
- if(curlx_str_until(&host, &target, MAX_IPADR_LEN, ']') ||
- curlx_str_single(&host, ']'))
- goto err;
- }
- else {
- if(curlx_str_until(&host, &target, 4096, ',')) {
- if(curlx_str_single(&host, ','))
- goto err;
- /* survive nothing but a comma */
- continue;
- }
- }
-#ifndef USE_IPV6
- if(memchr(curlx_str(&target), ':', curlx_strlen(&target))) {
- infof(data, "Ignoring resolve address '%.*s', missing IPv6 support.",
- (int)curlx_strlen(&target), curlx_str(&target));
- if(curlx_str_single(&host, ','))
- goto err;
- continue;
- }
+#ifndef CURLRES_ASYNCH
+ if(timeoutms)
+ infof(data, "timeout on name lookup is not supported");
#endif
- if(curlx_strlen(&target) >= sizeof(address))
- goto err;
-
- memcpy(address, curlx_str(&target), curlx_strlen(&target));
- address[curlx_strlen(&target)] = '\0';
-
- result = Curl_str2addr(address, (int)port, &ai);
- if(result) {
- infof(data, "Resolve address '%s' found illegal", address);
- goto err;
- }
-
- if(tail) {
- tail->ai_next = ai;
- tail = tail->ai_next;
- }
- else {
- head = tail = ai;
- }
- if(curlx_str_single(&host, ','))
- break;
- }
-
- if(!head)
- goto err;
-
- error = FALSE;
-err:
- if(error) {
- failf(data, "Could not parse CURLOPT_RESOLVE entry '%s'", hostp->data);
- Curl_freeaddrinfo(head);
- return CURLE_SETOPT_OPTION_SYNTAX;
- }
-
- /* Create an entry id, based upon the hostname and port */
- entry_len = create_dnscache_id(curlx_str(&source), curlx_strlen(&source),
- (int)port,
- entry_id, sizeof(entry_id));
-
- dnscache_lock(data, dnscache);
-
- /* See if it is already in our dns cache */
- dns = Curl_hash_pick(&dnscache->entries, entry_id, entry_len + 1);
-
- if(dns) {
- infof(data, "RESOLVE %.*s:%" CURL_FORMAT_CURL_OFF_T
- " - old addresses discarded", (int)curlx_strlen(&source),
- curlx_str(&source), port);
- /* delete old entry, there are two reasons for this
- 1. old entry may have different addresses.
- 2. even if entry with correct addresses is already in the cache,
- but if it is close to expire, then by the time next http
- request is made, it can get expired and pruned because old
- entry is not necessarily marked as permanent.
- 3. when adding a non-permanent entry, we want it to remove and
- replace an existing permanent entry.
- 4. when adding a non-permanent entry, we want it to get a "fresh"
- timeout that starts _now_. */
-
- Curl_hash_delete(&dnscache->entries, entry_id, entry_len + 1);
- }
-
- /* put this new host in the cache */
- dns = dnscache_add_addr(data, dnscache, &head, curlx_str(&source),
- curlx_strlen(&source), (int)port, permanent);
- if(dns)
- /* release the returned reference; the cache itself will keep the
- * entry alive: */
- dns->refcount--;
-
- dnscache_unlock(data, dnscache);
-
- if(!dns)
- return CURLE_OUT_OF_MEMORY;
-
- infof(data, "Added %.*s:%" CURL_FORMAT_CURL_OFF_T ":%s to DNS cache%s",
- (int)curlx_strlen(&source), curlx_str(&source), port, addresses,
- permanent ? "" : " (non-permanent)");
-
- /* Wildcard hostname */
- if(curlx_str_casecompare(&source, "*")) {
- infof(data, "RESOLVE *:%" CURL_FORMAT_CURL_OFF_T " using wildcard",
- port);
- data->state.wildcard_resolve = TRUE;
- }
- }
- }
- data->state.resolve = NULL; /* dealt with now */
-
- return CURLE_OK;
+ return hostip_resolv(data, hostname, port, ip_version, TRUE, entry);
}
+
#ifdef USE_CURL_ASYNC
-CURLcode Curl_resolv_check(struct Curl_easy *data,
- struct Curl_dns_entry **dns)
+CURLcode Curl_resolv_take_result(struct Curl_easy *data,
+ struct Curl_dns_entry **pdns)
{
+ struct Curl_resolv_async *async = data->state.async;
CURLcode result;
/* If async resolving is ongoing, this must be set */
- if(!data->state.async.hostname)
+ if(!async)
return CURLE_FAILED_INIT;
/* check if we have the name resolved by now (from someone else) */
- *dns = Curl_dnscache_get(data, data->state.async.hostname,
- data->state.async.port,
- data->state.async.ip_version);
- if(*dns) {
+ result = Curl_dnscache_get(data, async->hostname, async->port,
+ async->ip_version, pdns);
+ if(*pdns) {
/* Tell a possibly async resolver we no longer need the results. */
- infof(data, "Hostname '%s' was found in DNS cache",
- data->state.async.hostname);
+ infof(data, "Hostname '%s' was found in DNS cache", async->hostname);
Curl_async_shutdown(data);
- data->state.async.dns = *dns;
- data->state.async.done = TRUE;
return CURLE_OK;
}
+ else if(result) {
+ result = Curl_resolver_error(data, NULL);
+ return result;
+ }
#ifndef CURL_DISABLE_DOH
- if(data->conn->bits.doh) {
- result = Curl_doh_is_resolved(data, dns);
- if(result)
- Curl_resolver_error(data, NULL);
- }
+ if(data->conn->bits.doh)
+ result = Curl_doh_take_result(data, pdns);
else
#endif
- result = Curl_async_is_resolved(data, dns);
- if(*dns)
- show_resolve_info(data, *dns);
- if((result == CURLE_COULDNT_RESOLVE_HOST) ||
- (result == CURLE_COULDNT_RESOLVE_PROXY))
- store_negative_resolve(data, data->state.async.hostname,
- data->state.async.port);
+ result = Curl_async_take_result(data, async, pdns);
+
+ if(!result) {
+ DEBUGASSERT(*pdns);
+ show_resolve_info(data, *pdns);
+ }
+ else if(result == CURLE_AGAIN)
+ result = CURLE_OK;
+ else {
+ Curl_dnscache_add_negative(data, async->hostname, async->port,
+ async->ip_version);
+ Curl_async_shutdown(data);
+ Curl_resolver_error(data, NULL);
+ }
+
return result;
}
#endif
#endif
}
-/* Call this function after Curl_connect() has returned async=TRUE and
- then a successful name resolve has been received.
-
- Note: this function disconnects and frees the conn data in case of
- resolve failure */
-CURLcode Curl_once_resolved(struct Curl_easy *data,
- struct Curl_dns_entry *dns,
- bool *protocol_done)
-{
- CURLcode result;
- struct connectdata *conn = data->conn;
-
-#ifdef USE_CURL_ASYNC
- if(data->state.async.dns) {
- DEBUGASSERT(data->state.async.dns == dns);
- data->state.async.dns = NULL;
- }
-#endif
-
- result = Curl_setup_conn(data, dns, protocol_done);
-
- if(result) {
- Curl_detach_connection(data);
- Curl_conn_terminate(data, conn, TRUE);
- }
- return result;
-}
-
/*
* Curl_resolver_error() calls failf() with the appropriate message after a
* resolve error
struct easy_pollset;
struct Curl_https_rrinfo;
struct Curl_multi;
+struct Curl_dns_entry;
enum alpnid {
ALPN_none = 0,
ALPN_h3 = CURLALTSVC_H3
};
-struct Curl_dns_entry {
- struct Curl_addrinfo *addr;
-#ifdef USE_HTTPSRR
- struct Curl_https_rrinfo *hinfo;
-#endif
- /* timestamp == 0 -- permanent CURLOPT_RESOLVE entry (does not time out) */
- struct curltime timestamp;
- /* reference counter, entry is freed on reaching 0 */
- size_t refcount;
- /* hostname port number that resolved to addr. */
- int hostport;
- /* hostname that resolved to addr. may be NULL (Unix domain sockets). */
- char hostname[1];
-};
-
-struct Curl_dnscache {
- struct Curl_hash entries;
-};
-
bool Curl_host_is_ipnum(const char *hostname);
-/*
- * Curl_resolv() returns an entry with the info for the specified host
- * and port.
- *
- * The returned data *MUST* be "released" with Curl_resolv_unlink() after
- * use, or we will leak memory!
- */
-CURLcode Curl_resolv(struct Curl_easy *data,
- const char *hostname,
- int port,
- int ip_version,
- bool allowDOH,
- struct Curl_dns_entry **entry);
-
-CURLcode Curl_resolv_blocking(struct Curl_easy *data,
- const char *hostname,
- int port,
- int ip_version,
- struct Curl_dns_entry **entry);
-
-CURLcode Curl_resolv_timeout(struct Curl_easy *data,
- const char *hostname, int port,
- int ip_version,
- struct Curl_dns_entry **entry,
- timediff_t timeoutms);
-
#ifdef USE_IPV6
/* probe if it seems to work */
#define Curl_ipv6works(x) FALSE
#endif
-/* unlink a dns entry, potentially shared with a cache */
-void Curl_resolv_unlink(struct Curl_easy *data,
- struct Curl_dns_entry **pdns);
-
-/* init a new dns cache */
-void Curl_dnscache_init(struct Curl_dnscache *dns, size_t size);
-
-void Curl_dnscache_destroy(struct Curl_dnscache *dns);
-
-/* prune old entries from the DNS cache */
-void Curl_dnscache_prune(struct Curl_easy *data);
-
-/* clear the DNS cache */
-void Curl_dnscache_clear(struct Curl_easy *data);
-
/* IPv4 thread-safe resolve function used for synch and asynch builds */
-struct Curl_addrinfo *Curl_ipv4_resolve_r(const char *hostname, int port);
-
-CURLcode Curl_once_resolved(struct Curl_easy *data,
- struct Curl_dns_entry *dns,
- bool *protocol_done);
+struct Curl_addrinfo *Curl_ipv4_resolve_r(const char *hostname, uint16_t port);
/*
* Curl_printable_address() returns a printable version of the 1st address
* given in the 'ip' argument. The result will be stored in the buf that is
* bufsize bytes big.
*/
-void Curl_printable_address(const struct Curl_addrinfo *ai,
+void Curl_printable_address(const struct Curl_addrinfo *ip,
char *buf, size_t bufsize);
/*
- * Make a `Curl_dns_entry`.
- * Creates a dnscache entry *without* adding it to a dnscache. This allows
- * further modifications of the entry *before* then adding it to a cache.
- *
- * The entry is created with a reference count of 1.
- * Use `Curl_resolv_unlink()` to release your hold on it.
- *
- * The call takes ownership of `addr`, even in case of failure, and always
- * clears `*paddr`. It makes a copy of `hostname`.
- *
- * Returns entry or NULL on OOM.
- */
-struct Curl_dns_entry *
-Curl_dnscache_mk_entry(struct Curl_easy *data,
- struct Curl_addrinfo **paddr,
- const char *hostname,
- size_t hostlen, /* length or zero */
- int port,
- bool permanent);
-
-/*
- * Curl_dnscache_get() fetches a 'Curl_dns_entry' already in the DNS cache.
- *
- * Returns the Curl_dns_entry entry pointer or NULL if not in the cache.
+ * Curl_resolv() returns an entry with the info for the specified host
+ * and port.
*
- * The returned data *MUST* be "released" with Curl_resolv_unlink() after
+ * The returned data *MUST* be "released" with Curl_dns_entry_unlink() after
* use, or we will leak memory!
*/
-struct Curl_dns_entry *Curl_dnscache_get(struct Curl_easy *data,
- const char *hostname, int port,
- int ip_version);
+CURLcode Curl_resolv(struct Curl_easy *data,
+ const char *hostname,
+ uint16_t port,
+ uint8_t ip_version,
+ timediff_t timeoutms,
+ struct Curl_dns_entry **pdns);
-/*
- * Curl_dnscache_addr() adds `entry` to the cache, increasing its
- * reference count on success.
- */
-CURLcode Curl_dnscache_add(struct Curl_easy *data,
- struct Curl_dns_entry *entry);
+CURLcode Curl_resolv_blocking(struct Curl_easy *data,
+ const char *hostname,
+ uint16_t port,
+ uint8_t ip_version,
+ struct Curl_dns_entry **pdns);
-/*
- * Populate the cache with specified entries from CURLOPT_RESOLVE.
- */
-CURLcode Curl_loadhostpairs(struct Curl_easy *data);
+CURLcode Curl_resolv_timeout(struct Curl_easy *data,
+ const char *hostname, int port,
+ int ip_version,
+ struct Curl_dns_entry **entry,
+ timediff_t timeoutms);
#ifdef USE_CURL_ASYNC
-CURLcode Curl_resolv_check(struct Curl_easy *data,
- struct Curl_dns_entry **dns);
+CURLcode Curl_resolv_take_result(struct Curl_easy *data,
+ struct Curl_dns_entry **pdns);
#else
-#define Curl_resolv_check(x, y) CURLE_NOT_BUILT_IN
+#define Curl_resolv_take_result(x, y) CURLE_NOT_BUILT_IN
#endif
+
CURLcode Curl_resolv_pollset(struct Curl_easy *data,
struct easy_pollset *ps);
*/
struct Curl_addrinfo *Curl_sync_getaddrinfo(struct Curl_easy *data,
const char *hostname,
- int port,
- int ip_version);
+ uint16_t port,
+ uint8_t ip_version);
#endif
*/
struct Curl_addrinfo *Curl_sync_getaddrinfo(struct Curl_easy *data,
const char *hostname,
- int port,
- int ip_version)
+ uint16_t port,
+ uint8_t ip_version)
{
struct Curl_addrinfo *ai = NULL;
*
*/
struct Curl_addrinfo *Curl_ipv4_resolve_r(const char *hostname,
- int port)
+ uint16_t port)
{
#if !(defined(HAVE_GETADDRINFO) && defined(HAVE_GETADDRINFO_THREADSAFE)) && \
defined(HAVE_GETHOSTBYNAME_R_3)
*/
struct Curl_addrinfo *Curl_sync_getaddrinfo(struct Curl_easy *data,
const char *hostname,
- int port,
- int ip_version)
+ uint16_t port,
+ uint8_t ip_version)
{
struct addrinfo hints;
struct Curl_addrinfo *res;
data->state.done = TRUE; /* called now! */
data->state.recent_conn_id = conn->connection_id;
- Curl_resolv_unlink(data, &data->state.dns[0]); /* done with this */
- Curl_resolv_unlink(data, &data->state.dns[1]);
+ Curl_dns_entry_unlink(data, &data->state.dns[0]); /* done with this */
+ Curl_dns_entry_unlink(data, &data->state.dns[1]);
Curl_dnscache_prune(data);
if(multi_conn_should_close(conn, data, (bool)mdctx->premature)) {
CURLcode *resultp)
{
struct Curl_dns_entry *dns = NULL;
- CURLcode result;
CURLMcode mresult = CURLM_OK;
+ CURLcode result;
- result = Curl_resolv_check(data, &dns);
- CURL_TRC_DNS(data, "Curl_resolv_check() -> %d, %s",
+ result = Curl_resolv_take_result(data, &dns);
+ CURL_TRC_DNS(data, "Curl_resolv_take_result() -> %d, %s",
result, dns ? "found" : "missing");
+
/* Update sockets here, because the socket(s) may have been closed and the
application thus needs to be told, even if it is likely that the same
socket(s) will again be used further down. If the name has not yet been
bool connected;
/* Perform the next step in the connection phase, and then move on to the
WAITCONNECT state */
- result = Curl_once_resolved(data, dns, &connected);
-
- if(result)
- /* if Curl_once_resolved() returns failure, the connection struct is
- already freed and gone */
- data->conn = NULL; /* no more connection */
- else {
- /* call again please so that we get the next socket setup */
- mresult = CURLM_CALL_MULTI_PERFORM;
- if(connected)
- multistate(data, MSTATE_PROTOCONNECT);
- else {
- multistate(data, MSTATE_CONNECTING);
- }
+ result = Curl_setup_conn(data, dns, &connected);
+ if(result) {
+ /* setup failed, terminate connection */
+ struct connectdata *conn = data->conn;
+ Curl_detach_connection(data);
+ Curl_conn_terminate(data, conn, TRUE);
+ goto out;
}
+
+ /* call again please so that we get the next socket setup */
+ mresult = CURLM_CALL_MULTI_PERFORM;
+ multistate(data, connected ? MSTATE_PROTOCONNECT : MSTATE_CONNECTING);
}
+out:
if(result)
/* failure detected */
*stream_errorp = TRUE;
#include "hash.h"
#include "conncache.h"
#include "cshutdn.h"
-#include "hostip.h"
+#include "dnscache.h"
#include "multi_ev.h"
#include "multi_ntfy.h"
#include "psl.h"
data->psl = data->multi ? &data->multi->psl : NULL;
#endif
if(data->share->specifier & (1 << CURL_LOCK_DATA_DNS)) {
- Curl_resolv_unlink(data, &data->state.dns[0]);
- Curl_resolv_unlink(data, &data->state.dns[1]);
+ Curl_dns_entry_unlink(data, &data->state.dns[0]);
+ Curl_dns_entry_unlink(data, &data->state.dns[1]);
}
data->share->dirty--;
enum socks_state_t state;
struct bufq iobuf;
const char *hostname;
- int remote_port;
+ uint16_t remote_port;
const char *proxy_user;
const char *proxy_password;
CURLproxycode presult;
DEBUGASSERT(sx->hostname && *sx->hostname);
result = Curl_resolv(data, sx->hostname, sx->remote_port,
- cf->conn->ip_version, TRUE, &dns);
+ cf->conn->ip_version, 0, &dns);
if(result == CURLE_AGAIN) {
CURL_TRC_CF(data, cf, "SOCKS4 non-blocking resolve of %s", sx->hostname);
return CURLPX_OK;
}
else {
/* check if we have the name resolved by now */
- result = Curl_resolv_check(data, &dns);
+ result = Curl_resolv_take_result(data, &dns);
if(!result && !dns)
return CURLPX_OK;
}
if(result || !dns) {
failf(data, "Failed to resolve \"%s\" for SOCKS4 connect.", sx->hostname);
if(dns)
- Curl_resolv_unlink(data, &dns);
+ Curl_dns_entry_unlink(data, &dns);
return CURLPX_RESOLVE_HOST;
}
(unsigned char *)&saddr_in->sin_addr.s_addr, 4,
&nwritten);
- Curl_resolv_unlink(data, &dns); /* not used anymore from now on */
+ Curl_dns_entry_unlink(data, &dns); /* not used anymore from now on */
if(result || (nwritten != 4))
return CURLPX_SEND_REQUEST;
}
else {
- Curl_resolv_unlink(data, &dns);
+ Curl_dns_entry_unlink(data, &dns);
failf(data, "SOCKS4 connection to %s not supported", sx->hostname);
return CURLPX_RESOLVE_HOST;
}
/* SOCKS4 can only do IPv4, insist! */
cf->conn->ip_version = CURL_IPRESOLVE_V4;
- CURL_TRC_CF(data, cf, "SOCKS4%s communication to%s %s:%d",
+ CURL_TRC_CF(data, cf, "SOCKS4%s communication to%s %s:%u",
sx->socks4a ? "a" : "",
cf->conn->bits.httpproxy ? " HTTP proxy" : "",
sx->hostname, sx->remote_port);
result = Curl_bufq_write(&sx->iobuf, req, 2, &nwritten);
if(result || (nwritten != 2))
return CURLPX_SEND_REQUEST;
- CURL_TRC_CF(data, cf, "SOCKS5 connect to %s:%d (remotely resolved)",
+ CURL_TRC_CF(data, cf, "SOCKS5 connect to %s:%u (remotely resolved)",
sx->hostname, sx->remote_port);
return CURLPX_OK;
}
DEBUGASSERT(sx->hostname && *sx->hostname);
result = Curl_resolv(data, sx->hostname, sx->remote_port,
- cf->conn->ip_version, TRUE, &dns);
+ cf->conn->ip_version, 0, &dns);
if(result == CURLE_AGAIN) {
CURL_TRC_CF(data, cf, "SOCKS5 non-blocking resolve of %s", sx->hostname);
return CURLPX_OK;
}
else {
/* check if we have the name resolved by now */
- result = Curl_resolv_check(data, &dns);
+ result = Curl_resolv_take_result(data, &dns);
if(!result && !dns)
return CURLPX_OK;
}
destlen = 4;
saddr_in = (struct sockaddr_in *)(void *)hp->ai_addr;
destination = (const unsigned char *)&saddr_in->sin_addr.s_addr;
- CURL_TRC_CF(data, cf, "SOCKS5 connect to %s:%d (locally resolved)",
+ CURL_TRC_CF(data, cf, "SOCKS5 connect to %s:%u (locally resolved)",
dest, sx->remote_port);
}
#ifdef USE_IPV6
destlen = 16;
saddr_in6 = (struct sockaddr_in6 *)(void *)hp->ai_addr;
destination = (const unsigned char *)&saddr_in6->sin6_addr.s6_addr;
- CURL_TRC_CF(data, cf, "SOCKS5 connect to [%s]:%d (locally resolved)",
+ CURL_TRC_CF(data, cf, "SOCKS5 connect to [%s]:%u (locally resolved)",
dest, sx->remote_port);
}
#endif
out:
if(dns)
- Curl_resolv_unlink(data, &dns);
+ Curl_dns_entry_unlink(data, &dns);
*done = (presult == CURLPX_OK);
return presult;
}
case SOCKS5_ST_START:
if(cf->conn->bits.httpproxy)
- CURL_TRC_CF(data, cf, "SOCKS5: connecting to HTTP proxy %s port %d",
+ CURL_TRC_CF(data, cf, "SOCKS5: connecting to HTTP proxy %s port %u",
sx->hostname, sx->remote_port);
presult = socks5_req0_init(cf, sx, data);
if(presult)
sockindex == SECONDARYSOCKET ?
conn->secondaryhostname : conn->host.name;
sx->remote_port =
- conn->bits.httpproxy ? (int)conn->http_proxy.port :
+ conn->bits.httpproxy ? conn->http_proxy.port :
sockindex == SECONDARYSOCKET ? conn->secondary_port :
conn->bits.conn_to_port ? conn->conn_to_port :
- conn->remote_port;
+ (uint16_t)conn->remote_port;
sx->proxy_user = conn->socks_proxy.user;
sx->proxy_password = conn->socks_proxy.passwd;
Curl_bufq_init2(&sx->iobuf, SOCKS_CHUNK_SIZE, SOCKS_CHUNKS,
#include "hostip.h"
#include "cfilters.h"
#include "cw-out.h"
+#include "dnscache.h"
#include "transfer.h"
#include "sendf.h"
#include "curl_trc.h"
/* release any resolve information this transfer kept */
Curl_async_destroy(data);
- Curl_resolv_unlink(data, &data->state.dns[0]); /* done with this */
- Curl_resolv_unlink(data, &data->state.dns[1]);
+ Curl_dns_entry_unlink(data, &data->state.dns[0]); /* done with this */
+ Curl_dns_entry_unlink(data, &data->state.dns[1]);
data->set.verbose = FALSE; /* no more calls to DEBUGFUNCTION */
data->magic = 0; /* force a clear AFTER the possibly enforced removal from
if(port >= 0) {
conn->conn_to_port = (uint16_t)port;
conn->bits.conn_to_port = TRUE;
- infof(data, "Connecting to port: %d", port);
+ infof(data, "Connecting to port: %u", conn->conn_to_port);
}
else {
/* no "connect to port" */
*************************************************************/
static CURLcode resolve_server(struct Curl_easy *data,
struct connectdata *conn,
- bool *async,
struct Curl_dns_entry **pdns)
{
struct hostname *ehost;
ehost = conn->bits.conn_to_host ? &conn->conn_to_host : &conn->host;
/* If not connecting via a proxy, extract the port from the URL, if it is
* there, thus overriding any defaults that might have been set above. */
- eport = conn->bits.conn_to_port ? conn->conn_to_port : conn->remote_port;
+ eport = conn->bits.conn_to_port ?
+ conn->conn_to_port : (uint16_t)conn->remote_port;
}
- result = Curl_resolv_timeout(data, ehost->name,
- eport, conn->ip_version,
- pdns, timeout_ms);
+ result = Curl_resolv(data, ehost->name, eport,
+ conn->ip_version, timeout_ms, pdns);
DEBUGASSERT(!result || !*pdns);
- if(result == CURLE_AGAIN) {
- *async = TRUE;
+ if(!result) { /* resolved right away, either sync or from dnscache */
+ DEBUGASSERT(*pdns);
return CURLE_OK;
}
- else if(result == CURLE_OPERATION_TIMEDOUT) {
+ else if(result == CURLE_AGAIN) { /* async resolv in progress */
+ return CURLE_AGAIN;
+ }
+ else if(result == CURLE_OPERATION_TIMEDOUT) { /* took too long */
failf(data, "Failed to resolve %s '%s' with timeout after %"
FMT_TIMEDIFF_T " ms", peertype, ehost->dispname,
curlx_ptimediff_ms(Curl_pgrs_now(data),
&data->progress.t_startsingle));
return CURLE_OPERATION_TIMEDOUT;
}
- else if(result) {
+ else {
+ DEBUGASSERT(result);
failf(data, "Could not resolve %s: %s", peertype, ehost->dispname);
return result;
}
- DEBUGASSERT(*pdns);
- return CURLE_OK;
}
static void url_move_hostname(struct hostname *dest, struct hostname *src)
* Resolve the address of the server or proxy
*************************************************************/
struct Curl_dns_entry *dns;
- result = resolve_server(data, conn, asyncp, &dns);
+ result = resolve_server(data, conn, &dns);
if(!result) {
- *asyncp = !dns;
- if(dns)
- /* DNS resolution is done: that is either because this is a reused
- connection, in which case DNS was unnecessary, or because DNS
- really did finish already (synch resolver/fast async resolve) */
- result = Curl_setup_conn(data, dns, protocol_done);
+ DEBUGASSERT(dns);
+ /* DNS resolution is done: that is either because this is a reused
+ connection, in which case DNS was unnecessary, or because DNS
+ really did finish already (synch resolver/fast async resolve) */
+ result = Curl_setup_conn(data, dns, protocol_done);
+ }
+ else if(result == CURLE_AGAIN) {
+ *asyncp = TRUE;
+ result = CURLE_OK;
}
}
}
struct Curl_dns_entry *dns[2]; /* DNS to connect FIRST/SECONDARY */
#ifdef USE_CURL_ASYNC
- struct Curl_async async; /* asynchronous name resolver data */
+ struct Curl_resolv_async *async; /* asynchronous name resolver data */
+ uint32_t next_async_id; /* id of the next async resolve operation */
#endif
#ifdef USE_OPENSSL
size_t ech_config_len = 0;
char *outername = data->set.str[STRING_ECH_PUBLIC];
int trying_ech_now = 0;
- CURLcode result;
+ CURLcode result = CURLE_OK;
if(!CURLECH_ENABLED(data))
return CURLE_OK;
struct Curl_dns_entry *dns = NULL;
if(peer->hostname)
- dns = Curl_dnscache_get(data, peer->hostname, peer->port,
- cf->conn->ip_version);
+ result = Curl_dnscache_get(data, peer->hostname, peer->port,
+ cf->conn->ip_version, &dns);
if(!dns) {
+ if(result) {
+ failf(data, "ECH: could not resolve %s:%d",
+ peer->hostname, peer->port);
+ return result;
+ }
infof(data, "ECH: requested but no DNS info available");
if(data->set.tls_ech & CURLECH_HARD)
return CURLE_SSL_CONNECT_ERROR;
if(data->set.tls_ech & CURLECH_HARD)
return CURLE_SSL_CONNECT_ERROR;
}
- Curl_resolv_unlink(data, &dns);
+ Curl_dns_entry_unlink(data, &dns);
}
}
#ifdef HAVE_BORINGSSL_LIKE
}
else {
if(connssl->peer.hostname) {
- dns = Curl_dnscache_get(data, connssl->peer.hostname,
- connssl->peer.port, data->conn->ip_version);
+ result = Curl_dnscache_get(data, connssl->peer.hostname,
+ connssl->peer.port, data->conn->ip_version,
+ &dns);
}
if(!dns) {
+ if(result) {
+ failf(data, "ECH: could not resolve %s:%d",
+ connssl->peer.hostname, connssl->peer.port);
+ return result;
+ }
failf(data, "rustls: ECH requested but no DNS info available");
result = CURLE_SSL_CONNECT_ERROR;
goto cleanup;
curlx_free(ech_config);
}
if(dns) {
- Curl_resolv_unlink(data, &dns);
+ Curl_dns_entry_unlink(data, &dns);
}
return result;
}
{
ehostname = cf->conn->host.name;
edispname = cf->conn->host.dispname;
- peer->port = cf->conn->remote_port;
+ peer->port = (uint16_t)cf->conn->remote_port;
}
/* hostname MUST exist and not be empty */
struct ssl_connect_data *connssl = cf->ctx;
struct Curl_dns_entry *dns = NULL;
- dns = Curl_dnscache_get(data, connssl->peer.hostname, connssl->peer.port,
- cf->conn->ip_version);
+ result = Curl_dnscache_get(data, connssl->peer.hostname,
+ connssl->peer.port,
+ cf->conn->ip_version, &dns);
if(!dns) {
+ if(result) {
+ failf(data, "ECH: could not resolve %s:%d",
+ connssl->peer.hostname, connssl->peer.port);
+ goto out;
+ }
infof(data, "ECH: requested but no DNS info available");
if(data->set.tls_ech & CURLECH_HARD) {
result = CURLE_SSL_CONNECT_ERROR;
goto out;
}
}
- Curl_resolv_unlink(data, &dns);
+ Curl_dns_entry_unlink(data, &dns);
}
}
" -r <host>:<port>:<addr> resolve information\n"
" -T number max concurrent connections total\n"
" -V http_version (http/1.1, h2, h3) http version to use\n"
+ " -6 use ipv6 for resolving the FIRST url\n"
);
}
size_t max_total_conns = 0;
int fresh_connect = 0;
char *cafile = NULL;
+ bool first_ipv6 = FALSE;
CURLcode result = CURLE_OK;
(void)URL;
- while((ch = cgetopt(test_argc, test_argv, "aefhm:n:xA:C:F:M:P:r:T:V:"))
+ while((ch = cgetopt(test_argc, test_argv, "aefhm:n:xA:C:F:M:P:r:T:V:6"))
!= -1) {
const char *opt = coptarg;
curl_off_t num;
}
break;
}
+ case '6':
+ first_ipv6 = TRUE;
+ break;
default:
usage_hx_download("invalid option");
result = (CURLcode)1;
result = (CURLcode)1;
goto cleanup;
}
+ if(!i && first_ipv6)
+ curl_easy_setopt(t->curl, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V6);
curl_multi_add_handle(multi, t->curl);
t->started = 1;
++active_transfers;
extern int unitfail; /* for unittests */
+#ifdef UNITTESTS
+#include "unitprotos.h"
+#endif
+
#include "curlx/base64.h" /* for curlx_base64* */
#include "curlx/dynbuf.h" /* for curlx_dyn_*() */
#include "curlx/fopen.h" /* for curlx_f*() */
#include "curl_addrinfo.h"
#include "hash.h"
-#include "hostip.h"
+#include "dnscache.h"
static struct Curl_dnscache hp;
static char *data_key;