asyn-thread: use c-ares to resolve HTTPS RR

Allow building with c-ares and yet use threaded resolver for the main
host A/AAAA resolving:

  `--with-ares` provides the c-ares install path and defaults to use
  c-ares for name resolving

  `--with-threaded-resolver` still uses c-ares in the build (for HTTPS)
  but uses the threaded resolver for "normal" resolves.

It works similarly for cmake: ENABLE_ARES enables ares, and if
ENABLE_THREADED_RESOLVER also is set, c-ares is used for HTTPS RR and
the threaded resolver for "normal" resolves.

HTTPSRR and c-ares-rr are new features return by curl_version_info() and
thus shown by curl -V.

The c-ares-rr feature bit is there to make it possible to distinguish
between builds using c-ares for all name resolves and builds that use
the threaded resolves for the regular name resolves and c-ares for
HTTPSRR only. "c-ares-rr" means it does not use c-ares for "plain" name
resolves.

HTTPSRR support is EXPERIMENTAL only.

Closes #16054
This commit is contained in:
Daniel Stenberg 2025-01-21 11:42:20 +01:00
parent 520e67dd39
commit 0d4fdbf15d
No known key found for this signature in database
GPG key ID: 5CC908FDB71E12C2
18 changed files with 470 additions and 421 deletions

View file

