]> git.ipfire.org Git - thirdparty/curl.git/commitdiff
doh: send HTTPS RR requests for all HTTP(S) transfers
authorDaniel Stenberg <daniel@haxx.se>
Wed, 15 Jan 2025 10:04:46 +0000 (11:04 +0100)
committerDaniel Stenberg <daniel@haxx.se>
Wed, 15 Jan 2025 11:55:15 +0000 (12:55 +0100)
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

lib/altsvc.c
lib/altsvc.h
lib/connect.c
lib/connect.h
lib/doh.c
lib/hostip.c
lib/hostip.h
tests/FILEFORMAT.md
tests/data/test2100
tests/runtests.pl
tests/server/disabled.c

index a77b1cf27966530f544047507e42818473a46091..095b3c168a0e0507cd590fddd179cf6b4fee0fe3 100644 (file)
@@ -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"
 
 #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 = "";
index 48999efb3177638c6617edf341f390783a0f87d6..5f94f832b4e1b615aa3d10d9a0571f92268ed0e7 100644 (file)
 #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;
index f21bb8e4f8a2aab586adccc6dd1adba2ec35ba03..05aabff61af6b99f1afee566fd9fbf6bf80978ec 100644 (file)
@@ -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"
 #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
index 160db9420fd82ba470dc44d88fe9d294159f3855..b59c38d7ceb9465de5c60ef0dc6c5e4d25cbe8ef 100644 (file)
@@ -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,
index 6f814f296da7ad647358f4833dea620c941640c7..834c00346bb438923b0f4945943d4e420303007a 100644 (file)
--- 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)
index c036049a61a9ebc00eab69b32b69f0f3b5d212c8..5ab854d1ce8cfb86afb2ec2c1eac325e22bc6c64 100644 (file)
@@ -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);
index 3a7f327aebf854c8093b1695ea4519bf60d33e43..e5e0d32604c2e30332f06f09f5156137616af8de 100644 (file)
@@ -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 */
index f2b0507f69ceb2150e685ac374c3f4116ace618e..a763f8711ce44a87e11b0a82414abfe1922d92a9 100644 (file)
@@ -451,6 +451,7 @@ Features testable here are:
 - `http/2`
 - `http/3`
 - `HTTPS-proxy`
+- `HTTPSRR`
 - `IDN`
 - `IPv6`
 - `Kerberos`
index 3f5f5d923257c090c810419a412d663f2d4efb4b..69ed43466cff9e1752777a9debd65c38d3a41c61 100644 (file)
@@ -52,7 +52,7 @@ DoH
 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
@@ -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;
 </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
@@ -87,6 +112,7 @@ Host: foo.example.com:%HTTPPORT
 User-Agent: curl/%VERSION\r
 Accept: */*\r
 \r
+%endif
 </protocol>
 </verify>
 </testcase>
index 3d3a1a8603bf569e15e512f19d16c3067e37f929..175a843eef3ff23b7089dbd36d5a32b1f093cf67 100755 (executable)
@@ -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";
 
index ef047c54855118cba959425fd712b503c9302469..daf7b5d2a8dc469aa640d7952171eaf5a5b360ea 100644 (file)
@@ -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
 };