]> git.ipfire.org Git - thirdparty/bind9.git/commitdiff
Parse and print HTTPS and SVCB records
authorMark Andrews <marka@isc.org>
Thu, 5 Nov 2020 06:22:52 +0000 (17:22 +1100)
committerMark Andrews <marka@isc.org>
Wed, 18 Aug 2021 03:49:48 +0000 (13:49 +1000)
17 files changed:
bin/tests/system/checkzone/zones/bad-svcb-mandatory.db [new file with mode: 0644]
bin/tests/system/checkzone/zones/bad-svcb.db [new file with mode: 0644]
bin/tests/system/checkzone/zones/good-svcb.db [new file with mode: 0644]
bin/tests/system/doth/example.axfr.good
bin/tests/system/genzone.sh
bin/tests/system/rrchecker/typelist.good
bin/tests/system/xfer/dig1.good
bin/tests/system/xfer/dig2.good
lib/dns/include/dns/result.h
lib/dns/rdata.c
lib/dns/rdata/in_1/https_65.c [new file with mode: 0644]
lib/dns/rdata/in_1/https_65.h [new file with mode: 0644]
lib/dns/rdata/in_1/svcb_64.c [new file with mode: 0644]
lib/dns/rdata/in_1/svcb_64.h [new file with mode: 0644]
lib/dns/result.c
lib/dns/tests/rdata_test.c
util/copyrights

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 (file)
index 0000000..d704879
--- /dev/null
@@ -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 (file)
index 0000000..d40e8db
--- /dev/null
@@ -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 (file)
index 0000000..e949164
--- /dev/null
@@ -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"
index 01e15fa9a21d1d78879e763ba7db774b92493ac9..653fb46bda57c3f6ec2e5772b9bc71ee4ccf9807 100644 (file)
@@ -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.
index 2f8882b4016826e9cd725ef6813b8796e0246d33..88ae7f04e9136e965378fbabf1320c6afba5071a 100644 (file)
@@ -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 
index c843fc7b51f0b19759b42c07f813171753f39b45..3c3e5cd38405794890e1bb9a66a7cd955646c7c2 100644 (file)
@@ -59,6 +59,8 @@ CDNSKEY
 OPENPGPKEY
 CSYNC
 ZONEMD
+SVCB
+HTTPS
 SPF
 UINFO
 UID
index 0f8fad0f3127e4e541cd80ebb1f8f6e1a96a3864..9fa5437d38368bec03e213a533a5f68a658e8856 100644 (file)
@@ -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.
index fed8926817eda1b003da4607a32715924dd1a5da..2229f9ce7a07c0160bb43465cc09297b078a05f5 100644 (file)
@@ -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.
index 58e7426b3fac328a34c51fc085ba19dc26826c11..22a75b5d24937ee5b68cec89efc5b981d45454c1 100644 (file)
 #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.
index c8d684a38c12e46dfaa1e7b034fbfae7be7b5c36..29e472c266e416d1d619dd14ecab980eef3db2e7 100644 (file)
@@ -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 (file)
index 0000000..464b583
--- /dev/null
@@ -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, &region1);
+       dns_rdata_toregion(rdata2, &region2);
+
+       return (isc_region_compare(&region1, &region2));
+}
+
+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, &region1);
+       return ((digest)(arg, &region1));
+}
+
+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 (file)
index 0000000..2469a35
--- /dev/null
@@ -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 (file)
index 0000000..8987094
--- /dev/null
@@ -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, &region);
+       isc_region_consume(&region, 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(&region, 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, &region);
+
+       /*
+        * SvcPriority.
+        */
+       num = uint16_fromregion(&region);
+       isc_region_consume(&region, 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, &region);
+       isc_region_consume(&region, 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(&region);
+               isc_region_consume(&region, 2);
+               RETERR(str_totext(svcparamkey(num, &encoding, buf, sizeof(buf)),
+                                 target));
+
+               INSIST(region.length >= 2);
+               num = uint16_fromregion(&region);
+               isc_region_consume(&region, 2);
+
+               INSIST(region.length >= num);
+               r = region;
+               r.length = num;
+               isc_region_consume(&region, 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, &region);
+       if (region.length < 2) {
+               return (ISC_R_UNEXPECTEDEND);
+       }
+       RETERR(mem_tobuffer(target, region.base, 2));
+       alias = uint16_fromregion(&region) == 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, &region);
+       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(&region);
+               isc_region_consume(&region, 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(&region);
+               isc_region_consume(&region, 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(&region, 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, &region);
+       RETERR(mem_tobuffer(target, region.base, 2));
+       isc_region_consume(&region, 2);
+
+       /*
+        * TargetName.
+        */
+       dns_name_init(&name, offsets);
+       dns_name_fromregion(&name, &region);
+       RETERR(dns_name_towire(&name, cctx, target));
+       isc_region_consume(&region, 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, &region1);
+       dns_rdata_toregion(rdata2, &region2);
+
+       return (isc_region_compare(&region1, &region2));
+}
+
+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, &region);
+       RETERR(isc_buffer_copyregion(target, &region));
+
+       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, &region);
+
+       svcb->priority = uint16_fromregion(&region);
+       isc_region_consume(&region, 2);
+
+       dns_name_init(&svcb->svcdomain, NULL);
+       dns_name_init(&name, NULL);
+       dns_name_fromregion(&name, &region);
+       isc_region_consume(&region, 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, &region1);
+       return ((digest)(arg, &region1));
+}
+
+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, &region);
+       isc_region_consume(&region, 2);
+       dns_name_init(&name, NULL);
+       dns_name_fromregion(&name, &region);
+       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(&region, 2);
+       len = uint16_fromregion(&region);
+       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 (file)
index 0000000..750b682
--- /dev/null
@@ -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 */
index 1c8641227b95503812fc27d313830d5261f292eb..01ce2b4e855333ba83c10b038730f4869ec7eef9 100644 (file)
@@ -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] = {
index 131f7804a751174c2062fc51de9651e96d803b60..d520cdbb917eb27fa99ff705462b2ea95a1294cb 100644 (file)
@@ -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, &region);
+                       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, &region);
+                       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),
index 931e52ee9f36cf0068451d781e8e6444063336ce..0f4dab432d231a95e5c0fb3bd009e2835ad53821 100644 (file)
 ./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
 ./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