Commit f1e50a0d93 for bind
commit f1e50a0d93c603ad178d8e16e4a4a769a76922e5
Author: Evan Hunt <each@isc.org>
Date: Wed Sep 17 21:53:25 2025 -0700
Support for DELEG/DELEGPARAM rrtypes
Add support for the DELEG and DELEGPARAM rdata types, as
specified in https://datatracker.ietf.org/doc/draft-ietf-deleg/.
DELEG is assigned to type 61440, DELEGPARAM to 54. Both types
are class-IN specific.
In order to correctly handle parsing and printing of server-name
lists containing commas, we use commatxt_fromtext() for parsing, and
add a comma_name_totext() function to escape commas and backslashes
correctly when converting DELEG rdata to text.
diff --git a/bin/tests/system/checkzone/tests.sh b/bin/tests/system/checkzone/tests.sh
index 10aaae0dad..115bffdb52 100644
--- a/bin/tests/system/checkzone/tests.sh
+++ b/bin/tests/system/checkzone/tests.sh
@@ -31,6 +31,9 @@ for db in zones/good*.db; do
zones/good-rpz-prefix.db)
$CHECKZONE -P -i local db $db >test.out.$n 2>&1 || ret=1
;;
+ zones/good-deleg-root.db)
+ $CHECKZONE -i local . $db >test.out.$n 2>&1 || ret=1
+ ;;
*)
$CHECKZONE -i local example $db >test.out.$n 2>&1 || ret=1
;;
@@ -134,7 +137,7 @@ for db in zones/bad*.db; do
;;
zones/bad-svcb-mandatory.db)
$CHECKZONE -P -i local db $db >test.out.$n 2>&1 || v=$?
- grep "zones/bad-svcb-mandatory.db:6: near eol: disallowed (by application policy)" test.out.$n >/dev/null || ret=1
+ grep "zones/bad-svcb-mandatory.db:6: near eol: disallowed" test.out.$n >/dev/null || ret=1
;;
zones/bad-svcb-servername.db)
$CHECKZONE -k fail -i local 0.0.0.0.in-addr.arpa $db >test.out.$n 2>&1 || v=$?
diff --git a/bin/tests/system/checkzone/zones/bad-cname-and-type54.db b/bin/tests/system/checkzone/zones/bad-cname-and-type-540.db
similarity index 84%
rename from bin/tests/system/checkzone/zones/bad-cname-and-type54.db
rename to bin/tests/system/checkzone/zones/bad-cname-and-type-540.db
index c75ea512c4..d046b63a6f 100644
--- a/bin/tests/system/checkzone/zones/bad-cname-and-type54.db
+++ b/bin/tests/system/checkzone/zones/bad-cname-and-type-540.db
@@ -2,5 +2,5 @@ $TTL 600
@ SOA ns hostmaster 2011012708 3600 1200 604800 1200
NS ns
ns A 192.0.2.1
-bad TYPE54 \# 0
+bad TYPE540 \# 0
bad CNAME @
diff --git a/bin/tests/system/checkzone/zones/good-deleg-root.db b/bin/tests/system/checkzone/zones/good-deleg-root.db
new file mode 100644
index 0000000000..3730080ab1
--- /dev/null
+++ b/bin/tests/system/checkzone/zones/good-deleg-root.db
@@ -0,0 +1,33 @@
+; Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+;
+; SPDX-License-Identifier: MPL-2.0
+;
+; 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.
+
+$TTL 300
+. 86400 IN SOA a.root-servers.nil. hostmaster.root-servers.nil. 2025091700 1800 900 604800 86400
+. 240748 IN NS a.root-servers.nil.
+a.root-servers.nil. A 10.53.0.1
+
+example. DELEG server-ipv4=192.0.2.1 server-ipv6=2001:DB8::1
+example. DELEG server-name=ns2.example.net.,ns3.example.org.
+example. DELEG include-delegparam=other.
+
+example. NS a.example.
+example. NS b.example.net.
+example. NS c.example.org.
+
+example. DS 44444 13 2 FD03B2312C8F0FE72C1751EFA1007D743C94EC91594FF0047C23C37CE119BA0C
+
+; unsigned glue for legacy (NS) delegation
+a.example. A 192.0.2.1
+a.example. AAAA 2001:DB8::1
+
+other. DELEGPARAM server-ipv4=192.0.2.2
+other. DELEGPARAM server-ipv6=2001:DB8::2
+other. DELEGPARAM key123=foo
diff --git a/bin/tests/system/doth/example.axfr.good b/bin/tests/system/doth/example.axfr.good
index 1c67f07baf..cbd76c8737 100644
--- a/bin/tests/system/doth/example.axfr.good
+++ b/bin/tests/system/doth/example.axfr.good
@@ -2536,6 +2536,13 @@ cname02.example. 3600 IN CNAME cname-target.example.
cname03.example. 3600 IN CNAME .
csync01.example. 3600 IN CSYNC 0 0 A NS AAAA
csync02.example. 3600 IN CSYNC 0 0
+deleg.example. 3600 IN DELEG server-ipv4=192.0.2.1 server-ipv6=2001:db8::1
+deleg.example. 3600 IN DELEG mandatory=server-ipv4 server-ipv4=192.0.2.2
+deleg.example. 3600 IN DELEG server-name=ns1.example.net.,ns2.example.org.
+deleg.example. 3600 IN DELEG key12345=abc
+delegparam.example. 3600 IN DELEGPARAM server-ipv4=192.0.2.1 server-ipv6=2001:db8::1
+delegparam.example. 3600 IN DELEGPARAM server-name=ns1.example.net.,ns2.example.org.
+delegparam.example. 3600 IN DELEGPARAM key321=xyz
dhcid01.example. 3600 IN DHCID AAIBY2/AuCccgoJbsaxcQc9TUapptP69lOjxfNuVAA2kjEA=
dhcid02.example. 3600 IN DHCID AAEBOSD+XR3Os/0LozeXVqcNc7FwCfQdWL3b/NaiUDlW2No=
dhcid03.example. 3600 IN DHCID AAABxLmlskllE0MVjd57zHcWmEH3pCQ6VytcKD//7es/deY=
diff --git a/bin/tests/system/doth/example8.axfr.good b/bin/tests/system/doth/example8.axfr.good
index a474711cd5..effc3d84b0 100644
--- a/bin/tests/system/doth/example8.axfr.good
+++ b/bin/tests/system/doth/example8.axfr.good
@@ -2536,6 +2536,13 @@ cname02.example8. 3600 IN CNAME cname-target.example8.
cname03.example8. 3600 IN CNAME .
csync01.example8. 3600 IN CSYNC 0 0 A NS AAAA
csync02.example8. 3600 IN CSYNC 0 0
+deleg.example8. 3600 IN DELEG mandatory=server-ipv4 server-ipv4=192.0.2.2
+deleg.example8. 3600 IN DELEG server-ipv4=192.0.2.1 server-ipv6=2001:db8::1
+deleg.example8. 3600 IN DELEG server-name=ns1.example.net.,ns2.example.org.
+deleg.example8. 3600 IN DELEG key12345=abc
+delegparam.example8. 3600 IN DELEGPARAM server-ipv4=192.0.2.1 server-ipv6=2001:db8::1
+delegparam.example8. 3600 IN DELEGPARAM server-name=ns1.example.net.,ns2.example.org.
+delegparam.example8. 3600 IN DELEGPARAM key321=xyz
dhcid01.example8. 3600 IN DHCID AAIBY2/AuCccgoJbsaxcQc9TUapptP69lOjxfNuVAA2kjEA=
dhcid02.example8. 3600 IN DHCID AAEBOSD+XR3Os/0LozeXVqcNc7FwCfQdWL3b/NaiUDlW2No=
dhcid03.example8. 3600 IN DHCID AAABxLmlskllE0MVjd57zHcWmEH3pCQ6VytcKD//7es/deY=
diff --git a/bin/tests/system/genzone.sh b/bin/tests/system/genzone.sh
index d244f103ba..bc87435620 100644
--- a/bin/tests/system/genzone.sh
+++ b/bin/tests/system/genzone.sh
@@ -524,6 +524,15 @@ keydata TYPE65533 \# 0
keydata TYPE65533 \# 6 010203040506
keydata TYPE65533 \# 18 010203040506010203040506010203040506
+; deleg and delegparam (61440 and 54)
+deleg DELEG server-name=ns1.example.net.,ns2.example.org.
+deleg DELEG server-ipv4=192.0.2.1 server-ipv6=2001:DB8::1
+deleg DELEG server-ipv4=192.0.2.2 mandatory=server-ipv4
+deleg DELEG key12345=abc
+delegparam DELEGPARAM server-name=ns1.example.net.,ns2.example.org.
+delegparam DELEGPARAM server-ipv4=192.0.2.1 server-ipv6=2001:DB8::1
+delegparam DELEGPARAM key321=xyz
+
; type 65535 (reserved)
EOF
diff --git a/bin/tests/system/masterformat/setup.sh b/bin/tests/system/masterformat/setup.sh
index 2737abc7ed..d76a648cbb 100755
--- a/bin/tests/system/masterformat/setup.sh
+++ b/bin/tests/system/masterformat/setup.sh
@@ -47,7 +47,7 @@ awk 'END {
}' </dev/null >>ns1/above-limit.db
cp ns1/empty.db.in ns1/255types.db
-for ntype in $(seq 65280 65534); do
+for ntype in $(seq 65280 65431) $(seq 65434 65534); do
echo "m TYPE${ntype} \# 0"
done >>ns1/255types.db
echo "m TXT bunny" >>ns1/255types.db
diff --git a/bin/tests/system/reclimit/tests.sh b/bin/tests/system/reclimit/tests.sh
index 674c5c335a..d9cda0aa12 100644
--- a/bin/tests/system/reclimit/tests.sh
+++ b/bin/tests/system/reclimit/tests.sh
@@ -474,7 +474,7 @@ for ntype in $(seq 65280 65534); do
done
# Wait at least one second
sleep 1
-for ntype in $(seq 65280 65534); do
+for ntype in $(seq 65280 65432) $(seq 65434 65534); do
check_manytypes 2 manytypes.big "TYPE${ntype}" NOERROR manytypes.big "TYPE${ntype}" "" 120 || ret=1
done
diff --git a/bin/tests/system/rrchecker/tests_rrchecker.py b/bin/tests/system/rrchecker/tests_rrchecker.py
index f3734d926f..8fc4a32cf6 100644
--- a/bin/tests/system/rrchecker/tests_rrchecker.py
+++ b/bin/tests/system/rrchecker/tests_rrchecker.py
@@ -44,6 +44,8 @@ pytestmark = pytest.mark.extra_artifacts(
"CERT",
"CNAME",
"CSYNC",
+ "DELEG",
+ "DELEGPARAM",
"DHCID",
"DLV",
"DNAME",
@@ -137,7 +139,7 @@ def run_rrchecker(option, rr_class, rr_type, rr_rest):
@pytest.mark.parametrize(
"option",
- ["-p", "-u", "multi-line at class", " multi-line at type", "multi-line at data"],
+ ["-p", "-u", "multi-line at class", "multi-line at type", "multi-line at data"],
)
def test_rrchecker_conversions(option):
tempzone_file = "tempzone"
diff --git a/bin/tests/system/xfer/response1.good b/bin/tests/system/xfer/response1.good
index 5d82b40405..e031deaad2 100644
--- a/bin/tests/system/xfer/response1.good
+++ b/bin/tests/system/xfer/response1.good
@@ -35,6 +35,13 @@ cname02.example. 3600 IN CNAME cname-target.example.
cname03.example. 3600 IN CNAME .
csync01.example. 3600 IN CSYNC 0 0 A NS AAAA
csync02.example. 3600 IN CSYNC 0 0
+deleg.example. 3600 IN TYPE61440 \# 14 00000002000100010004c0000202
+deleg.example. 3600 IN TYPE61440 \# 28 00010004c00002010002001020010db8 000000000000000000000001
+deleg.example. 3600 IN TYPE61440 \# 38 00030022036e7331076578616d706c65 036e657400036e7332076578616d706c 65036f726700
+deleg.example. 3600 IN TYPE61440 \# 7 30390003616263
+delegparam.example. 3600 IN TYPE54 \# 28 00010004c00002010002001020010db8 000000000000000000000001
+delegparam.example. 3600 IN TYPE54 \# 38 00030022036e7331076578616d706c65 036e657400036e7332076578616d706c 65036f726700
+delegparam.example. 3600 IN TYPE54 \# 7 0141000378797a
dhcid01.example. 3600 IN DHCID AAIBY2/AuCccgoJbsaxcQc9TUapptP69 lOjxfNuVAA2kjEA=
dhcid02.example. 3600 IN DHCID AAEBOSD+XR3Os/0LozeXVqcNc7FwCfQd WL3b/NaiUDlW2No=
dhcid03.example. 3600 IN DHCID AAABxLmlskllE0MVjd57zHcWmEH3pCQ6 VytcKD//7es/deY=
diff --git a/bin/tests/system/xfer/response2.good b/bin/tests/system/xfer/response2.good
index 4ffb43a728..ac3b7ea47b 100644
--- a/bin/tests/system/xfer/response2.good
+++ b/bin/tests/system/xfer/response2.good
@@ -35,6 +35,13 @@ cname02.example. 3600 IN CNAME cname-target.example.
cname03.example. 3600 IN CNAME .
csync01.example. 3600 IN CSYNC 0 0 A NS AAAA
csync02.example. 3600 IN CSYNC 0 0
+deleg.example. 3600 IN TYPE61440 \# 14 00000002000100010004c0000202
+deleg.example. 3600 IN TYPE61440 \# 28 00010004c00002010002001020010db8 000000000000000000000001
+deleg.example. 3600 IN TYPE61440 \# 38 00030022036e7331076578616d706c65 036e657400036e7332076578616d706c 65036f726700
+deleg.example. 3600 IN TYPE61440 \# 7 30390003616263
+delegparam.example. 3600 IN TYPE54 \# 28 00010004c00002010002001020010db8 000000000000000000000001
+delegparam.example. 3600 IN TYPE54 \# 38 00030022036e7331076578616d706c65 036e657400036e7332076578616d706c 65036f726700
+delegparam.example. 3600 IN TYPE54 \# 7 0141000378797a
dhcid01.example. 3600 IN DHCID AAIBY2/AuCccgoJbsaxcQc9TUapptP69 lOjxfNuVAA2kjEA=
dhcid02.example. 3600 IN DHCID AAEBOSD+XR3Os/0LozeXVqcNc7FwCfQd WL3b/NaiUDlW2No=
dhcid03.example. 3600 IN DHCID AAABxLmlskllE0MVjd57zHcWmEH3pCQ6 VytcKD//7es/deY=
diff --git a/lib/dns/db_p.h b/lib/dns/db_p.h
index a77acd0fda..f5b5b5ded5 100644
--- a/lib/dns/db_p.h
+++ b/lib/dns/db_p.h
@@ -109,6 +109,8 @@ prio_type(dns_typepair_t type) {
case DNS_SIGTYPEPAIR(dns_rdatatype_dname):
case dns_rdatatype_svcb:
case DNS_SIGTYPEPAIR(dns_rdatatype_svcb):
+ case dns_rdatatype_deleg:
+ case DNS_SIGTYPEPAIR(dns_rdatatype_deleg):
case dns_rdatatype_https:
case DNS_SIGTYPEPAIR(dns_rdatatype_https):
case dns_rdatatype_dnskey:
diff --git a/lib/dns/rdata.c b/lib/dns/rdata.c
index 13241a8ef6..d4dc6d4493 100644
--- a/lib/dns/rdata.c
+++ b/lib/dns/rdata.c
@@ -75,7 +75,8 @@
#define ARGS_FROMTEXT \
int rdclass, dns_rdatatype_t type, isc_lex_t *lexer, \
const dns_name_t *origin, unsigned int options, \
- isc_buffer_t *target, dns_rdatacallbacks_t *callbacks
+ isc_buffer_t *target, \
+ dns_rdatacallbacks_t *callbacks ISC_ATTR_UNUSED
#define CALL_FROMTEXT rdclass, type, lexer, origin, options, target, callbacks
@@ -113,9 +114,9 @@
#define CALL_DIGEST rdata, digest, arg
-#define ARGS_CHECKOWNER \
- const dns_name_t *name, dns_rdataclass_t rdclass, \
- dns_rdatatype_t type, bool wildcard
+#define ARGS_CHECKOWNER \
+ const dns_name_t *name ISC_ATTR_UNUSED, dns_rdataclass_t rdclass, \
+ dns_rdatatype_t type, bool wildcard ISC_ATTR_UNUSED
#define CALL_CHECKOWNER name, rdclass, type, wildcard
@@ -146,14 +147,15 @@ 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);
+commatxt_fromtext(isc_textregion_t *source, bool comma, bool isname,
+ 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);
+multitxt_totext(isc_region_t *source, bool quote, isc_buffer_t *target);
static isc_result_t
multitxt_fromtext(isc_textregion_t *source, isc_buffer_t *target);
@@ -303,6 +305,19 @@ 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 *);
+static isc_result_t generic_fromtext_in_deleg(ARGS_FROMTEXT);
+static isc_result_t generic_totext_in_deleg(ARGS_TOTEXT);
+static isc_result_t generic_fromwire_in_deleg(ARGS_FROMWIRE);
+static isc_result_t generic_fromstruct_in_deleg(ARGS_FROMSTRUCT);
+static isc_result_t generic_tostruct_in_deleg(ARGS_TOSTRUCT);
+static bool generic_checknames_in_deleg(ARGS_CHECKNAMES);
+static isc_result_t
+generic_rdata_in_deleg_first(dns_rdata_in_deleg_t *);
+static isc_result_t
+generic_rdata_in_deleg_next(dns_rdata_in_deleg_t *);
+static void
+generic_rdata_in_deleg_current(dns_rdata_in_deleg_t *, isc_region_t *);
+
/*% INT16 Size */
#define NS_INT16SZ 2
/*% IPv6 Address Size */
@@ -1617,7 +1632,8 @@ txt_totext(isc_region_t *source, bool quote, isc_buffer_t *target) {
}
static isc_result_t
-commatxt_fromtext(isc_textregion_t *source, bool comma, isc_buffer_t *target) {
+commatxt_fromtext(isc_textregion_t *source, bool comma, bool isname,
+ isc_buffer_t *target) {
isc_region_t tregion;
bool escape = false, comma_escape = false, seen_comma = false;
unsigned int n, nrem;
@@ -1685,19 +1701,26 @@ commatxt_fromtext(isc_textregion_t *source, bool comma, isc_buffer_t *target) {
* 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;
+ if (comma && !comma_escape) {
+ if (c == ',') {
+ seen_comma = true;
+ break;
+ }
+ if (c == '\\') {
+ comma_escape = true;
+ continue;
+ }
}
comma_escape = false;
- if (nrem == 0) {
+
+ if (nrem == (isname && c == '\\') ? 1 : 0) {
return (tregion.length <= 256U) ? ISC_R_NOSPACE
: DNS_R_SYNTAX;
}
+ if (isname && c == '\\') {
+ *t++ = c;
+ nrem--;
+ }
*t++ = c;
nrem--;
}
@@ -1738,7 +1761,7 @@ commatxt_fromtext(isc_textregion_t *source, bool comma, isc_buffer_t *target) {
static isc_result_t
txt_fromtext(isc_textregion_t *source, isc_buffer_t *target) {
- return commatxt_fromtext(source, false, target);
+ return commatxt_fromtext(source, false, false, target);
}
static isc_result_t
@@ -1773,7 +1796,7 @@ txt_fromwire(isc_buffer_t *source, isc_buffer_t *target) {
* Conversion of TXT-like rdata fields without length limits.
*/
static isc_result_t
-multitxt_totext(isc_region_t *source, isc_buffer_t *target) {
+multitxt_totext(isc_region_t *source, bool quote, isc_buffer_t *target) {
unsigned int tl;
unsigned int n0, n;
unsigned char *sp;
@@ -1788,8 +1811,10 @@ multitxt_totext(isc_region_t *source, isc_buffer_t *target) {
if (tl < 1) {
return ISC_R_NOSPACE;
}
- *tp++ = '"';
- tl--;
+ if (quote) {
+ *tp++ = '"';
+ tl--;
+ }
do {
n = source->length;
n0 = source->length - 1;
@@ -1826,8 +1851,10 @@ multitxt_totext(isc_region_t *source, isc_buffer_t *target) {
if (tl < 1) {
return ISC_R_NOSPACE;
}
- *tp++ = '"';
- tl--;
+ if (quote) {
+ *tp++ = '"';
+ tl--;
+ }
POST(tl);
isc_buffer_add(target, (unsigned int)(tp - (char *)region.base));
return ISC_R_SUCCESS;
diff --git a/lib/dns/rdata/generic/caa_257.c b/lib/dns/rdata/generic/caa_257.c
index 1ec6522659..9d77603564 100644
--- a/lib/dns/rdata/generic/caa_257.c
+++ b/lib/dns/rdata/generic/caa_257.c
@@ -357,7 +357,7 @@ totext_caa(ARGS_TOTEXT) {
/*
* Value
*/
- RETERR(multitxt_totext(®ion, target));
+ RETERR(multitxt_totext(®ion, true, target));
return ISC_R_SUCCESS;
}
diff --git a/lib/dns/rdata/generic/uri_256.c b/lib/dns/rdata/generic/uri_256.c
index 4aa55107ba..85e73a66a1 100644
--- a/lib/dns/rdata/generic/uri_256.c
+++ b/lib/dns/rdata/generic/uri_256.c
@@ -94,7 +94,7 @@ totext_uri(ARGS_TOTEXT) {
/*
* Target URI
*/
- RETERR(multitxt_totext(®ion, target));
+ RETERR(multitxt_totext(®ion, true, target));
return ISC_R_SUCCESS;
}
diff --git a/lib/dns/rdata/in_1/deleg_61440.c b/lib/dns/rdata/in_1/deleg_61440.c
new file mode 100644
index 0000000000..14d6d1c03b
--- /dev/null
+++ b/lib/dns/rdata/in_1/deleg_61440.c
@@ -0,0 +1,1113 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * 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.
+ */
+
+#pragma once
+
+#include <stdbool.h>
+
+#include <isc/result.h>
+
+#define RRTYPE_DELEG_ATTRIBUTES \
+ (DNS_RDATATYPEATTR_FOLLOWADDITIONAL | DNS_RDATATYPEATTR_ATPARENT | \
+ DNS_RDATATYPEATTR_ZONECUTAUTH)
+
+/*
+ * Delegation Information Registry
+ */
+enum deleg_encoding {
+ deleg_unknown,
+ deleg_namelist,
+ deleg_ipv4,
+ deleg_ipv6,
+ deleg_keylist
+};
+
+enum deleg_type {
+ delegkey_mandatory = 0,
+ delegkey_ipv4 = 1,
+ delegkey_ipv6 = 2,
+ delegkey_name = 3,
+ delegkey_include = 4,
+};
+
+static const struct {
+ const char *name;
+ unsigned int value;
+ enum deleg_encoding encoding;
+ bool initial; /* Part of the first defined set of encodings. */
+} direg[] = {
+ { "mandatory", delegkey_mandatory, deleg_keylist, true },
+ { "server-ipv4", delegkey_ipv4, deleg_ipv4, true },
+ { "server-ipv6", delegkey_ipv6, deleg_ipv6, true },
+ { "server-name", delegkey_name, deleg_namelist, true },
+ { "include-delegparam", delegkey_include, deleg_namelist, true },
+};
+
+static int
+deleg_keycmp(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
+deleg_sortkeylist(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, deleg_keycmp);
+ /* 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
+deleg_validate(uint16_t key, isc_region_t *region) {
+ size_t i, j;
+
+ for (i = 0; i < ARRAY_SIZE(direg); i++) {
+ if (direg[i].value == key) {
+ switch (direg[i].encoding) {
+ case deleg_ipv4:
+ if ((region->length % 4) != 0 ||
+ region->length == 0)
+ {
+ return DNS_R_FORMERR;
+ }
+ break;
+ case deleg_ipv6:
+ if ((region->length % 16) != 0 ||
+ region->length == 0)
+ {
+ return DNS_R_FORMERR;
+ }
+ break;
+ case deleg_namelist:
+ if (region->length == 0) {
+ return DNS_R_FORMERR;
+ }
+ break;
+ case deleg_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 deleg_unknown:
+ for (j = 0; j < region->length; j++) {
+ if (!islower(region->base[j]) &&
+ !isdigit(region->base[j]) &&
+ region->base[j] != '-')
+ {
+ return DNS_R_FORMERR;
+ }
+ }
+ break;
+ default:
+ UNREACHABLE();
+ }
+ }
+ }
+ return ISC_R_SUCCESS;
+}
+
+/*
+ * Parse keyname from region.
+ */
+static isc_result_t
+deleg_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 < ARRAY_SIZE(direg); i++) {
+ size_t len = strlen(direg[i].name);
+ if (strncasecmp(region->base, direg[i].name, len) != 0) {
+ continue;
+ }
+
+ INSIST(region->length >= len);
+ if (region->length != len && region->base[len] != sep) {
+ continue;
+ }
+ isc_textregion_consume(region, len);
+ ul = direg[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] != '=')
+ {
+ 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));
+ SET_IF_NOT_NULL(value, ul);
+ return ISC_R_SUCCESS;
+}
+
+static isc_result_t
+servname_fromtxt(isc_textregion_t *region, const dns_name_t *origin,
+ unsigned int options, isc_lex_t *lexer,
+ dns_rdatacallbacks_t *callbacks, isc_buffer_t *target) {
+ isc_textregion_t src = *region;
+
+ if (origin == NULL) {
+ origin = dns_rootname;
+ }
+
+ do {
+ char buf[DNS_NAME_FORMATSIZE];
+ isc_buffer_t b;
+ dns_fixedname_t fn;
+ dns_name_t *name = dns_fixedname_initname(&fn);
+
+ isc_buffer_init(&b, buf, sizeof(buf));
+ RETERR(commatxt_fromtext(&src, true, true, &b));
+
+ /* skip the leading length byte */
+ isc_buffer_forward(&b, 1);
+
+ RETERR(dns_name_fromtext(name, &b, origin, options));
+ RETERR(dns_name_towire(name, NULL, target));
+
+ /* check for valid hostnames */
+ if ((options & DNS_RDATA_CHECKNAMES) == 0 ||
+ dns_name_ishostname(name, false))
+ {
+ continue;
+ }
+
+ if ((options & DNS_RDATA_CHECKNAMESFAIL) != 0) {
+ RETERR(DNS_R_BADNAME);
+ }
+ if (callbacks != NULL) {
+ warn_badname(name, lexer, callbacks);
+ }
+ } while (src.length != 0);
+
+ isc_textregion_consume(region, region->length);
+ return ISC_R_SUCCESS;
+}
+
+static isc_result_t
+deleg_fromtext(isc_textregion_t *region, const dns_name_t *origin,
+ unsigned int options, isc_lex_t *lexer,
+ dns_rdatacallbacks_t *callbacks, 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;
+
+ for (i = 0; i < ARRAY_SIZE(direg); i++) {
+ len = strlen(direg[i].name);
+ if (strncmp(region->base, direg[i].name, len) != 0 ||
+ (region->base[len] != 0 && region->base[len] != '='))
+ {
+ continue;
+ }
+
+ if (region->base[len] == '=') {
+ len++;
+ }
+
+ RETERR(uint16_tobuffer(direg[i].value, target));
+ isc_textregion_consume(region, len);
+
+ sb = *target;
+ RETERR(uint16_tobuffer(0, target)); /* dummy length */
+
+ switch (direg[i].encoding) {
+ case deleg_ipv4:
+ 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 deleg_ipv6:
+ 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 deleg_namelist:
+ RETERR(servname_fromtxt(region, origin, options, lexer,
+ callbacks, target));
+ break;
+ case deleg_keylist:
+ if (region->length == 0) {
+ return DNS_R_SYNTAX;
+ }
+ used = isc_buffer_usedlength(target);
+ while (region->length != 0) {
+ RETERR(deleg_keyfromregion(region, ',', NULL,
+ target));
+ }
+ RETERR(deleg_sortkeylist(target, used));
+ break;
+ case deleg_unknown:
+ default:
+ UNREACHABLE();
+ }
+
+ len = isc_buffer_usedlength(target) -
+ isc_buffer_usedlength(&sb) - 2;
+ RETERR(uint16_tobuffer(len, &sb)); /* actual length */
+ return ISC_R_SUCCESS;
+ }
+
+ RETERR(deleg_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 deleg_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 deleg_validate(key, &keyregion);
+}
+
+static isc_result_t
+deleg_sortkeys(isc_buffer_t *target, unsigned int used) {
+ isc_region_t r1, r2, man = { .base = NULL, .length = 0 };
+ bool have_address = false, have_name = false;
+ unsigned char buf[1024];
+ uint16_t mankey = 0;
+
+ 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 == 0) {
+ /* Lone mandatory field. */
+ return DNS_R_DISALLOWED;
+ }
+
+ /* Both name and address are present. */
+ if ((have_address && key1 == delegkey_name) ||
+ (have_name &&
+ (key1 == delegkey_ipv4 || key1 == delegkey_ipv6)))
+ {
+ 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 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 == 0) {
+ man = r2;
+ man.length = len1 + 4;
+ isc_region_consume(&man, 4);
+ if (man.length < 2) {
+ return DNS_R_SYNTAX;
+ }
+
+ mankey = uint16_fromregion(&man);
+ isc_region_consume(&man, 2);
+ if (mankey == 0) {
+ return DNS_R_DISALLOWED;
+ }
+ }
+
+ /*
+ * Remember if this was a name or address field.
+ */
+ switch (key1) {
+ case delegkey_ipv4:
+ case delegkey_ipv6:
+ have_address = true;
+ break;
+ case delegkey_name:
+ have_name = true;
+ break;
+ default:
+ break;
+ }
+
+ /*
+ * Consume the smallest parameter.
+ */
+ isc_region_consume(&r2, len1 + 4);
+ r1 = r2;
+ }
+}
+
+static isc_result_t
+generic_fromtext_in_deleg(ARGS_FROMTEXT) {
+ isc_token_t token;
+ unsigned int used;
+ bool eolok = false;
+
+ UNUSED(type);
+ UNUSED(rdclass);
+
+ /*
+ * DelegInfos
+ */
+ used = isc_buffer_usedlength(target);
+ while (1) {
+ RETERR(isc_lex_getmastertoken(lexer, &token,
+ isc_tokentype_qvpair, eolok));
+ if (token.type == isc_tokentype_eol ||
+ token.type == isc_tokentype_eof)
+ {
+ isc_lex_ungettoken(lexer, &token);
+ return deleg_sortkeys(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(deleg_fromtext(&token.value.as_textregion, origin,
+ options, lexer, callbacks, target));
+ eolok = true;
+ }
+}
+
+static isc_result_t
+comma_name_totext(const dns_name_t *name, isc_buffer_t *target) {
+ char in[DNS_NAME_FORMATSIZE];
+ size_t len = isc_buffer_availablelength(target);
+
+ dns_name_format(name, in, sizeof(in));
+
+ for (char *sp = in; *sp != '\0'; sp++) {
+ if (*sp == ',') {
+ if (len < 3) {
+ return ISC_R_NOSPACE;
+ }
+ isc_buffer_putstr(target, "\\\\,");
+ } else if (*sp == '\\') {
+ if (len < 2) {
+ return ISC_R_NOSPACE;
+ }
+ isc_buffer_putstr(target, "\\\\");
+ } else {
+ if (len < 1) {
+ return ISC_R_NOSPACE;
+ }
+ isc_buffer_putuint8(target, *sp);
+ }
+ len--;
+ }
+
+ if (len < 1) {
+ return ISC_R_NOSPACE;
+ }
+ isc_buffer_putuint8(target, '.');
+
+ return ISC_R_SUCCESS;
+}
+
+static inline int
+param_index(unsigned short value) {
+ for (size_t i = 0; i < ARRAY_SIZE(direg); i++) {
+ if (direg[i].value == value) {
+ return (int)i;
+ }
+ }
+ return -1;
+}
+
+static inline enum deleg_encoding
+param_encoding(int i) {
+ if (i >= 0) {
+ return direg[i].encoding;
+ }
+ return deleg_unknown;
+}
+
+static const char *
+deleg_paramkey(unsigned short value, char *buf, size_t len) {
+ int i = param_index(value);
+
+ if (i >= 0) {
+ return direg[i].name;
+ } else {
+ int n = snprintf(buf, len, "key%u", value);
+ INSIST(n > 0 && (unsigned int)n < len);
+ return buf;
+ }
+}
+
+static isc_result_t
+generic_totext_in_deleg(ARGS_TOTEXT) {
+ isc_region_t region;
+ char buf[sizeof("xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:255.255.255.255")];
+ bool compat = (tctx->flags & DNS_STYLEFLAG_SVCPARAMKEYCOMPAT) != 0;
+ bool first = true;
+
+ REQUIRE(rdata->length != 0);
+
+ dns_rdata_toregion(rdata, ®ion);
+
+ while (region.length > 0) {
+ enum deleg_encoding encoding = deleg_unknown;
+ uint16_t num;
+ isc_region_t r;
+
+ if (!first) {
+ RETERR(str_totext(" ", target));
+ }
+ first = false;
+
+ INSIST(region.length >= 2);
+ num = uint16_fromregion(®ion);
+ isc_region_consume(®ion, 2);
+
+ int i = param_index(num);
+ if (i >= 0 && ((direg[i].initial) || !compat)) {
+ encoding = param_encoding(i);
+ }
+ RETERR(str_totext(deleg_paramkey(num, 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;
+ }
+ RETERR(str_totext("=", target));
+ switch (encoding) {
+ case deleg_ipv4:
+ 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 deleg_ipv6:
+ 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 deleg_namelist:
+ while (r.length > 0U) {
+ dns_fixedname_t fn;
+ dns_name_t *name = dns_fixedname_initname(&fn);
+ isc_buffer_t b;
+
+ isc_buffer_init(&b, r.base, r.length);
+ isc_buffer_add(&b, r.length);
+ isc_buffer_setactive(&b, r.length);
+ RETERR(dns_name_fromwire(name, &b, NULL, NULL));
+ RETERR(comma_name_totext(name, target));
+
+ isc_region_consume(
+ &r, isc_buffer_consumedlength(&b));
+ if (r.length != 0U) {
+ RETERR(str_totext(",", target));
+ }
+ }
+ break;
+ case deleg_keylist:
+ while (r.length > 0) {
+ num = uint16_fromregion(&r);
+ isc_region_consume(&r, 2);
+ RETERR(str_totext(
+ deleg_paramkey(num, buf, sizeof(buf)),
+ target));
+ if (r.length != 0) {
+ RETERR(str_totext(",", target));
+ }
+ }
+ break;
+ case deleg_unknown:
+ RETERR(multitxt_totext(&r, false, target));
+ break;
+ default:
+ UNREACHABLE();
+ }
+ }
+ return ISC_R_SUCCESS;
+}
+
+static isc_result_t
+generic_fromwire_in_deleg(ARGS_FROMWIRE) {
+ isc_region_t region, man = { .base = NULL, .length = 0 };
+ uint16_t lastkey = 0, mankey = 0;
+ bool first = true;
+
+ UNUSED(type);
+ UNUSED(rdclass);
+ UNUSED(dctx);
+
+ /*
+ * DelegInfos
+ */
+ isc_buffer_activeregion(source, ®ion);
+ if (region.length < 2) {
+ return ISC_R_UNEXPECTEDEND;
+ }
+
+ while (region.length > 0U) {
+ isc_region_t keyregion;
+ uint16_t key, len;
+
+ /*
+ * DelegInfoKey
+ */
+ 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;
+ }
+
+ first = false;
+ lastkey = key;
+
+ /*
+ * 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;
+ }
+ }
+ }
+
+ /*
+ * DelegInfoValue 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);
+
+ /*
+ * DelegInfoValue.
+ */
+ if (region.length < len) {
+ return ISC_R_UNEXPECTEDEND;
+ }
+
+ /*
+ * Remember manatory key.
+ */
+ if (key == delegkey_mandatory) {
+ man = region;
+ man.length = len;
+ /* Get first mandatory key */
+ if (man.length >= 2) {
+ mankey = uint16_fromregion(&man);
+ isc_region_consume(&man, 2);
+ if (mankey == delegkey_mandatory) {
+ return DNS_R_FORMERR;
+ }
+ } else {
+ return DNS_R_FORMERR;
+ }
+ }
+
+ keyregion = region;
+ keyregion.length = len;
+ RETERR(deleg_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 isc_result_t
+generic_fromstruct_in_deleg(ARGS_FROMSTRUCT) {
+ dns_rdata_in_deleg_t *deleg = source;
+
+ REQUIRE(deleg != NULL);
+ REQUIRE(deleg->common.rdtype == type);
+ REQUIRE(deleg->common.rdclass == rdclass);
+
+ UNUSED(type);
+ UNUSED(rdclass);
+
+ return mem_tobuffer(target, deleg->dinfo, deleg->dinfolen);
+}
+
+static isc_result_t
+generic_tostruct_in_deleg(ARGS_TOSTRUCT) {
+ isc_region_t region;
+ dns_rdata_in_deleg_t *deleg = target;
+
+ REQUIRE(deleg != NULL);
+ REQUIRE(rdata->length != 0);
+
+ DNS_RDATACOMMON_INIT(deleg, rdata->type, rdata->rdclass);
+
+ dns_rdata_toregion(rdata, ®ion);
+
+ deleg->dinfolen = region.length;
+ deleg->dinfo = region.base;
+
+ deleg->offset = 0;
+
+ return ISC_R_SUCCESS;
+}
+
+static bool
+generic_checknames_in_deleg(ARGS_CHECKNAMES) {
+ isc_region_t region;
+
+ dns_rdata_toregion(rdata, ®ion);
+ while (region.length > 0) {
+ uint16_t key = uint16_fromregion(®ion);
+
+ isc_region_consume(®ion, 2);
+
+ /* get the DelegInfo lenth */
+ uint16_t len = uint16_fromregion(®ion);
+ isc_region_consume(®ion, 2);
+
+ isc_region_t r = { .base = region.base, .length = len };
+ isc_region_consume(®ion, len);
+
+ if (param_encoding(param_index(key)) != deleg_namelist) {
+ continue;
+ }
+
+ while (r.length > 0U) {
+ dns_name_t name = DNS_NAME_INITEMPTY;
+
+ dns_name_fromregion(&name, &r);
+ isc_region_consume(&r, name.length);
+
+ if (bad != NULL) {
+ dns_name_clone(&name, bad);
+ }
+ if (!dns_name_ishostname(&name, false)) {
+ return false;
+ }
+ if (dns_name_issubdomain(&name, owner)) {
+ return false;
+ }
+ }
+ }
+ return true;
+}
+
+static isc_result_t
+generic_rdata_in_deleg_first(dns_rdata_in_deleg_t *deleg) {
+ if (deleg->dinfolen == 0) {
+ return ISC_R_NOMORE;
+ }
+ deleg->offset = 0;
+ return ISC_R_SUCCESS;
+}
+
+static isc_result_t
+generic_rdata_in_deleg_next(dns_rdata_in_deleg_t *deleg) {
+ isc_region_t region;
+ size_t len;
+
+ if (deleg->offset >= deleg->dinfolen) {
+ return ISC_R_NOMORE;
+ }
+
+ region.base = deleg->dinfo + deleg->offset;
+ region.length = deleg->dinfolen - deleg->offset;
+ INSIST(region.length >= 4);
+ isc_region_consume(®ion, 2);
+ len = uint16_fromregion(®ion);
+ INSIST(region.length >= len + 2);
+ deleg->offset += len + 4;
+ return deleg->offset >= deleg->dinfolen ? ISC_R_NOMORE : ISC_R_SUCCESS;
+}
+
+static void
+generic_rdata_in_deleg_current(dns_rdata_in_deleg_t *deleg,
+ isc_region_t *region) {
+ size_t len;
+
+ INSIST(deleg->offset <= deleg->dinfolen);
+
+ region->base = deleg->dinfo + deleg->offset;
+ region->length = deleg->dinfolen - deleg->offset;
+ INSIST(region->length >= 4);
+ isc_region_consume(region, 2);
+ len = uint16_fromregion(region);
+ INSIST(region->length >= len + 2);
+ region->base = deleg->dinfo + deleg->offset;
+ region->length = len + 4;
+}
+
+static isc_result_t
+fromtext_in_deleg(ARGS_FROMTEXT) {
+ REQUIRE(type == dns_rdatatype_deleg);
+ REQUIRE(rdclass == dns_rdataclass_in);
+
+ return generic_fromtext_in_deleg(CALL_FROMTEXT);
+}
+
+static isc_result_t
+totext_in_deleg(ARGS_TOTEXT) {
+ REQUIRE(rdata->type == dns_rdatatype_deleg);
+ REQUIRE(rdata->rdclass == dns_rdataclass_in);
+ REQUIRE(rdata->length != 0);
+
+ return generic_totext_in_deleg(CALL_TOTEXT);
+}
+
+static isc_result_t
+fromwire_in_deleg(ARGS_FROMWIRE) {
+ REQUIRE(type == dns_rdatatype_deleg);
+ REQUIRE(rdclass == dns_rdataclass_in);
+
+ return generic_fromwire_in_deleg(CALL_FROMWIRE);
+}
+
+static int
+compare_in_deleg(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_deleg);
+ 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 isc_result_t
+fromstruct_in_deleg(ARGS_FROMSTRUCT) {
+ dns_rdata_in_deleg_t *deleg = source;
+
+ REQUIRE(type == dns_rdatatype_deleg);
+ REQUIRE(rdclass == dns_rdataclass_in);
+ REQUIRE(deleg != NULL);
+ REQUIRE(deleg->common.rdtype == type);
+ REQUIRE(deleg->common.rdclass == rdclass);
+
+ return generic_fromstruct_in_deleg(CALL_FROMSTRUCT);
+}
+
+static isc_result_t
+tostruct_in_deleg(ARGS_TOSTRUCT) {
+ dns_rdata_in_deleg_t *deleg = target;
+
+ REQUIRE(rdata->rdclass == dns_rdataclass_in);
+ REQUIRE(rdata->type == dns_rdatatype_deleg);
+ REQUIRE(deleg != NULL);
+ REQUIRE(rdata->length != 0);
+
+ return generic_tostruct_in_deleg(CALL_TOSTRUCT);
+}
+
+static isc_result_t
+additionaldata_in_deleg(ARGS_ADDLDATA) {
+ REQUIRE(rdata->type == dns_rdatatype_deleg);
+ REQUIRE(rdata->rdclass == dns_rdataclass_in);
+
+ UNUSED(rdata);
+ UNUSED(owner);
+ UNUSED(add);
+ UNUSED(arg);
+
+ return ISC_R_SUCCESS;
+}
+
+static isc_result_t
+digest_in_deleg(ARGS_DIGEST) {
+ isc_region_t region1;
+
+ REQUIRE(rdata->type == dns_rdatatype_deleg);
+ REQUIRE(rdata->rdclass == dns_rdataclass_in);
+
+ dns_rdata_toregion(rdata, ®ion1);
+ return (digest)(arg, ®ion1);
+}
+
+static bool
+checkowner_in_deleg(ARGS_CHECKOWNER) {
+ REQUIRE(type == dns_rdatatype_deleg);
+ REQUIRE(rdclass == dns_rdataclass_in);
+
+ return true;
+}
+
+static bool
+checknames_in_deleg(ARGS_CHECKNAMES) {
+ REQUIRE(rdata->type == dns_rdatatype_deleg);
+ REQUIRE(rdata->rdclass == dns_rdataclass_in);
+
+ return generic_checknames_in_deleg(CALL_CHECKNAMES);
+}
+
+static int
+casecompare_in_deleg(ARGS_COMPARE) {
+ return compare_in_deleg(rdata1, rdata2);
+}
+
+isc_result_t
+dns_rdata_in_deleg_first(dns_rdata_in_deleg_t *deleg) {
+ REQUIRE(deleg != NULL);
+ REQUIRE(deleg->common.rdtype == dns_rdatatype_deleg);
+ REQUIRE(deleg->common.rdclass == dns_rdataclass_in);
+
+ return generic_rdata_in_deleg_first(deleg);
+}
+
+isc_result_t
+dns_rdata_in_deleg_next(dns_rdata_in_deleg_t *deleg) {
+ REQUIRE(deleg != NULL);
+ REQUIRE(deleg->common.rdtype == dns_rdatatype_deleg);
+ REQUIRE(deleg->common.rdclass == dns_rdataclass_in);
+
+ return generic_rdata_in_deleg_next(deleg);
+}
+
+void
+dns_rdata_in_deleg_current(dns_rdata_in_deleg_t *deleg, isc_region_t *region) {
+ REQUIRE(deleg != NULL);
+ REQUIRE(deleg->common.rdtype == dns_rdatatype_deleg);
+ REQUIRE(deleg->common.rdclass == dns_rdataclass_in);
+ REQUIRE(region != NULL);
+
+ generic_rdata_in_deleg_current(deleg, region);
+}
diff --git a/lib/dns/rdata/in_1/deleg_61440.h b/lib/dns/rdata/in_1/deleg_61440.h
new file mode 100644
index 0000000000..0070b5600f
--- /dev/null
+++ b/lib/dns/rdata/in_1/deleg_61440.h
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * 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.
+ */
+
+#pragma once
+
+/*!
+ * \brief Per draft-wesplaap-deleg-01
+ */
+
+#include <isc/region.h>
+#include <isc/result.h>
+
+typedef struct dns_rdata_in_deleg {
+ dns_rdatacommon_t common;
+ isc_mem_t *mctx;
+ unsigned char *dinfo;
+ uint16_t dinfolen;
+ uint16_t offset;
+} dns_rdata_in_deleg_t;
+
+isc_result_t
+dns_rdata_in_deleg_first(dns_rdata_in_deleg_t *);
+
+isc_result_t
+dns_rdata_in_deleg_next(dns_rdata_in_deleg_t *);
+
+void
+dns_rdata_in_deleg_current(dns_rdata_in_deleg_t *, isc_region_t *);
diff --git a/lib/dns/rdata/in_1/delegparam_54.c b/lib/dns/rdata/in_1/delegparam_54.c
new file mode 100644
index 0000000000..e54d09ab6f
--- /dev/null
+++ b/lib/dns/rdata/in_1/delegparam_54.c
@@ -0,0 +1,162 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * 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.
+ */
+
+#pragma once
+
+#include <stdbool.h>
+
+#include <isc/result.h>
+
+#define RRTYPE_DELEGPARAM_ATTRIBUTES (DNS_RDATATYPEATTR_FOLLOWADDITIONAL)
+
+static isc_result_t
+fromtext_in_delegparam(ARGS_FROMTEXT) {
+ REQUIRE(type == dns_rdatatype_delegparam);
+ REQUIRE(rdclass == dns_rdataclass_in);
+
+ return generic_fromtext_in_deleg(CALL_FROMTEXT);
+}
+
+static isc_result_t
+totext_in_delegparam(ARGS_TOTEXT) {
+ REQUIRE(rdata->type == dns_rdatatype_delegparam);
+ REQUIRE(rdata->rdclass == dns_rdataclass_in);
+ REQUIRE(rdata->length != 0);
+
+ return generic_totext_in_deleg(CALL_TOTEXT);
+}
+
+static isc_result_t
+fromwire_in_delegparam(ARGS_FROMWIRE) {
+ REQUIRE(type == dns_rdatatype_delegparam);
+ REQUIRE(rdclass == dns_rdataclass_in);
+
+ return generic_fromwire_in_deleg(CALL_FROMWIRE);
+}
+
+static int
+compare_in_delegparam(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_delegparam);
+ 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 isc_result_t
+fromstruct_in_delegparam(ARGS_FROMSTRUCT) {
+ dns_rdata_in_delegparam_t *delegparam = source;
+
+ REQUIRE(type == dns_rdatatype_delegparam);
+ REQUIRE(rdclass == dns_rdataclass_in);
+ REQUIRE(delegparam != NULL);
+ REQUIRE(delegparam->common.rdtype == type);
+ REQUIRE(delegparam->common.rdclass == rdclass);
+
+ return generic_fromstruct_in_deleg(CALL_FROMSTRUCT);
+}
+
+static isc_result_t
+tostruct_in_delegparam(ARGS_TOSTRUCT) {
+ dns_rdata_in_delegparam_t *delegparam = target;
+
+ REQUIRE(rdata->rdclass == dns_rdataclass_in);
+ REQUIRE(rdata->type == dns_rdatatype_delegparam);
+ REQUIRE(delegparam != NULL);
+ REQUIRE(rdata->length != 0);
+
+ return generic_tostruct_in_deleg(CALL_TOSTRUCT);
+}
+
+static isc_result_t
+additionaldata_in_delegparam(ARGS_ADDLDATA) {
+ REQUIRE(rdata->type == dns_rdatatype_delegparam);
+ REQUIRE(rdata->rdclass == dns_rdataclass_in);
+
+ UNUSED(rdata);
+ UNUSED(owner);
+ UNUSED(add);
+ UNUSED(arg);
+
+ return ISC_R_SUCCESS;
+}
+
+static isc_result_t
+digest_in_delegparam(ARGS_DIGEST) {
+ isc_region_t region1;
+
+ REQUIRE(rdata->type == dns_rdatatype_delegparam);
+ REQUIRE(rdata->rdclass == dns_rdataclass_in);
+
+ dns_rdata_toregion(rdata, ®ion1);
+ return (digest)(arg, ®ion1);
+}
+
+static bool
+checkowner_in_delegparam(ARGS_CHECKOWNER) {
+ REQUIRE(type == dns_rdatatype_delegparam);
+ REQUIRE(rdclass == dns_rdataclass_in);
+
+ return true;
+}
+
+static bool
+checknames_in_delegparam(ARGS_CHECKNAMES) {
+ REQUIRE(rdata->type == dns_rdatatype_delegparam);
+ REQUIRE(rdata->rdclass == dns_rdataclass_in);
+
+ return generic_checknames_in_deleg(CALL_CHECKNAMES);
+}
+
+static int
+casecompare_in_delegparam(ARGS_COMPARE) {
+ return compare_in_delegparam(rdata1, rdata2);
+}
+
+isc_result_t
+dns_rdata_in_delegparam_first(dns_rdata_in_delegparam_t *delegparam) {
+ REQUIRE(delegparam != NULL);
+ REQUIRE(delegparam->common.rdtype == dns_rdatatype_delegparam);
+ REQUIRE(delegparam->common.rdclass == dns_rdataclass_in);
+
+ return generic_rdata_in_deleg_first(delegparam);
+}
+
+isc_result_t
+dns_rdata_in_delegparam_next(dns_rdata_in_delegparam_t *delegparam) {
+ REQUIRE(delegparam != NULL);
+ REQUIRE(delegparam->common.rdtype == dns_rdatatype_delegparam);
+ REQUIRE(delegparam->common.rdclass == dns_rdataclass_in);
+
+ return generic_rdata_in_deleg_next(delegparam);
+}
+
+void
+dns_rdata_in_delegparam_current(dns_rdata_in_delegparam_t *delegparam,
+ isc_region_t *region) {
+ REQUIRE(delegparam != NULL);
+ REQUIRE(delegparam->common.rdtype == dns_rdatatype_delegparam);
+ REQUIRE(delegparam->common.rdclass == dns_rdataclass_in);
+ REQUIRE(region != NULL);
+
+ generic_rdata_in_deleg_current(delegparam, region);
+}
diff --git a/lib/dns/rdata/in_1/delegparam_54.h b/lib/dns/rdata/in_1/delegparam_54.h
new file mode 100644
index 0000000000..f8b8b6129a
--- /dev/null
+++ b/lib/dns/rdata/in_1/delegparam_54.h
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * 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.
+ */
+
+#pragma once
+
+#include <isc/region.h>
+#include <isc/result.h>
+
+typedef struct dns_rdata_in_deleg dns_rdata_in_delegparam_t;
+
+isc_result_t
+dns_rdata_in_delegparam_first(dns_rdata_in_delegparam_t *);
+
+isc_result_t
+dns_rdata_in_delegparam_next(dns_rdata_in_delegparam_t *);
+
+void
+dns_rdata_in_delegparam_current(dns_rdata_in_delegparam_t *, isc_region_t *);
diff --git a/lib/dns/rdata/in_1/meson.build b/lib/dns/rdata/in_1/meson.build
index 8e2341286a..4964b16fa2 100644
--- a/lib/dns/rdata/in_1/meson.build
+++ b/lib/dns/rdata/in_1/meson.build
@@ -20,6 +20,10 @@ dns_header_depfiles += files(
'apl_42.h',
'atma_34.c',
'atma_34.h',
+ 'deleg_61440.c',
+ 'deleg_61440.h',
+ 'delegparam_54.c',
+ 'delegparam_54.h',
'dhcid_49.c',
'dhcid_49.h',
'eid_31.c',
diff --git a/lib/dns/rdata/in_1/svcb_64.c b/lib/dns/rdata/in_1/svcb_64.c
index c11afacc43..c556439645 100644
--- a/lib/dns/rdata/in_1/svcb_64.c
+++ b/lib/dns/rdata/in_1/svcb_64.c
@@ -58,7 +58,7 @@ 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));
+ RETERR(commatxt_fromtext(&source0, true, false, target));
} while (source0.length != 0);
return ISC_R_SUCCESS;
}
@@ -715,7 +715,7 @@ generic_totext_in_svcb(ARGS_TOTEXT) {
switch (encoding) {
case sbpr_text:
case sbpr_dohpath:
- RETERR(multitxt_totext(&r, target));
+ RETERR(multitxt_totext(&r, true, target));
break;
case sbpr_port:
num = uint16_fromregion(&r);
diff --git a/lib/isc/result.c b/lib/isc/result.c
index fcbfdcce48..65c9fa884a 100644
--- a/lib/isc/result.c
+++ b/lib/isc/result.c
@@ -98,7 +98,7 @@ static const char *description[ISC_R_NRESULTS] = {
[DNS_R_UNKNOWN] = "unknown class/type",
[DNS_R_BADLABELTYPE] = "bad label type",
[DNS_R_BADPOINTER] = "bad compression pointer",
- [DNS_R_DISALLOWED] = "disallowed (by application policy)",
+ [DNS_R_DISALLOWED] = "disallowed",
[DNS_R_EXTRATOKEN] = "extra input text",
[DNS_R_EXTRADATA] = "extra input data",
[DNS_R_TEXTTOOLONG] = "text too long",
@@ -183,7 +183,7 @@ static const char *description[ISC_R_NRESULTS] = {
[DNS_R_UNKNOWNCOMMAND] = "unknown command",
[DNS_R_COVERINGNSEC] = "covering NSEC record returned",
[DNS_R_MXISADDRESS] = "MX is an address",
- [DNS_R_DUPLICATE] = "duplicate query",
+ [DNS_R_DUPLICATE] = "duplicate",
[DNS_R_INVALIDNSEC3] = "invalid NSEC3 owner name",
[DNS_R_NOTPRIMARY] = "not primary",
[DNS_R_BROKENCHAIN] = "broken trust chain",
diff --git a/tests/dns/rdata_test.c b/tests/dns/rdata_test.c
index c7e3c1a5df..af5271dc45 100644
--- a/tests/dns/rdata_test.c
+++ b/tests/dns/rdata_test.c
@@ -3897,6 +3897,135 @@ ISC_RUN_TEST_IMPL(decompression_hop_limit) {
}
}
+ISC_RUN_TEST_IMPL(deleg) {
+ /*
+ * Known keys: mandatory, server-ipv4, server-ipv6,
+ * server-name, include-delegparam
+ */
+ text_ok_t text_ok[] = {
+ /* unknown key invalid */
+ TEXT_INVALID("unknown="),
+ /* minimal record */
+ TEXT_INVALID(""),
+ /* server-ipv4 */
+ TEXT_VALID("server-ipv4=1.2.3.4"),
+ TEXT_VALID("server-ipv4=1.2.3.4,2.3.4.5,3.4.5.6"),
+ TEXT_INVALID("server-ipv4=dead::beef"),
+ TEXT_INVALID("server-ipv4=xxxxx"), TEXT_INVALID("server-ipv4="),
+ /* server-ipv6 */
+ TEXT_VALID("server-ipv6=dead::beef"),
+ TEXT_VALID("server-ipv6=dead::beef,beef::dead"),
+ TEXT_INVALID("server-ipv6=1.2.3.4"),
+ TEXT_INVALID("server-ipv6=xxxxx"), TEXT_INVALID("server-ipv6="),
+ /* both */
+ TEXT_VALID("server-ipv4=1.2.3.4 server-ipv6=dead::beef"),
+ TEXT_VALID_CHANGED("server-ipv6=dead::beef server-ipv4=1.2.3.4",
+ "server-ipv4=1.2.3.4 "
+ "server-ipv6=dead::beef"),
+ TEXT_VALID("server-ipv4=1.2.3.4,4.3.2.1 "
+ "server-ipv6=dead::beef,beef::dead"),
+ TEXT_INVALID("server-ipv6=1.2.3.4 server-ipv4=dead::beef"),
+ /* server-name */
+ TEXT_VALID("server-name=ns.example.com."),
+ TEXT_VALID_CHANGED("server-name=ns.example.com",
+ "server-name=ns.example.com."),
+ TEXT_INVALID("server-name=...."), TEXT_VALID("server-name===."),
+ TEXT_VALID_CHANGED(
+ "server-name=\"part1,part2,part3\\\\,part4\\\\\\\\\"",
+ "server-name=part1.,part2.,part3\\\\,part4\\\\\\\\."),
+ /* server-name mixed with server addresses */
+ TEXT_INVALID("server-ipv4=1.2.3.4 server-name=example."),
+ TEXT_INVALID("server-name=example. server-ipv4=1.2.3.4"),
+ /* include-delegparam */
+ TEXT_VALID("include-delegparam=foo."),
+ TEXT_VALID("server-ipv4=1.2.3.4 include-delegparam=foo."),
+ TEXT_VALID("server-ipv6=dead::beef include-delegparam=foo."),
+ TEXT_VALID("server-name=ns.foo. include-delegparam=bar."),
+ TEXT_VALID_CHANGED("include-delegparam=bar. "
+ "server-name=ns.foo.",
+ "server-name=ns.foo. "
+ "include-delegparam=bar."),
+ /* mandatory */
+ TEXT_VALID("mandatory=server-ipv4 server-ipv4=1.2.3.4"),
+ TEXT_VALID_CHANGED("server-ipv4=1.2.3.4 mandatory=server-ipv4",
+ "mandatory=server-ipv4 server-ipv4=1.2.3.4"),
+ TEXT_INVALID("mandatory=server-ipv6 server-ipv4=1.2.3.4"),
+ TEXT_INVALID("mandatory=server-ipv4 server-name=example."),
+ TEXT_INVALID("mandatory=mandatory"),
+ /* unknown keys, including key-only forms */
+ TEXT_VALID("key123=foo"), TEXT_VALID("key123"),
+ TEXT_VALID("mandatory=key123 key122 key123"),
+ TEXT_VALID("mandatory=key123 key123=foo"),
+ TEXT_VALID("mandatory=key123 key123"),
+ TEXT_INVALID("mandatory=key1234 key123=foo"),
+ TEXT_INVALID("mandatory=key1234 key123"), TEXT_SENTINEL()
+ };
+ wire_ok_t wire_ok[] = {
+ /* too short */
+ WIRE_INVALID(0x00, 0x00),
+ /* mandatory=mandatory*/
+ WIRE_INVALID(0x00, 0x00, 0x00, 0x02, 0x00, 0x00),
+ /* mandatory=key32 key32 */
+ WIRE_VALID(0x00, 0x00, 0x00, 0x02, 0x00, 0x20, 0x00, 0x20, 0x00,
+ 0x00),
+ /* mandatory=key32 key33 */
+ WIRE_INVALID(0x00, 0x00, 0x00, 0x02, 0x00, 0x20, 0x00, 0x21,
+ 0x00, 0x00),
+ /* mandatory=key32 */
+ WIRE_INVALID(0x00, 0x00, 0x00, 0x02, 0x00, 0x20),
+ /* mandatory=key32 key31 key32 */
+ WIRE_VALID(0x00, 0x00, 0x00, 0x02, 0x00, 0x20, 0x00, 0x19, 0x00,
+ 0x00, 0x00, 0x20, 0x00, 0x00),
+ /*
+ * mandatory=key10,key11,key12
+ * key10=1 key11=2 key12=3
+ */
+ WIRE_VALID(0x00, 0x00, 0x00, 0x06, 0x00, 0x0A, 0x00, 0x0B, 0x00,
+ 0x0C, 0x00, 0x0A, 0x00, 0x01, 0x31, 0x00, 0x0B, 0x00,
+ 0x01, 0x32, 0x00, 0x0C, 0x00, 0x01, 0x33),
+ /*
+ * mandatory=key10,key11,key12
+ * key11=2 key12=3
+ */
+ WIRE_INVALID(0x00, 0x00, 0x00, 0x06, 0x00, 0x0A, 0x00, 0x0B,
+ 0x00, 0x0C, 0x00, 0x0B, 0x00, 0x01, 0x32, 0x00,
+ 0x0C, 0x00, 0x01, 0x33),
+ /*
+ * mandatory=key10,key11,key12
+ * key10=1 key12=3
+ */
+ WIRE_INVALID(0x00, 0x00, 0x00, 0x06, 0x00, 0x0A, 0x00, 0x0B,
+ 0x00, 0x0C, 0x00, 0x0A, 0x00, 0x01, 0x31, 0x00,
+ 0x0C, 0x00, 0x01, 0x33),
+ /*
+ * mandatory=key10,key11,key12
+ * key10=1 key11=2
+ */
+ WIRE_INVALID(0x00, 0x00, 0x00, 0x06, 0x00, 0x0A, 0x00, 0x0B,
+ 0x00, 0x0C, 0x00, 0x0A, 0x00, 0x01, 0x31, 0x00,
+ 0x0B, 0x00, 0x01, 0x32),
+
+ WIRE_SENTINEL()
+ };
+ textvsunknown_t textvsunknown[] = {
+ /* server-ipv4=1.2.3.4 */
+ { "server-ipv4=1.2.3.4", "\\# 8 0001000401020304" },
+
+ { NULL, NULL }
+ };
+
+ check_rdata(text_ok, wire_ok, NULL, false, dns_rdataclass_in,
+ dns_rdatatype_deleg, sizeof(dns_rdata_in_deleg_t));
+ check_rdata(text_ok, wire_ok, NULL, false, dns_rdataclass_in,
+ dns_rdatatype_delegparam,
+ sizeof(dns_rdata_in_delegparam_t));
+
+ check_textvsunknown(textvsunknown, dns_rdataclass_in,
+ dns_rdatatype_deleg);
+ check_textvsunknown(textvsunknown, dns_rdataclass_in,
+ dns_rdatatype_delegparam);
+}
+
ISC_RUN_TEST_IMPL(atcname) {
unsigned int i;
@@ -3930,6 +4059,7 @@ ISC_RUN_TEST_IMPL(atparent) {
bool tf = dns_rdatatype_atparent((dns_rdatatype_t)i);
switch (i) {
case dns_rdatatype_ds:
+ case dns_rdatatype_deleg:
if (!tf) {
print_message(UNR, i);
}
@@ -3954,6 +4084,7 @@ ISC_RUN_TEST_IMPL(iszonecutauth) {
switch (i) {
case dns_rdatatype_ns:
case dns_rdatatype_ds:
+ case dns_rdatatype_deleg:
case dns_rdatatype_nsec:
case dns_rdatatype_rrsig:
if (!tf) {
@@ -4094,6 +4225,7 @@ ISC_TEST_ENTRY(uri)
ISC_TEST_ENTRY(wallet)
ISC_TEST_ENTRY(wks)
ISC_TEST_ENTRY(zonemd)
+ISC_TEST_ENTRY(deleg)
/* other tests */
ISC_TEST_ENTRY(edns_client_subnet)