From: Daniel Stenberg Date: Fri, 4 Apr 2025 21:21:41 +0000 (+0200) Subject: test1658: add unit test for the HTTPS RR decoder X-Git-Tag: curl-8_14_0~353 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=badfb951ec5c136185d0e522648a0cc5e319a918;p=thirdparty%2Fcurl.git test1658: add unit test for the HTTPS RR decoder Made the HTTPS-RR parser a little stricter while at it. Drop the ALPN escape handling, that was not needed. Make the hode handle (and ignore) duplicate ALPN entries. Closes #16972 --- diff --git a/lib/doh.c b/lib/doh.c index def9781e47..fe5faccbef 100644 --- a/lib/doh.c +++ b/lib/doh.c @@ -38,11 +38,13 @@ #include "connect.h" #include "strdup.h" #include "dynbuf.h" +#include "escape.h" +#include "urlapi-int.h" + /* The last 3 #include files should be in this order */ #include "curl_printf.h" #include "curl_memory.h" #include "memdebug.h" -#include "escape.h" #define DNS_CLASS_IN 0x01 @@ -1014,10 +1016,10 @@ UNITTEST void de_cleanup(struct dohentry *d) * just after the end of the DNS name encoding on output. (And * that is why it is an "unsigned char **" :-) */ -static CURLcode doh_decode_rdata_name(unsigned char **buf, size_t *remaining, - char **dnsname) +static CURLcode doh_decode_rdata_name(const unsigned char **buf, + size_t *remaining, char **dnsname) { - unsigned char *cp = NULL; + const unsigned char *cp = NULL; size_t rem = 0; unsigned char clen = 0; /* chunk len */ struct dynbuf thename; @@ -1057,43 +1059,23 @@ static CURLcode doh_decode_rdata_name(unsigned char **buf, size_t *remaining, return CURLE_OK; } -#ifdef DEBUGBUILD -static CURLcode doh_test_alpn_escapes(void) -{ - /* we will use an example from draft-ietf-dnsop-svcb, figure 10 */ - static unsigned char example[] = { - 0x08, /* length 8 */ - 0x66, 0x5c, 0x6f, 0x6f, 0x2c, 0x62, 0x61, 0x72, /* value "f\\oo,bar" */ - 0x02, /* length 2 */ - 0x68, 0x32 /* value "h2" */ - }; - size_t example_len = sizeof(example); - unsigned char aval[MAX_HTTPSRR_ALPNS] = { 0 }; - static const char expected[2] = { ALPN_h2, ALPN_none }; - - 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; - return CURLE_OK; -} -#endif +UNITTEST CURLcode doh_resp_decode_httpsrr(struct Curl_easy *data, + const unsigned char *cp, size_t len, + struct Curl_https_rrinfo **hrr); -static CURLcode doh_resp_decode_httpsrr(struct Curl_easy *data, - unsigned char *cp, size_t len, - struct Curl_https_rrinfo **hrr) +/* @unittest 1658 */ +UNITTEST CURLcode doh_resp_decode_httpsrr(struct Curl_easy *data, + const unsigned char *cp, size_t len, + struct Curl_https_rrinfo **hrr) { uint16_t pcode = 0, plen = 0; uint32_t expected_min_pcode = 0; struct Curl_https_rrinfo *lhrr = NULL; char *dnsname = NULL; CURLcode result = CURLE_OUT_OF_MEMORY; + size_t olen; -#ifdef DEBUGBUILD - /* a few tests of escaping, should not be here but ok for now */ - if(doh_test_alpn_escapes() != CURLE_OK) - return CURLE_OUT_OF_MEMORY; -#endif + *hrr = NULL; if(len <= 2) return CURLE_BAD_FUNCTION_ARGUMENT; lhrr = calloc(1, sizeof(struct Curl_https_rrinfo)); @@ -1105,6 +1087,11 @@ static CURLcode doh_resp_decode_httpsrr(struct Curl_easy *data, if(doh_decode_rdata_name(&cp, &len, &dnsname) != CURLE_OK) goto err; lhrr->target = dnsname; + if(Curl_junkscan(dnsname, &olen, FALSE)) { + /* unacceptable hostname content */ + result = CURLE_WEIRD_SERVER_REPLY; + goto err; + } lhrr->port = -1; /* until set */ while(len >= 4) { pcode = doh_get16bit(cp, 0); @@ -1131,9 +1118,12 @@ err: return result; } -# ifdef DEBUGBUILD -static void doh_print_httpsrr(struct Curl_easy *data, - struct Curl_https_rrinfo *hrr) +#ifdef DEBUGBUILD +UNITTEST void doh_print_httpsrr(struct Curl_easy *data, + struct Curl_https_rrinfo *hrr); + +UNITTEST void doh_print_httpsrr(struct Curl_easy *data, + struct Curl_https_rrinfo *hrr) { DEBUGASSERT(hrr); infof(data, "HTTPS RR: priority %d, target: %s", diff --git a/lib/httpsrr.c b/lib/httpsrr.c index 9a0bc827ef..003790bcbc 100644 --- a/lib/httpsrr.c +++ b/lib/httpsrr.c @@ -38,76 +38,67 @@ #include "curl_memory.h" #include "memdebug.h" -CURLcode Curl_httpsrr_decode_alpn(const unsigned char *cp, size_t len, - unsigned char *alpns) +#define MAX_ALPN_LENGTH 255 + +static CURLcode httpsrr_decode_alpn(const 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;-) + * The wire-format value for "alpn" consists of at least one alpn-id + * prefixed by its length as a single octet, and these length-value pairs + * are concatenated to form the SvcParamValue. These pairs MUST exactly fill + * the SvcParamValue; otherwise, the SvcParamValue is malformed. */ - 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; + return CURLE_BAD_CONTENT_ENCODING; /* we only store ALPN ids we know about */ - id = Curl_alpn2alpnid(Curl_dyn_ptr(&dval), Curl_dyn_len(&dval)); + id = Curl_alpn2alpnid(cp, tlen); if(id != ALPN_none) { if(idnum == MAX_HTTPSRR_ALPNS) break; - alpns[idnum++] = (unsigned char)id; + if(idnum && memchr(alpns, id, idnum)) + /* this ALPN id is already stored */ + ; + else + alpns[idnum++] = (unsigned char)id; } - Curl_dyn_reset(&dval); + cp += tlen; + len -= tlen; } - 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; } CURLcode Curl_httpsrr_set(struct Curl_easy *data, struct Curl_https_rrinfo *hi, uint16_t rrkey, const uint8_t *val, size_t vlen) { + CURLcode result = CURLE_OK; switch(rrkey) { + case HTTPS_RR_CODE_MANDATORY: + CURL_TRC_DNS(data, "HTTPS RR MANDATORY left to implement"); + break; case HTTPS_RR_CODE_ALPN: /* str_list */ - Curl_httpsrr_decode_alpn(val, vlen, hi->alpns); + result = httpsrr_decode_alpn((const char *)val, vlen, hi->alpns); CURL_TRC_DNS(data, "HTTPS RR ALPN: %u %u %u %u", hi->alpns[0], hi->alpns[1], hi->alpns[2], hi->alpns[3]); break; case HTTPS_RR_CODE_NO_DEF_ALPN: + if(vlen) /* no data */ + return CURLE_BAD_FUNCTION_ARGUMENT; hi->no_def_alpn = TRUE; CURL_TRC_DNS(data, "HTTPS RR no-def-alpn"); break; case HTTPS_RR_CODE_IPV4: /* addr4 list */ - if(!vlen) + if(!vlen || (vlen & 3)) /* the size must be 4-byte aligned */ return CURLE_BAD_FUNCTION_ARGUMENT; hi->ipv4hints = Curl_memdup(val, vlen); if(!hi->ipv4hints) @@ -125,7 +116,7 @@ CURLcode Curl_httpsrr_set(struct Curl_easy *data, CURL_TRC_DNS(data, "HTTPS RR ECH"); break; case HTTPS_RR_CODE_IPV6: /* addr6 list */ - if(!vlen) + if(!vlen || (vlen & 15)) /* the size must be 16-byte aligned */ return CURLE_BAD_FUNCTION_ARGUMENT; hi->ipv6hints = Curl_memdup(val, vlen); if(!hi->ipv6hints) @@ -143,7 +134,7 @@ CURLcode Curl_httpsrr_set(struct Curl_easy *data, CURL_TRC_DNS(data, "HTTPS RR unknown code"); break; } - return CURLE_OK; + return result; } struct Curl_https_rrinfo * diff --git a/lib/httpsrr.h b/lib/httpsrr.h index 847aa9abf6..1d71a57433 100644 --- a/lib/httpsrr.h +++ b/lib/httpsrr.h @@ -68,6 +68,7 @@ void Curl_httpsrr_cleanup(struct Curl_https_rrinfo *rrinfo); /* * Code points for DNS wire format SvcParams as per RFC 9460 */ +#define HTTPS_RR_CODE_MANDATORY 0x00 #define HTTPS_RR_CODE_ALPN 0x01 #define HTTPS_RR_CODE_NO_DEF_ALPN 0x02 #define HTTPS_RR_CODE_PORT 0x03 @@ -75,9 +76,6 @@ void Curl_httpsrr_cleanup(struct Curl_https_rrinfo *rrinfo); #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); - #if defined(USE_ARES) void Curl_dnsrec_done_cb(void *arg, ares_status_t status, size_t timeouts, diff --git a/lib/urlapi-int.h b/lib/urlapi-int.h index fcffab2e95..fbce1837ff 100644 --- a/lib/urlapi-int.h +++ b/lib/urlapi-int.h @@ -30,6 +30,8 @@ size_t Curl_is_absolute_url(const char *url, char *buf, size_t buflen, CURLUcode Curl_url_set_authority(CURLU *u, const char *authority); +CURLUcode Curl_junkscan(const char *url, size_t *urllen, bool allowspace); + #ifdef UNITTESTS UNITTEST CURLUcode Curl_parse_port(struct Curl_URL *u, struct dynbuf *host, bool has_scheme); diff --git a/lib/urlapi.c b/lib/urlapi.c index bbf7947c2a..88cfa82418 100644 --- a/lib/urlapi.c +++ b/lib/urlapi.c @@ -303,7 +303,7 @@ static CURLUcode redirect_url(const char *base, const char *relurl, } /* scan for byte values <= 31, 127 and sometimes space */ -static CURLUcode junkscan(const char *url, size_t *urllen, bool allowspace) +CURLUcode Curl_junkscan(const char *url, size_t *urllen, bool allowspace) { size_t n = strlen(url); size_t i; @@ -918,7 +918,7 @@ static CURLUcode parseurl(const char *url, CURLU *u, unsigned int flags) Curl_dyn_init(&host, CURL_MAX_INPUT_LENGTH); - result = junkscan(url, &urllen, !!(flags & CURLU_ALLOW_SPACE)); + result = Curl_junkscan(url, &urllen, !!(flags & CURLU_ALLOW_SPACE)); if(result) goto fail; diff --git a/tests/data/Makefile.am b/tests/data/Makefile.am index 02c3cbe5d5..00f5b659c7 100644 --- a/tests/data/Makefile.am +++ b/tests/data/Makefile.am @@ -220,6 +220,7 @@ test1620 test1621 \ test1630 test1631 test1632 test1633 test1634 test1635 \ \ test1650 test1651 test1652 test1653 test1654 test1655 test1656 test1657 \ +test1658 \ test1660 test1661 test1662 test1663 test1664 \ \ test1670 test1671 \ diff --git a/tests/data/test1658 b/tests/data/test1658 new file mode 100644 index 0000000000..96e247c324 --- /dev/null +++ b/tests/data/test1658 @@ -0,0 +1,30 @@ + + + +unittest +doh +httpsrr + + + +# +# Client-side + + +none + + +unittest +DoH + + +unit test for doh_resp_decode_httpsrr + + + + +URL: - +Test ended with result 0 + + + diff --git a/tests/unit/Makefile.inc b/tests/unit/Makefile.inc index 4a9369326e..0c13da94c9 100644 --- a/tests/unit/Makefile.inc +++ b/tests/unit/Makefile.inc @@ -39,6 +39,7 @@ UNITPROGS = unit1300 unit1302 unit1303 unit1304 unit1305 unit1307 \ unit1608 unit1609 unit1610 unit1611 unit1612 unit1614 unit1615 unit1616 \ unit1620 unit1621 \ unit1650 unit1651 unit1652 unit1653 unit1654 unit1655 unit1656 unit1657 \ + unit1658 \ unit1660 unit1661 unit1663 unit1664 \ unit2600 unit2601 unit2602 unit2603 unit2604 \ unit3200 \ @@ -126,6 +127,8 @@ unit1656_SOURCES = unit1656.c $(UNITFILES) unit1657_SOURCES = unit1657.c $(UNITFILES) +unit1658_SOURCES = unit1658.c $(UNITFILES) + unit1660_SOURCES = unit1660.c $(UNITFILES) unit1661_SOURCES = unit1661.c $(UNITFILES) diff --git a/tests/unit/unit1658.c b/tests/unit/unit1658.c new file mode 100644 index 0000000000..c6f28b2c84 --- /dev/null +++ b/tests/unit/unit1658.c @@ -0,0 +1,553 @@ +/*************************************************************************** + * _ _ ____ _ + * 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 "curlcheck.h" + +#include "doh.h" /* from the lib dir */ + +static CURLcode unit_setup(void) +{ + /* whatever you want done first */ + curl_global_init(CURL_GLOBAL_ALL); + return CURLE_OK; +} + +static void unit_stop(void) +{ + curl_global_cleanup(); + /* done before shutting down and exiting */ +} + +/* DoH + HTTPSRR are required */ +#if !defined(CURL_DISABLE_DOH) && defined(USE_HTTPSRR) + +extern CURLcode doh_resp_decode_httpsrr(struct Curl_easy *data, + const unsigned char *cp, size_t len, + struct Curl_https_rrinfo **hrr); +extern void doh_print_httpsrr(struct Curl_easy *data, + struct Curl_https_rrinfo *hrr); + +struct test { + const char *name; + const unsigned char *dns; + size_t len; /* size of the dns packet */ + const char *expect; +}; + +/* + * The idea here is that we pass one DNS packet at the time to the decoder. we + * then generate a string output with the results and compare if it matches + * the expected. One by one. + */ + +static char rrbuffer[256]; +static void rrresults(struct Curl_https_rrinfo *rr, CURLcode result) +{ + char *p = rrbuffer; + char *pend = rrbuffer + sizeof(rrbuffer); + msnprintf(rrbuffer, sizeof(rrbuffer), "r:%d|", (int)result); + p += strlen(rrbuffer); + + if(rr) { + unsigned int i; + msnprintf(p, pend - p, "p:%d|", rr->priority); + p += strlen(p); + + msnprintf(p, pend - p, "%s|", rr->target ? rr->target : "-"); + p += strlen(p); + + for(i = 0; i < MAX_HTTPSRR_ALPNS && rr->alpns[i] != ALPN_none; i++) { + msnprintf(p, pend - p, "alpn:%x|", rr->alpns[i]); + p += strlen(p); + } + if(rr->no_def_alpn) { + msnprintf(p, pend - p, "no-def-alpn|"); + p += strlen(p); + } + if(rr->port >= 0) { + msnprintf(p, pend - p, "port:%d|", rr->port); + p += strlen(p); + } + if(rr->ipv4hints) { + for(i = 0; i < rr->ipv4hints_len; i += 4) { + msnprintf(p, pend - p, "ipv4:%d.%d.%d.%d|", + rr->ipv4hints[i], + rr->ipv4hints[i + 1], + rr->ipv4hints[i + 2], + rr->ipv4hints[i + 3]); + p += strlen(p); + } + } + if(rr->echconfiglist) { + msnprintf(p, pend - p, "ech:"); + p += strlen(p); + for(i = 0; i < rr->echconfiglist_len; i++) { + msnprintf(p, pend - p, "%02x", rr->echconfiglist[i]); + p += strlen(p); + } + msnprintf(p, pend - p, "|"); + p += strlen(p); + } + if(rr->ipv6hints) { + for(i = 0; i < rr->ipv6hints_len; i += 16) { + int x; + msnprintf(p, pend - p, "ipv6:"); + p += strlen(p); + for(x = 0; x < 16; x += 2) { + msnprintf(p, pend - p, "%s%02x%02x", + x ? ":" : "", + rr->ipv6hints[i + x], + rr->ipv6hints[i + x + 1]); + p += strlen(p); + } + msnprintf(p, pend - p, "|"); + p += strlen(p); + } + } + } +} + +UNITTEST_START +{ + /* The "SvcParamKeys" specified within the HTTPS RR packet *must* be + provided in numerical order. */ + + static struct test t[] = { + { + "single h2 alpn", + (const unsigned char *)"\x00\x00" /* 16-bit prio */ + "\x04name\x00" /* RNAME */ + "\x00\x01" /* RR (1 == ALPN) */ + "\x00\x03" /* data size */ + "\x02" /* length byte */ + "h2", + 15, + "r:0|p:0|name.|alpn:10|" + }, + { + "single h2 alpn missing last byte", + (const unsigned char *)"\x00\x00" /* 16-bit prio */ + "\x04name\x00" /* RNAME */ + "\x00\x01" /* RR (1 == ALPN) */ + "\x00\x03" /* data size */ + "\x02" /* length byte */ + "h", /* missing byte */ + 14, + "r:8|" + }, + { + "two alpns", + (const unsigned char *)"\x00\x00" /* 16-bit prio */ + "\x04name\x04some\x00" /* RNAME */ + "\x00\x01" /* RR (1 == ALPN) */ + "\x00\x06" /* data size */ + "\x02" /* ALPN length byte */ + "h2" + "\x02" /* APLN length byte */ + "h3", + 23, + "r:0|p:0|name.some.|alpn:10|alpn:20|" + }, + { + "wrong syntax alpns", + (const unsigned char *)"\x00\x00" /* 16-bit prio */ + "\x04name\x04some\x00" /* RNAME */ + "\x00\x01" /* RR (1 == ALPN) */ + "\x00\x06" /* data size */ + "\x02" /* ALPN length byte */ + "h2" + "\x03" /* APLN length byte (WRONG) */ + "h3", + 23, + "r:61|" + }, + { + "five alpns (ignore dupes)", /* we only support four */ + (const unsigned char *)"\x00\x00" /* 16-bit prio */ + "\x04name\x04some\x00" /* RNAME */ + "\x00\x01" /* RR (1 == ALPN) */ + "\x00\x0f" /* data size */ + "\x02" /* ALPN length byte */ + "h2" + "\x02" /* ALPN length byte */ + "h2" + "\x02" /* ALPN length byte */ + "h2" + "\x02" /* ALPN length byte */ + "h2" + "\x02" /* APLN length byte */ + "h3", + 32, + "r:0|p:0|name.some.|alpn:10|alpn:20|" + }, + { + "rname only", + (const unsigned char *)"\x00\x00" /* 16-bit prio */ + "\x04name\x04some\x00", /* RNAME */ + 13, + "r:0|p:0|name.some.|" + }, + { + "rname with low ascii byte", + (const unsigned char *)"\x00\x00" /* 16-bit prio */ + "\x04name\x04som\x03\x00", /* RNAME */ + 13, + "r:8|" + }, + { + "rname with null byte", + (const unsigned char *)"\x00\x00" /* 16-bit prio */ + "\x04sa\x00e\x04some\x00", /* RNAME */ + 13, + "r:27|" + }, + { + "rname only (missing byte)", + (const unsigned char *)"\x00\x00" /* 16-bit prio */ + "\x04name\x05some\x00", /* RNAME */ + /* it lacks a byte */ + 13, + "r:27|" + }, + { + "unrecognized alpn", + (const unsigned char *)"\x00\x00" /* 16-bit prio */ + "\x04name\x04some\x00" /* RNAME */ + "\x00\x01" /* RR (1 == ALPN) */ + "\x00\x06" /* data size */ + "\x02" /* ALPN length byte */ + "h8" /* unrecognized */ + "\x02" /* APLN length byte */ + "h1", + 23, + "r:0|p:0|name.some.|alpn:8|" + }, + { + "alnt + no-default-alpn", + (const unsigned char *)"\x00\x00" /* 16-bit prio */ + "\x04name\x04some\x00" /* RNAME */ + "\x00\x01" /* RR (1 == ALPN) */ + "\x00\x03" /* data size */ + "\x02" /* ALPN length byte */ + "h2" + "\x00\x02" /* RR (2 == NO DEFALT ALPN) */ + "\x00\x00", /* must be zero */ + 24, + "r:0|p:0|name.some.|alpn:10|no-def-alpn|" + }, + { + "alnt + no-default-alpn with size", + (const unsigned char *)"\x00\x00" /* 16-bit prio */ + "\x04name\x04some\x00" /* RNAME */ + "\x00\x01" /* RR (1 == ALPN) */ + "\x00\x03" /* data size */ + "\x02" /* ALPN length byte */ + "h2" + "\x00\x02" /* RR (2 == NO DEFALT ALPN) */ + "\x00\x01" /* must be zero */ + "\xff", + 25, + "r:43|" + }, + { + "alnt + no-default-alpn with size too short package", + (const unsigned char *)"\x00\x00" /* 16-bit prio */ + "\x04name\x04some\x00" /* RNAME */ + "\x00\x01" /* RR (1 == ALPN) */ + "\x00\x03" /* data size */ + "\x02" /* ALPN length byte */ + "h2" + "\x00\x02" /* RR (2 == NO DEFALT ALPN) */ + "\x00\x01", /* must be zero */ + /* missing last byte in the packet */ + 24, + "r:8|" + }, + { + "rname + blank alpn field", + (const unsigned char *)"\x11\x11" /* 16-bit prio */ + "\x04name\x04some\x00" /* RNAME */ + "\x00\x01" /* RR (1 == ALPN) */ + "\x00\x00", /* data size, strictly speaking this is illegal: + "one or more alpn-ids" */ + 17, + "r:0|p:4369|name.some.|" + }, + { + "no rname + blank alpn", + (const unsigned char *)"\x00\x11" /* 16-bit prio */ + "\x00" /* no RNAME */ + "\x00\x01" /* RR (1 == ALPN) */ + "\x00\x00", /* data size */ + 7, + "r:0|p:17|.|" + }, + { + "unsupported field", + (const unsigned char *)"\xff\xff" /* 16-bit prio */ + "\x00" /* no RNAME */ + "\x00\x07" /* RR (7 == not a supported data) */ + "\x00\x02" /* data size */ + "FF", /* unknown to curl */ + 9, + "r:0|p:65535|.|" + }, + { + "unsupported field (wrong size)", + (const unsigned char *)"\xff\xff" /* 16-bit prio */ + "\x00" /* no RNAME */ + "\x00\x07" /* RR (7 == not a supported data) */ + "\x00\x02" /* data size */ + "F", /* unknown to curl */ + 8, + "r:8|" + }, + { + "port number", + (const unsigned char *)"\x00\x10" /* 16-bit prio */ + "\x00" /* no RNAME */ + "\x00\x01" /* RR (1 == ALPN) */ + "\x00\x03" /* data size */ + "\x02" /* ALPN length byte */ + "h2" + "\x00\x03" /* RR (3 == PORT) */ + "\x00\x02" /* data size */ + "\x12\x34", /* port number */ + 16, + "r:0|p:16|.|alpn:10|port:4660|" + }, + { + "port number with wrong size (3 bytes)", + (const unsigned char *)"\x00\x10" /* 16-bit prio */ + "\x00" /* no RNAME */ + "\x00\x01" /* RR (1 == ALPN) */ + "\x00\x03" /* data size */ + "\x02" /* ALPN length byte */ + "h2" + "\x00\x03" /* RR (3 == PORT) */ + "\x00\x03" /* data size */ + "\x12\x34\x00", /* 24 bit port number! */ + 17, + "r:43|" + }, + { + "port number with wrong size (1 byte)", + (const unsigned char *)"\x00\x10" /* 16-bit prio */ + "\x00" /* no RNAME */ + "\x00\x01" /* RR (1 == ALPN) */ + "\x00\x03" /* data size */ + "\x02" /* ALPN length byte */ + "h2" + "\x00\x03" /* RR (3 == PORT) */ + "\x00\x01" /* data size */ + "\x12", /* 8 bit port number! */ + 15, + "r:43|" + }, + { + "alpn + two ipv4 addreses", + (const unsigned char *)"\x00\x10" /* 16-bit prio */ + "\x00" /* no RNAME */ + "\x00\x01" /* RR (1 == ALPN) */ + "\x00\x03" /* data size */ + "\x02" /* ALPN length byte */ + "h2" + "\x00\x04" /* RR (4 == Ipv4hints) */ + "\x00\x08" /* data size */ + "\xc0\xa8\x00\x01" /* 32 bits */ + "\xc0\xa8\x00\x02", /* 32 bits */ + 22, + "r:0|p:16|.|alpn:10|ipv4:192.168.0.1|ipv4:192.168.0.2|" + }, + { + "alpn + two ipv4 addreses in wrong order", + (const unsigned char *)"\x00\x10" /* 16-bit prio */ + "\x00" /* no RNAME */ + "\x00\x04" /* RR (4 == Ipv4hints) */ + "\x00\x08" /* data size */ + "\xc0\xa8\x00\x01" /* 32 bits */ + "\xc0\xa8\x00\x02" /* 32 bits */ + "\x00\x01" /* RR (1 == ALPN) */ + "\x00\x03" /* data size */ + "\x02" /* ALPN length byte */ + "h2", + 22, + "r:8|" + }, + { + "alpn + ipv4 address with wrong size", + (const unsigned char *)"\x00\x10" /* 16-bit prio */ + "\x00" /* no RNAME */ + "\x00\x01" /* RR (1 == ALPN) */ + "\x00\x03" /* data size */ + "\x02" /* ALPN length byte */ + "h2" + "\x00\x04" /* RR (4 == Ipv4hints) */ + "\x00\x05" /* data size */ + "\xc0\xa8\x00\x01\xff", /* 32 + 8 bits */ + 19, + "r:43|" + }, + { + "alpn + one ipv6 address", + (const unsigned char *)"\x00\x10" /* 16-bit prio */ + "\x00" /* no RNAME */ + "\x00\x01" /* RR (1 == ALPN) */ + "\x00\x03" /* data size */ + "\x02" /* ALPN length byte */ + "h2" + "\x00\x06" /* RR (6 == Ipv6hints) */ + "\x00\x10" /* data size */ + "\xfe\x80\xda\xbb\xc1\xff\xfe\xa3\x8a\x22\x12\x34\x56\x78\x91\x23", + 30, + "r:0|p:16|.|alpn:10|ipv6:fe80:dabb:c1ff:fea3:8a22:1234:5678:9123|" + }, + { + "alpn + one ipv6 address with wrong size", + (const unsigned char *)"\x00\x10" /* 16-bit prio */ + "\x00" /* no RNAME */ + "\x00\x01" /* RR (1 == ALPN) */ + "\x00\x03" /* data size */ + "\x02" /* ALPN length byte */ + "h2" + "\x00\x06" /* RR (6 == Ipv6hints) */ + "\x00\x11" /* data size */ + "\xfe\x80\xda\xbb\xc1\xff\xfe\xa3\x8a\x22\x12\x34\x56\x78\x91\x23\x45", + 31, + "r:43|" + }, + { + "alpn + two ipv6 addresses", + (const unsigned char *)"\x00\x10" /* 16-bit prio */ + "\x00" /* no RNAME */ + "\x00\x01" /* RR (1 == ALPN) */ + "\x00\x03" /* data size */ + "\x02" /* ALPN length byte */ + "h2" + "\x00\x06" /* RR (6 == Ipv6hints) */ + "\x00\x20" /* data size */ + "\xfe\x80\xda\xbb\xc1\xff\xfe\xa3\x8a\x22\x12\x34\x56\x78\x91\x23" + "\xee\x80\xda\xbb\xc1\xff\xfe\xa3\x8a\x22\x12\x34\x56\x78\x91\x25", + 46, + "r:0|p:16|.|alpn:10|ipv6:fe80:dabb:c1ff:fea3:8a22:1234:5678:9123|" + "ipv6:ee80:dabb:c1ff:fea3:8a22:1234:5678:9125|" + }, + { + "alpn + ech", + (const unsigned char *)"\x00\x10" /* 16-bit prio */ + "\x00" /* no RNAME */ + "\x00\x01" /* RR (1 == ALPN) */ + "\x00\x03" /* data size */ + "\x02" /* ALPN length byte */ + "h2" + "\x00\x05" /* RR (5 == ECH) */ + "\x00\x10" /* data size */ + "\xfe\x80\xda\xbb\xc1\xff\xfe\xa3\x8a\x22\x12\x34\x56\x78\x91\x23", + 30, + "r:0|p:16|.|alpn:10|ech:fe80dabbc1fffea38a22123456789123|" + }, + { + "fully packed", + (const unsigned char *)"\xa0\x0b" /* 16-bit prio */ + "\x00" /* no RNAME */ + "\x00\x00" /* RR (0 == MANDATORY) */ + "\x00\x00" /* data size */ + "\x00\x01" /* RR (1 == ALPN) */ + "\x00\x06" /* data size */ + "\x02" /* ALPN length byte */ + "h2" + "\x02" /* ALPN length byte */ + "h1" + "\x00\x02" /* RR (2 == NO DEFALT ALPN) */ + "\x00\x00" /* must be zero */ + "\x00\x03" /* RR (3 == PORT) */ + "\x00\x02" /* data size */ + "\xbc\x71" /* port number */ + "\x00\x04" /* RR (4 == Ipv4hints) */ + "\x00\x08" /* data size */ + "\xc0\xa8\x00\x01" /* 32 bits */ + "\xc0\xa8\x00\x02" /* 32 bits */ + "\x00\x05" /* RR (5 == ECH) */ + "\x00\x10" /* data size */ + "\xfe\x80\xda\xbb\xc1\xff\x7e\xb3\x8a\x22\x12\x34\x56\x78\x91\x23" + "\x00\x06" /* RR (6 == Ipv6hints) */ + "\x00\x20" /* data size */ + "\xfe\x80\xda\xbb\xc1\xff\xfe\xa3\x8a\x22\x12\x34\x56\x78\x91\x23" + "\xee\x80\xda\xbb\xc1\xff\xfe\xa3\x8a\x22\x12\x34\x56\x78\x91\x25" + "\x01\x07" /* RR (263 == not supported) */ + "\x00\x04" /* data size */ + "FFAA", /* unknown to the world */ + 103, + "r:0|p:40971|.|alpn:10|alpn:8|no-def-alpn|port:48241|" + "ipv4:192.168.0.1|ipv4:192.168.0.2|" + "ech:fe80dabbc1ff7eb38a22123456789123|" + "ipv6:fe80:dabb:c1ff:fea3:8a22:1234:5678:9123|" + "ipv6:ee80:dabb:c1ff:fea3:8a22:1234:5678:9125|" + } + }; + + CURLcode result = CURLE_OUT_OF_MEMORY; + CURL *easy; + + easy = curl_easy_init(); + /* so that we get the log output: */ + curl_easy_setopt(easy, CURLOPT_VERBOSE, 1L); + if(easy) { + unsigned int i; + + for(i = 0; i < CURL_ARRAYSIZE(t); i++) { + struct Curl_https_rrinfo *hrr; + + printf("test %i: %s\n", i, t[i].name); + + result = doh_resp_decode_httpsrr(easy, t[i].dns, t[i].len, &hrr); + + /* create an output */ + rrresults(hrr, result); + + /* is the output the expected? */ + if(strcmp(rrbuffer, t[i].expect)) { + fprintf(stderr, "Test %s (%i) failed\n" + "Expected: %s\n" + "Received: %s\n", t[i].name, i, t[i].expect, rrbuffer); + unitfail++; + } + + /* free the generated struct */ + if(hrr) { + Curl_httpsrr_cleanup(hrr); + curl_free(hrr); + } + } + curl_easy_cleanup(easy); + } +} +UNITTEST_STOP + +#else /* CURL_DISABLE_DOH or not HTTPSRR enabled */ + +UNITTEST_START +/* nothing to do, just succeed */ +UNITTEST_STOP + +#endif