diff --git a/CHANGES b/CHANGES
index 44b2e55848..a57db6aaed 100644
--- a/CHANGES
+++ b/CHANGES
@@ -1,3 +1,6 @@
+5408. [protocol] Print Extended DNS Errors if present in OPT record.
+ [GL #1835]
+
5407. [func] The zone timers are now exported to the statistics
channel. Thanks to Paul Frieden, Verizon Media.
[GL #1232]
diff --git a/bin/dig/dighost.c b/bin/dig/dighost.c
index bfc0c2c65f..0384d7ccad 100644
--- a/bin/dig/dighost.c
+++ b/bin/dig/dighost.c
@@ -1452,6 +1452,7 @@ dig_ednsoptname_t optnames[] = {
{ 12, "PAD" }, /* shorthand */
{ 13, "CHAIN" }, /* RFC 7901 */
{ 14, "KEY-TAG" }, /* RFC 8145 */
+ { 15, "EDE" }, /* ietf-dnsop-extended-error-16 */
{ 16, "CLIENT-TAG" }, /* draft-bellis-dnsop-edns-tags */
{ 17, "SERVER-TAG" }, /* draft-bellis-dnsop-edns-tags */
{ 26946, "DEVICEID" }, /* Brian Hartvigsen */
diff --git a/bin/tests/system/digdelv/tests.sh b/bin/tests/system/digdelv/tests.sh
index 2d2c63e285..bfb6ccb9c8 100644
--- a/bin/tests/system/digdelv/tests.sh
+++ b/bin/tests/system/digdelv/tests.sh
@@ -568,7 +568,8 @@ if [ -x "$DIG" ] ; then
echo_i "checking ednsopt LLQ prints as expected ($n)"
ret=0
dig_with_opts @10.53.0.3 +ednsopt=llq:0001000200001234567812345678fefefefe +qr a.example > dig.out.test$n 2>&1 || ret=1
- grep 'LLQ: Version: 1, Opcode: 2, Error: 0, Identifier: 1311768465173141112, Lifetime: 4278124286$' dig.out.test$n > /dev/null || ret=1
+ pat='LLQ: Version: 1, Opcode: 2, Error: 0, Identifier: 1311768465173141112, Lifetime: 4278124286$'
+ tr -d '\r' < dig.out.test$n | grep "$pat" > /dev/null || ret=1
if [ $ret -ne 0 ]; then echo_i "failed"; fi
status=$((status+ret))
@@ -662,6 +663,51 @@ if [ -x "$DIG" ] ; then
if [ $ret -ne 0 ]; then echo_i "failed"; fi
status=$((status+ret))
+ n=$((n+1))
+ echo_i "check that Extended DNS Error 0 is printed correctly ($n)"
+ # First defined EDE code, additional text "foo".
+ dig_with_opts @10.53.0.3 +ednsopt=ede:0000666f6f a.example +qr > dig.out.test$n 2>&1 || ret=1
+ pat='^; EDE: 0 (Other): (foo)$'
+ tr -d '\r' < dig.out.test$n | grep "$pat" > /dev/null || ret=1
+ if [ $ret -ne 0 ]; then echo_i "failed"; fi
+ status=$((status+ret))
+
+ n=$((n+1))
+ echo_i "check that Extended DNS Error 24 is printed correctly ($n)"
+ # Last defined EDE code, no additional text.
+ dig_with_opts @10.53.0.3 +ednsopt=ede:0018 a.example +qr > dig.out.test$n 2>&1 || ret=1
+ pat='^; EDE: 24 (Invalid Data)$'
+ tr -d '\r' < dig.out.test$n | grep "$pat" > /dev/null || ret=1
+ if [ $ret -ne 0 ]; then echo_i "failed"; fi
+ status=$((status+ret))
+
+ n=$((n+1))
+ echo_i "check that Extended DNS Error 25 is printed correctly ($n)"
+ # First undefined EDE code, additional text "foo".
+ dig_with_opts @10.53.0.3 +ednsopt=ede:0019666f6f a.example +qr > dig.out.test$n 2>&1 || ret=1
+ pat='^; EDE: 25: (foo)$'
+ tr -d '\r' < dig.out.test$n | grep "$pat" > /dev/null || ret=1
+ if [ $ret -ne 0 ]; then echo_i "failed"; fi
+ status=$((status+ret))
+
+ n=$((n+1))
+ echo_i "check that invalid Extended DNS Error (length 0) is printed ($n)"
+ # EDE payload is too short
+ dig_with_opts @10.53.0.3 +ednsopt=ede a.example +qr > dig.out.test$n 2>&1 || ret=1
+ pat='^; EDE:$'
+ tr -d '\r' < dig.out.test$n | grep "$pat" > /dev/null || ret=1
+ if [ $ret -ne 0 ]; then echo_i "failed"; fi
+ status=$((status+ret))
+
+ n=$((n+1))
+ echo_i "check that invalid Extended DNS Error (length 1) is printed ($n)"
+ # EDE payload is too short
+ dig_with_opts @10.53.0.3 +ednsopt=ede:00 a.example +qr > dig.out.test$n 2>&1 || ret=1
+ pat='^; EDE: 00 (".")$'
+ tr -d '\r' < dig.out.test$n | grep "$pat" > /dev/null || ret=1
+ if [ $ret -ne 0 ]; then echo_i "failed"; fi
+ status=$((status+ret))
+
n=$((n+1))
echo_i "check that dig handles malformed option '+ednsopt=:' gracefully ($n)"
ret=0
@@ -686,7 +732,8 @@ if [ -x "$DIG" ] ; then
echo_i "check that dig -q -m works ($n)"
ret=0
dig_with_opts @10.53.0.3 -q -m > dig.out.test$n 2>&1
- grep '^;-m\..*IN.*A$' dig.out.test$n > /dev/null || ret=1
+ pat='^;-m\..*IN.*A$'
+ tr -d '\r' < dig.out.test$n | grep "$pat" > /dev/null || ret=1
grep "Dump of all outstanding memory allocations" dig.out.test$n > /dev/null && ret=1
if [ $ret -ne 0 ]; then echo_i "failed"; fi
status=$((status+ret))
@@ -780,7 +827,8 @@ if [ -x "$DIG" ] ; then
echo_i "check that dig +short +expandaaaa works ($n)"
ret=0
dig_with_opts @10.53.0.3 +short +expandaaaa AAAA ns2.example > dig.out.test$n 2>&1 || ret=1
- grep '^fd92:7065:0b8e:ffff:0000:0000:0000:0002$' dig.out.test$n > /dev/null || ret=1
+ pat='^fd92:7065:0b8e:ffff:0000:0000:0000:0002$'
+ tr -d '\r' < dig.out.test$n | grep "$pat" > /dev/null || ret=1
if [ $ret -ne 0 ]; then echo_i "failed"; fi
status=$((status+ret))
diff --git a/doc/arm/notes-9.17.2.xml b/doc/arm/notes-9.17.2.xml
index 193c13c2c1..2c70de6135 100644
--- a/doc/arm/notes-9.17.2.xml
+++ b/doc/arm/notes-9.17.2.xml
@@ -96,6 +96,13 @@
Contributed by Paul Frieden, Verizon Media. [GL #1232]
+
+
+ dig and other tools can now print the Extended
+ DNS Error (EDE) option when it appears in a request or response.
+ [GL #1834]
+
+
diff --git a/lib/dns/include/dns/message.h b/lib/dns/include/dns/message.h
index e2eef88c06..ec3e065daa 100644
--- a/lib/dns/include/dns/message.h
+++ b/lib/dns/include/dns/message.h
@@ -107,6 +107,7 @@
#define DNS_OPT_TCP_KEEPALIVE 11 /*%< TCP keepalive opt code */
#define DNS_OPT_PAD 12 /*%< PAD opt code */
#define DNS_OPT_KEY_TAG 14 /*%< Key tag opt code */
+#define DNS_OPT_EDE 15 /*%< Extended DNS Error opt code */
#define DNS_OPT_CLIENT_TAG 16 /*%< Client tag opt code */
#define DNS_OPT_SERVER_TAG 17 /*%< Server tag opt code */
diff --git a/lib/dns/message.c b/lib/dns/message.c
index 4e4d692a47..a3b0ae4ea6 100644
--- a/lib/dns/message.c
+++ b/lib/dns/message.c
@@ -23,6 +23,7 @@
#include
#include
#include /* Required for HP/UX (and others?) */
+#include
#include
#include
@@ -127,6 +128,32 @@ static const char *opcodetext[] = { "QUERY", "IQUERY", "STATUS",
"RESERVED12", "RESERVED13", "RESERVED14",
"RESERVED15" };
+static const char *edetext[] = { "Other",
+ "Unsupported DNSKEY Algorithm",
+ "Unsupported DS Digest Type",
+ "Stale Answer",
+ "Forged Answer",
+ "DNSSEC Indeterminate",
+ "DNSSEC Bogus",
+ "Signature Expired",
+ "Signature Not Yet Valid",
+ "DNSKEY Missing",
+ "RRSIGs Missing",
+ "No Zone Key Bit Set",
+ "NSEC Missing",
+ "Cached Error",
+ "Not Ready",
+ "Blocked",
+ "Censored",
+ "Filtered",
+ "Prohibited",
+ "Stale NXDOMAIN Answer",
+ "Not Authoritative",
+ "Not Supported",
+ "No Reachable Authority",
+ "Network Error",
+ "Invalid Data" };
+
/*%
* "helper" type, which consists of a block of some type, and is linkable.
* For it to work, sizeof(dns_msgblock_t) must be a multiple of the pointer
@@ -3590,7 +3617,7 @@ dns_message_pseudosectiontoyaml(dns_message_t *msg, dns_pseudosection_t section,
* Print EDNS info, if any.
*
* WARNING: The option contents may be malformed as
- * dig +ednsopt=value: does not validity
+ * dig +ednsopt=value: does not perform validity
* checking.
*/
dns_rdata_init(&rdata);
@@ -4004,6 +4031,36 @@ dns_message_pseudosectiontotext(dns_message_t *msg, dns_pseudosection_t section,
ADD_STRING(target, "\n");
continue;
}
+ } else if (optcode == DNS_OPT_EDE) {
+ ADD_STRING(target, "; EDE:");
+ if (optlen >= 2U) {
+ uint16_t ede;
+ ede = isc_buffer_getuint16(&optbuf);
+ snprintf(buf, sizeof(buf), " %u", ede);
+ ADD_STRING(target, buf);
+#define ARRAY_SIZE(x) (sizeof(x) / sizeof(x[0]))
+ if (ede < ARRAY_SIZE(edetext)) {
+ ADD_STRING(target, " (");
+ ADD_STRING(target,
+ edetext[ede]);
+ ADD_STRING(target, ")");
+ }
+ optlen -= 2;
+ if (optlen != 0) {
+ ADD_STRING(target, ":");
+ }
+ } else if (optlen == 1U) {
+ /* Malformed */
+ optdata = isc_buffer_current(&optbuf);
+ snprintf(buf, sizeof(buf),
+ " %02x (\"%c\")\n", optdata[0],
+ isprint(optdata[0])
+ ? optdata[0]
+ : '.');
+ isc_buffer_forward(&optbuf, optlen);
+ ADD_STRING(target, buf);
+ continue;
+ }
} else if (optcode == DNS_OPT_CLIENT_TAG) {
uint16_t id;
ADD_STRING(target, "; CLIENT-TAG:");
@@ -4034,23 +4091,31 @@ dns_message_pseudosectiontotext(dns_message_t *msg, dns_pseudosection_t section,
if (optlen != 0) {
int i;
+ bool utf8ok = false;
ADD_STRING(target, " ");
optdata = isc_buffer_current(&optbuf);
- for (i = 0; i < optlen; i++) {
- const char *sep;
- switch (optcode) {
- case DNS_OPT_COOKIE:
- sep = "";
- break;
- default:
- sep = " ";
- break;
+ if (optcode == DNS_OPT_EDE) {
+ utf8ok = isc_utf8_valid(optdata,
+ optlen);
+ }
+ if (!utf8ok) {
+ for (i = 0; i < optlen; i++) {
+ const char *sep;
+ switch (optcode) {
+ case DNS_OPT_COOKIE:
+ sep = "";
+ break;
+ default:
+ sep = " ";
+ break;
+ }
+ snprintf(buf, sizeof(buf),
+ "%02x%s", optdata[i],
+ sep);
+ ADD_STRING(target, buf);
}
- snprintf(buf, sizeof(buf), "%02x%s",
- optdata[i], sep);
- ADD_STRING(target, buf);
}
isc_buffer_forward(&optbuf, optlen);
@@ -4087,9 +4152,13 @@ dns_message_pseudosectiontotext(dns_message_t *msg, dns_pseudosection_t section,
/*
* For non-COOKIE options, add a printable
- * version
+ * version.
*/
- ADD_STRING(target, "(\"");
+ if (optcode != DNS_OPT_EDE) {
+ ADD_STRING(target, "(\"");
+ } else {
+ ADD_STRING(target, "(");
+ }
if (isc_buffer_availablelength(target) < optlen)
{
return (ISC_R_NOSPACE);
@@ -4098,11 +4167,18 @@ dns_message_pseudosectiontotext(dns_message_t *msg, dns_pseudosection_t section,
if (isprint(optdata[i])) {
isc_buffer_putmem(
target, &optdata[i], 1);
+ } else if (utf8ok && optdata[i] > 127) {
+ isc_buffer_putmem(
+ target, &optdata[i], 1);
} else {
isc_buffer_putstr(target, ".");
}
}
- ADD_STRING(target, "\")");
+ if (optcode != DNS_OPT_EDE) {
+ ADD_STRING(target, "\")");
+ } else {
+ ADD_STRING(target, ")");
+ }
}
ADD_STRING(target, "\n");
}
diff --git a/lib/dns/rdata/generic/opt_41.c b/lib/dns/rdata/generic/opt_41.c
index 22c3f472e6..55a96853f1 100644
--- a/lib/dns/rdata/generic/opt_41.c
+++ b/lib/dns/rdata/generic/opt_41.c
@@ -18,6 +18,8 @@
(DNS_RDATATYPEATTR_SINGLETON | DNS_RDATATYPEATTR_META | \
DNS_RDATATYPEATTR_NOTQUESTION)
+#include
+
static inline isc_result_t
fromtext_opt(ARGS_FROMTEXT) {
/*
@@ -206,6 +208,24 @@ fromwire_opt(ARGS_FROMWIRE) {
}
isc_region_consume(&sregion, length);
break;
+ case DNS_OPT_EDE:
+ if (length < 2) {
+ return (DNS_R_OPTERR);
+ }
+ /* UTF-8 Byte Order Mark is not permitted. RFC 5198 */
+ if (isc_utf8_bom(sregion.base + 2, length - 2)) {
+ return (DNS_R_OPTERR);
+ }
+ /*
+ * The EXTRA-TEXT field is specified as UTF-8, and
+ * therefore must be validated for correctness
+ * according to RFC 3269 security considerations.
+ */
+ if (!isc_utf8_valid(sregion.base + 2, length - 2)) {
+ return (DNS_R_OPTERR);
+ }
+ isc_region_consume(&sregion, length);
+ break;
case DNS_OPT_CLIENT_TAG:
/* FALLTHROUGH */
case DNS_OPT_SERVER_TAG:
diff --git a/lib/isc/Makefile.am b/lib/isc/Makefile.am
index 702d02012a..2d96823e93 100644
--- a/lib/isc/Makefile.am
+++ b/lib/isc/Makefile.am
@@ -88,6 +88,7 @@ libisc_la_HEADERS = \
include/isc/timer.h \
include/isc/tm.h \
include/isc/types.h \
+ include/isc/utf8.h \
include/isc/util.h \
pthreads/include/isc/condition.h\
pthreads/include/isc/mutex.h \
@@ -205,6 +206,7 @@ libisc_la_SOURCES = \
taskpool.c \
timer.c \
tm.c \
+ utf8.c \
pthreads/condition.c \
pthreads/mutex.c \
pthreads/thread.c \
diff --git a/lib/isc/include/isc/utf8.h b/lib/isc/include/isc/utf8.h
new file mode 100644
index 0000000000..a642c3facd
--- /dev/null
+++ b/lib/isc/include/isc/utf8.h
@@ -0,0 +1,41 @@
+/*
+ * 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.
+ */
+
+/*! \file isc/utf8.h */
+
+#pragma once
+
+#include
+#include
+
+ISC_LANG_BEGINDECLS
+
+bool
+isc_utf8_bom(const unsigned char *buf, size_t len);
+/*<
+ * Returns 'true' if the string of bytes in 'buf' starts
+ * with an UTF-8 Byte Order Mark.
+ *
+ * Requires:
+ *\li 'buf' != NULL
+ */
+
+bool
+isc_utf8_valid(const unsigned char *buf, size_t len);
+/*<
+ * Returns 'true' if the string of bytes in 'buf' is a valid UTF-8
+ * byte sequence otherwise 'false' is returned.
+ *
+ * Requires:
+ *\li 'buf' != NULL
+ */
+
+ISC_LANG_ENDDECLS
diff --git a/lib/isc/utf8.c b/lib/isc/utf8.c
new file mode 100644
index 0000000000..5bfd94f8d1
--- /dev/null
+++ b/lib/isc/utf8.c
@@ -0,0 +1,86 @@
+/*
+ * 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.
+ */
+
+#include
+
+#include
+#include
+
+/*
+ * UTF-8 is defined in "The Unicode Standard -- Version 4.0"
+ * Also see RFC 3629.
+ *
+ * Char. number range | UTF-8 octet sequence
+ * (hexadecimal) | (binary)
+ * --------------------+---------------------------------------------
+ * 0000 0000-0000 007F | 0xxxxxxx
+ * 0000 0080-0000 07FF | 110xxxxx 10xxxxxx
+ * 0000 0800-0000 FFFF | 1110xxxx 10xxxxxx 10xxxxxx
+ * 0001 0000-0010 FFFF | 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
+ */
+bool
+isc_utf8_valid(const unsigned char *buf, size_t len) {
+ REQUIRE(buf != NULL);
+
+ for (size_t i = 0; i < len; i++) {
+ if (buf[i] <= 0x7f) {
+ continue;
+ }
+ if ((i + 1) < len && (buf[i] & 0xe0) == 0xc0 &&
+ (buf[i + 1] & 0xc0) == 0x80) {
+ unsigned int w;
+ w = (buf[i] & 0x1f) << 6;
+ w |= (buf[++i] & 0x3f);
+ if (w < 0x80) {
+ return (false);
+ }
+ continue;
+ }
+ if ((i + 2) < len && (buf[i] & 0xf0) == 0xe0 &&
+ (buf[i + 1] & 0xc0) == 0x80 && (buf[i + 2] & 0xc0) == 0x80)
+ {
+ unsigned int w;
+ w = (buf[i] & 0x0f) << 12;
+ w |= (buf[++i] & 0x3f) << 6;
+ w |= (buf[++i] & 0x3f);
+ if (w < 0x0800) {
+ return (false);
+ }
+ continue;
+ }
+ if ((i + 3) < len && (buf[i] & 0xf8) == 0xf0 &&
+ (buf[i + 1] & 0xc0) == 0x80 &&
+ (buf[i + 2] & 0xc0) == 0x80 && (buf[i + 3] & 0xc0) == 0x80)
+ {
+ unsigned int w;
+ w = (buf[i] & 0x07) << 18;
+ w |= (buf[++i] & 0x3f) << 12;
+ w |= (buf[++i] & 0x3f) << 6;
+ w |= (buf[++i] & 0x3f);
+ if (w < 0x10000 || w > 0x10FFFF) {
+ return (false);
+ }
+ continue;
+ }
+ return (false);
+ }
+ return (true);
+}
+
+bool
+isc_utf8_bom(const unsigned char *buf, size_t len) {
+ REQUIRE(buf != NULL);
+
+ if (len >= 3U && !memcmp(buf, "\xef\xbb\xbf", 3)) {
+ return (true);
+ }
+ return (false);
+}
diff --git a/lib/isc/win32/libisc.def.in b/lib/isc/win32/libisc.def.in
index aecf800244..7b47b79c26 100644
--- a/lib/isc/win32/libisc.def.in
+++ b/lib/isc/win32/libisc.def.in
@@ -689,6 +689,8 @@ isc_timermgr_destroy
isc_timermgr_poke
isc_tm_timegm
isc_tm_strptime
+isc_utf8_bom
+isc_utf8_valid
isc_win32os_versioncheck
openlog
@IF PKCS11
diff --git a/lib/isc/win32/libisc.vcxproj.filters.in b/lib/isc/win32/libisc.vcxproj.filters.in
index f4e1964f06..5dcf67ab50 100644
--- a/lib/isc/win32/libisc.vcxproj.filters.in
+++ b/lib/isc/win32/libisc.vcxproj.filters.in
@@ -260,6 +260,9 @@
Library Header Files
+
+ Library Header Files
+
Library Header Files
@@ -605,6 +608,9 @@
Library Source Files
+
+ Library Source Files
+
@IF PKCS11
Library Source Files
diff --git a/lib/isc/win32/libisc.vcxproj.in b/lib/isc/win32/libisc.vcxproj.in
index b4574460b7..297df38905 100644
--- a/lib/isc/win32/libisc.vcxproj.in
+++ b/lib/isc/win32/libisc.vcxproj.in
@@ -370,6 +370,7 @@ copy InstallFiles ..\Build\Release\
+
@IF PKCS11
@@ -471,6 +472,7 @@ copy InstallFiles ..\Build\Release\
+
@IF PKCS11
diff --git a/util/copyrights b/util/copyrights
index 4aecf882c5..5c71e8dee8 100644
--- a/util/copyrights
+++ b/util/copyrights
@@ -1866,6 +1866,7 @@
./lib/isc/include/isc/timer.h C 1998,1999,2000,2001,2002,2004,2005,2006,2007,2008,2009,2012,2013,2014,2016,2018,2019,2020
./lib/isc/include/isc/tm.h C 2014,2016,2018,2019,2020
./lib/isc/include/isc/types.h C 1999,2000,2001,2002,2003,2004,2005,2006,2007,2008,2009,2012,2013,2014,2016,2017,2018,2019,2020
+./lib/isc/include/isc/utf8.h C 2020
./lib/isc/include/isc/util.h C 1998,1999,2000,2001,2004,2005,2006,2007,2010,2011,2012,2015,2016,2017,2018,2019,2020
./lib/isc/include/pk11/constants.h C 2014,2016,2017,2018,2019,2020
./lib/isc/include/pk11/internal.h C 2014,2016,2018,2019,2020
@@ -1990,6 +1991,7 @@
./lib/isc/unix/stdtime.c C 1999,2000,2001,2004,2005,2007,2016,2018,2019,2020
./lib/isc/unix/syslog.c C 2001,2004,2005,2007,2016,2018,2019,2020
./lib/isc/unix/time.c C 1998,1999,2000,2001,2003,2004,2005,2006,2007,2008,2011,2012,2014,2015,2016,2017,2018,2019,2020
+./lib/isc/utf8.c C 2020
./lib/isc/win32/DLLMain.c C 2001,2004,2007,2016,2018,2019,2020
./lib/isc/win32/condition.c C 1998,1999,2000,2001,2004,2006,2007,2016,2018,2019,2020
./lib/isc/win32/dir.c C 1999,2000,2001,2004,2007,2008,2009,2011,2012,2013,2016,2017,2018,2019,2020