@ -24,14 +24,14 @@
#include "curl_setup.h"
#ifdef USE_ARES
/***********************************************************************
* Only for ares-enabled builds
* And only for functions that fulfill the asynch resolver backend API
* as defined in asyn.h, nothing else belongs in this file!
**********************************************************************/
#ifdef CURLRES_ARES
#include <limits.h>
#ifdef HAVE_NETINET_IN_H
#include <netinet/in.h>
@ -70,6 +70,94 @@
#include <ares_version.h> /* really old c-ares did not include this by
itself */
/*
* Curl_ares_getsock() is called when the outside world (using
* curl_multi_fdset()) wants to get our fd_set setup and we are talking with
* ares. The caller must make sure that this function is only called when we
* have a working ares channel.
*
* Returns: sockets-in-use-bitmap
*/
int Curl_ares_getsock(struct Curl_easy *data,
ares_channel channel,
curl_socket_t *socks)
{
struct timeval maxtime = { CURL_TIMEOUT_RESOLVE, 0 };
struct timeval timebuf;
int max = ares_getsock(channel,
(ares_socket_t *)socks, MAX_SOCKSPEREASYHANDLE);
struct timeval *timeout = ares_timeout(channel, &maxtime, &timebuf);
timediff_t milli = curlx_tvtoms(timeout);
Curl_expire(data, milli, EXPIRE_ASYNC_NAME);
return max;
}
/*
* Curl_ares_perform()
*
* 1) Ask ares what sockets it currently plays with, then
* 2) wait for the timeout period to check for action on ares' sockets.
* 3) tell ares to act on all the sockets marked as "with action"
*
* return number of sockets it worked on, or -1 on error
*/
int Curl_ares_perform(ares_channel channel,
timediff_t timeout_ms)
{
int nfds;
int bitmask;
ares_socket_t socks[ARES_GETSOCK_MAXNUM];
struct pollfd pfd[ARES_GETSOCK_MAXNUM];
int i;
int num = 0;
bitmask = ares_getsock(channel, socks, ARES_GETSOCK_MAXNUM);
for(i = 0; i < ARES_GETSOCK_MAXNUM; i++) {
pfd[i].events = 0;
pfd[i].revents = 0;
if(ARES_GETSOCK_READABLE(bitmask, i)) {
pfd[i].fd = socks[i];
pfd[i].events |= POLLRDNORM|POLLIN;
}
if(ARES_GETSOCK_WRITABLE(bitmask, i)) {
pfd[i].fd = socks[i];
pfd[i].events |= POLLWRNORM|POLLOUT;
}
if(pfd[i].events)
num++;
else
break;
}
if(num) {
nfds = Curl_poll(pfd, (unsigned int)num, timeout_ms);
if(nfds < 0)
return -1;
}
else
nfds = 0;
if(!nfds)
/* Call ares_process() unconditionally here, even if we simply timed out
above, as otherwise the ares name resolve will not timeout! */
ares_process_fd(channel, ARES_SOCKET_BAD, ARES_SOCKET_BAD);
else {
/* move through the descriptors and ask for processing on them */
for(i = 0; i < num; i++)
ares_process_fd(channel,
(pfd[i].revents & (POLLRDNORM|POLLIN)) ?
pfd[i].fd : ARES_SOCKET_BAD,
(pfd[i].revents & (POLLWRNORM|POLLOUT)) ?
pfd[i].fd : ARES_SOCKET_BAD);
}
return nfds;
}
#ifdef CURLRES_ARES
#if ARES_VERSION >= 0x010500
/* c-ares 1.5.0 or later, the callback proto is modified */
#define HAVE_CARES_CALLBACK_TIMEOUTS 1
@ -98,8 +186,9 @@
#if ARES_VERSION >= 0x011c00
/* 1.28.0 and later have ares_query_dnsrec */
#define HAVE_ARES_QUERY_DNSREC 1
#else
#undef USE_HTTPSRR
#ifdef USE_HTTPSRR
#define USE_HTTPSRR_ARES 1
#endif
#endif
/* The last 3 #include files should be in this order */
@ -107,20 +196,6 @@
#include "curl_memory.h"
#include "memdebug.h"
struct thread_data {
int num_pending; /* number of outstanding c-ares requests */
struct Curl_addrinfo *temp_ai; /* intermediary result while fetching c-ares
parts */
int last_status;
#ifndef HAVE_CARES_GETADDRINFO
struct curltime happy_eyeballs_dns_time; /* when this timer started, or 0 */
#endif
#ifdef USE_HTTPSRR
struct Curl_https_rrinfo hinfo;
#endif
char hostname[1];
};
/* How long we are willing to wait for additional parallel responses after
obtaining a "definitive" one. For old c-ares without getaddrinfo.
@ -292,89 +367,13 @@ static void destroy_async_data(struct Curl_async *async)
/*
* Curl_resolver_getsock() is called when someone from the outside world
* (using curl_multi_fdset()) wants to get our fd_set setup and we are talking
* with ares. The caller must make sure that this function is only called when
* we have a working ares channel.
*
* Returns: sockets-in-use-bitmap
* (using curl_multi_fdset()) wants to get our fd_set setup.
*/
int Curl_resolver_getsock(struct Curl_easy *data,
curl_socket_t *socks)
int Curl_resolver_getsock(struct Curl_easy *data, curl_socket_t *socks)
{
struct timeval maxtime = { CURL_TIMEOUT_RESOLVE, 0 };
struct timeval timebuf;
int max = ares_getsock((ares_channel)data->state.async.resolver,
(ares_socket_t *)socks, MAX_SOCKSPEREASYHANDLE);
struct timeval *timeout =
ares_timeout((ares_channel)data->state.async.resolver, &maxtime, &timebuf);
timediff_t milli = curlx_tvtoms(timeout);
Curl_expire(data, milli, EXPIRE_ASYNC_NAME);
return max;
}
/*
* waitperform()
*
* 1) Ask ares what sockets it currently plays with, then
* 2) wait for the timeout period to check for action on ares' sockets.
* 3) tell ares to act on all the sockets marked as "with action"
*
* return number of sockets it worked on, or -1 on error
*/
static int waitperform(struct Curl_easy *data, timediff_t timeout_ms)
{
int nfds;
int bitmask;
ares_socket_t socks[ARES_GETSOCK_MAXNUM];
struct pollfd pfd[ARES_GETSOCK_MAXNUM];
int i;
int num = 0;
bitmask = ares_getsock((ares_channel)data->state.async.resolver, socks,
ARES_GETSOCK_MAXNUM);
for(i = 0; i < ARES_GETSOCK_MAXNUM; i++) {
pfd[i].events = 0;
pfd[i].revents = 0;
if(ARES_GETSOCK_READABLE(bitmask, i)) {
pfd[i].fd = socks[i];
pfd[i].events |= POLLRDNORM|POLLIN;
}
if(ARES_GETSOCK_WRITABLE(bitmask, i)) {
pfd[i].fd = socks[i];
pfd[i].events |= POLLWRNORM|POLLOUT;
}
if(pfd[i].events)
num++;
else
break;
}
if(num) {
nfds = Curl_poll(pfd, (unsigned int)num, timeout_ms);
if(nfds < 0)
return -1;
}
else
nfds = 0;
if(!nfds)
/* Call ares_process() unconditionally here, even if we simply timed out
above, as otherwise the ares name resolve will not timeout! */
ares_process_fd((ares_channel)data->state.async.resolver, ARES_SOCKET_BAD,
ARES_SOCKET_BAD);
else {
/* move through the descriptors and ask for processing on them */
for(i = 0; i < num; i++)
ares_process_fd((ares_channel)data->state.async.resolver,
(pfd[i].revents & (POLLRDNORM|POLLIN)) ?
pfd[i].fd : ARES_SOCKET_BAD,
(pfd[i].revents & (POLLWRNORM|POLLOUT)) ?
pfd[i].fd : ARES_SOCKET_BAD);
}
return nfds;
return Curl_ares_getsock(data, (ares_channel)data->state.async.resolver,
socks);
}
/*
@ -393,7 +392,7 @@ CURLcode Curl_resolver_is_resolved(struct Curl_easy *data,
DEBUGASSERT(dns);
*dns = NULL;
if(waitperform(data, 0) < 0)
if(Curl_ares_perform((ares_channel)data->state.async.resolver, 0) < 0)
return CURLE_UNRECOVERABLE_POLL;
#ifndef HAVE_CARES_GETADDRINFO
@ -431,7 +430,7 @@ CURLcode Curl_resolver_is_resolved(struct Curl_easy *data,
result = Curl_resolver_error(data);
else {
*dns = data->state.async.dns;
#ifdef USE_HTTPSRR
#ifdef USE_HTTPSRR_ARES
{
struct Curl_https_rrinfo *lhrr =
Curl_memdup(&res->hinfo, sizeof(struct Curl_https_rrinfo));
@ -503,7 +502,8 @@ CURLcode Curl_resolver_wait_resolv(struct Curl_easy *data,
else
timeout_ms = 1000;
if(waitperform(data, timeout_ms) < 0)
if(Curl_ares_perform((ares_channel)data->state.async.resolver,
timeout_ms) < 0)
return CURLE_UNRECOVERABLE_POLL;
result = Curl_resolver_is_resolved(data, entry);
@ -768,72 +768,6 @@ static void addrinfo_cb(void *arg, int status, int timeouts,
#endif
#ifdef USE_HTTPSRR
static void httpsrr_opt(struct Curl_easy *data,
const ares_dns_rr_t *rr,
ares_dns_rr_key_t key, size_t idx)
{
size_t len = 0;
const unsigned char *val = NULL;
unsigned short code;
struct thread_data *res = data->state.async.tdata;
code = ares_dns_rr_get_opt(rr, key, idx, &val, &len);
switch(code) {
case HTTPS_RR_CODE_ALPN: /* str_list */
Curl_httpsrr_decode_alpn(val, len, res->hinfo.alpns);
infof(data, "HTTPS RR ALPN: %u %u %u %u",
res->hinfo.alpns[0], res->hinfo.alpns[1], res->hinfo.alpns[2],
res->hinfo.alpns[3]);
break;
case HTTPS_RR_CODE_NO_DEF_ALPN:
infof(data, "HTTPS RR no-def-alpn");
break;
case HTTPS_RR_CODE_IPV4: /* addr4 list */
infof(data, "HTTPS RR IPv4");
break;
case HTTPS_RR_CODE_ECH:
infof(data, "HTTPS RR ECH");
break;
case HTTPS_RR_CODE_IPV6: /* addr6 list */
infof(data, "HTTPS RR IPv6");
break;
case HTTPS_RR_CODE_PORT:
infof(data, "HTTPS RR port");
break;
default:
infof(data, "HTTPS RR unknown code");
break;
}
}
static void dnsrec_done_cb(void *arg, ares_status_t status,
size_t timeouts,
const ares_dns_record_t *dnsrec)
{
struct Curl_easy *data = arg;
size_t i;
struct thread_data *res = data->state.async.tdata;
(void)timeouts;
res->num_pending--;
if((ARES_SUCCESS != status) || !dnsrec)
return;
for(i = 0; i < ares_dns_record_rr_cnt(dnsrec, ARES_SECTION_ANSWER); i++) {
size_t opt;
const ares_dns_rr_t *rr =
ares_dns_record_rr_get_const(dnsrec, ARES_SECTION_ANSWER, i);
if(ares_dns_rr_get_type(rr) != ARES_REC_TYPE_HTTPS)
continue;
for(opt = 0; opt < ares_dns_rr_get_opt_cnt(rr, ARES_RR_HTTPS_PARAMS);
opt++)
httpsrr_opt(data, rr, ARES_RR_HTTPS_PARAMS, opt);
}
}
#endif
/*
* Curl_resolver_getaddrinfo() - when using ares
*
@ -916,14 +850,14 @@ struct Curl_addrinfo *Curl_resolver_getaddrinfo(struct Curl_easy *data,
query_completed_cb, data);
}
#endif
#ifdef USE_HTTPSRR
#ifdef USE_HTTPSRR_ARES
{
res->num_pending++; /* one more */
memset(&res->hinfo, 0, sizeof(struct Curl_https_rrinfo));
ares_query_dnsrec((ares_channel)data->state.async.resolver,
hostname, ARES_CLASS_IN,
ARES_REC_TYPE_HTTPS,
dnsrec_done_cb, data, NULL);
Curl_dnsrec_done_cb, data, NULL);
}
#endif
*waitp = 1; /* expect asynchronous response */
@ -1058,3 +992,5 @@ CURLcode Curl_set_dns_local_ip6(struct Curl_easy *data,
#endif
}
#endif /* CURLRES_ARES */
#endif /* USE_ARES */

