From 36f34a3e79d6228078794defc2c626dad031993f Mon Sep 17 00:00:00 2001 From: Mark Andrews Date: Thu, 5 Nov 2020 17:22:52 +1100 Subject: [PATCH] Parse and print HTTPS and SVCB records --- .../checkzone/zones/bad-svcb-mandatory.db | 15 + bin/tests/system/checkzone/zones/bad-svcb.db | 15 + bin/tests/system/checkzone/zones/good-svcb.db | 23 + bin/tests/system/doth/example.axfr.good | 4 + bin/tests/system/genzone.sh | 6 + bin/tests/system/rrchecker/typelist.good | 2 + bin/tests/system/xfer/dig1.good | 4 + bin/tests/system/xfer/dig2.good | 4 + lib/dns/include/dns/result.h | 3 +- lib/dns/rdata.c | 132 +- lib/dns/rdata/in_1/https_65.c | 184 +++ lib/dns/rdata/in_1/https_65.h | 33 + lib/dns/rdata/in_1/svcb_64.c | 1217 +++++++++++++++++ lib/dns/rdata/in_1/svcb_64.h | 38 + lib/dns/result.c | 2 + lib/dns/tests/rdata_test.c | 254 +++- util/copyrights | 4 + 17 files changed, 1913 insertions(+), 27 deletions(-) create mode 100644 bin/tests/system/checkzone/zones/bad-svcb-mandatory.db create mode 100644 bin/tests/system/checkzone/zones/bad-svcb.db create mode 100644 bin/tests/system/checkzone/zones/good-svcb.db create mode 100644 lib/dns/rdata/in_1/https_65.c create mode 100644 lib/dns/rdata/in_1/https_65.h create mode 100644 lib/dns/rdata/in_1/svcb_64.c create mode 100644 lib/dns/rdata/in_1/svcb_64.h diff --git a/bin/tests/system/checkzone/zones/bad-svcb-mandatory.db b/bin/tests/system/checkzone/zones/bad-svcb-mandatory.db new file mode 100644 index 0000000000..d7048798e9 --- /dev/null +++ b/bin/tests/system/checkzone/zones/bad-svcb-mandatory.db @@ -0,0 +1,15 @@ +; Copyright (C) Internet Systems Consortium, Inc. ("ISC") +; +; This Source Code Form is subject to the terms of the Mozilla Public +; License, v. 2.0. If a copy of the MPL was not distributed with this +; file, You can obtain one at http://mozilla.org/MPL/2.0/. +; +; See the COPYRIGHT file distributed with this work for additional +; information regarding copyright ownership. + +$TTL 600 +@ SOA ns hostmaster 2011012708 3600 1200 604800 1200 + NS ns +ns A 192.0.2.1 + +svcb SVCB 0 . mandatory=alpn diff --git a/bin/tests/system/checkzone/zones/bad-svcb.db b/bin/tests/system/checkzone/zones/bad-svcb.db new file mode 100644 index 0000000000..d40e8db980 --- /dev/null +++ b/bin/tests/system/checkzone/zones/bad-svcb.db @@ -0,0 +1,15 @@ +; Copyright (C) Internet Systems Consortium, Inc. ("ISC") +; +; This Source Code Form is subject to the terms of the Mozilla Public +; License, v. 2.0. If a copy of the MPL was not distributed with this +; file, You can obtain one at http://mozilla.org/MPL/2.0/. +; +; See the COPYRIGHT file distributed with this work for additional +; information regarding copyright ownership. + +$TTL 600 +@ SOA ns hostmaster 2011012708 3600 1200 604800 1200 + NS ns +ns A 192.0.2.1 + +svcb SVCB 0 . unknown=wha diff --git a/bin/tests/system/checkzone/zones/good-svcb.db b/bin/tests/system/checkzone/zones/good-svcb.db new file mode 100644 index 0000000000..e94916402c --- /dev/null +++ b/bin/tests/system/checkzone/zones/good-svcb.db @@ -0,0 +1,23 @@ +; Copyright (C) Internet Systems Consortium, Inc. ("ISC") +; +; This Source Code Form is subject to the terms of the Mozilla Public +; License, v. 2.0. If a copy of the MPL was not distributed with this +; file, You can obtain one at http://mozilla.org/MPL/2.0/. +; +; See the COPYRIGHT file distributed with this work for additional +; information regarding copyright ownership. + +$TTL 600 +@ SOA ns hostmaster 2011012708 3600 1200 604800 1200 + NS ns +ns A 192.0.2.1 + +svcb0 SVCB 0 example.net. +svcb1 SVCB 1 . port=60 alpn=h3 ech="ZWFzdGVyIGVnZyE=" +svcb2 SVCB 2 . no-default-alpn alpn=alpn +svcb3 SVCB 3 . ipv4hint="10.10.10.10" +svcb4 SVCB 4 . ipv6hint="feed:a::bee" +svcb5 SVCB 5 . key9999="something" +svcb6 SVCB 6 . mandatory=port,alpn port=60 alpn=h3 +svcb7 SVCB 7 . mandatory=port,alpn port=60 alpn=h1,h3 +svcb8 SVCB 8 . mandatory=port,alpn port=60 alpn="h1\\,h2,h3" diff --git a/bin/tests/system/doth/example.axfr.good b/bin/tests/system/doth/example.axfr.good index 01e15fa9a2..653fb46bda 100644 --- a/bin/tests/system/doth/example.axfr.good +++ b/bin/tests/system/doth/example.axfr.good @@ -2558,6 +2558,8 @@ hinfo01.example. 3600 IN HINFO "Generic PC clone" "NetBSD-1.4" hinfo02.example. 3600 IN HINFO "PC" "NetBSD" hip1.example. 3600 IN HIP 2 200100107B1A74DF365639CC39F1D578 AwEAAbdxyhNuSutc5EMzxTs9LBPCIkOFH8cIvM4p9+LrV4e19WzK00+CI6zBCQTdtWsuxKbWIy87UOoJTwkUs7lBu+Upr1gsNrut79ryra+bSRGQb1slImA8YVJyuIDsj7kwzG7jnERNqnWxZ48AWkskmdHaVDP4BcelrTI3rMXdXF5D hip2.example. 3600 IN HIP 2 200100107B1A74DF365639CC39F1D578 AwEAAbdxyhNuSutc5EMzxTs9LBPCIkOFH8cIvM4p9+LrV4e19WzK00+CI6zBCQTdtWsuxKbWIy87UOoJTwkUs7lBu+Upr1gsNrut79ryra+bSRGQb1slImA8YVJyuIDsj7kwzG7jnERNqnWxZ48AWkskmdHaVDP4BcelrTI3rMXdXF5D rvs.example.com. +https0.example. 3600 IN HTTPS 0 example.net. +https1.example. 3600 IN HTTPS 1 . port=60 ipseckey01.example. 3600 IN IPSECKEY 10 1 2 192.0.2.38 AQNRU3mG7TVTO2BkR47usntb102uFJtugbo6BSGvgqt4AQ== ipseckey02.example. 3600 IN IPSECKEY 10 0 2 . AQNRU3mG7TVTO2BkR47usntb102uFJtugbo6BSGvgqt4AQ== ipseckey03.example. 3600 IN IPSECKEY 10 1 2 192.0.2.3 AQNRU3mG7TVTO2BkR47usntb102uFJtugbo6BSGvgqt4AQ== @@ -2634,6 +2636,8 @@ srv01.example. 3600 IN SRV 0 0 0 . srv02.example. 3600 IN SRV 65535 65535 65535 old-slow-box.example. sshfp01.example. 3600 IN SSHFP 4 2 C76D8329954DA2835751E371544E963EFDA099080D6C58DD2BFD9A31 6E162C83 sshfp02.example. 3600 IN SSHFP 1 2 BF29468C83AC58CCF8C85AB7B3BEB054ECF1E38512B8353AB36471FA 88961DCC +svcb0.example. 3600 IN SVCB 0 example.net. +svcb1.example. 3600 IN SVCB 1 . port=60 ta.example. 3600 IN TA 30795 1 1 310D27F4D82C1FC2400704EA9939FE6E1CEAA3B9 talink0.example. 3600 IN TALINK . talink1.example. talink1.example. 3600 IN TALINK talink0.example. talink2.example. diff --git a/bin/tests/system/genzone.sh b/bin/tests/system/genzone.sh index 2f8882b401..88ae7f04e9 100644 --- a/bin/tests/system/genzone.sh +++ b/bin/tests/system/genzone.sh @@ -493,6 +493,12 @@ dlv DLV 30795 1 1 ( ; type 65280-65534 (private use) +https0 HTTPS 0 example.net. +https1 HTTPS 1 . port=60 + +svcb0 SVCB 0 example.net. +svcb1 SVCB 1 . port=60 + ; keydata (internal type used for managed keys) keydata TYPE65533 \# 0 keydata TYPE65533 \# 6 010203040506 diff --git a/bin/tests/system/rrchecker/typelist.good b/bin/tests/system/rrchecker/typelist.good index c843fc7b51..3c3e5cd384 100644 --- a/bin/tests/system/rrchecker/typelist.good +++ b/bin/tests/system/rrchecker/typelist.good @@ -59,6 +59,8 @@ CDNSKEY OPENPGPKEY CSYNC ZONEMD +SVCB +HTTPS SPF UINFO UID diff --git a/bin/tests/system/xfer/dig1.good b/bin/tests/system/xfer/dig1.good index 0f8fad0f31..9fa5437d38 100644 --- a/bin/tests/system/xfer/dig1.good +++ b/bin/tests/system/xfer/dig1.good @@ -59,6 +59,8 @@ hinfo01.example. 3600 IN HINFO "Generic PC clone" "NetBSD-1.4" hinfo02.example. 3600 IN HINFO "PC" "NetBSD" hip1.example. 3600 IN HIP 2 200100107B1A74DF365639CC39F1D578 AwEAAbdxyhNuSutc5EMzxTs9LBPCIkOFH8cIvM4p9+LrV4e19WzK00+CI6zBCQTdtWsuxKbWIy87UOoJTwkUs7lBu+Upr1gsNrut79ryra+bSRGQb1slImA8YVJyuIDsj7kwzG7jnERNqnWxZ48AWkskmdHaVDP4BcelrTI3rMXdXF5D hip2.example. 3600 IN HIP 2 200100107B1A74DF365639CC39F1D578 AwEAAbdxyhNuSutc5EMzxTs9LBPCIkOFH8cIvM4p9+LrV4e19WzK00+CI6zBCQTdtWsuxKbWIy87UOoJTwkUs7lBu+Upr1gsNrut79ryra+bSRGQb1slImA8YVJyuIDsj7kwzG7jnERNqnWxZ48AWkskmdHaVDP4BcelrTI3rMXdXF5D rvs.example.com. +https0.example. 3600 IN HTTPS 0 example.net. +https1.example. 3600 IN HTTPS 1 . port=60 ipseckey01.example. 3600 IN IPSECKEY 10 1 2 192.0.2.38 AQNRU3mG7TVTO2BkR47usntb102uFJtugbo6BSGvgqt4AQ== ipseckey02.example. 3600 IN IPSECKEY 10 0 2 . AQNRU3mG7TVTO2BkR47usntb102uFJtugbo6BSGvgqt4AQ== ipseckey03.example. 3600 IN IPSECKEY 10 1 2 192.0.2.3 AQNRU3mG7TVTO2BkR47usntb102uFJtugbo6BSGvgqt4AQ== @@ -137,6 +139,8 @@ sink02.example. 3600 IN SINK 8 0 2 l4ik smimea.example. 3600 IN SMIMEA 1 1 2 92003BA34942DC74152E2F2C408D29ECA5A520E7F2E06BB944F4DCA3 46BAF63C1B177615D466F6C4B71C216A50292BD58C9EBDD2F74E38FE 51FFD48C43326CBC srv01.example. 3600 IN SRV 0 0 0 . srv02.example. 3600 IN SRV 65535 65535 65535 old-slow-box.example. +svcb0.example. 3600 IN SVCB 0 example.net. +svcb1.example. 3600 IN SVCB 1 . port=60 ta.example. 3600 IN TA 30795 1 1 310D27F4D82C1FC2400704EA9939FE6E1CEAA3B9 talink0.example. 3600 IN TALINK . talink1.example. talink1.example. 3600 IN TALINK talink0.example. talink2.example. diff --git a/bin/tests/system/xfer/dig2.good b/bin/tests/system/xfer/dig2.good index fed8926817..2229f9ce7a 100644 --- a/bin/tests/system/xfer/dig2.good +++ b/bin/tests/system/xfer/dig2.good @@ -69,6 +69,8 @@ isdn04.example. 3600 IN ISDN "isdn-address" "subaddress" hip1.example. 3600 IN HIP 2 200100107B1A74DF365639CC39F1D578 AwEAAbdxyhNuSutc5EMzxTs9LBPCIkOFH8cIvM4p9+LrV4e19WzK00+CI6zBCQTdtWsuxKbWIy87UOoJTwkUs7lBu+Upr1gsNrut79ryra+bSRGQb1slImA8YVJyuIDsj7kwzG7jnERNqnWxZ48AWkskmdHaVDP4BcelrTI3rMXdXF5D hip2.example. 3600 IN HIP 2 200100107B1A74DF365639CC39F1D578 AwEAAbdxyhNuSutc5EMzxTs9LBPCIkOFH8cIvM4p9+LrV4e19WzK00+CI6zBCQTdtWsuxKbWIy87UOoJTwkUs7lBu+Upr1gsNrut79ryra+bSRGQb1slImA8YVJyuIDsj7kwzG7jnERNqnWxZ48AWkskmdHaVDP4BcelrTI3rMXdXF5D rvs.example.com. dnskey01.example. 3600 IN DNSKEY 512 255 1 AQMFD5raczCJHViKtLYhWGz8hMY9UGRuniJDBzC7w0aRyzWZriO6i2od GWWQVucZqKVsENW91IOW4vqudngPZsY3GvQ/xVA8/7pyFj6b7Esga60z yGW6LFe9r8n6paHrlG5ojqf0BaqHT+8= +https0.example. 3600 IN HTTPS 0 example.net. +https1.example. 3600 IN HTTPS 1 . port=60 keydata.example. 3600 IN TYPE65533 \# 0 keydata.example. 3600 IN TYPE65533 \# 6 010203040506 keydata.example. 3600 IN TYPE65533 \# 18 010203040506010203040506010203040506 @@ -137,6 +139,8 @@ srv01.example. 3600 IN SRV 0 0 0 . srv02.example. 3600 IN SRV 65535 65535 65535 old-slow-box.example. sshfp01.example. 3600 IN SSHFP 4 2 C76D8329954DA2835751E371544E963EFDA099080D6C58DD2BFD9A31 6E162C83 sshfp02.example. 3600 IN SSHFP 1 2 BF29468C83AC58CCF8C85AB7B3BEB054ECF1E38512B8353AB36471FA 88961DCC +svcb0.example. 3600 IN SVCB 0 example.net. +svcb1.example. 3600 IN SVCB 1 . port=60 ta.example. 3600 IN TA 30795 1 1 310D27F4D82C1FC2400704EA9939FE6E1CEAA3B9 talink0.example. 3600 IN TALINK . talink1.example. talink1.example. 3600 IN TALINK talink0.example. talink2.example. diff --git a/lib/dns/include/dns/result.h b/lib/dns/include/dns/result.h index 58e7426b3f..22a75b5d24 100644 --- a/lib/dns/include/dns/result.h +++ b/lib/dns/include/dns/result.h @@ -162,8 +162,9 @@ #define DNS_R_NSEC3SALTRANGE (ISC_RESULTCLASS_DNS + 124) #define DNS_R_NSEC3BADALG (ISC_RESULTCLASS_DNS + 125) #define DNS_R_NSEC3RESALT (ISC_RESULTCLASS_DNS + 126) +#define DNS_R_INCONSISTENTRR (ISC_RESULTCLASS_DNS + 127) -#define DNS_R_NRESULTS 127 /*%< Number of results */ +#define DNS_R_NRESULTS 128 /*%< Number of results */ /* * DNS wire format rcodes. diff --git a/lib/dns/rdata.c b/lib/dns/rdata.c index c8d684a38c..29e472c266 100644 --- a/lib/dns/rdata.c +++ b/lib/dns/rdata.c @@ -159,6 +159,13 @@ txt_fromtext(isc_textregion_t *source, isc_buffer_t *target); static isc_result_t txt_fromwire(isc_buffer_t *source, isc_buffer_t *target); +static isc_result_t +commatxt_fromtext(isc_textregion_t *source, bool comma, isc_buffer_t *target); + +static isc_result_t +commatxt_totext(isc_region_t *source, bool quote, bool comma, + isc_buffer_t *target); + static isc_result_t multitxt_totext(isc_region_t *source, isc_buffer_t *target); @@ -302,6 +309,22 @@ static isc_result_t generic_tostruct_tlsa(ARGS_TOSTRUCT); static void generic_freestruct_tlsa(ARGS_FREESTRUCT); +static isc_result_t generic_fromtext_in_svcb(ARGS_FROMTEXT); +static isc_result_t generic_totext_in_svcb(ARGS_TOTEXT); +static isc_result_t generic_fromwire_in_svcb(ARGS_FROMWIRE); +static isc_result_t generic_towire_in_svcb(ARGS_TOWIRE); +static isc_result_t generic_fromstruct_in_svcb(ARGS_FROMSTRUCT); +static isc_result_t generic_tostruct_in_svcb(ARGS_TOSTRUCT); +static void generic_freestruct_in_svcb(ARGS_FREESTRUCT); +static isc_result_t generic_additionaldata_in_svcb(ARGS_ADDLDATA); +static bool generic_checknames_in_svcb(ARGS_CHECKNAMES); +static isc_result_t +generic_rdata_in_svcb_first(dns_rdata_in_svcb_t *); +static isc_result_t +generic_rdata_in_svcb_next(dns_rdata_in_svcb_t *); +static void +generic_rdata_in_svcb_current(dns_rdata_in_svcb_t *, isc_region_t *); + /*% INT16 Size */ #define NS_INT16SZ 2 /*% IPv6 Address Size */ @@ -1415,7 +1438,8 @@ name_length(const dns_name_t *name) { } static isc_result_t -txt_totext(isc_region_t *source, bool quote, isc_buffer_t *target) { +commatxt_totext(isc_region_t *source, bool quote, bool comma, + isc_buffer_t *target) { unsigned int tl; unsigned int n; unsigned char *sp; @@ -1445,30 +1469,48 @@ txt_totext(isc_region_t *source, bool quote, isc_buffer_t *target) { /* * \DDD space (0x20) if not quoting. */ - if (*sp < (quote ? 0x20 : 0x21) || *sp >= 0x7f) { + if (*sp < (quote ? ' ' : '!') || *sp >= 0x7f) { if (tl < 4) { return (ISC_R_NOSPACE); } - *tp++ = 0x5c; - *tp++ = 0x30 + ((*sp / 100) % 10); - *tp++ = 0x30 + ((*sp / 10) % 10); - *tp++ = 0x30 + (*sp % 10); + *tp++ = '\\'; + *tp++ = '0' + ((*sp / 100) % 10); + *tp++ = '0' + ((*sp / 10) % 10); + *tp++ = '0' + (*sp % 10); sp++; tl -= 4; continue; } /* * Escape double quote and backslash. If we are not - * enclosing the string in double quotes also escape - * at sign and semicolon. + * enclosing the string in double quotes, also escape + * at sign (@) and semicolon (;) unless comma is set. + * If comma is set, then only escape commas (,). */ - if (*sp == 0x22 || *sp == 0x5c || - (!quote && (*sp == 0x40 || *sp == 0x3b))) { + if (*sp == '"' || *sp == '\\' || (comma && *sp == ',') || + (!comma && !quote && (*sp == '@' || *sp == ';'))) + { if (tl < 2) { return (ISC_R_NOSPACE); } *tp++ = '\\'; tl--; + /* + * Perform comma escape processing. + * ',' => '\\,' + * '\' => '\\\\' + */ + if (comma && (*sp == ',' || *sp == '\\')) { + if (tl < ((*sp == '\\') ? 3 : 2)) { + return (ISC_R_NOSPACE); + } + *tp++ = '\\'; + tl--; + if (*sp == '\\') { + *tp++ = '\\'; + tl--; + } + } } if (tl < 1) { return (ISC_R_NOSPACE); @@ -1490,9 +1532,14 @@ txt_totext(isc_region_t *source, bool quote, isc_buffer_t *target) { } static isc_result_t -txt_fromtext(isc_textregion_t *source, isc_buffer_t *target) { +txt_totext(isc_region_t *source, bool quote, isc_buffer_t *target) { + return (commatxt_totext(source, quote, false, target)); +} + +static isc_result_t +commatxt_fromtext(isc_textregion_t *source, bool comma, isc_buffer_t *target) { isc_region_t tregion; - bool escape; + bool escape = false, comma_escape = false, seen_comma = false; unsigned int n, nrem; char *s; unsigned char *t; @@ -1504,7 +1551,6 @@ txt_fromtext(isc_textregion_t *source, isc_buffer_t *target) { n = source->length; t = tregion.base; nrem = tregion.length; - escape = false; if (nrem < 1) { return (ISC_R_NOSPACE); } @@ -1549,6 +1595,25 @@ txt_fromtext(isc_textregion_t *source, isc_buffer_t *target) { continue; } escape = false; + /* + * Level 1 escape processing complete. + * If comma is set perform comma escape processing. + * + * Level 1 Level 2 ALPN's + * h1\,h2 => h1,h2 => h1 and h2 + * h1\\,h2 => h1\,h2 => h1,h2 + * h1\\h2 => h1\h2 => h1h2 + * h1\\\\h2 => h1\\h2 => h1\h2 + */ + if (comma && !comma_escape && c == ',') { + seen_comma = true; + break; + } + if (comma && !comma_escape && c == '\\') { + comma_escape = true; + continue; + } + comma_escape = false; if (nrem == 0) { return ((tregion.length <= 256U) ? ISC_R_NOSPACE : DNS_R_SYNTAX); @@ -1556,14 +1621,41 @@ txt_fromtext(isc_textregion_t *source, isc_buffer_t *target) { *t++ = c; nrem--; } - if (escape) { + + /* + * Incomplete escape processing? + */ + if (escape || (comma && comma_escape)) { return (DNS_R_SYNTAX); } + + if (comma) { + /* + * Disallow empty ALPN at start (",h1") or in the + * middle ("h1,,h2"). + */ + if (s == source->base || (seen_comma && s == source->base + 1)) + { + return (DNS_R_SYNTAX); + } + isc_textregion_consume(source, s - source->base); + /* + * Disallow empty ALPN at end ("h1,"). + */ + if (seen_comma && source->length == 0) { + return (DNS_R_SYNTAX); + } + } *tregion.base = (unsigned char)(t - tregion.base - 1); isc_buffer_add(target, *tregion.base + 1); return (ISC_R_SUCCESS); } +static isc_result_t +txt_fromtext(isc_textregion_t *source, isc_buffer_t *target) { + return (commatxt_fromtext(source, false, target)); +} + static isc_result_t txt_fromwire(isc_buffer_t *source, isc_buffer_t *target) { unsigned int n; @@ -1618,20 +1710,20 @@ multitxt_totext(isc_region_t *source, isc_buffer_t *target) { n0 = source->length - 1; while (n--) { - if (*sp < 0x20 || *sp >= 0x7f) { + if (*sp < ' ' || *sp >= 0x7f) { if (tl < 4) { return (ISC_R_NOSPACE); } - *tp++ = 0x5c; - *tp++ = 0x30 + ((*sp / 100) % 10); - *tp++ = 0x30 + ((*sp / 10) % 10); - *tp++ = 0x30 + (*sp % 10); + *tp++ = '\\'; + *tp++ = '0' + ((*sp / 100) % 10); + *tp++ = '0' + ((*sp / 10) % 10); + *tp++ = '0' + (*sp % 10); sp++; tl -= 4; continue; } /* double quote, backslash */ - if (*sp == 0x22 || *sp == 0x5c) { + if (*sp == '"' || *sp == '\\') { if (tl < 2) { return (ISC_R_NOSPACE); } diff --git a/lib/dns/rdata/in_1/https_65.c b/lib/dns/rdata/in_1/https_65.c new file mode 100644 index 0000000000..464b58337d --- /dev/null +++ b/lib/dns/rdata/in_1/https_65.c @@ -0,0 +1,184 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +/* draft-ietf-dnsop-svcb-https-02 */ + +#ifndef RDATA_IN_1_HTTPS_65_C +#define RDATA_IN_1_HTTPS_65_C + +#define RRTYPE_HTTPS_ATTRIBUTES 0 + +/* + * Most of these functions refer to equivalent functions for SVCB, + * since wire and presentation formats are identical. + */ + +static inline isc_result_t +fromtext_in_https(ARGS_FROMTEXT) { + REQUIRE(type == dns_rdatatype_https); + REQUIRE(rdclass == dns_rdataclass_in); + + return (generic_fromtext_in_svcb(CALL_FROMTEXT)); +} + +static inline isc_result_t +totext_in_https(ARGS_TOTEXT) { + REQUIRE(rdata->type == dns_rdatatype_https); + REQUIRE(rdata->rdclass == dns_rdataclass_in); + REQUIRE(rdata->length != 0); + + return (generic_totext_in_svcb(CALL_TOTEXT)); +} + +static inline isc_result_t +fromwire_in_https(ARGS_FROMWIRE) { + REQUIRE(type == dns_rdatatype_https); + REQUIRE(rdclass == dns_rdataclass_in); + + return (generic_fromwire_in_svcb(CALL_FROMWIRE)); +} + +static inline isc_result_t +towire_in_https(ARGS_TOWIRE) { + REQUIRE(rdata->type == dns_rdatatype_https); + REQUIRE(rdata->length != 0); + + return (generic_towire_in_svcb(CALL_TOWIRE)); +} + +static inline int +compare_in_https(ARGS_COMPARE) { + isc_region_t region1; + isc_region_t region2; + + REQUIRE(rdata1->type == rdata2->type); + REQUIRE(rdata1->rdclass == rdata2->rdclass); + REQUIRE(rdata1->type == dns_rdatatype_https); + REQUIRE(rdata1->rdclass == dns_rdataclass_in); + REQUIRE(rdata1->length != 0); + REQUIRE(rdata2->length != 0); + + dns_rdata_toregion(rdata1, ®ion1); + dns_rdata_toregion(rdata2, ®ion2); + + return (isc_region_compare(®ion1, ®ion2)); +} + +static inline isc_result_t +fromstruct_in_https(ARGS_FROMSTRUCT) { + dns_rdata_in_https_t *https = source; + + REQUIRE(type == dns_rdatatype_https); + REQUIRE(rdclass == dns_rdataclass_in); + REQUIRE(https != NULL); + REQUIRE(https->common.rdtype == type); + REQUIRE(https->common.rdclass == rdclass); + + return (generic_fromstruct_in_svcb(CALL_FROMSTRUCT)); +} + +static inline isc_result_t +tostruct_in_https(ARGS_TOSTRUCT) { + dns_rdata_in_https_t *https = target; + + REQUIRE(rdata->rdclass == dns_rdataclass_in); + REQUIRE(rdata->type == dns_rdatatype_https); + REQUIRE(https != NULL); + REQUIRE(rdata->length != 0); + + return (generic_tostruct_in_svcb(CALL_TOSTRUCT)); +} + +static inline void +freestruct_in_https(ARGS_FREESTRUCT) { + dns_rdata_in_https_t *https = source; + + REQUIRE(https != NULL); + REQUIRE(https->common.rdclass == dns_rdataclass_in); + REQUIRE(https->common.rdtype == dns_rdatatype_https); + + generic_freestruct_in_svcb(CALL_FREESTRUCT); +} + +static inline isc_result_t +additionaldata_in_https(ARGS_ADDLDATA) { + REQUIRE(rdata->type == dns_rdatatype_https); + REQUIRE(rdata->rdclass == dns_rdataclass_in); + + return (generic_additionaldata_in_svcb(CALL_ADDLDATA)); +} + +static inline isc_result_t +digest_in_https(ARGS_DIGEST) { + isc_region_t region1; + + REQUIRE(rdata->type == dns_rdatatype_https); + REQUIRE(rdata->rdclass == dns_rdataclass_in); + + dns_rdata_toregion(rdata, ®ion1); + return ((digest)(arg, ®ion1)); +} + +static inline bool +checkowner_in_https(ARGS_CHECKOWNER) { + REQUIRE(type == dns_rdatatype_https); + REQUIRE(rdclass == dns_rdataclass_in); + + UNUSED(name); + UNUSED(type); + UNUSED(rdclass); + UNUSED(wildcard); + + return (true); +} + +static inline bool +checknames_in_https(ARGS_CHECKNAMES) { + REQUIRE(rdata->type == dns_rdatatype_https); + REQUIRE(rdata->rdclass == dns_rdataclass_in); + + return (generic_checknames_in_svcb(CALL_CHECKNAMES)); +} + +static inline int +casecompare_in_https(ARGS_COMPARE) { + return (compare_in_https(rdata1, rdata2)); +} + +isc_result_t +dns_rdata_in_https_first(dns_rdata_in_https_t *https) { + REQUIRE(https != NULL); + REQUIRE(https->common.rdtype == dns_rdatatype_https); + REQUIRE(https->common.rdclass == dns_rdataclass_in); + + return (generic_rdata_in_svcb_first(https)); +} + +isc_result_t +dns_rdata_in_https_next(dns_rdata_in_https_t *https) { + REQUIRE(https != NULL); + REQUIRE(https->common.rdtype == dns_rdatatype_https); + REQUIRE(https->common.rdclass == dns_rdataclass_in); + + return (generic_rdata_in_svcb_next(https)); +} + +void +dns_rdata_in_https_current(dns_rdata_in_https_t *https, isc_region_t *region) { + REQUIRE(https != NULL); + REQUIRE(https->common.rdtype == dns_rdatatype_https); + REQUIRE(https->common.rdclass == dns_rdataclass_in); + REQUIRE(region != NULL); + + generic_rdata_in_svcb_current(https, region); +} + +#endif /* RDATA_IN_1_HTTPS_65_C */ diff --git a/lib/dns/rdata/in_1/https_65.h b/lib/dns/rdata/in_1/https_65.h new file mode 100644 index 0000000000..2469a35962 --- /dev/null +++ b/lib/dns/rdata/in_1/https_65.h @@ -0,0 +1,33 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +#ifndef IN_1_HTTPS_65_H +#define IN_1_HTTPS_65_H 1 + +/*! + * \brief Per draft-ietf-dnsop-svcb-https-02 + */ + +/* + * Wire and presentation formats for HTTPS are identical to SVCB. + */ +typedef struct dns_rdata_in_svcb dns_rdata_in_https_t; + +isc_result_t +dns_rdata_in_https_first(dns_rdata_in_https_t *); + +isc_result_t +dns_rdata_in_https_next(dns_rdata_in_https_t *); + +void +dns_rdata_in_https_current(dns_rdata_in_https_t *, isc_region_t *); + +#endif /* IN_1_HTTPS_65_H */ diff --git a/lib/dns/rdata/in_1/svcb_64.c b/lib/dns/rdata/in_1/svcb_64.c new file mode 100644 index 0000000000..8987094aa3 --- /dev/null +++ b/lib/dns/rdata/in_1/svcb_64.c @@ -0,0 +1,1217 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +/* draft-ietf-dnsop-svcb-https-02 */ + +#ifndef RDATA_IN_1_SVCB_64_C +#define RDATA_IN_1_SVCB_64_C + +#define RRTYPE_SVCB_ATTRIBUTES 0 + +#define SVCB_MAN_KEY 0 +#define SVCB_ALPN_KEY 1 +#define SVCB_NO_DEFAULT_ALPN_KEY 2 + +/* + * Service Binding Parameter Registry + */ +enum encoding { + sbpr_text, + sbpr_port, + sbpr_ipv4s, + sbpr_ipv6s, + sbpr_base64, + sbpr_empty, + sbpr_alpn, + sbpr_keylist +}; +static const struct { + const char *name; /* Restricted to lowercase LDH by registry. */ + unsigned int value; + enum encoding encoding; + bool initial; +} sbpr[] = { + { "mandatory", 0, sbpr_keylist, true }, + { "alpn", 1, sbpr_alpn, true }, + { "no-default-alpn", 2, sbpr_empty, true }, + { "port", 3, sbpr_port, true }, + { "ipv4hint", 4, sbpr_ipv4s, true }, + { "ech", 5, sbpr_base64, true }, + { "ipv6hint", 6, sbpr_ipv6s, true }, +}; + +static isc_result_t +alpn_fromtxt(isc_textregion_t *source, isc_buffer_t *target) { + isc_textregion_t source0 = *source; + do { + RETERR(commatxt_fromtext(&source0, true, target)); + } while (source0.length != 0); + return (ISC_R_SUCCESS); +} + +static int +svckeycmp(const void *a1, const void *a2) { + const unsigned char *u1 = a1, *u2 = a2; + if (*u1 != *u2) { + return (*u1 - *u2); + } + return (*(++u1) - *(++u2)); +} + +static isc_result_t +svcsortkeylist(isc_buffer_t *target, unsigned int used) { + isc_region_t region; + + isc_buffer_usedregion(target, ®ion); + isc_region_consume(®ion, used); + INSIST(region.length > 0U); + qsort(region.base, region.length / 2, 2, svckeycmp); + /* Reject duplicates. */ + while (region.length >= 4) { + if (region.base[0] == region.base[2] && + region.base[1] == region.base[3]) { + return (DNS_R_SYNTAX); + } + isc_region_consume(®ion, 2); + } + return (ISC_R_SUCCESS); +} + +static isc_result_t +svcb_validate(uint16_t key, isc_region_t *region) { + size_t i; + +#ifndef ARRAYSIZE +/* defined in winnt.h */ +#define ARRAYSIZE(x) (sizeof(x) / sizeof(*x)) +#endif + + for (i = 0; i < ARRAYSIZE(sbpr); i++) { + if (sbpr[i].value == key) { + switch (sbpr[i].encoding) { + case sbpr_port: + if (region->length != 2) { + return (DNS_R_FORMERR); + } + break; + case sbpr_ipv4s: + if ((region->length % 4) != 0 || + region->length == 0) { + return (DNS_R_FORMERR); + } + break; + case sbpr_ipv6s: + if ((region->length % 16) != 0 || + region->length == 0) { + return (DNS_R_FORMERR); + } + break; + case sbpr_alpn: { + if (region->length == 0) { + return (DNS_R_FORMERR); + } + while (region->length != 0) { + size_t l = *region->base + 1; + if (l > region->length) { + return (DNS_R_FORMERR); + } + isc_region_consume(region, l); + } + break; + } + case sbpr_keylist: { + if ((region->length % 2) != 0 || + region->length == 0) { + return (DNS_R_FORMERR); + } + /* In order? */ + while (region->length >= 4) { + if (region->base[0] > region->base[2] || + (region->base[0] == + region->base[2] && + region->base[1] >= + region->base[3])) + { + return (DNS_R_FORMERR); + } + isc_region_consume(region, 2); + } + break; + } + case sbpr_text: + case sbpr_base64: + break; + case sbpr_empty: + if (region->length != 0) { + return (DNS_R_FORMERR); + } + break; + } + } + } + return (ISC_R_SUCCESS); +} + +/* + * Parse keyname from region. + */ +static isc_result_t +svc_keyfromregion(isc_textregion_t *region, char sep, uint16_t *value, + isc_buffer_t *target) { + char *e = NULL; + size_t i; + unsigned long ul; + + /* Look for known key names. */ + for (i = 0; i < ARRAYSIZE(sbpr); i++) { + size_t len = strlen(sbpr[i].name); + if (strncasecmp(region->base, sbpr[i].name, len) != 0 || + (region->base[len] != 0 && region->base[len] != sep)) + { + continue; + } + isc_textregion_consume(region, len); + ul = sbpr[i].value; + goto finish; + } + /* Handle keyXXXXX form. */ + if (strncmp(region->base, "key", 3) != 0) { + return (DNS_R_SYNTAX); + } + isc_textregion_consume(region, 3); + /* Disallow [+-]XXXXX which is allowed by strtoul. */ + if (region->length == 0 || *region->base == '-' || *region->base == '+') + { + return (DNS_R_SYNTAX); + } + /* No zero padding. */ + if (region->length > 1 && *region->base == '0' && + region->base[1] != sep) { + return (DNS_R_SYNTAX); + } + ul = strtoul(region->base, &e, 10); + /* Valid number? */ + if (e == region->base || (*e != sep && *e != 0)) { + return (DNS_R_SYNTAX); + } + if (ul > 0xffff) { + return (ISC_R_RANGE); + } + isc_textregion_consume(region, e - region->base); +finish: + if (sep == ',' && region->length == 1) { + return (DNS_R_SYNTAX); + } + /* Consume separator. */ + if (region->length != 0) { + isc_textregion_consume(region, 1); + } + RETERR(uint16_tobuffer(ul, target)); + if (value != NULL) { + *value = ul; + } + return (ISC_R_SUCCESS); +} + +static isc_result_t +svc_fromtext(isc_textregion_t *region, isc_buffer_t *target) { + char *e = NULL; + char abuf[16]; + char tbuf[sizeof("aaaa:aaaa:aaaa:aaaa:aaaa:aaaa:255.255.255.255,")]; + isc_buffer_t sb; + isc_region_t keyregion; + size_t len; + uint16_t key; + unsigned int i; + unsigned int used; + unsigned long ul; + + for (i = 0; i < ARRAYSIZE(sbpr); i++) { + len = strlen(sbpr[i].name); + if (strncmp(region->base, sbpr[i].name, len) != 0 || + (region->base[len] != 0 && region->base[len] != '=')) + { + continue; + } + + if (region->base[len] == '=') { + len++; + } + + RETERR(uint16_tobuffer(sbpr[i].value, target)); + isc_textregion_consume(region, len); + + sb = *target; + RETERR(uint16_tobuffer(0, target)); /* length */ + + switch (sbpr[i].encoding) { + case sbpr_text: + RETERR(multitxt_fromtext(region, target)); + break; + case sbpr_alpn: + RETERR(alpn_fromtxt(region, target)); + break; + case sbpr_port: + if (!isdigit(*region->base)) { + return (DNS_R_SYNTAX); + } + ul = strtoul(region->base, &e, 10); + if (*e != '\0') { + return (DNS_R_SYNTAX); + } + if (ul > 0xffff) { + return (ISC_R_RANGE); + } + RETERR(uint16_tobuffer(ul, target)); + break; + case sbpr_ipv4s: + do { + snprintf(tbuf, sizeof(tbuf), "%*s", + (int)(region->length), region->base); + e = strchr(tbuf, ','); + if (e != NULL) { + *e++ = 0; + isc_textregion_consume(region, + e - tbuf); + } + if (inet_pton(AF_INET, tbuf, abuf) != 1) { + return (DNS_R_SYNTAX); + } + mem_tobuffer(target, abuf, 4); + } while (e != NULL); + break; + case sbpr_ipv6s: + do { + snprintf(tbuf, sizeof(tbuf), "%*s", + (int)(region->length), region->base); + e = strchr(tbuf, ','); + if (e != NULL) { + *e++ = 0; + isc_textregion_consume(region, + e - tbuf); + } + if (inet_pton(AF_INET6, tbuf, abuf) != 1) { + return (DNS_R_SYNTAX); + } + mem_tobuffer(target, abuf, 16); + } while (e != NULL); + break; + case sbpr_base64: + RETERR(isc_base64_decodestring(region->base, target)); + break; + case sbpr_empty: + if (region->length != 0) { + return (DNS_R_SYNTAX); + } + break; + case sbpr_keylist: + if (region->length == 0) { + return (DNS_R_SYNTAX); + } + used = isc_buffer_usedlength(target); + while (region->length != 0) { + RETERR(svc_keyfromregion(region, ',', NULL, + target)); + } + RETERR(svcsortkeylist(target, used)); + break; + default: + INSIST(0); + ISC_UNREACHABLE(); + } + + len = isc_buffer_usedlength(target) - + isc_buffer_usedlength(&sb) - 2; + RETERR(uint16_tobuffer(len, &sb)); /* length */ + return (ISC_R_SUCCESS); + } + + RETERR(svc_keyfromregion(region, '=', &key, target)); + if (region->length == 0) { + RETERR(uint16_tobuffer(0, target)); /* length */ + /* Sanity check keyXXXXX form. */ + keyregion.base = isc_buffer_used(target); + keyregion.length = 0; + return (svcb_validate(key, &keyregion)); + } + sb = *target; + RETERR(uint16_tobuffer(0, target)); /* dummy length */ + RETERR(multitxt_fromtext(region, target)); + len = isc_buffer_usedlength(target) - isc_buffer_usedlength(&sb) - 2; + RETERR(uint16_tobuffer(len, &sb)); /* length */ + /* Sanity check keyXXXXX form. */ + keyregion.base = isc_buffer_used(&sb); + keyregion.length = len; + return (svcb_validate(key, &keyregion)); +} + +static const char * +svcparamkey(unsigned short value, enum encoding *encoding, char *buf, + size_t len) { + size_t i; + int n; + + for (i = 0; i < ARRAYSIZE(sbpr); i++) { + if (sbpr[i].value == value && sbpr[i].initial) { + *encoding = sbpr[i].encoding; + return (sbpr[i].name); + } + } + n = snprintf(buf, len, "key%u", value); + INSIST(n > 0 && (unsigned)n < len); + *encoding = sbpr_text; + return (buf); +} + +static isc_result_t +svcsortkeys(isc_buffer_t *target, unsigned int used) { + isc_region_t r1, r2, man = { .base = NULL, .length = 0 }; + unsigned char buf[1024]; + uint16_t mankey = 0; + bool have_alpn = false; + + if (isc_buffer_usedlength(target) == used) { + return (ISC_R_SUCCESS); + } + + /* + * Get the parameters into r1. + */ + isc_buffer_usedregion(target, &r1); + isc_region_consume(&r1, used); + + while (1) { + uint16_t key1, len1, key2, len2; + unsigned char *base1, *base2; + + r2 = r1; + + /* + * Get the first parameter. + */ + base1 = r1.base; + key1 = uint16_fromregion(&r1); + isc_region_consume(&r1, 2); + len1 = uint16_fromregion(&r1); + isc_region_consume(&r1, 2); + isc_region_consume(&r1, len1); + + /* + * Was there only one key left? + */ + if (r1.length == 0) { + if (mankey != 0) { + /* Is this the last mandatory key? */ + if (key1 != mankey || man.length != 0) { + return (DNS_R_INCONSISTENTRR); + } + } else if (key1 == SVCB_MAN_KEY) { + /* Lone mandatory field. */ + return (DNS_R_DISALLOWED); + } else if (key1 == SVCB_NO_DEFAULT_ALPN_KEY && + !have_alpn) { + /* Missing required ALPN field. */ + return (DNS_R_DISALLOWED); + } + return (ISC_R_SUCCESS); + } + + /* + * Find the smallest parameter. + */ + while (r1.length != 0) { + base2 = r1.base; + key2 = uint16_fromregion(&r1); + isc_region_consume(&r1, 2); + len2 = uint16_fromregion(&r1); + isc_region_consume(&r1, 2); + isc_region_consume(&r1, len2); + if (key2 == key1) { + return (DNS_R_DUPLICATE); + } + if (key2 < key1) { + base1 = base2; + key1 = key2; + len1 = len2; + } + } + + /* + * Do we need to move the smallest parameter to the start? + */ + if (base1 != r2.base) { + size_t offset = 0; + size_t bytes = len1 + 4; + size_t length = base1 - r2.base; + + /* + * Move the smallest parameter to the start. + */ + while (bytes > 0) { + size_t count; + + if (bytes > sizeof(buf)) { + count = sizeof(buf); + } else { + count = bytes; + } + memmove(buf, base1, count); + memmove(r2.base + offset + count, + r2.base + offset, length); + memmove(r2.base + offset, buf, count); + base1 += count; + bytes -= count; + offset += count; + } + } + + /* + * Check ALPN is present when NO-DEFAULT-ALPN is set. + */ + if (key1 == SVCB_ALPN_KEY) { + have_alpn = true; + } else if (key1 == SVCB_NO_DEFAULT_ALPN_KEY && !have_alpn) { + /* Missing required ALPN field. */ + return (DNS_R_DISALLOWED); + } + + /* + * Check key against mandatory key list. + */ + if (mankey != 0) { + if (key1 > mankey) { + return (DNS_R_INCONSISTENTRR); + } + if (key1 == mankey) { + if (man.length >= 2) { + mankey = uint16_fromregion(&man); + isc_region_consume(&man, 2); + } else { + mankey = 0; + } + } + } + + /* + * Is this the mandatory key? + */ + if (key1 == SVCB_MAN_KEY) { + man = r2; + man.length = len1 + 4; + isc_region_consume(&man, 4); + if (man.length >= 2) { + mankey = uint16_fromregion(&man); + isc_region_consume(&man, 2); + if (mankey == SVCB_MAN_KEY) { + return (DNS_R_DISALLOWED); + } + } else { + return (DNS_R_SYNTAX); + } + } + + /* + * Consume the smallest parameter. + */ + isc_region_consume(&r2, len1 + 4); + r1 = r2; + } +} + +static inline isc_result_t +generic_fromtext_in_svcb(ARGS_FROMTEXT) { + isc_token_t token; + dns_name_t name; + isc_buffer_t buffer; + bool alias; +#if 0 + bool ok = true; +#endif + unsigned int used; + + UNUSED(type); + UNUSED(rdclass); + UNUSED(callbacks); + + /* + * SvcPriority. + */ + RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_number, + false)); + if (token.value.as_ulong > 0xffffU) { + RETTOK(ISC_R_RANGE); + } + RETERR(uint16_tobuffer(token.value.as_ulong, target)); + + alias = token.value.as_ulong == 0; + + /* + * TargetName. + */ + RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_qstring, + false)); + dns_name_init(&name, NULL); + buffer_fromregion(&buffer, &token.value.as_region); + if (origin == NULL) { + origin = dns_rootname; + } + RETTOK(dns_name_fromtext(&name, &buffer, origin, options, target)); +#if 0 + if (!alias && (options & DNS_RDATA_CHECKNAMES) != 0) { + ok = dns_name_ishostname(&name, false); + } + if (!ok && (options & DNS_RDATA_CHECKNAMESFAIL) != 0) { + RETTOK(DNS_R_BADNAME); + } + if (!ok && callbacks != NULL) { + warn_badname(&name, lexer, callbacks); + } +#endif + + if (alias) { + return (ISC_R_SUCCESS); + } + + /* + * SvcParams + */ + used = isc_buffer_usedlength(target); + while (1) { + RETERR(isc_lex_getmastertoken(lexer, &token, + isc_tokentype_qvpair, true)); + if (token.type == isc_tokentype_eol || + token.type == isc_tokentype_eof) { + isc_lex_ungettoken(lexer, &token); + return (svcsortkeys(target, used)); + } + + if (token.type != isc_tokentype_string && /* key only */ + token.type != isc_tokentype_qvpair && + token.type != isc_tokentype_vpair) + { + RETTOK(DNS_R_SYNTAX); + } + RETTOK(svc_fromtext(&token.value.as_textregion, target)); + } +} + +static inline isc_result_t +fromtext_in_svcb(ARGS_FROMTEXT) { + REQUIRE(type == dns_rdatatype_svcb); + REQUIRE(rdclass == dns_rdataclass_in); + UNUSED(type); + UNUSED(rdclass); + UNUSED(callbacks); + + return (generic_fromtext_in_svcb(CALL_FROMTEXT)); +} + +static inline isc_result_t +generic_totext_in_svcb(ARGS_TOTEXT) { + isc_region_t region; + dns_name_t name; + dns_name_t prefix; + bool sub; + char buf[sizeof("xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:255.255.255.255")]; + unsigned short num; + int n; + + REQUIRE(rdata->length != 0); + + dns_name_init(&name, NULL); + dns_name_init(&prefix, NULL); + + dns_rdata_toregion(rdata, ®ion); + + /* + * SvcPriority. + */ + num = uint16_fromregion(®ion); + isc_region_consume(®ion, 2); + n = snprintf(buf, sizeof(buf), "%u ", num); + INSIST(n > 0 && (unsigned)n < sizeof(buf)); + RETERR(str_totext(buf, target)); + + /* + * TargetName. + */ + dns_name_fromregion(&name, ®ion); + isc_region_consume(®ion, name_length(&name)); + sub = name_prefix(&name, tctx->origin, &prefix); + RETERR(dns_name_totext(&prefix, sub, target)); + + while (region.length > 0) { + isc_region_t r; + enum encoding encoding; + + RETERR(str_totext(" ", target)); + + INSIST(region.length >= 2); + num = uint16_fromregion(®ion); + isc_region_consume(®ion, 2); + RETERR(str_totext(svcparamkey(num, &encoding, buf, sizeof(buf)), + target)); + + INSIST(region.length >= 2); + num = uint16_fromregion(®ion); + isc_region_consume(®ion, 2); + + INSIST(region.length >= num); + r = region; + r.length = num; + isc_region_consume(®ion, num); + if (num == 0) { + continue; + } + if (encoding != sbpr_empty) { + RETERR(str_totext("=", target)); + } + switch (encoding) { + case sbpr_text: + RETERR(multitxt_totext(&r, target)); + break; + case sbpr_port: + num = uint16_fromregion(&r); + isc_region_consume(&r, 2); + n = snprintf(buf, sizeof(buf), "%u", num); + INSIST(n > 0 && (unsigned)n < sizeof(buf)); + RETERR(str_totext(buf, target)); + INSIST(r.length == 0U); + break; + case sbpr_ipv4s: + while (r.length > 0U) { + INSIST(r.length >= 4U); + inet_ntop(AF_INET, r.base, buf, sizeof(buf)); + RETERR(str_totext(buf, target)); + isc_region_consume(&r, 4); + if (r.length != 0U) { + RETERR(str_totext(",", target)); + } + } + break; + case sbpr_ipv6s: + while (r.length > 0U) { + INSIST(r.length >= 16U); + inet_ntop(AF_INET6, r.base, buf, sizeof(buf)); + RETERR(str_totext(buf, target)); + isc_region_consume(&r, 16); + if (r.length != 0U) { + RETERR(str_totext(",", target)); + } + } + break; + case sbpr_base64: + RETERR(isc_base64_totext(&r, 0, "", target)); + break; + case sbpr_alpn: + INSIST(r.length != 0U); + RETERR(str_totext("\"", target)); + while (r.length != 0) { + commatxt_totext(&r, false, true, target); + if (r.length != 0) { + RETERR(str_totext(",", target)); + } + } + RETERR(str_totext("\"", target)); + break; + case sbpr_empty: + INSIST(r.length == 0U); + break; + case sbpr_keylist: + while (r.length > 0) { + num = uint16_fromregion(&r); + isc_region_consume(&r, 2); + RETERR(str_totext(svcparamkey(num, &encoding, + buf, sizeof(buf)), + target)); + if (r.length != 0) { + RETERR(str_totext(",", target)); + } + } + break; + default: + INSIST(0); + ISC_UNREACHABLE(); + } + } + return (ISC_R_SUCCESS); +} + +static inline isc_result_t +totext_in_svcb(ARGS_TOTEXT) { + REQUIRE(rdata->type == dns_rdatatype_svcb); + REQUIRE(rdata->rdclass == dns_rdataclass_in); + REQUIRE(rdata->length != 0); + + return (generic_totext_in_svcb(CALL_TOTEXT)); +} + +static inline isc_result_t +generic_fromwire_in_svcb(ARGS_FROMWIRE) { + dns_name_t name; + isc_region_t region, man = { .base = NULL, .length = 0 }; + bool alias, first = true; + uint16_t lastkey = 0, mankey = 0; + + UNUSED(type); + UNUSED(rdclass); + + dns_decompress_setmethods(dctx, DNS_COMPRESS_NONE); + + dns_name_init(&name, NULL); + + /* + * SvcPriority. + */ + isc_buffer_activeregion(source, ®ion); + if (region.length < 2) { + return (ISC_R_UNEXPECTEDEND); + } + RETERR(mem_tobuffer(target, region.base, 2)); + alias = uint16_fromregion(®ion) == 0; + isc_buffer_forward(source, 2); + + /* + * TargetName. + */ + RETERR(dns_name_fromwire(&name, source, dctx, options, target)); + + if (alias) { + return (ISC_R_SUCCESS); + } + + /* + * SvcParams. + */ + isc_buffer_activeregion(source, ®ion); + while (region.length > 0U) { + isc_region_t keyregion; + uint16_t key, len; + + /* + * SvcParamKey + */ + if (region.length < 2U) { + return (ISC_R_UNEXPECTEDEND); + } + RETERR(mem_tobuffer(target, region.base, 2)); + key = uint16_fromregion(®ion); + isc_region_consume(®ion, 2); + + /* + * Keys must be unique and in order. + */ + if (!first && key <= lastkey) { + return (DNS_R_FORMERR); + } + + /* + * Check mandatory keys. + */ + if (mankey != 0) { + /* Missing mandatory key? */ + if (key > mankey) { + return (DNS_R_FORMERR); + } + if (key == mankey) { + /* Get next mandatory key. */ + if (man.length >= 2) { + mankey = uint16_fromregion(&man); + isc_region_consume(&man, 2); + } else { + mankey = 0; + } + } + } + + first = false; + lastkey = key; + + /* + * SvcParamValue length. + */ + if (region.length < 2U) { + return (ISC_R_UNEXPECTEDEND); + } + RETERR(mem_tobuffer(target, region.base, 2)); + len = uint16_fromregion(®ion); + isc_region_consume(®ion, 2); + + /* + * SvcParamValue. + */ + if (region.length < len) { + return (ISC_R_UNEXPECTEDEND); + } + + /* + * Remember manatory key. + */ + if (key == SVCB_MAN_KEY) { + man = region; + man.length = len; + /* Get first mandatory key */ + if (man.length >= 2) { + mankey = uint16_fromregion(&man); + isc_region_consume(&man, 2); + if (mankey == SVCB_MAN_KEY) { + return (DNS_R_FORMERR); + } + } else { + return (DNS_R_FORMERR); + } + } + keyregion = region; + keyregion.length = len; + RETERR(svcb_validate(key, &keyregion)); + RETERR(mem_tobuffer(target, region.base, len)); + isc_region_consume(®ion, len); + isc_buffer_forward(source, len + 4); + } + + /* + * Do we have an outstanding mandatory key? + */ + if (mankey != 0) { + return (DNS_R_FORMERR); + } + + return (ISC_R_SUCCESS); +} + +static inline isc_result_t +fromwire_in_svcb(ARGS_FROMWIRE) { + REQUIRE(type == dns_rdatatype_svcb); + REQUIRE(rdclass == dns_rdataclass_in); + + return (generic_fromwire_in_svcb(CALL_FROMWIRE)); +} + +static inline isc_result_t +generic_towire_in_svcb(ARGS_TOWIRE) { + dns_name_t name; + dns_offsets_t offsets; + isc_region_t region; + + REQUIRE(rdata->length != 0); + + dns_compress_setmethods(cctx, DNS_COMPRESS_NONE); + + /* + * SvcPriority. + */ + dns_rdata_toregion(rdata, ®ion); + RETERR(mem_tobuffer(target, region.base, 2)); + isc_region_consume(®ion, 2); + + /* + * TargetName. + */ + dns_name_init(&name, offsets); + dns_name_fromregion(&name, ®ion); + RETERR(dns_name_towire(&name, cctx, target)); + isc_region_consume(®ion, name_length(&name)); + + /* + * SvcParams. + */ + return (mem_tobuffer(target, region.base, region.length)); +} + +static inline isc_result_t +towire_in_svcb(ARGS_TOWIRE) { + REQUIRE(rdata->type == dns_rdatatype_svcb); + REQUIRE(rdata->length != 0); + + return (generic_towire_in_svcb(CALL_TOWIRE)); +} + +static inline int +compare_in_svcb(ARGS_COMPARE) { + isc_region_t region1; + isc_region_t region2; + + REQUIRE(rdata1->type == rdata2->type); + REQUIRE(rdata1->rdclass == rdata2->rdclass); + REQUIRE(rdata1->type == dns_rdatatype_svcb); + REQUIRE(rdata1->rdclass == dns_rdataclass_in); + REQUIRE(rdata1->length != 0); + REQUIRE(rdata2->length != 0); + + dns_rdata_toregion(rdata1, ®ion1); + dns_rdata_toregion(rdata2, ®ion2); + + return (isc_region_compare(®ion1, ®ion2)); +} + +static inline isc_result_t +generic_fromstruct_in_svcb(ARGS_FROMSTRUCT) { + dns_rdata_in_svcb_t *svcb = source; + isc_region_t region; + + REQUIRE(svcb != NULL); + REQUIRE(svcb->common.rdtype == type); + REQUIRE(svcb->common.rdclass == rdclass); + + UNUSED(type); + UNUSED(rdclass); + + RETERR(uint16_tobuffer(svcb->priority, target)); + dns_name_toregion(&svcb->svcdomain, ®ion); + RETERR(isc_buffer_copyregion(target, ®ion)); + + return (mem_tobuffer(target, svcb->svc, svcb->svclen)); +} + +static inline isc_result_t +fromstruct_in_svcb(ARGS_FROMSTRUCT) { + dns_rdata_in_svcb_t *svcb = source; + + REQUIRE(type == dns_rdatatype_svcb); + REQUIRE(rdclass == dns_rdataclass_in); + REQUIRE(svcb != NULL); + REQUIRE(svcb->common.rdtype == type); + REQUIRE(svcb->common.rdclass == rdclass); + + return (generic_fromstruct_in_svcb(CALL_FROMSTRUCT)); +} + +static inline isc_result_t +generic_tostruct_in_svcb(ARGS_TOSTRUCT) { + isc_region_t region; + dns_rdata_in_svcb_t *svcb = target; + dns_name_t name; + + REQUIRE(svcb != NULL); + REQUIRE(rdata->length != 0); + + svcb->common.rdclass = rdata->rdclass; + svcb->common.rdtype = rdata->type; + ISC_LINK_INIT(&svcb->common, link); + + dns_rdata_toregion(rdata, ®ion); + + svcb->priority = uint16_fromregion(®ion); + isc_region_consume(®ion, 2); + + dns_name_init(&svcb->svcdomain, NULL); + dns_name_init(&name, NULL); + dns_name_fromregion(&name, ®ion); + isc_region_consume(®ion, name_length(&name)); + + RETERR(name_duporclone(&name, mctx, &svcb->svcdomain)); + svcb->svclen = region.length; + svcb->svc = mem_maybedup(mctx, region.base, region.length); + + if (svcb->svc == NULL) { + if (mctx != NULL) { + dns_name_free(&svcb->svcdomain, svcb->mctx); + } + return (ISC_R_NOMEMORY); + } + + svcb->offset = 0; + svcb->mctx = mctx; + + return (ISC_R_SUCCESS); +} + +static inline isc_result_t +tostruct_in_svcb(ARGS_TOSTRUCT) { + dns_rdata_in_svcb_t *svcb = target; + + REQUIRE(rdata->rdclass == dns_rdataclass_in); + REQUIRE(rdata->type == dns_rdatatype_svcb); + REQUIRE(svcb != NULL); + REQUIRE(rdata->length != 0); + + return (generic_tostruct_in_svcb(CALL_TOSTRUCT)); +} + +static inline void +generic_freestruct_in_svcb(ARGS_FREESTRUCT) { + dns_rdata_in_svcb_t *svcb = source; + + REQUIRE(svcb != NULL); + + if (svcb->mctx == NULL) { + return; + } + + dns_name_free(&svcb->svcdomain, svcb->mctx); + isc_mem_free(svcb->mctx, svcb->svc); + svcb->mctx = NULL; +} + +static inline void +freestruct_in_svcb(ARGS_FREESTRUCT) { + dns_rdata_in_svcb_t *svcb = source; + + REQUIRE(svcb != NULL); + REQUIRE(svcb->common.rdclass == dns_rdataclass_in); + REQUIRE(svcb->common.rdtype == dns_rdatatype_svcb); + + generic_freestruct_in_svcb(CALL_FREESTRUCT); +} + +static inline isc_result_t +generic_additionaldata_in_svcb(ARGS_ADDLDATA) { + UNUSED(rdata); + UNUSED(add); + UNUSED(arg); + + return (ISC_R_SUCCESS); +} + +static inline isc_result_t +additionaldata_in_svcb(ARGS_ADDLDATA) { + REQUIRE(rdata->type == dns_rdatatype_svcb); + REQUIRE(rdata->rdclass == dns_rdataclass_in); + + return (generic_additionaldata_in_svcb(CALL_ADDLDATA)); +} + +static inline isc_result_t +digest_in_svcb(ARGS_DIGEST) { + isc_region_t region1; + + REQUIRE(rdata->type == dns_rdatatype_svcb); + REQUIRE(rdata->rdclass == dns_rdataclass_in); + + dns_rdata_toregion(rdata, ®ion1); + return ((digest)(arg, ®ion1)); +} + +static inline bool +checkowner_in_svcb(ARGS_CHECKOWNER) { + REQUIRE(type == dns_rdatatype_svcb); + REQUIRE(rdclass == dns_rdataclass_in); + + UNUSED(name); + UNUSED(type); + UNUSED(rdclass); + UNUSED(wildcard); + + return (true); +} + +static inline bool +generic_checknames_in_svcb(ARGS_CHECKNAMES) { +#if 0 + isc_region_t region; + dns_name_t name; +#endif + + UNUSED(rdata); + UNUSED(bad); + UNUSED(owner); + +#if 0 + dns_rdata_toregion(rdata, ®ion); + isc_region_consume(®ion, 2); + dns_name_init(&name, NULL); + dns_name_fromregion(&name, ®ion); + if (!dns_name_ishostname(&name, false)) { + if (bad != NULL) { + dns_name_clone(&name, bad); + } + return (false); + } +#endif + return (true); +} + +static inline bool +checknames_in_svcb(ARGS_CHECKNAMES) { + REQUIRE(rdata->type == dns_rdatatype_svcb); + REQUIRE(rdata->rdclass == dns_rdataclass_in); + + return (generic_checknames_in_svcb(CALL_CHECKNAMES)); +} + +static inline int +casecompare_in_svcb(ARGS_COMPARE) { + return (compare_in_svcb(rdata1, rdata2)); +} + +static isc_result_t +generic_rdata_in_svcb_first(dns_rdata_in_svcb_t *svcb) { + if (svcb->svclen == 0) { + return (ISC_R_NOMORE); + } + svcb->offset = 0; + return (ISC_R_SUCCESS); +} + +static isc_result_t +generic_rdata_in_svcb_next(dns_rdata_in_svcb_t *svcb) { + isc_region_t region; + size_t len; + + if (svcb->offset >= svcb->svclen) { + return (ISC_R_NOMORE); + } + + region.base = svcb->svc + svcb->offset; + region.length = svcb->svclen - svcb->offset; + INSIST(region.length >= 4); + isc_region_consume(®ion, 2); + len = uint16_fromregion(®ion); + INSIST(region.length >= len + 2); + svcb->offset += len + 4; + return (svcb->offset >= svcb->svclen ? ISC_R_NOMORE : ISC_R_SUCCESS); +} + +static void +generic_rdata_in_svcb_current(dns_rdata_in_svcb_t *svcb, isc_region_t *region) { + size_t len; + + INSIST(svcb->offset <= svcb->svclen); + + region->base = svcb->svc + svcb->offset; + region->length = svcb->svclen - svcb->offset; + INSIST(region->length >= 4); + isc_region_consume(region, 2); + len = uint16_fromregion(region); + INSIST(region->length >= len + 2); + region->base = svcb->svc + svcb->offset; + region->length = len + 4; +} + +isc_result_t +dns_rdata_in_svcb_first(dns_rdata_in_svcb_t *svcb) { + REQUIRE(svcb != NULL); + REQUIRE(svcb->common.rdtype == dns_rdatatype_svcb); + REQUIRE(svcb->common.rdclass == dns_rdataclass_in); + + return (generic_rdata_in_svcb_first(svcb)); +} + +isc_result_t +dns_rdata_in_svcb_next(dns_rdata_in_svcb_t *svcb) { + REQUIRE(svcb != NULL); + REQUIRE(svcb->common.rdtype == dns_rdatatype_svcb); + REQUIRE(svcb->common.rdclass == dns_rdataclass_in); + + return (generic_rdata_in_svcb_next(svcb)); +} + +void +dns_rdata_in_svcb_current(dns_rdata_in_svcb_t *svcb, isc_region_t *region) { + REQUIRE(svcb != NULL); + REQUIRE(svcb->common.rdtype == dns_rdatatype_svcb); + REQUIRE(svcb->common.rdclass == dns_rdataclass_in); + REQUIRE(region != NULL); + + generic_rdata_in_svcb_current(svcb, region); +} + +#endif /* RDATA_IN_1_SVCB_64_C */ diff --git a/lib/dns/rdata/in_1/svcb_64.h b/lib/dns/rdata/in_1/svcb_64.h new file mode 100644 index 0000000000..750b6820da --- /dev/null +++ b/lib/dns/rdata/in_1/svcb_64.h @@ -0,0 +1,38 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +#ifndef IN_1_SVCB_64_H +#define IN_1_SVCB_64_H 1 + +/*! + * \brief Per draft-ietf-dnsop-svcb-https-02 + */ + +typedef struct dns_rdata_in_svcb { + dns_rdatacommon_t common; + isc_mem_t *mctx; + uint16_t priority; + dns_name_t svcdomain; + unsigned char *svc; + uint16_t svclen; + uint16_t offset; +} dns_rdata_in_svcb_t; + +isc_result_t +dns_rdata_in_svcb_first(dns_rdata_in_svcb_t *); + +isc_result_t +dns_rdata_in_svcb_next(dns_rdata_in_svcb_t *); + +void +dns_rdata_in_svcb_current(dns_rdata_in_svcb_t *, isc_region_t *); + +#endif /* IN_1_SVCB_64_H */ diff --git a/lib/dns/result.c b/lib/dns/result.c index 1c8641227b..01ce2b4e85 100644 --- a/lib/dns/result.c +++ b/lib/dns/result.c @@ -174,6 +174,7 @@ static const char *text[DNS_R_NRESULTS] = { "cannot use NSEC3 with key algorithm", /*%< 125 DNS_R_NSEC3BADALG */ "NSEC3 resalt", /*%< 126 DNS_R_NSEC3RESALT */ + "inconsistent resource record", /*%< 127 DNS_R_INCONSISTENTRR */ }; static const char *ids[DNS_R_NRESULTS] = { @@ -308,6 +309,7 @@ static const char *ids[DNS_R_NRESULTS] = { "DNS_R_NSEC3SALTRANGE", "DNS_R_NSEC3BADALG", "DNS_R_NSEC3RESALT", + "DNS_R_INCONSISTENTRR", }; static const char *rcode_text[DNS_R_NRCODERESULTS] = { diff --git a/lib/dns/tests/rdata_test.c b/lib/dns/tests/rdata_test.c index 131f7804a7..d520cdbb91 100644 --- a/lib/dns/tests/rdata_test.c +++ b/lib/dns/tests/rdata_test.c @@ -299,7 +299,8 @@ check_struct_conversions(dns_rdata_t *rdata, size_t structsize, assert_memory_equal(buf, rdata->data, rdata->length); /* - * Check that one can walk hip rendezvous servers. + * Check that one can walk hip rendezvous servers and + * https/svcb parameters. */ switch (type) { case dns_rdatatype_hip: { @@ -319,6 +320,38 @@ check_struct_conversions(dns_rdata_t *rdata, size_t structsize, assert_int_equal(count, loop); break; } + case dns_rdatatype_https: { + dns_rdata_in_https_t *https = rdata_struct; + + for (result = dns_rdata_in_https_first(https); + result == ISC_R_SUCCESS; + result = dns_rdata_in_https_next(https)) + { + isc_region_t region; + dns_rdata_in_https_current(https, ®ion); + assert_true(region.length >= 4); + count++; + } + assert_int_equal(result, ISC_R_NOMORE); + assert_int_equal(count, loop); + break; + } + case dns_rdatatype_svcb: { + dns_rdata_in_svcb_t *svcb = rdata_struct; + + for (result = dns_rdata_in_svcb_first(svcb); + result == ISC_R_SUCCESS; + result = dns_rdata_in_svcb_next(svcb)) + { + isc_region_t region; + dns_rdata_in_svcb_current(svcb, ®ion); + assert_true(region.length >= 4); + count++; + } + assert_int_equal(result, ISC_R_NOMORE); + assert_int_equal(count, loop); + break; + } } isc_mem_free(dt_mctx, rdata_struct); @@ -339,6 +372,10 @@ check_text_ok_single(const text_ok_t *text_ok, dns_rdataclass_t rdclass, isc_result_t result; size_t length = 0; + if (debug) { + fprintf(stdout, "#check_text_ok_single(%s)\n", + text_ok->text_in); + } /* * Try converting text form RDATA into uncompressed wire form. */ @@ -350,7 +387,9 @@ check_text_ok_single(const text_ok_t *text_ok, dns_rdataclass_t rdclass, */ if (text_ok->text_out != NULL) { if (debug && result != ISC_R_SUCCESS) { - fprintf(stdout, "#'%s'\n", text_ok->text_in); + fprintf(stdout, "# '%s'\n", text_ok->text_in); + fprintf(stdout, "# result=%s\n", + dns_result_totext(result)); } assert_int_equal(result, ISC_R_SUCCESS); } else { @@ -374,6 +413,18 @@ check_text_ok_single(const text_ok_t *text_ok, dns_rdataclass_t rdclass, */ isc_buffer_init(&target, buf_totext, sizeof(buf_totext)); result = dns_rdata_totext(&rdata, NULL, &target); + if (result != ISC_R_SUCCESS && debug) { + size_t i; + fprintf(stdout, "# dns_rdata_totext -> %s", + dns_result_totext(result)); + for (i = 0; i < rdata.length; i++) { + if ((i % 16) == 0) { + fprintf(stdout, "\n#"); + } + fprintf(stdout, " %02x", rdata.data[i]); + } + fprintf(stdout, "\n"); + } assert_int_equal(result, ISC_R_SUCCESS); /* * Ensure buf_totext is properly NUL terminated as dns_rdata_totext() @@ -387,6 +438,10 @@ check_text_ok_single(const text_ok_t *text_ok, dns_rdataclass_t rdclass, } assert_string_equal(buf_totext, text_ok->text_out); + if (debug) { + fprintf(stdout, "#dns_rdata_totext -> '%s'\n", buf_totext); + } + /* * Ensure that fromtext_*() output is valid input for fromwire_*(). */ @@ -463,6 +518,10 @@ check_text_conversions(dns_rdata_t *rdata) { result = dns_test_rdatafromstring(&rdata2, rdata->rdclass, rdata->type, buf_fromtext, sizeof(buf_fromtext), buf_totext, false); + if (debug && result != ISC_R_SUCCESS) { + fprintf(stdout, "# result = %s\n", dns_result_totext(result)); + fprintf(stdout, "# '%s'\n", buf_fromtext); + } assert_int_equal(result, ISC_R_SUCCESS); assert_int_equal(rdata2.length, rdata->length); assert_memory_equal(buf_fromtext, rdata->data, rdata->length); @@ -2412,6 +2471,186 @@ wks(void **state) { dns_rdatatype_wks, sizeof(dns_rdata_in_wks_t)); } +static void +https_svcb(void **state) { + /* + * Known keys: mandatory, apln, no-default-alpn, port, + * ipv4hint, port, ipv6hint. + */ + text_ok_t text_ok[] = { + /* unknown key invalid */ + TEXT_INVALID("1 . unknown="), + /* no domain */ + TEXT_INVALID("0"), + /* minimal record */ + TEXT_VALID_LOOP(0, "0 ."), + /* Alias form requires SvcFieldValue to be empty */ + TEXT_INVALID("0 . alpn=\"h2\""), + /* no "key" prefix */ + TEXT_INVALID("2 svc.example.net. 0=\"2222\""), + /* no key value */ + TEXT_INVALID("2 svc.example.net. key"), + /* no key value */ + TEXT_INVALID("2 svc.example.net. key=\"2222\""), + /* zero pad invalid */ + TEXT_INVALID("2 svc.example.net. key07=\"2222\""), + TEXT_VALID_LOOP(1, "2 svc.example.net. key7=\"2222\""), + TEXT_VALID_LOOPCHG(1, "2 svc.example.net. key7=2222", + "2 svc.example.net. key7=\"2222\""), + TEXT_VALID_LOOPCHG(1, "2 svc.example.net. alpn=h2", + "2 svc.example.net. alpn=\"h2\""), + TEXT_VALID_LOOPCHG(1, "2 svc.example.net. alpn=h3", + "2 svc.example.net. alpn=\"h3\""), + /* alpn has 2 sub field "h2" and "h3" */ + TEXT_VALID_LOOPCHG(1, "2 svc.example.net. alpn=h2,h3", + "2 svc.example.net. alpn=\"h2,h3\""), + /* apln has 2 sub fields "h1,h2" and "h3" (comma escaped) */ + TEXT_VALID_LOOPCHG(1, "2 svc.example.net. alpn=h1\\\\,h2,h3", + "2 svc.example.net. alpn=\"h1\\\\,h2,h3\""), + TEXT_VALID_LOOP(1, "2 svc.example.net. port=50"), + /* no-default-alpn, alpn is required */ + TEXT_INVALID("2 svc.example.net. no-default-alpn"), + /* no-default-alpn with alpn present */ + TEXT_VALID_LOOPCHG( + 2, "2 svc.example.net. no-default-alpn alpn=h2", + "2 svc.example.net. alpn=\"h2\" no-default-alpn"), + /* empty hint */ + TEXT_INVALID("2 svc.example.net. ipv4hint="), + TEXT_VALID_LOOP(1, "2 svc.example.net. " + "ipv4hint=10.50.0.1,10.50.0.2"), + /* empty hint */ + TEXT_INVALID("2 svc.example.net. ipv6hint="), + TEXT_VALID_LOOP(1, "2 svc.example.net. ipv6hint=::1,2002::1"), + TEXT_VALID_LOOP(1, "2 svc.example.net. ech=abcdefghijkl"), + /* bad base64 */ + TEXT_INVALID("2 svc.example.net. ech=abcdefghijklm"), + TEXT_VALID_LOOP(1, "2 svc.example.net. key7=\"2222\""), + /* Out of key order on input (alpn == key1). */ + TEXT_VALID_LOOPCHG(2, + "2 svc.example.net. key7=\"2222\" alpn=h2", + "2 svc.example.net. alpn=\"h2\" " + "key7=\"2222\""), + TEXT_VALID_LOOP(1, "2 svc.example.net. key65535=\"2222\""), + TEXT_INVALID("2 svc.example.net. key65536=\"2222\""), + TEXT_VALID_LOOP(1, "2 svc.example.net. key10"), + TEXT_VALID_LOOPCHG(1, "2 svc.example.net. key11=", + "2 svc.example.net. key11"), + TEXT_VALID_LOOPCHG(1, "2 svc.example.net. key12=\"\"", + "2 svc.example.net. key12"), + /* empty alpn-id sub fields */ + TEXT_INVALID("2 svc.example.net. alpn"), + TEXT_INVALID("2 svc.example.net. alpn="), + TEXT_INVALID("2 svc.example.net. alpn=,h1"), + TEXT_INVALID("2 svc.example.net. alpn=h1,"), + TEXT_INVALID("2 svc.example.net. alpn=h1,,h2"), + /* mandatory */ + TEXT_VALID_LOOP(2, "2 svc.example.net. mandatory=alpn " + "alpn=\"h2\""), + TEXT_VALID_LOOP(3, "2 svc.example.net. mandatory=alpn,port " + "alpn=\"h2\" port=443"), + TEXT_VALID_LOOPCHG(3, + "2 svc.example.net. mandatory=port,alpn " + "alpn=\"h2\" port=443", + "2 svc.example.net. mandatory=alpn,port " + "alpn=\"h2\" port=443"), + TEXT_INVALID("2 svc.example.net. mandatory=mandatory"), + TEXT_INVALID("2 svc.example.net. mandatory=port"), + TEXT_INVALID("2 svc.example.net. mandatory=,port port=433"), + TEXT_INVALID("2 svc.example.net. mandatory=port, port=433"), + TEXT_INVALID("2 svc.example.net. " + "mandatory=alpn,,port alpn=h2 port=433"), + /* mandatory w/ unknown key values */ + TEXT_VALID_LOOP(2, "2 svc.example.net. mandatory=key7 key7"), + TEXT_VALID_LOOP(3, "2 svc.example.net. mandatory=key7,key8 " + "key7 key8"), + TEXT_VALID_LOOPCHG( + 3, "2 svc.example.net. mandatory=key8,key7 key7 key8", + "2 svc.example.net. mandatory=key7,key8 key7 key8"), + TEXT_INVALID("2 svc.example.net. " + "mandatory=key7,key7"), + TEXT_INVALID("2 svc.example.net. mandatory=,key7"), + TEXT_INVALID("2 svc.example.net. mandatory=key7,"), + TEXT_INVALID("2 svc.example.net. " + "mandatory=key7,,key7"), + TEXT_SENTINEL() + + }; + wire_ok_t wire_ok[] = { + /* + * Too short + */ + WIRE_INVALID(0x00, 0x00), + /* + * Minimal length record. + */ + WIRE_VALID(0x00, 0x00, 0x00), + /* + * Alias with non-empty SvcFieldValue (key7=""). + */ + WIRE_INVALID(0x00, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00), + /* + * Bad key7= length (longer than rdata). + */ + WIRE_INVALID(0x00, 0x01, 0x00, 0x00, 0x07, 0x00, 0x01), + /* + * Port (0x03) too small (zero and one octets). + */ + WIRE_INVALID(0x00, 0x01, 0x00, 0x00, 0x03, 0x00, 0x00), + WIRE_INVALID(0x00, 0x01, 0x00, 0x00, 0x03, 0x00, 0x01, 0x00), + /* Valid port */ + WIRE_VALID_LOOP(1, 0x00, 0x01, 0x00, 0x00, 0x03, 0x00, 0x02, + 0x00, 0x00), + /* + * Port (0x03) too big (three octets). + */ + WIRE_INVALID(0x00, 0x01, 0x00, 0x00, 0x03, 0x00, 0x03, 0x00, + 0x00, 0x00), + /* + * Duplicate keys. + */ + WIRE_INVALID(0x01, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, + 0x80, 0x00, 0x00), + /* + * Out of order keys. + */ + WIRE_INVALID(0x01, 0x01, 0x00, 0x00, 0x81, 0x00, 0x00, 0x00, + 0x80, 0x00, 0x00), + /* + * Empty of mandatory key list. + */ + WIRE_INVALID(0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00), + /* + * "mandatory=mandatory" is invalid + */ + WIRE_INVALID(0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, + 0x00), + /* + * Out of order mandatory key list. + */ + WIRE_INVALID(0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, + 0x80, 0x00, 0x71, 0x00, 0x71, 0x00, 0x00, 0x00, + 0x80, 0x00, 0x00), + /* + * Alpn(0x00 0x01) (length 0x00 0x09) "h1,h2" + "h3" + */ + WIRE_VALID_LOOP(0x01, 0x00, 0x01, 0x00, 0x00, 0x01, 0x00, 0x09, + 5, 'h', '1', ',', 'h', '2', 2, 'h', '3'), + /* + * Alpn(0x00 0x01) (length 0x00 0x09) "h1\h2" + "h3" + */ + WIRE_VALID_LOOP(0x01, 0x00, 0x01, 0x00, 0x00, 0x01, 0x00, 0x09, + 5, 'h', '1', '\\', 'h', '2', 2, 'h', '3'), + WIRE_SENTINEL() + }; + + UNUSED(state); + + check_rdata(text_ok, wire_ok, NULL, false, dns_rdataclass_in, + dns_rdatatype_svcb, sizeof(dns_rdata_in_svcb_t)); + check_rdata(text_ok, wire_ok, NULL, false, dns_rdataclass_in, + dns_rdatatype_https, sizeof(dns_rdata_in_https_t)); +} + /* * ZONEMD tests. * @@ -2755,18 +2994,18 @@ iszonecutauth(void **state) { int main(int argc, char **argv) { const struct CMUnitTest tests[] = { + /* types */ cmocka_unit_test_setup_teardown(amtrelay, _setup, _teardown), cmocka_unit_test_setup_teardown(apl, _setup, _teardown), cmocka_unit_test_setup_teardown(atma, _setup, _teardown), cmocka_unit_test_setup_teardown(cdnskey, _setup, _teardown), cmocka_unit_test_setup_teardown(csync, _setup, _teardown), - cmocka_unit_test_setup_teardown(doa, _setup, _teardown), cmocka_unit_test_setup_teardown(dnskey, _setup, _teardown), + cmocka_unit_test_setup_teardown(doa, _setup, _teardown), cmocka_unit_test_setup_teardown(ds, _setup, _teardown), cmocka_unit_test_setup_teardown(eid, _setup, _teardown), - cmocka_unit_test_setup_teardown(edns_client_subnet, _setup, - _teardown), cmocka_unit_test_setup_teardown(hip, _setup, _teardown), + cmocka_unit_test_setup_teardown(https_svcb, _setup, _teardown), cmocka_unit_test_setup_teardown(isdn, _setup, _teardown), cmocka_unit_test_setup_teardown(key, _setup, _teardown), cmocka_unit_test_setup_teardown(loc, _setup, _teardown), @@ -2774,10 +3013,13 @@ main(int argc, char **argv) { cmocka_unit_test_setup_teardown(nsec, _setup, _teardown), cmocka_unit_test_setup_teardown(nsec3, _setup, _teardown), cmocka_unit_test_setup_teardown(nxt, _setup, _teardown), + cmocka_unit_test_setup_teardown(rkey, _setup, _teardown), cmocka_unit_test_setup_teardown(sshfp, _setup, _teardown), cmocka_unit_test_setup_teardown(wks, _setup, _teardown), - cmocka_unit_test_setup_teardown(rkey, _setup, _teardown), cmocka_unit_test_setup_teardown(zonemd, _setup, _teardown), + /* other tests */ + cmocka_unit_test_setup_teardown(edns_client_subnet, _setup, + _teardown), cmocka_unit_test_setup_teardown(atcname, NULL, NULL), cmocka_unit_test_setup_teardown(atparent, NULL, NULL), cmocka_unit_test_setup_teardown(iszonecutauth, NULL, NULL), diff --git a/util/copyrights b/util/copyrights index 931e52ee9f..0f4dab432d 100644 --- a/util/copyrights +++ b/util/copyrights @@ -1497,6 +1497,8 @@ ./lib/dns/rdata/in_1/dhcid_49.h C 2006,2007,2016,2018,2019,2020,2021 ./lib/dns/rdata/in_1/eid_31.c C 2018,2019,2020,2021 ./lib/dns/rdata/in_1/eid_31.h C 2018,2019,2020,2021 +./lib/dns/rdata/in_1/https_65.c C 2019,2020,2021 +./lib/dns/rdata/in_1/https_65.h C 2019,2020,2021 ./lib/dns/rdata/in_1/kx_36.c C 1999,2000,2001,2003,2004,2005,2007,2009,2015,2016,2017,2018,2019,2020,2021 ./lib/dns/rdata/in_1/kx_36.h C 1999,2000,2001,2004,2005,2007,2016,2018,2019,2020,2021 ./lib/dns/rdata/in_1/nimloc_32.c C 2018,2019,2020,2021 @@ -1509,6 +1511,8 @@ ./lib/dns/rdata/in_1/px_26.h C 1999,2000,2001,2004,2005,2007,2016,2018,2019,2020,2021 ./lib/dns/rdata/in_1/srv_33.c C 1999,2000,2001,2003,2004,2005,2007,2009,2015,2016,2018,2019,2020,2021 ./lib/dns/rdata/in_1/srv_33.h C 1999,2000,2001,2004,2005,2007,2016,2018,2019,2020,2021 +./lib/dns/rdata/in_1/svcb_64.c C 2019,2020,2021 +./lib/dns/rdata/in_1/svcb_64.h C 2019,2020,2021 ./lib/dns/rdata/in_1/wks_11.c C 1999,2000,2001,2002,2004,2007,2009,2011,2012,2013,2014,2015,2016,2017,2018,2019,2020,2021 ./lib/dns/rdata/in_1/wks_11.h C 1999,2000,2001,2004,2007,2016,2018,2019,2020,2021 ./lib/dns/rdata/rdatastructpre.h C 1999,2000,2001,2004,2007,2016,2018,2019,2020,2021