From: Daniel Stenberg Date: Sat, 18 Jan 2025 21:47:54 +0000 (+0100) Subject: asyn-ares: initial HTTPS resolve support X-Git-Tag: curl-8_12_0~85 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=836824990768a8de8c46cdb76ea4c9ee345dcbca;p=thirdparty%2Fcurl.git 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 --- diff --git a/lib/Makefile.inc b/lib/Makefile.inc index d2c7b6e888..fc6201e9e6 100644 --- a/lib/Makefile.inc +++ b/lib/Makefile.inc @@ -180,6 +180,7 @@ LIB_CFILES = \ http_negotiate.c \ http_ntlm.c \ http_proxy.c \ + httpsrr.c \ idn.c \ if2ip.c \ imap.c \ @@ -320,6 +321,7 @@ LIB_HFILES = \ http_negotiate.h \ http_ntlm.h \ http_proxy.h \ + httpsrr.h \ idn.h \ if2ip.h \ imap.h \ diff --git a/lib/asyn-ares.c b/lib/asyn-ares.c index 6f81675995..8123226b6e 100644 --- a/lib/asyn-ares.c +++ b/lib/asyn-ares.c @@ -59,6 +59,8 @@ #include "select.h" #include "progress.h" #include "timediff.h" +#include "httpsrr.h" +#include "strdup.h" #if defined(CURL_STATICLIB) && !defined(CARES_STATICLIB) && \ defined(_WIN32) @@ -93,6 +95,13 @@ #define HAVE_CARES_GETADDRINFO 1 #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 */ #include "curl_printf.h" #include "curl_memory.h" @@ -105,6 +114,9 @@ struct thread_data { 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]; }; @@ -417,8 +429,18 @@ CURLcode Curl_resolver_is_resolved(struct Curl_easy *data, if(!data->state.async.dns) result = Curl_resolver_error(data); - else + else { *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); } @@ -745,6 +767,73 @@ 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 * @@ -826,6 +915,16 @@ struct Curl_addrinfo *Curl_resolver_getaddrinfo(struct Curl_easy *data, hostname, PF_INET, 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 *waitp = 1; /* expect asynchronous response */ } diff --git a/lib/doh.c b/lib/doh.c index d4500b3bd5..617a761f97 100644 --- a/lib/doh.c +++ b/lib/doh.c @@ -1064,59 +1064,6 @@ static CURLcode doh_decode_rdata_name(unsigned char **buf, size_t *remaining, 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 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 }; 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; if(memcmp(aval, expected, sizeof(expected))) return CURLE_BAD_CONTENT_ENCODING; @@ -1170,7 +1117,7 @@ static CURLcode doh_resp_decode_httpsrr(unsigned char *cp, size_t len, len -= 4; switch(pcode) { 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; break; case HTTPS_RR_CODE_NO_DEF_ALPN: diff --git a/lib/doh.h b/lib/doh.h index aae32a6540..53644863e6 100644 --- a/lib/doh.h +++ b/lib/doh.h @@ -28,6 +28,7 @@ #include "curl_addrinfo.h" #ifdef USE_HTTPSRR # include +# include "httpsrr.h" #endif #ifndef CURL_DISABLE_DOH @@ -123,19 +124,6 @@ struct dohaddr { #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 * value. diff --git a/lib/httpsrr.c b/lib/httpsrr.c new file mode 100644 index 0000000000..6e6b0a51c9 --- /dev/null +++ b/lib/httpsrr.c @@ -0,0 +1,88 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, , 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 diff --git a/lib/httpsrr.h b/lib/httpsrr.h new file mode 100644 index 0000000000..26f7a9826a --- /dev/null +++ b/lib/httpsrr.h @@ -0,0 +1,41 @@ +#ifndef HEADER_CURL_HTTPSRR_H +#define HEADER_CURL_HTTPSRR_H +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, , 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 */