asyn-ares: initial HTTPS resolve support

Gets the ALPN list the same way DoH does. Needs c-ares 1.28.0 or later.

Thanks-to: Brad House

Closes #16039
This commit is contained in:
Daniel Stenberg 2025-01-18 22:47:54 +01:00
parent ea76380299
commit 8368249907
No known key found for this signature in database
GPG Key ID: 5CC908FDB71E12C2
6 changed files with 234 additions and 69 deletions

View File

@ -180,6 +180,7 @@ LIB_CFILES = \
http_negotiate.c \ http_negotiate.c \
http_ntlm.c \ http_ntlm.c \
http_proxy.c \ http_proxy.c \
httpsrr.c \
idn.c \ idn.c \
if2ip.c \ if2ip.c \
imap.c \ imap.c \
@ -320,6 +321,7 @@ LIB_HFILES = \
http_negotiate.h \ http_negotiate.h \
http_ntlm.h \ http_ntlm.h \
http_proxy.h \ http_proxy.h \
httpsrr.h \
idn.h \ idn.h \
if2ip.h \ if2ip.h \
imap.h \ imap.h \

View File

@ -59,6 +59,8 @@
#include "select.h" #include "select.h"
#include "progress.h" #include "progress.h"
#include "timediff.h" #include "timediff.h"
#include "httpsrr.h"
#include "strdup.h"
#if defined(CURL_STATICLIB) && !defined(CARES_STATICLIB) && \ #if defined(CURL_STATICLIB) && !defined(CARES_STATICLIB) && \
defined(_WIN32) defined(_WIN32)
@ -93,6 +95,13 @@
#define HAVE_CARES_GETADDRINFO 1 #define HAVE_CARES_GETADDRINFO 1
#endif #endif
#if ARES_VERSION >= 0x011c00
/* 1.28.0 and later have ares_query_dnsrec */
#define HAVE_ARES_QUERY_DNSREC 1
#else
#undef USE_HTTPSRR
#endif
/* The last 3 #include files should be in this order */ /* The last 3 #include files should be in this order */
#include "curl_printf.h" #include "curl_printf.h"
#include "curl_memory.h" #include "curl_memory.h"
@ -105,6 +114,9 @@ struct thread_data {
int last_status; int last_status;
#ifndef HAVE_CARES_GETADDRINFO #ifndef HAVE_CARES_GETADDRINFO
struct curltime happy_eyeballs_dns_time; /* when this timer started, or 0 */ struct curltime happy_eyeballs_dns_time; /* when this timer started, or 0 */
#endif
#ifdef USE_HTTPSRR
struct Curl_https_rrinfo hinfo;
#endif #endif
char hostname[1]; char hostname[1];
}; };
@ -417,8 +429,18 @@ CURLcode Curl_resolver_is_resolved(struct Curl_easy *data,
if(!data->state.async.dns) if(!data->state.async.dns)
result = Curl_resolver_error(data); result = Curl_resolver_error(data);
else else {
*dns = data->state.async.dns; *dns = data->state.async.dns;
#ifdef USE_HTTPSRR
{
struct Curl_https_rrinfo *lhrr =
Curl_memdup(&res->hinfo, sizeof(struct Curl_https_rrinfo));
if(!lhrr)
return CURLE_OUT_OF_MEMORY;
(*dns)->hinfo = lhrr;
}
#endif
}
destroy_async_data(&data->state.async); destroy_async_data(&data->state.async);
} }
@ -745,6 +767,73 @@ static void addrinfo_cb(void *arg, int status, int timeouts,
} }
#endif #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 * Curl_resolver_getaddrinfo() - when using ares
* *
@ -826,6 +915,16 @@ struct Curl_addrinfo *Curl_resolver_getaddrinfo(struct Curl_easy *data,
hostname, PF_INET, hostname, PF_INET,
query_completed_cb, data); query_completed_cb, data);
} }
#endif
#ifdef USE_HTTPSRR
{
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);
}
#endif #endif
*waitp = 1; /* expect asynchronous response */ *waitp = 1; /* expect asynchronous response */
} }