View file

@ -64,6 +64,14 @@
#include "inet_ntop.h"
#include "curl_threads.h"
#include "connect.h"
#ifdef USE_ARES
#include <ares.h>
#ifdef USE_HTTPSRR
#define USE_HTTPSRR_ARES 1 /* the combo */
#endif
#endif
/* The last 3 #include files should be in this order */
#include "curl_printf.h"
#include "curl_memory.h"
@ -145,32 +153,6 @@ static bool init_resolve_thread(struct Curl_easy *data,
const struct addrinfo *hints);
/* Data for synchronization between resolver thread and its parent */
struct thread_sync_data {
curl_mutex_t *mtx;
int done;
int port;
char *hostname; /* hostname to resolve, Curl_async.hostname
duplicate */
#ifndef CURL_DISABLE_SOCKETPAIR
struct Curl_easy *data;
curl_socket_t sock_pair[2]; /* eventfd/pipes/socket pair */
#endif
int sock_error;
struct Curl_addrinfo *res;
#ifdef HAVE_GETADDRINFO
struct addrinfo hints;
#endif
struct thread_data *td; /* for thread-self cleanup */
};
struct thread_data {
curl_thread_t thread_hnd;
unsigned int poll_interval;
timediff_t interval_end;
struct thread_sync_data tsd;
};
static struct thread_sync_data *conn_thread_sync_data(struct Curl_easy *data)
{
return &(data->state.async.tdata->tsd);
@ -220,7 +202,7 @@ int init_thread_sync_data(struct thread_data *td,
/* Treat the request as done until the thread actually starts so any early
* cleanup gets done properly.
*/
tsd->done = 1;
tsd->done = TRUE;
#ifdef HAVE_GETADDRINFO
DEBUGASSERT(hints);
tsd->hints = *hints;
@ -343,7 +325,7 @@ CURL_STDCALL getaddrinfo_thread(void *arg)
}
}
#endif
tsd->done = 1;
tsd->done = TRUE;
Curl_mutex_release(tsd->mtx);
}
@ -382,7 +364,7 @@ CURL_STDCALL gethostbyname_thread(void *arg)
free(td);
}
else {
tsd->done = 1;
tsd->done = TRUE;
Curl_mutex_release(tsd->mtx);
}
@ -398,19 +380,22 @@ static void destroy_async_data(struct Curl_async *async)
{
if(async->tdata) {
struct thread_data *td = async->tdata;
int done;
bool done;
#ifndef CURL_DISABLE_SOCKETPAIR
curl_socket_t sock_rd = td->tsd.sock_pair[0];
struct Curl_easy *data = td->tsd.data;
#endif
#ifdef USE_HTTPSRR_ARES
ares_destroy(data->state.async.tdata->channel);
#endif
/*
* if the thread is still blocking in the resolve syscall, detach it and
* let the thread do the cleanup...
*/
Curl_mutex_acquire(td->tsd.mtx);
done = td->tsd.done;
td->tsd.done = 1;
td->tsd.done = TRUE;
Curl_mutex_release(td->tsd.mtx);
if(!done) {
@ -439,6 +424,24 @@ static void destroy_async_data(struct Curl_async *async)
async->hostname = NULL;
}
#ifdef USE_HTTPSRR_ARES
static CURLcode resolve_httpsrr(struct Curl_easy *data,
struct Curl_async *asp)
{
int status = ares_init(&asp->tdata->channel);
if(status != ARES_SUCCESS)
return CURLE_FAILED_INIT;
memset(&asp->tdata->hinfo, 0, sizeof(struct Curl_https_rrinfo));
ares_query_dnsrec(asp->tdata->channel,
asp->hostname, ARES_CLASS_IN,
ARES_REC_TYPE_HTTPS,
Curl_dnsrec_done_cb, data, NULL);
return CURLE_OK;
}
#endif
/*
* init_resolve_thread() starts a new thread that performs the actual
* resolve. This function returns before the resolve is done.
@ -474,8 +477,8 @@ static bool init_resolve_thread(struct Curl_easy *data,
if(!asp->hostname)
goto err_exit;
/* The thread will set this to 1 when complete. */
td->tsd.done = 0;
/* The thread will set this TRUE when complete. */
td->tsd.done = FALSE;
#ifdef HAVE_GETADDRINFO
td->thread_hnd = Curl_thread_create(getaddrinfo_thread, &td->tsd);
@ -485,11 +488,14 @@ static bool init_resolve_thread(struct Curl_easy *data,
if(td->thread_hnd == curl_thread_t_null) {
/* The thread never started, so mark it as done here for proper cleanup. */
td->tsd.done = 1;
td->tsd.done = TRUE;
err = errno;
goto err_exit;
}
#ifdef USE_HTTPSRR_ARES
if(resolve_httpsrr(data, asp))
goto err_exit;
#endif
return TRUE;
err_exit:
@ -587,7 +593,7 @@ CURLcode Curl_resolver_is_resolved(struct Curl_easy *data,
struct Curl_dns_entry **entry)
{
struct thread_data *td = data->state.async.tdata;
int done = 0;
bool done = FALSE;
DEBUGASSERT(entry);
*entry = NULL;
@ -596,6 +602,10 @@ CURLcode Curl_resolver_is_resolved(struct Curl_easy *data,
DEBUGASSERT(td);
return CURLE_COULDNT_RESOLVE_HOST;
}
#ifdef USE_HTTPSRR_ARES
if(Curl_ares_perform(data->state.async.tdata->channel, 0) < 0)
return CURLE_UNRECOVERABLE_POLL;
#endif
Curl_mutex_acquire(td->tsd.mtx);
done = td->tsd.done;
@ -643,18 +653,28 @@ int Curl_resolver_getsock(struct Curl_easy *data, curl_socket_t *socks)
timediff_t milli;
timediff_t ms;
struct resdata *reslv = (struct resdata *)data->state.async.resolver;
int socketi = 0;
#ifndef CURL_DISABLE_SOCKETPAIR
struct thread_data *td = data->state.async.tdata;
#else
(void)socks;
#endif
#ifdef USE_HTTPSRR_ARES
if(data->state.async.tdata) {
ret_val = Curl_ares_getsock(data, data->state.async.tdata->channel, socks);
for(socketi = 0; socketi < (MAX_SOCKSPEREASYHANDLE - 1); socketi++)
if(!ARES_GETSOCK_READABLE(ret_val, socketi) &&
!ARES_GETSOCK_WRITABLE(ret_val, socketi))
break;
}
#endif
#ifndef CURL_DISABLE_SOCKETPAIR
if(td) {
/* return read fd to client for polling the DNS resolution status */
socks[0] = td->tsd.sock_pair[0];
socks[socketi] = td->tsd.sock_pair[0];
td->tsd.data = data;
ret_val = GETSOCK_READSOCK(0);
ret_val = GETSOCK_READSOCK(socketi);
}
else {
#endif

View file

@ -26,6 +26,7 @@
#include "curl_setup.h"
#include "curl_addrinfo.h"
#include "httpsrr.h"
struct addrinfo;
struct hostent;
@ -33,6 +34,69 @@ struct Curl_easy;
struct connectdata;
struct Curl_dns_entry;
#ifdef CURLRES_THREADED
#include "curl_threads.h"
/* Data for synchronization between resolver thread and its parent */
struct thread_sync_data {
curl_mutex_t *mtx;
bool done;
int port;
char *hostname; /* hostname to resolve, Curl_async.hostname
duplicate */
#ifndef CURL_DISABLE_SOCKETPAIR
struct Curl_easy *data;
curl_socket_t sock_pair[2]; /* eventfd/pipes/socket pair */
#endif
int sock_error;
struct Curl_addrinfo *res;
#ifdef HAVE_GETADDRINFO
struct addrinfo hints;
#endif
struct thread_data *td; /* for thread-self cleanup */
};
struct thread_data {
curl_thread_t thread_hnd;
unsigned int poll_interval;
timediff_t interval_end;
struct thread_sync_data tsd;
#if defined(USE_HTTPSRR) && defined(USE_ARES)
struct Curl_https_rrinfo hinfo;
ares_channel channel;
#endif
};
#elif defined(CURLRES_ARES) /* CURLRES_THREADED */
struct thread_data {
int num_pending; /* number of outstanding c-ares requests */
struct Curl_addrinfo *temp_ai; /* intermediary result while fetching c-ares
parts */
int last_status;
#ifndef HAVE_CARES_GETADDRINFO
struct curltime happy_eyeballs_dns_time; /* when this timer started, or 0 */
#endif
#ifdef USE_HTTPSRR
struct Curl_https_rrinfo hinfo;
#endif
char hostname[1];
};
#endif /* CURLRES_ARES */
#ifdef USE_ARES
#include <ares.h>
/* for HTTPS RR purposes as well */
int Curl_ares_getsock(struct Curl_easy *data,
ares_channel channel,
curl_socket_t *socks);
int Curl_ares_perform(ares_channel channel,
timediff_t timeout_ms);
#endif
/*
* This header defines all functions in the internal asynch resolver interface.
* All asynch resolvers need to provide these functions.

View file

@ -282,14 +282,6 @@
# define CURL_DISABLE_HTTP_AUTH 1
#endif
/*
* ECH requires HTTPSRR.
*/
#if defined(USE_ECH) && !defined(USE_HTTPSRR)
# define USE_HTTPSRR
#endif
/* ================================================================ */
/* No system header file shall be included in this file before this */
/* point. */
@ -710,15 +702,15 @@
# define CURLRES_IPV4
#endif
#ifdef USE_ARES
#if defined(USE_THREADS_POSIX) || defined(USE_THREADS_WIN32)
# define CURLRES_ASYNCH
# define CURLRES_THREADED
#elif defined(USE_ARES)
# define CURLRES_ASYNCH
# define CURLRES_ARES
/* now undef the stock libc functions just to avoid them being used */
# undef HAVE_GETADDRINFO
# undef HAVE_FREEADDRINFO
#elif defined(USE_THREADS_POSIX) || defined(USE_THREADS_WIN32)
# define CURLRES_ASYNCH
# define CURLRES_THREADED
#else
# define CURLRES_SYNCH
#endif

View file

@ -29,6 +29,7 @@
#include "curl_addrinfo.h"
#include "timeval.h" /* for timediff_t */
#include "asyn.h"
#include "httpsrr.h"
#include <setjmp.h>
@ -69,31 +70,6 @@ enum alpnid {
*/
struct Curl_hash *Curl_global_host_cache_init(void);
#ifdef USE_HTTPSRR
#define CURL_MAXLEN_host_name 253
#define MAX_HTTPSRR_ALPNS 4
struct Curl_https_rrinfo {
/*
* Fields from HTTPS RR. The only mandatory fields are priority and target.
* See https://datatracker.ietf.org/doc/html/rfc9460#section-14.3.2
*/
char *target;
unsigned char *ipv4hints; /* keytag = 4 */
size_t ipv4hints_len;
unsigned char *echconfiglist; /* keytag = 5 */
size_t echconfiglist_len;
unsigned char *ipv6hints; /* keytag = 6 */
size_t ipv6hints_len;
unsigned char alpns[MAX_HTTPSRR_ALPNS]; /* keytag = 1 */
/* store parsed alpnid entries in the array, end with ALPN_none */
int port; /* -1 means not set */
uint16_t priority;
bool no_def_alpn; /* keytag = 2 */
};
#endif
struct Curl_dns_entry {
struct Curl_addrinfo *addr;
#ifdef USE_HTTPSRR

View file

@ -30,6 +30,12 @@
#include "curl_addrinfo.h"
#include "httpsrr.h"
#include "connect.h"
#include "sendf.h"
/* The last 3 #include files should be in this order */
#include "curl_printf.h"
#include "curl_memory.h"
#include "memdebug.h"
CURLcode Curl_httpsrr_decode_alpn(const unsigned char *cp, size_t len,
unsigned char *alpns)
@ -85,4 +91,77 @@ err:
return CURLE_BAD_CONTENT_ENCODING;
}
#ifdef USE_ARES
static void httpsrr_opt(struct Curl_easy *data,
const ares_dns_rr_t *rr,
ares_dns_rr_key_t key, size_t idx)
{
size_t len = 0;
const unsigned char *val = NULL;
unsigned short code;
struct thread_data *res = data->state.async.tdata;
struct Curl_https_rrinfo *hi = &res->hinfo;
code = ares_dns_rr_get_opt(rr, key, idx, &val, &len);
switch(code) {
case HTTPS_RR_CODE_ALPN: /* str_list */
Curl_httpsrr_decode_alpn(val, len, hi->alpns);
infof(data, "HTTPS RR ALPN: %u %u %u %u",
hi->alpns[0], hi->alpns[1], hi->alpns[2], hi->alpns[3]);
break;
case HTTPS_RR_CODE_NO_DEF_ALPN:
infof(data, "HTTPS RR no-def-alpn");
break;
case HTTPS_RR_CODE_IPV4: /* addr4 list */
infof(data, "HTTPS RR IPv4");
break;
case HTTPS_RR_CODE_ECH:
infof(data, "HTTPS RR ECH");
break;
case HTTPS_RR_CODE_IPV6: /* addr6 list */
infof(data, "HTTPS RR IPv6");
break;
case HTTPS_RR_CODE_PORT:
infof(data, "HTTPS RR port");
break;
default:
infof(data, "HTTPS RR unknown code");
break;
}
}
void Curl_dnsrec_done_cb(void *arg, ares_status_t status,
size_t timeouts,
const ares_dns_record_t *dnsrec)
{
struct Curl_easy *data = arg;
size_t i;
#ifdef CURLRES_ARES
struct thread_data *res = data->state.async.tdata;
res->num_pending--;
#endif
(void)timeouts;
if((ARES_SUCCESS != status) || !dnsrec)
return;
for(i = 0; i < ares_dns_record_rr_cnt(dnsrec, ARES_SECTION_ANSWER); i++) {
size_t opt;
const ares_dns_rr_t *rr =
ares_dns_record_rr_get_const(dnsrec, ARES_SECTION_ANSWER, i);
if(ares_dns_rr_get_type(rr) != ARES_REC_TYPE_HTTPS)
continue;
/* When SvcPriority is 0, the SVCB record is in AliasMode. Otherwise, it
is in ServiceMode */
infof(data, "HTTPS RR priority: %u",
ares_dns_rr_get_u16(rr, ARES_RR_HTTPS_PRIORITY));
for(opt = 0; opt < ares_dns_rr_get_opt_cnt(rr, ARES_RR_HTTPS_PARAMS);
opt++)
httpsrr_opt(data, rr, ARES_RR_HTTPS_PARAMS, opt);
}
}
#endif /* USE_ARES */
#endif /* USE_HTTPSRR */

View file

@ -24,6 +24,36 @@
*
***************************************************************************/
#include "curl_setup.h"
#ifdef USE_ARES
#include <ares.h>
#endif
#ifdef USE_HTTPSRR
#define CURL_MAXLEN_host_name 253
#define MAX_HTTPSRR_ALPNS 4
struct Curl_https_rrinfo {
/*
* Fields from HTTPS RR. The only mandatory fields are priority and target.
* See https://datatracker.ietf.org/doc/html/rfc9460#section-14.3.2
*/
char *target;
unsigned char *ipv4hints; /* keytag = 4 */
size_t ipv4hints_len;
unsigned char *echconfiglist; /* keytag = 5 */
size_t echconfiglist_len;
unsigned char *ipv6hints; /* keytag = 6 */
size_t ipv6hints_len;
unsigned char alpns[MAX_HTTPSRR_ALPNS]; /* keytag = 1 */
/* store parsed alpnid entries in the array, end with ALPN_none */
int port; /* -1 means not set */
uint16_t priority;
bool no_def_alpn; /* keytag = 2 */
};
#endif
/*
* Code points for DNS wire format SvcParams as per RFC 9460
@ -38,4 +68,9 @@
CURLcode Curl_httpsrr_decode_alpn(const unsigned char *cp, size_t len,
unsigned char *alpns);
#if defined(USE_ARES) && defined(USE_HTTPSRR)
void Curl_dnsrec_done_cb(void *arg, ares_status_t status,
size_t timeouts,
const ares_dns_record_t *dnsrec);
#endif
#endif /* HEADER_CURL_HTTPSRR_H */

View file

@ -467,11 +467,18 @@ static const struct feat features_table[] = {
#ifdef HAVE_BROTLI
FEATURE("brotli", NULL, CURL_VERSION_BROTLI),
#endif
#if defined(CURLRES_ARES) && defined(CURLRES_THREADED)
FEATURE("c-ares-rr", NULL, 0),
#endif
#ifdef DEBUGBUILD
FEATURE("Debug", NULL, CURL_VERSION_DEBUG),
#endif
#if defined(USE_SSL) && defined(USE_ECH)
FEATURE("ECH", ech_present, 0),
#ifndef USE_HTTPSRR
#error "ECH enabled but not HTTPSRR, must be a config error"
#endif
#endif
#ifdef USE_GSASL
FEATURE("gsasl", NULL, CURL_VERSION_GSASL),
@ -492,6 +499,9 @@ static const struct feat features_table[] = {
!defined(CURL_DISABLE_HTTP)
FEATURE("HTTPS-proxy", https_proxy_present, CURL_VERSION_HTTPS_PROXY),
#endif
#if defined(USE_HTTPSRR)
FEATURE("HTTPSRR", NULL, 0),
#endif
#if defined(USE_LIBIDN2) || defined(USE_WIN32_IDN) || defined(USE_APPLE_IDN)
FEATURE("IDN", idn_present, CURL_VERSION_IDN),
#endif