From: Daniel Stenberg Date: Wed, 15 Jan 2025 10:04:46 +0000 (+0100) Subject: doh: send HTTPS RR requests for all HTTP(S) transfers X-Git-Tag: curl-8_12_0~113 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=bb93536270883b3528b3d80f011c95a4aa8c4bdb;p=thirdparty%2Fcurl.git doh: send HTTPS RR requests for all HTTP(S) transfers When enabled in the build. Update test2100: verify with HTTPS RR included Adjust runtests and server/disabled.c to include "HTTPSRR" as a feature in the test suite. Also, decode the ALPN list in HTTPS records straight into IDs. There's no point in storing everything in string format. Skip ALPNs we do not support. Closes #16007 --- diff --git a/lib/altsvc.c b/lib/altsvc.c index a77b1cf279..095b3c168a 100644 --- a/lib/altsvc.c +++ b/lib/altsvc.c @@ -41,6 +41,7 @@ #include "strdup.h" #include "inet_pton.h" #include "strparse.h" +#include "connect.h" /* The last 3 #include files should be in this order */ #include "curl_printf.h" @@ -54,23 +55,6 @@ #define H3VERSION "h3" -static enum alpnid alpn2alpnid(char *name, size_t len) -{ - if(len == 2) { - if(strncasecompare(name, "h1", 2)) - return ALPN_h1; - if(strncasecompare(name, "h2", 2)) - return ALPN_h2; - if(strncasecompare(name, "h3", 2)) - return ALPN_h3; - } - else if(len == 8) { - if(strncasecompare(name, "http/1.1", 8)) - return ALPN_h1; - } - return ALPN_none; /* unknown, probably rubbish input */ -} - /* Given the ALPN ID, return the name */ const char *Curl_alpnid2str(enum alpnid id) { @@ -154,8 +138,8 @@ static struct altsvc *altsvc_create(struct Curl_str *srchost, size_t srcport, size_t dstport) { - enum alpnid dstalpnid = alpn2alpnid(dstalpn->str, dstalpn->len); - enum alpnid srcalpnid = alpn2alpnid(srcalpn->str, srcalpn->len); + enum alpnid dstalpnid = Curl_alpn2alpnid(dstalpn->str, dstalpn->len); + enum alpnid srcalpnid = Curl_alpn2alpnid(srcalpn->str, srcalpn->len); if(!srcalpnid || !dstalpnid) return NULL; return altsvc_createid(srchost->str, srchost->len, @@ -537,7 +521,7 @@ CURLcode Curl_altsvc_parse(struct Curl_easy *data, do { if(*p == '=') { /* [protocol]="[host][:port]" */ - enum alpnid dstalpnid = alpn2alpnid(alpnbuf, alpnlen); + enum alpnid dstalpnid = Curl_alpn2alpnid(alpnbuf, alpnlen); p++; if(*p == '\"') { const char *dsthost = ""; diff --git a/lib/altsvc.h b/lib/altsvc.h index 48999efb31..5f94f832b4 100644 --- a/lib/altsvc.h +++ b/lib/altsvc.h @@ -29,13 +29,6 @@ #include #include "llist.h" -enum alpnid { - ALPN_none = 0, - ALPN_h1 = CURLALTSVC_H1, - ALPN_h2 = CURLALTSVC_H2, - ALPN_h3 = CURLALTSVC_H3 -}; - struct althost { char *host; unsigned short port; diff --git a/lib/connect.c b/lib/connect.c index f21bb8e4f8..05aabff61a 100644 --- a/lib/connect.c +++ b/lib/connect.c @@ -78,6 +78,7 @@ #include "vquic/vquic.h" /* for quic cfilters */ #include "http_proxy.h" #include "socks.h" +#include "strcase.h" /* The last 3 #include files should be in this order */ #include "curl_printf.h" @@ -88,6 +89,27 @@ #define ARRAYSIZE(A) (sizeof(A)/sizeof((A)[0])) #endif +#if !defined(CURL_DISABLE_ALTSVC) || defined(USE_HTTPSRR) + +enum alpnid Curl_alpn2alpnid(char *name, size_t len) +{ + if(len == 2) { + if(strncasecompare(name, "h1", 2)) + return ALPN_h1; + if(strncasecompare(name, "h2", 2)) + return ALPN_h2; + if(strncasecompare(name, "h3", 2)) + return ALPN_h3; + } + else if(len == 8) { + if(strncasecompare(name, "http/1.1", 8)) + return ALPN_h1; + } + return ALPN_none; /* unknown, probably rubbish input */ +} + +#endif + /* * Curl_timeleft() returns the amount of milliseconds left allowed for the * transfer/connection. If the value is 0, there is no timeout (ie there is diff --git a/lib/connect.h b/lib/connect.h index 160db9420f..b59c38d7ce 100644 --- a/lib/connect.h +++ b/lib/connect.h @@ -32,6 +32,8 @@ struct Curl_dns_entry; struct ip_quadruple; +enum alpnid Curl_alpn2alpnid(char *name, size_t len); + /* generic function that returns how much time there is left to run, according to the timeouts set */ timediff_t Curl_timeleft(struct Curl_easy *data, diff --git a/lib/doh.c b/lib/doh.c index 6f814f296d..834c00346b 100644 --- a/lib/doh.c +++ b/lib/doh.c @@ -456,17 +456,8 @@ struct Curl_addrinfo *Curl_doh(struct Curl_easy *data, #endif #ifdef USE_HTTPSRR - /* - * TODO: Figure out the conditions under which we want to make a request for - * an HTTPS RR when we are not doing ECH. For now, making this request - * breaks a bunch of DoH tests, e.g. test2100, where the additional request - * does not match the pre-cooked data files, so there is a bit of work - * attached to making the request in a non-ECH use-case. For the present, we - * will only make the request when ECH is enabled in the build and is being - * used for the curl operation. - */ -# ifdef USE_ECH - if(data->set.tls_ech & (CURLECH_ENABLE|CURLECH_HARD)) { + if(conn->handler->protocol & PROTO_FAMILY_HTTP) { + /* Only use HTTPS RR for HTTP(S) transfers */ char *qname = NULL; if(port != PORT_HTTPS) { qname = aprintf("_%d._https.%s", port, hostname); @@ -482,7 +473,6 @@ struct Curl_addrinfo *Curl_doh(struct Curl_easy *data, goto error; dohp->pending++; } -# endif #endif *waitp = TRUE; /* this never returns synchronously */ return NULL; @@ -1075,7 +1065,7 @@ static CURLcode doh_decode_rdata_name(unsigned char **buf, size_t *remaining, } static CURLcode doh_decode_rdata_alpn(unsigned char *cp, size_t len, - char **alpns) + unsigned char *alpns) { /* * spec here is as per RFC 9460, section-7.1.1 @@ -1088,19 +1078,14 @@ static CURLcode doh_decode_rdata_alpn(unsigned char *cp, size_t len, * backslash - same goes for a backslash character, and of course * we need to use two backslashes in strings when we mean one;-) */ - char *oval; struct dynbuf dval; + int idnum = 0; - if(!alpns) - return CURLE_OUT_OF_MEMORY; Curl_dyn_init(&dval, DYN_DOH_RESPONSE); while(len > 0) { size_t tlen = (size_t) *cp++; size_t i; - - /* if not 1st time, add comma */ - if(Curl_dyn_len(&dval) && Curl_dyn_addn(&dval, ",", 1)) - goto err; + enum alpnid id; len--; if(tlen > len) goto err; @@ -1114,12 +1099,18 @@ static CURLcode doh_decode_rdata_alpn(unsigned char *cp, size_t len, 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++] = id; + } + Curl_dyn_reset(&dval); } - /* this string is always null terminated */ - oval = Curl_dyn_ptr(&dval); - if(!oval) - goto err; - *alpns = oval; + if(idnum < MAX_HTTPSRR_ALPNS) + alpns[idnum] = ALPN_none; /* terminate the list */ return CURLE_OK; err: Curl_dyn_free(&dval); @@ -1137,14 +1128,12 @@ static CURLcode doh_test_alpn_escapes(void) 0x68, 0x32 /* value "h2" */ }; size_t example_len = sizeof(example); - char *aval = NULL; - static const char *expected = "f\\\\oo\\,bar,h2"; + 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) - return CURLE_BAD_CONTENT_ENCODING; - if(strlen(aval) != strlen(expected)) + if(doh_decode_rdata_alpn(example, example_len, aval) != CURLE_OK) return CURLE_BAD_CONTENT_ENCODING; - if(memcmp(aval, expected, strlen(aval))) + if(memcmp(aval, expected, sizeof(expected))) return CURLE_BAD_CONTENT_ENCODING; return CURLE_OK; } @@ -1181,7 +1170,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(doh_decode_rdata_alpn(cp, plen, lhrr->alpns) != CURLE_OK) goto err; break; case HTTPS_RR_CODE_NO_DEF_ALPN: @@ -1228,7 +1217,6 @@ static CURLcode doh_resp_decode_httpsrr(unsigned char *cp, size_t len, err: Curl_safefree(lhrr->target); Curl_safefree(lhrr->echconfiglist); - Curl_safefree(lhrr->alpns); Curl_safefree(lhrr); return CURLE_OUT_OF_MEMORY; } @@ -1240,8 +1228,9 @@ static void doh_print_httpsrr(struct Curl_easy *data, DEBUGASSERT(hrr); infof(data, "HTTPS RR: priority %d, target: %s", hrr->priority, hrr->target); - if(hrr->alpns) - infof(data, "HTTPS RR: alpns %s", hrr->alpns); + if(hrr->alpns[0] != ALPN_none) + infof(data, "HTTPS RR: alpns %u %u %u %u", + hrr->alpns[0], hrr->alpns[1], hrr->alpns[2], hrr->alpns[3]); else infof(data, "HTTPS RR: no alpns"); if(hrr->no_def_alpn) diff --git a/lib/hostip.c b/lib/hostip.c index c036049a61..5ab854d1ce 100644 --- a/lib/hostip.c +++ b/lib/hostip.c @@ -1080,7 +1080,6 @@ static void hostcache_unlink_entry(void *entry) #ifdef USE_HTTPSRR if(dns->hinfo) { free(dns->hinfo->target); - free(dns->hinfo->alpns); free(dns->hinfo->ipv4hints); free(dns->hinfo->echconfiglist); free(dns->hinfo->ipv6hints); diff --git a/lib/hostip.h b/lib/hostip.h index 3a7f327aeb..e5e0d32604 100644 --- a/lib/hostip.h +++ b/lib/hostip.h @@ -53,6 +53,13 @@ struct hostent; struct Curl_easy; struct connectdata; +enum alpnid { + ALPN_none = 0, + ALPN_h1 = CURLALTSVC_H1, + ALPN_h2 = CURLALTSVC_H2, + ALPN_h3 = CURLALTSVC_H3 +}; + /* * Curl_global_host_cache_init() initializes and sets up a global DNS cache. * Global DNS cache is general badness. Do not use. This will be removed in @@ -65,6 +72,7 @@ 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 { /* @@ -72,13 +80,14 @@ struct Curl_https_rrinfo { * See https://datatracker.ietf.org/doc/html/rfc9460#section-14.3.2 */ char *target; - char *alpns; /* keytag = 1 */ 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 */ diff --git a/tests/FILEFORMAT.md b/tests/FILEFORMAT.md index f2b0507f69..a763f8711c 100644 --- a/tests/FILEFORMAT.md +++ b/tests/FILEFORMAT.md @@ -451,6 +451,7 @@ Features testable here are: - `http/2` - `http/3` - `HTTPS-proxy` +- `HTTPSRR` - `IDN` - `IPv6` - `Kerberos` diff --git a/tests/data/test2100 b/tests/data/test2100 index 3f5f5d9232..69ed43466c 100644 --- a/tests/data/test2100 +++ b/tests/data/test2100 @@ -52,7 +52,7 @@ DoH IPv6 -HTTP GET using DoH +HTTP GET using DoH (with HTTPS RR) http://foo.example.com:%HTTPPORT/%TESTNUMBER --doh-url http://%HOSTIP:%HTTPPORT/%TESTNUMBER0001 @@ -70,6 +70,31 @@ http://foo.example.com:%HTTPPORT/%TESTNUMBER --doh-url http://%HOSTIP:%HTTPPORT/ s/com\x00\x00(\x1c|\x01)/com-00-00!/g; +%if HTTPSRR +POST /%TESTNUMBER0001 HTTP/1.1 +Host: %HOSTIP:%HTTPPORT +Accept: */* +Content-Type: application/dns-message +Content-Length: 33 + +%hex[%00%00%01%00%00%01%00%00%00%00%00%00%03foo%07example%03com-00-00!%00%01]hex%POST /%TESTNUMBER0001 HTTP/1.1 +Host: %HOSTIP:%HTTPPORT +Accept: */* +Content-Type: application/dns-message +Content-Length: 33 + +%hex[%00%00%01%00%00%01%00%00%00%00%00%00%03foo%07example%03com-00-00!%00%01]hex%POST /%TESTNUMBER0001 HTTP/1.1 +Host: %HOSTIP:%HTTPPORT +Accept: */* +Content-Type: application/dns-message +Content-Length: 47 + +%hex[%00%00%01%00%00%01%00%00%00%00%00%00%06_%HTTPPORT%06_https%03foo%07example%03com%00%00A%00%01]hex%GET /%TESTNUMBER HTTP/1.1 +Host: foo.example.com:%HTTPPORT +User-Agent: curl/%VERSION +Accept: */* + +%else POST /%TESTNUMBER0001 HTTP/1.1 Host: %HOSTIP:%HTTPPORT Accept: */* @@ -87,6 +112,7 @@ Host: foo.example.com:%HTTPPORT User-Agent: curl/%VERSION Accept: */* +%endif diff --git a/tests/runtests.pl b/tests/runtests.pl index 3d3a1a8603..175a843eef 100755 --- a/tests/runtests.pl +++ b/tests/runtests.pl @@ -822,6 +822,7 @@ sub checksystemfeatures { $feature{"large-time"} = 1; $feature{"large-size"} = 1; $feature{"sha512-256"} = 1; + $feature{"HTTPSRR"} = 1; $feature{"local-http"} = servers::localhttp(); $feature{"codeset-utf8"} = lc(langinfo(CODESET())) eq "utf-8"; diff --git a/tests/server/disabled.c b/tests/server/disabled.c index ef047c5485..daf7b5d2a8 100644 --- a/tests/server/disabled.c +++ b/tests/server/disabled.c @@ -115,6 +115,9 @@ static const char *disabled[]={ #ifndef CURL_CA_SEARCH_SAFE "win32-ca-search-safe", #endif +#endif +#ifndef USE_HTTPSRR + "HTTPSRR", #endif NULL };