View File

@ -1064,59 +1064,6 @@ static CURLcode doh_decode_rdata_name(unsigned char **buf, size_t *remaining,
return CURLE_OK; return CURLE_OK;
} }
static CURLcode doh_decode_rdata_alpn(unsigned char *cp, size_t len,
unsigned char *alpns)
{
/*
* spec here is as per RFC 9460, section-7.1.1
* encoding is a concatenated list of strings each preceded by a one
* octet length
* output is comma-sep list of the strings
* implementations may or may not handle quoting of comma within
* string values, so we might see a comma within the wire format
* version of a string, in which case we will precede that by a
* backslash - same goes for a backslash character, and of course
* we need to use two backslashes in strings when we mean one;-)
*/
struct dynbuf dval;
int idnum = 0;
Curl_dyn_init(&dval, DYN_DOH_RESPONSE);
while(len > 0) {
size_t tlen = (size_t) *cp++;
size_t i;
enum alpnid id;
len--;
if(tlen > len)
goto err;
/* add escape char if needed, clunky but easier to read */
for(i = 0; i != tlen; i++) {
if('\\' == *cp || ',' == *cp) {
if(Curl_dyn_addn(&dval, "\\", 1))
goto err;
}
if(Curl_dyn_addn(&dval, cp++, 1))
goto err;
}
len -= tlen;
/* we only store ALPN ids we know about */
id = Curl_alpn2alpnid(Curl_dyn_ptr(&dval), Curl_dyn_len(&dval));
if(id != ALPN_none) {
if(idnum == MAX_HTTPSRR_ALPNS)
break;
alpns[idnum++] = (unsigned char)id;
}
Curl_dyn_reset(&dval);
}
if(idnum < MAX_HTTPSRR_ALPNS)
alpns[idnum] = ALPN_none; /* terminate the list */
return CURLE_OK;
err:
Curl_dyn_free(&dval);
return CURLE_BAD_CONTENT_ENCODING;
}
#ifdef DEBUGBUILD #ifdef DEBUGBUILD
static CURLcode doh_test_alpn_escapes(void) static CURLcode doh_test_alpn_escapes(void)
{ {
@ -1131,7 +1078,7 @@ static CURLcode doh_test_alpn_escapes(void)
unsigned char aval[MAX_HTTPSRR_ALPNS] = { 0 }; unsigned char aval[MAX_HTTPSRR_ALPNS] = { 0 };
static const char expected[2] = { ALPN_h2, ALPN_none }; static const char expected[2] = { ALPN_h2, ALPN_none };
if(doh_decode_rdata_alpn(example, example_len, aval) != CURLE_OK) if(Curl_httpsrr_decode_alpn(example, example_len, aval) != CURLE_OK)
return CURLE_BAD_CONTENT_ENCODING; return CURLE_BAD_CONTENT_ENCODING;
if(memcmp(aval, expected, sizeof(expected))) if(memcmp(aval, expected, sizeof(expected)))
return CURLE_BAD_CONTENT_ENCODING; return CURLE_BAD_CONTENT_ENCODING;
@ -1170,7 +1117,7 @@ static CURLcode doh_resp_decode_httpsrr(unsigned char *cp, size_t len,
len -= 4; len -= 4;
switch(pcode) { switch(pcode) {
case HTTPS_RR_CODE_ALPN: case HTTPS_RR_CODE_ALPN:
if(doh_decode_rdata_alpn(cp, plen, lhrr->alpns) != CURLE_OK) if(Curl_httpsrr_decode_alpn(cp, plen, lhrr->alpns) != CURLE_OK)
goto err; goto err;
break; break;
case HTTPS_RR_CODE_NO_DEF_ALPN: case HTTPS_RR_CODE_NO_DEF_ALPN:

View File

@ -28,6 +28,7 @@
#include "curl_addrinfo.h" #include "curl_addrinfo.h"
#ifdef USE_HTTPSRR #ifdef USE_HTTPSRR
# include <stdint.h> # include <stdint.h>
# include "httpsrr.h"
#endif #endif
#ifndef CURL_DISABLE_DOH #ifndef CURL_DISABLE_DOH
@ -123,19 +124,6 @@ struct dohaddr {
#ifdef USE_HTTPSRR #ifdef USE_HTTPSRR
/*
* These are the code points for DNS wire format SvcParams as
* per draft-ietf-dnsop-svcb-https
* Not all are supported now, and even those that are may need
* more work in future to fully support the spec.
*/
#define HTTPS_RR_CODE_ALPN 0x01
#define HTTPS_RR_CODE_NO_DEF_ALPN 0x02
#define HTTPS_RR_CODE_PORT 0x03
#define HTTPS_RR_CODE_IPV4 0x04
#define HTTPS_RR_CODE_ECH 0x05
#define HTTPS_RR_CODE_IPV6 0x06
/* /*
* These may need escaping when found within an ALPN string * These may need escaping when found within an ALPN string
* value. * value.

88
lib/httpsrr.c Normal file
View File

@ -0,0 +1,88 @@
/***************************************************************************
* _ _ ____ _
* 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 USE_HTTPSRR
#include "urldata.h"
#include "curl_addrinfo.h"
#include "httpsrr.h"
#include "connect.h"
CURLcode Curl_httpsrr_decode_alpn(const unsigned char *cp, size_t len,
unsigned char *alpns)
{
/*
* spec here is as per RFC 9460, section-7.1.1
* encoding is a concatenated list of strings each preceded by a one
* octet length
* output is comma-sep list of the strings
* implementations may or may not handle quoting of comma within
* string values, so we might see a comma within the wire format
* version of a string, in which case we will precede that by a
* backslash - same goes for a backslash character, and of course
* we need to use two backslashes in strings when we mean one;-)
*/
struct dynbuf dval;
int idnum = 0;
Curl_dyn_init(&dval, DYN_DOH_RESPONSE);
while(len > 0) {
size_t tlen = (size_t) *cp++;
size_t i;
enum alpnid id;
len--;
if(tlen > len)
goto err;
/* add escape char if needed, clunky but easier to read */
for(i = 0; i != tlen; i++) {
if('\\' == *cp || ',' == *cp) {
if(Curl_dyn_addn(&dval, "\\", 1))
goto err;
}
if(Curl_dyn_addn(&dval, cp++, 1))
goto err;
}
len -= tlen;
/* we only store ALPN ids we know about */
id = Curl_alpn2alpnid(Curl_dyn_ptr(&dval), Curl_dyn_len(&dval));
if(id != ALPN_none) {
if(idnum == MAX_HTTPSRR_ALPNS)
break;
alpns[idnum++] = (unsigned char)id;
}
Curl_dyn_reset(&dval);
}
Curl_dyn_free(&dval);
if(idnum < MAX_HTTPSRR_ALPNS)
alpns[idnum] = ALPN_none; /* terminate the list */
return CURLE_OK;
err:
Curl_dyn_free(&dval);
return CURLE_BAD_CONTENT_ENCODING;
}
#endif

41
lib/httpsrr.h Normal file
View File

@ -0,0 +1,41 @@
#ifndef HEADER_CURL_HTTPSRR_H
#define HEADER_CURL_HTTPSRR_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
*
***************************************************************************/
/*
* Code points for DNS wire format SvcParams as per RFC 9460
*/
#define HTTPS_RR_CODE_ALPN 0x01
#define HTTPS_RR_CODE_NO_DEF_ALPN 0x02
#define HTTPS_RR_CODE_PORT 0x03
#define HTTPS_RR_CODE_IPV4 0x04
#define HTTPS_RR_CODE_ECH 0x05
#define HTTPS_RR_CODE_IPV6 0x06
CURLcode Curl_httpsrr_decode_alpn(const unsigned char *cp, size_t len,
unsigned char *alpns);
#endif /* HEADER_CURL_HTTPSRR_H */