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) + if(doh_decode_rdata_alpn(example, example_len, aval) != CURLE_OK) return CURLE_BAD_CONTENT_ENCODING; - if(strlen(aval) != strlen(expected)) - 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 };