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
#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"
#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)
{
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,
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 = "";
#include <curl/curl.h>
#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;
#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"
#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
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,
#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);
goto error;
dohp->pending++;
}
-# endif
#endif
*waitp = TRUE; /* this never returns synchronously */
return NULL;
}
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
* 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;
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);
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;
}
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:
err:
Curl_safefree(lhrr->target);
Curl_safefree(lhrr->echconfiglist);
- Curl_safefree(lhrr->alpns);
Curl_safefree(lhrr);
return CURLE_OUT_OF_MEMORY;
}
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)
#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);
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
#ifdef USE_HTTPSRR
#define CURL_MAXLEN_host_name 253
+#define MAX_HTTPSRR_ALPNS 4
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 */
- `http/2`
- `http/3`
- `HTTPS-proxy`
+- `HTTPSRR`
- `IDN`
- `IPv6`
- `Kerberos`
IPv6
</features>
<name>
-HTTP GET using DoH
+HTTP GET using DoH (with HTTPS RR)
</name>
<command>
http://foo.example.com:%HTTPPORT/%TESTNUMBER --doh-url http://%HOSTIP:%HTTPPORT/%TESTNUMBER0001
s/com\x00\x00(\x1c|\x01)/com-00-00!/g;
</strippart>
<protocol>
+%if HTTPSRR
+POST /%TESTNUMBER0001 HTTP/1.1\r
+Host: %HOSTIP:%HTTPPORT\r
+Accept: */*\r
+Content-Type: application/dns-message\r
+Content-Length: 33\r
+\r
+%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\r
+Host: %HOSTIP:%HTTPPORT\r
+Accept: */*\r
+Content-Type: application/dns-message\r
+Content-Length: 33\r
+\r
+%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\r
+Host: %HOSTIP:%HTTPPORT\r
+Accept: */*\r
+Content-Type: application/dns-message\r
+Content-Length: 47\r
+\r
+%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\r
+Host: foo.example.com:%HTTPPORT\r
+User-Agent: curl/%VERSION\r
+Accept: */*\r
+\r
+%else
POST /%TESTNUMBER0001 HTTP/1.1\r
Host: %HOSTIP:%HTTPPORT\r
Accept: */*\r
User-Agent: curl/%VERSION\r
Accept: */*\r
\r
+%endif
</protocol>
</verify>
</testcase>
$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";
#ifndef CURL_CA_SEARCH_SAFE
"win32-ca-search-safe",
#endif
+#endif
+#ifndef USE_HTTPSRR
+ "HTTPSRR",
#endif
NULL
};