]> git.ipfire.org Git - thirdparty/bind9.git/commitdiff
Add send-report-channel option
authorMark Andrews <marka@isc.org>
Sun, 6 Nov 2022 14:18:44 +0000 (14:18 +0000)
committerEvan Hunt <each@isc.org>
Wed, 23 Oct 2024 21:29:32 +0000 (21:29 +0000)
This commit adds support for the EDNS Report-Channel option,
which is returned in authoritative responses when EDNS is in use.

"send-report-channel" sets the Agent-Domain value that will be
included in EDNS Report-Channel options.  This is configurable at
the options/view level; the value is a DNS name. Setting the
Agent-Domain to the root zone (".") disables the option.

When this value has been set, incoming queries matchng the form
_er.<qtype>.<qname>.<extended-error-code>._er.<agent-domain>/TXT
will be logged to the dns-reporting-agent channel at INFO level.

(Note: error reporting queries will only be accepted if sent via
TCP or with a good server cookie.  If neither is present, named
returns BADCOOKIE to complete the DNS COOKIE handshake, or TC=1
to switch the client to TCP.)

29 files changed:
bin/named/server.c
bin/tests/system/auth/clean.sh
bin/tests/system/auth/ns1/named.conf.in
bin/tests/system/auth/tests.sh
bin/tests/system/checkconf/bad-rad.conf [new file with mode: 0644]
bin/tests/system/checkconf/good-rad-view.conf [new file with mode: 0644]
bin/tests/system/checkconf/good-rad.conf [new file with mode: 0644]
bin/tests/system/checkconf/tests.sh
bin/tests/system/checkconf/warn-rad.conf [new file with mode: 0644]
doc/arm/general.rst
doc/arm/logging-categories.inc.rst
doc/arm/reference.rst
doc/misc/options
lib/dns/include/dns/message.h
lib/dns/include/dns/name.h
lib/dns/include/dns/view.h
lib/dns/message.c
lib/dns/name.c
lib/dns/rdata/generic/opt_41.c
lib/dns/view.c
lib/isc/include/isc/log.h
lib/isc/log.c
lib/isccfg/check.c
lib/isccfg/namedconf.c
lib/ns/client.c
lib/ns/include/ns/client.h
lib/ns/query.c
tests/dns/rdata_test.c
util/check-categories.sh

index 35b3ac92533a5be301c71841c29027d31c1ecd4c..64e5e061666c3a8b228865539379ec4ab6b4115d 100644 (file)
@@ -4271,6 +4271,22 @@ configure_view(dns_view_t *view, dns_viewlist_t *viewlist, cfg_obj_t *config,
                }
        }
 
+       obj = NULL;
+       result = named_config_get(maps, "send-report-channel", &obj);
+       if (view->rad != NULL) {
+               dns_name_free(view->rad, view->mctx);
+               isc_mem_put(view->mctx, view->rad, sizeof(*view->rad));
+       }
+       if (result == ISC_R_SUCCESS) {
+               str = cfg_obj_asstring(obj);
+               if (strcmp(str, ".") != 0 && strcmp(str, "") != 0) {
+                       view->rad = isc_mem_get(mctx, sizeof(*view->rad));
+                       dns_name_init(view->rad, NULL);
+                       CHECK(dns_name_fromstring(view->rad, str, dns_rootname,
+                                                 0, mctx));
+               }
+       }
+
        obj = NULL;
        result = named_config_get(maps, "dnssec-accept-expired", &obj);
        INSIST(result == ISC_R_SUCCESS);
index 5fb37aca0297444b67afb1572764c7227f8263a9..56a99a55c7a334a442df73fbdbf3791e0882dff3 100644 (file)
 # See the COPYRIGHT file distributed with this work for additional
 # information regarding copyright ownership.
 
+rm -f */named.conf
 rm -f */named.memstats
 rm -f */named.run
-rm -f */named.conf
+rm -f */named.run.prev
 rm -f dig.out.test*
+rm -f ns*/managed-keys.bind* ns*/*mkeys*
 rm -f ns2/example.com.bk
 rm -f ns2/example.net.bk
-rm -f ns*/managed-keys.bind* ns*/*mkeys*
index 7fdc1a1524ab7329f56c8ac3da6d582864b79c66..027a08851d5dfa2746166fb0e942002471db3d90 100644 (file)
@@ -22,6 +22,7 @@ options {
        recursion no;
        notify yes;
        dnssec-validation no;
+       send-report-channel "rad.example.net";
 };
 
 view main in {
index 2499b469508aa7e593cd2d55f3968d9c946a4bff..591adfd9c29cf2b96f0844671b98339a5c298bc2 100644 (file)
@@ -186,5 +186,23 @@ lines=$(wc -l <dig.out.test$n)
 [ $ret -eq 0 ] || echo_i "failed"
 status=$((status + ret))
 
+n=$((n + 1))
+echo_i "check that a Report-Channel EDNS option is added to responses ($n)"
+ret=0
+$DIG $DIGOPTS @10.53.0.1 example.net >dig.out.test$n
+grep "; Report-Channel: rad.example.net" dig.out.test$n >/dev/null || ret=1
+[ $ret -eq 0 ] || echo_i "failed"
+status=$((status + ret))
+
+n=$((n + 1))
+echo_i "check that error report queries are logged and no Report-Channel option is present in the response ($n)"
+ret=0
+nextpart ns1/named.run >/dev/null
+$DIG $DIGOPTS @10.53.0.1 _er.0.example.1._er.rad.example.net TXT >dig.out.test$n
+nextpart ns1/named.run | grep "dns-reporting-agent '_er.0.example.1._er.rad.example.net/IN'" >/dev/null || ret=1
+grep "; Report-Channel: rad.example.net" dig.out.test$n >/dev/null && ret=1
+[ $ret -eq 0 ] || echo_i "failed"
+status=$((status + ret))
+
 echo_i "exit status: $status"
 [ $status -eq 0 ] || exit 1
diff --git a/bin/tests/system/checkconf/bad-rad.conf b/bin/tests/system/checkconf/bad-rad.conf
new file mode 100644 (file)
index 0000000..f311f2d
--- /dev/null
@@ -0,0 +1,17 @@
+/*
+ * 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.
+ */
+
+options {
+       /* invalid domain name */
+       send-report-channel example..com;
+};
diff --git a/bin/tests/system/checkconf/good-rad-view.conf b/bin/tests/system/checkconf/good-rad-view.conf
new file mode 100644 (file)
index 0000000..da7bd91
--- /dev/null
@@ -0,0 +1,16 @@
+/*
+ * 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.
+ */
+
+view view {
+       send-report-channel example.com;
+};
diff --git a/bin/tests/system/checkconf/good-rad.conf b/bin/tests/system/checkconf/good-rad.conf
new file mode 100644 (file)
index 0000000..6d56da5
--- /dev/null
@@ -0,0 +1,21 @@
+/*
+ * 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.
+ */
+
+options {
+       send-report-channel example.com;
+};
+
+zone example.com {
+       type primary;
+       file "example.db";
+};
index 461e4ef45146055a04599c54764e7d4f8d61e8cd..3f83a131007d067b31d8070f6e4115f376d7d92a 100644 (file)
@@ -772,5 +772,16 @@ if [ $ret != 0 ]; then
 fi
 status=$((status + ret))
 
+n=$((n + 1))
+echo_i "check that 'send-report-channel' warns if no matching zone exists ($n)"
+ret=0
+$CHECKCONF -z warn-rad.conf >checkconf.out$n 2>&1 || ret=1
+grep -F "send-report-channel 'example.com' is not a primary or secondary zone" checkconf.out$n >/dev/null || ret=1
+if [ $ret != 0 ]; then
+  echo_i "failed"
+  ret=1
+fi
+status=$((status + ret))
+
 echo_i "exit status: $status"
 [ $status -eq 0 ] || exit 1
diff --git a/bin/tests/system/checkconf/warn-rad.conf b/bin/tests/system/checkconf/warn-rad.conf
new file mode 100644 (file)
index 0000000..a6bfc33
--- /dev/null
@@ -0,0 +1,17 @@
+/*
+ * 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.
+ */
+
+options {
+       /* nonexistent zone used as agent-domain */
+       send-report-channel example.com;
+};
index 0a125674a1c5e701e11d787c98668dcfd076cfe7..5cf402cfd76fdb56e724d7096e72c4310a47501f 100644 (file)
@@ -388,6 +388,8 @@ in the DNS.* February 2014.
 :rfc:`8749` - W. Mekking and D. Mahoney. *Moving DNSSEC Lookaside Validation
 (DLV) to Historic Status.* March 2020.
 
+:rfc:`9567` - R. Arends and M. Larson. *DNS Error Reporting*
+
 Notes
 ~~~~~
 
index c3c2e685024a6f64878a3eb4d0d5b83547e562a9..91f82b36d2f48d330fbe81fe95ca8477a67f3f7a 100644 (file)
@@ -43,6 +43,9 @@
 ``general``
     A catch-all for many things that still are not classified into categories.
 
+``dns-reporting-agent``
+    Logs reports from clients that the there is an error in our responses.
+
 ``lame-servers``
     Misconfigurations in remote servers, discovered by BIND 9 when trying to query those servers during resolution.
 
index 3eaeab576edef958a3fa37c324cd9bed910adb45..c6ff719c12308de4d1a45c1fe6c1d438945846a7 100644 (file)
@@ -1680,6 +1680,41 @@ default is used.
    If all supported digest types are disabled, the zones covered by
    :any:`disable-ds-digests` are treated as insecure.
 
+.. namedconf:statement:: send-report-channel
+   :tags: query
+   :short: Sets the Agent Domain value for the EDNS Report-Channel option
+
+   The EDNS Report-Channel option can be added to responses by an
+   authoritative server to inform clients of a domain name to which
+   operational and protocol errors may be reported. This can help
+   operators find out about configuration errors that are causing
+   problems with resolution or validation elsewhere (for example,
+   expired DNSSEC signatures).
+
+   When :any:`send-report-channel` is set in :namedconf:ref:`options`, or
+   :namedconf:ref:`view` :iscman:`named` adds a Report-Channel option to
+   authoritative responses, using the specified domain name as the
+   Agent-Domain.  :iscman:`named` also logs any TXT queries received for
+   names matching the prescribed error-reporting format
+   (_er.<type>.<name>.<extended-rcode>._er.<agent-domain>) to the
+   ``dns-reporting-agent`` logging category at level ``info``.
+
+   There should be a zone delegated to respond to these queries with TXT
+   records, to avoid unnecessarily query repetition. For example:
+
+   ::
+
+      e.g.
+          $ORIGIN <agent-domain>
+          @ 600 SOA <namserver1> <contact-email> 0 0 0 0 600
+          @ 600 NS <nameserver1>
+          @ 600 NS <nameserver2>
+          *._er 600 TXT ""
+
+   If :any:`send-report-channel` is not set, or is set to ``.``, then
+   the EDNS option is not added to responses, and error-report queries are
+   not logged.
+
 .. namedconf:statement:: dnssec-must-be-secure
    :tags: deprecated
    :short: Defines hierarchies that must or may not be secure (signed and validated).
index 4e54305b58188617c97a039b123aa3a13e80fc59..44186cd37b11bc8c8a3573d0f630b2a694041cda 100644 (file)
@@ -266,6 +266,7 @@ options {
        rrset-order { [ class <string> ] [ type <string> ] [ name <quoted_string> ] <string> <string>; ... };
        secroots-file <quoted_string>;
        send-cookie <boolean>;
+       send-report-channel <string>;
        serial-query-rate <integer>;
        serial-update-method ( date | increment | unixtime );
        server-id ( <quoted_string> | none | hostname );
@@ -545,6 +546,7 @@ view <string> [ <class> ] {
        root-key-sentinel <boolean>;
        rrset-order { [ class <string> ] [ type <string> ] [ name <quoted_string> ] <string> <string>; ... };
        send-cookie <boolean>;
+       send-report-channel <string>;
        serial-update-method ( date | increment | unixtime );
        server <netprefix> {
                bogus <boolean>;
index 5970f61160cc23f2c2b22ecfc0975b56f79c64eb..48367d1d572410b1e253f52d6bc738ac06806bc9 100644 (file)
 #define DNS_MESSAGEEXTFLAG_DO 0x8000U
 
 /*%< EDNS0 extended OPT codes */
-#define DNS_OPT_LLQ          1  /*%< LLQ opt code */
-#define DNS_OPT_UL           2  /*%< UL opt code */
-#define DNS_OPT_NSID         3  /*%< NSID opt code */
-#define DNS_OPT_CLIENT_SUBNET 8         /*%< client subnet opt code */
-#define DNS_OPT_EXPIRE       9  /*%< EXPIRE opt code */
-#define DNS_OPT_COOKIE       10 /*%< COOKIE opt code */
-#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 */
+
+#define DNS_OPT_LLQ           1  /*%< LLQ opt code */
+#define DNS_OPT_UL            2  /*%< UL opt code */
+#define DNS_OPT_NSID          3  /*%< NSID opt code */
+#define DNS_OPT_CLIENT_SUBNET  8  /*%< client subnet opt code */
+#define DNS_OPT_EXPIRE        9  /*%< EXPIRE opt code */
+#define DNS_OPT_COOKIE        10 /*%< COOKIE opt code */
+#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 */
+#define DNS_OPT_REPORT_CHANNEL 18 /*%< DNS Reporting Channel */
 
 /*%< Experimental options [65001...65534] as per RFC6891 */
 
  * options we know about. Extended DNS Errors may occur multiple times, but we
  * will set only one per message (for now).
  */
-#define DNS_EDNSOPTIONS 8
+#define DNS_EDNSOPTIONS 9
 
 /*%< EDNS0 extended DNS errors */
 #define DNS_EDE_OTHER               0  /*%< Other Error */
index 7d90ff836f93d990c14275cac4e74b385cde3a28..740fb9c3f937ccc172ae8f916dbc7c69fdaba2b4 100644 (file)
@@ -1350,6 +1350,19 @@ dns_name_size(const dns_name_t *name);
 /*%<
  * Return the amount of dynamically allocated memory associated with
  * 'name' (which is 0 if 'name' is not dynamic).
+ */
+
+bool
+dns_name_israd(const dns_name_t *name, const dns_name_t *rad);
+/*%<
+ * Determine whether 'name' matches the prescribed format of a
+ * DNS error-reporting name:
+ *
+ * _er.<TYPE>.<QNAME>.<EDE>._er.<AGENT-DOMAIN>.
+ *
+ * AGENT-DOMAIN is specified by the 'rad' parameter.
+ * EDE is a numeric value representing an extended DNS error code.
+ * TYPE and EDE are not currently checked.
  *
  * Requires:
  * \li 'name' to be valid.
index 845531c0d839b239777a534414f4d3c1907bc77b..d6fbf86b64ff5b537e07573be0514d43915c8e62 100644 (file)
@@ -186,6 +186,7 @@ struct dns_view {
        uint32_t              maxrrperset;
        uint32_t              maxtypepername;
        uint8_t               max_restarts;
+       dns_name_t           *rad; /* reporting agent domain */
 
        /*
         * Configurable data for server use only,
index a2f766ed159aab73cc0a8be71044320d5bd9002d..fd239f40951319bcd29348bac6ecfecdcfbea5f4 100644 (file)
@@ -3606,6 +3606,26 @@ cleanup:
        return (result);
 }
 
+static isc_result_t
+render_reportchan(isc_buffer_t *optbuf, isc_buffer_t *target) {
+       dns_decompress_t dctx = DNS_DECOMPRESS_NEVER;
+       dns_fixedname_t fixed;
+       dns_name_t *name = dns_fixedname_initname(&fixed);
+       char namebuf[DNS_NAME_FORMATSIZE];
+       isc_result_t result;
+
+       result = dns_name_fromwire(name, optbuf, dctx, NULL);
+       if (result == ISC_R_SUCCESS && isc_buffer_activelength(optbuf) == 0) {
+               dns_name_format(name, namebuf, sizeof(namebuf));
+               ADD_STRING(target, " ");
+               ADD_STRING(target, namebuf);
+               return (result);
+       }
+       result = ISC_R_FAILURE;
+cleanup:
+       return (result);
+}
+
 static isc_result_t
 dns_message_pseudosectiontoyaml(dns_message_t *msg, dns_pseudosection_t section,
                                const dns_master_style_t *style,
@@ -3862,6 +3882,19 @@ dns_message_pseudosectiontoyaml(dns_message_t *msg, dns_pseudosection_t section,
                                        ADD_STRING(target, buf);
                                        continue;
                                }
+                       } else if (optcode == DNS_OPT_REPORT_CHANNEL) {
+                               INDENT(style);
+                               ADD_STRING(target, "Report-Channel:");
+                               if (optlen > 0U) {
+                                       isc_buffer_t sb = optbuf;
+                                       isc_buffer_setactive(&optbuf, optlen);
+                                       result = render_reportchan(&optbuf,
+                                                                  target);
+                                       if (result == ISC_R_SUCCESS) {
+                                               continue;
+                                       }
+                                       optbuf = sb;
+                               }
                        } else {
                                INDENT(style);
                                ADD_STRING(target, "OPT=");
@@ -4249,6 +4282,19 @@ dns_message_pseudosectiontotext(dns_message_t *msg, dns_pseudosection_t section,
                                        ADD_STRING(target, buf);
                                        continue;
                                }
+                       } else if (optcode == DNS_OPT_REPORT_CHANNEL) {
+                               ADD_STRING(target, "; Report-Channel:");
+                               if (optlen > 0U) {
+                                       isc_buffer_t sb = optbuf;
+                                       isc_buffer_setactive(&optbuf, optlen);
+                                       result = render_reportchan(&optbuf,
+                                                                  target);
+                                       if (result == ISC_R_SUCCESS) {
+                                               ADD_STRING(target, "\n");
+                                               continue;
+                                       }
+                                       optbuf = sb;
+                               }
                        } else {
                                ADD_STRING(target, "; OPT=");
                                snprintf(buf, sizeof(buf), "%u:", optcode);
index 4c1390e7193a8587d531b02ec3c6a1690313e9e5..32770402bac2c2764abeb627107807483e0e8779 100644 (file)
@@ -2281,3 +2281,72 @@ dns_name_isdnssvcb(const dns_name_t *name) {
 
        return (false);
 }
+
+bool
+dns_name_israd(const dns_name_t *name, const dns_name_t *rad) {
+       dns_name_t suffix;
+       dns_offsets_t offsets;
+       char labelbuf[64];
+       unsigned long v, last = ULONG_MAX;
+       char *end, *l;
+
+       REQUIRE(DNS_NAME_VALID(name));
+       REQUIRE(DNS_NAME_VALID(rad));
+
+       if (name->labels < rad->labels + 4U || name->length < 4U) {
+               return (false);
+       }
+
+       if (name->ndata[0] != 3 || name->ndata[1] != '_' ||
+           tolower(name->ndata[2]) != 'e' || tolower(name->ndata[3]) != 'r')
+       {
+               return (false);
+       }
+
+       dns_name_init(&suffix, offsets);
+       dns_name_split(name, rad->labels + 1, NULL, &suffix);
+
+       if (suffix.ndata[0] != 3 || suffix.ndata[1] != '_' ||
+           tolower(suffix.ndata[2]) != 'e' || tolower(suffix.ndata[3]) != 'r')
+       {
+               return (false);
+       }
+
+       /* type list */
+       dns_name_split(name, name->labels - 1, NULL, &suffix);
+       INSIST(*suffix.ndata < sizeof(labelbuf));
+       memmove(labelbuf, suffix.ndata + 1, *suffix.ndata);
+       labelbuf[*suffix.ndata] = 0;
+       if (strlen(labelbuf) != *suffix.ndata) {
+               return (false);
+       }
+       l = labelbuf;
+       do {
+               v = strtoul(l, &end, 10);
+               if (v > 0xffff || (*end != 0 && *end != '-') || end == l) {
+                       return (false);
+               }
+               if (last != ULONG_MAX && v <= last) {
+                       return (false);
+               }
+               last = v;
+               if (*end == '-') {
+                       l = end + 1;
+               }
+       } while (*end != 0);
+
+       /* extended error code */
+       dns_name_split(name, rad->labels + 2, NULL, &suffix);
+       INSIST(*suffix.ndata < sizeof(labelbuf));
+       memmove(labelbuf, suffix.ndata + 1, *suffix.ndata);
+       labelbuf[*suffix.ndata] = 0;
+       if (strlen(labelbuf) != *suffix.ndata) {
+               return (false);
+       }
+       v = strtoul(labelbuf, &end, 10);
+       if (v > 0xfff || *end != 0) {
+               return (false);
+       }
+
+       return (dns_name_issubdomain(name, rad));
+}
index 433df37299b20edfa9a91f763a7d0621c507354c..84bbe1cc7f5c9e602ed8b20b6394e59a3e24d7f9 100644 (file)
@@ -93,17 +93,22 @@ totext_opt(ARGS_TOTEXT) {
 
 static isc_result_t
 fromwire_opt(ARGS_FROMWIRE) {
+       dns_fixedname_t fixed;
+       dns_name_t *name;
+       isc_buffer_t b;
        isc_region_t sregion;
        isc_region_t tregion;
-       uint16_t opt;
+       isc_result_t result;
        uint16_t length;
+       uint16_t opt;
        unsigned int total;
 
        REQUIRE(type == dns_rdatatype_opt);
 
        UNUSED(type);
        UNUSED(rdclass);
-       UNUSED(dctx);
+
+       dctx = dns_decompress_setpermitted(dctx, false);
 
        isc_buffer_activeregion(source, &sregion);
        if (sregion.length == 0) {
@@ -245,6 +250,22 @@ fromwire_opt(ARGS_FROMWIRE) {
                        }
                        isc_region_consume(&sregion, length);
                        break;
+               case DNS_OPT_REPORT_CHANNEL:
+                       /* A domain name in wire format. RFC 9567 */
+                       if (length == 0 || length > DNS_NAME_MAXWIRE) {
+                               return (DNS_R_OPTERR);
+                       }
+                       isc_buffer_init(&b, sregion.base, length);
+                       isc_buffer_add(&b, length);
+                       name = dns_fixedname_initname(&fixed);
+                       result = dns_name_fromwire(name, &b, dctx, NULL);
+                       if (result != ISC_R_SUCCESS || name->length != length ||
+                           !dns_name_isabsolute(name))
+                       {
+                               return (DNS_R_OPTERR);
+                       }
+                       isc_region_consume(&sregion, length);
+                       break;
                default:
                        isc_region_consume(&sregion, length);
                        break;
index 2be324156c2c0106af0dfd7953e4d51c7dc67a85..fd261a9919b3a6bcc8740e19831c9e113a39b1d1 100644 (file)
@@ -376,6 +376,10 @@ destroy(dns_view_t *view) {
                dns_dns64_unlink(&view->dns64, dns64);
                dns_dns64_destroy(&dns64);
        }
+       if (view->rad != NULL) {
+               dns_name_free(view->rad, view->mctx);
+               isc_mem_put(view->mctx, view->rad, sizeof(*view->rad));
+       }
        if (view->managed_keys != NULL) {
                dns_zone_detach(&view->managed_keys);
        }
index 665add8d658017910cde0d555330e32158d2532f..f1c1a47576d1c029f2e08728ecf7cae8abaac324 100644 (file)
@@ -149,6 +149,7 @@ enum isc_logcategory {
        NS_LOGCATEGORY_TAT,
        NS_LOGCATEGORY_SERVE_STALE,
        NS_LOGCATEGORY_RESPONSES,
+       NS_LOGCATEGORY_DRA,
        /* cfg categories */
        CFG_LOGCATEGORY_CONFIG,
        /* named categories */
index 8551b34fdc9e94208de7d0c19fdd3e6ebef1574f..04be355ae231f75256c383beaf24df1b6528068b 100644 (file)
@@ -197,6 +197,7 @@ static const char *categories_description[] = {
        [NS_LOGCATEGORY_UPDATE_SECURITY] = "update-security",
        [NS_LOGCATEGORY_QUERY_ERRORS] = "query-errors",
        [NS_LOGCATEGORY_TAT] = "trust-anchor-telemetry",
+       [NS_LOGCATEGORY_DRA] = "dns-reporting-agent",
        [NS_LOGCATEGORY_SERVE_STALE] = "serve-stale",
        [NS_LOGCATEGORY_RESPONSES] = "responses",
        /* cfg categories */
index 31546f3b67c31daba3447b1bfce80e2b3f1584cf..524c2b122d89cbdcf4d5c044814ce3e044b37a44 100644 (file)
@@ -1617,6 +1617,23 @@ check_options(const cfg_obj_t *options, const cfg_obj_t *config,
                }
        }
 
+       /*
+        * Check send-report-channel.
+        */
+       obj = NULL;
+       (void)cfg_map_get(options, "send-report-channel", &obj);
+       if (obj != NULL) {
+               str = cfg_obj_asstring(obj);
+               tresult = check_name(str);
+               if (tresult != ISC_R_SUCCESS) {
+                       cfg_obj_log(obj, ISC_LOG_ERROR,
+                                   "'%s' is not a valid name", str);
+                       if (result == ISC_R_SUCCESS) {
+                               result = tresult;
+                       }
+               }
+       }
+
        /*
         * Check dnssec-must-be-secure.
         */
@@ -5053,28 +5070,73 @@ cleanup:
 
 typedef enum { special_zonetype_rpz, special_zonetype_catz } special_zonetype_t;
 
+static bool
+iszone(const cfg_obj_t *nameobj, const char *what, const char *forview,
+       const char *viewname, int level, isc_symtab_t *symtab) {
+       char namebuf[DNS_NAME_FORMATSIZE];
+       const cfg_obj_t *obj = NULL;
+       const char *zonename = cfg_obj_asstring(nameobj);
+       const char *zonetype = "";
+       dns_fixedname_t fixed;
+       dns_name_t *name = dns_fixedname_initname(&fixed);
+       isc_result_t result;
+       isc_symvalue_t value;
+
+       if (viewname == NULL) {
+               viewname = "";
+               forview = "";
+       }
+
+       result = dns_name_fromstring(name, zonename, dns_rootname, 0, NULL);
+       if (result != ISC_R_SUCCESS) {
+               cfg_obj_log(nameobj, ISC_LOG_ERROR, "bad domain name '%s'",
+                           zonename);
+               return (false);
+       }
+
+       dns_name_format(name, namebuf, sizeof(namebuf));
+       result = isc_symtab_lookup(symtab, namebuf, 3, &value);
+       if (result == ISC_R_SUCCESS) {
+               const cfg_obj_t *zoneobj = value.as_cpointer;
+               if (zoneobj != NULL && cfg_obj_istuple(zoneobj)) {
+                       zoneobj = cfg_tuple_get(zoneobj, "options");
+               }
+               if (zoneobj != NULL && cfg_obj_ismap(zoneobj)) {
+                       (void)cfg_map_get(zoneobj, "type", &obj);
+               }
+               if (obj != NULL) {
+                       zonetype = cfg_obj_asstring(obj);
+               }
+       }
+
+       if (strcasecmp(zonetype, "primary") != 0 &&
+           strcasecmp(zonetype, "master") != 0 &&
+           strcasecmp(zonetype, "secondary") != 0 &&
+           strcasecmp(zonetype, "slave") != 0)
+       {
+               cfg_obj_log(nameobj, level,
+                           "%s '%s'%s%s is not a primary or secondary zone",
+                           what, zonename, forview, viewname);
+               return (false);
+       }
+       return (true);
+}
+
 static isc_result_t
 check_rpz_catz(const char *rpz_catz, const cfg_obj_t *rpz_obj,
               const char *viewname, isc_symtab_t *symtab,
               special_zonetype_t specialzonetype) {
        const cfg_listelt_t *element;
-       const cfg_obj_t *obj, *nameobj, *zoneobj;
-       const char *zonename, *zonetype;
+       const cfg_obj_t *obj, *nameobj;
        const char *forview = " for view ";
-       isc_symvalue_t value;
-       isc_result_t result, tresult;
-       dns_fixedname_t fixed;
-       dns_name_t *name;
-       char namebuf[DNS_NAME_FORMATSIZE];
+       isc_result_t result = ISC_R_SUCCESS;
        unsigned int num_zones = 0;
 
        if (viewname == NULL) {
                viewname = "";
                forview = "";
        }
-       result = ISC_R_SUCCESS;
 
-       name = dns_fixedname_initname(&fixed);
        obj = cfg_tuple_get(rpz_obj, "zone list");
 
        for (element = cfg_list_first(obj); element != NULL;
@@ -5082,56 +5144,21 @@ check_rpz_catz(const char *rpz_catz, const cfg_obj_t *rpz_obj,
        {
                obj = cfg_listelt_value(element);
                nameobj = cfg_tuple_get(obj, "zone name");
-               zonename = cfg_obj_asstring(nameobj);
-               zonetype = "";
 
                if (specialzonetype == special_zonetype_rpz) {
                        if (++num_zones > 64) {
                                cfg_obj_log(nameobj, ISC_LOG_ERROR,
                                            "more than 64 response policy "
-                                           "zones in view '%s'",
-                                           viewname);
+                                           "zones%s'%s'",
+                                           forview, viewname);
                                return (ISC_R_FAILURE);
                        }
                }
 
-               tresult = dns_name_fromstring(name, zonename, dns_rootname, 0,
-                                             NULL);
-               if (tresult != ISC_R_SUCCESS) {
-                       cfg_obj_log(nameobj, ISC_LOG_ERROR,
-                                   "bad domain name '%s'", zonename);
-                       if (result == ISC_R_SUCCESS) {
-                               result = tresult;
-                       }
-                       continue;
-               }
-               dns_name_format(name, namebuf, sizeof(namebuf));
-               tresult = isc_symtab_lookup(symtab, namebuf, 3, &value);
-               if (tresult == ISC_R_SUCCESS) {
-                       obj = NULL;
-                       zoneobj = value.as_cpointer;
-                       if (zoneobj != NULL && cfg_obj_istuple(zoneobj)) {
-                               zoneobj = cfg_tuple_get(zoneobj, "options");
-                       }
-                       if (zoneobj != NULL && cfg_obj_ismap(zoneobj)) {
-                               (void)cfg_map_get(zoneobj, "type", &obj);
-                       }
-                       if (obj != NULL) {
-                               zonetype = cfg_obj_asstring(obj);
-                       }
-               }
-               if (strcasecmp(zonetype, "primary") != 0 &&
-                   strcasecmp(zonetype, "master") != 0 &&
-                   strcasecmp(zonetype, "secondary") != 0 &&
-                   strcasecmp(zonetype, "slave") != 0)
+               if (!iszone(nameobj, rpz_catz, forview, viewname, ISC_LOG_ERROR,
+                           symtab))
                {
-                       cfg_obj_log(nameobj, ISC_LOG_ERROR,
-                                   "%s '%s'%s%s is not a primary or secondary "
-                                   "zone",
-                                   rpz_catz, zonename, forview, viewname);
-                       if (result == ISC_R_SUCCESS) {
-                               result = ISC_R_FAILURE;
-                       }
+                       result = ISC_R_FAILURE;
                }
        }
        return (result);
@@ -5411,7 +5438,8 @@ check_viewconf(const cfg_obj_t *config, const cfg_obj_t *voptions,
 
        /*
         * Check that the response-policy and catalog-zones options
-        * refer to zones that exist.
+        * refer to zones that exist.  Also warn if send-report-channel
+        * is not a zone.
         */
        if (opts != NULL) {
                obj = NULL;
@@ -5433,6 +5461,14 @@ check_viewconf(const cfg_obj_t *config, const cfg_obj_t *voptions,
                {
                        result = ISC_R_FAILURE;
                }
+
+               obj = NULL;
+               if ((cfg_map_get(opts, "send-report-channel", &obj) ==
+                    ISC_R_SUCCESS))
+               {
+                       (void)iszone(obj, "send-report-channel", " for view ",
+                                    viewname, ISC_LOG_WARNING, symtab);
+               }
        }
 
        /*
index cee0036810530b87997a234b2a639b0f493d705c..d951101525c28a73521e75435e402b396e1aa2b2 100644 (file)
@@ -2132,6 +2132,7 @@ static cfg_clausedef_t view_clauses[] = {
        { "queryport-pool-updateinterval", NULL, CFG_CLAUSEFLAG_ANCIENT },
        { "rate-limit", &cfg_type_rrl, 0 },
        { "recursion", &cfg_type_boolean, 0 },
+       { "send-report-channel", &cfg_type_astring, 0 },
        { "request-nsid", &cfg_type_boolean, 0 },
        { "request-sit", NULL, CFG_CLAUSEFLAG_ANCIENT },
        { "require-server-cookie", &cfg_type_boolean, 0 },
index df20be335b3328d445c787c8c7dea52103f9835b..25bb7e5a8a65f2775d0dd2c09192266b957ca001 100644 (file)
 #define COOKIE_SIZE 24U /* 8 + 4 + 4 + 8 */
 #define ECS_SIZE    20U /* 2 + 1 + 1 + [0..16] */
 
-#define TCPBUFFERS_FILLCOUNT 1U
-#define TCPBUFFERS_FREEMAX   8U
-
-#define WANTNSID(x)    (((x)->attributes & NS_CLIENTATTR_WANTNSID) != 0)
+#define USEKEEPALIVE(x) (((x)->attributes & NS_CLIENTATTR_USEKEEPALIVE) != 0)
 #define WANTEXPIRE(x)  (((x)->attributes & NS_CLIENTATTR_WANTEXPIRE) != 0)
+#define WANTNSID(x)    (((x)->attributes & NS_CLIENTATTR_WANTNSID) != 0)
 #define WANTPAD(x)     (((x)->attributes & NS_CLIENTATTR_WANTPAD) != 0)
-#define USEKEEPALIVE(x) (((x)->attributes & NS_CLIENTATTR_USEKEEPALIVE) != 0)
+#define WANTRC(x)      (((x)->attributes & NS_CLIENTATTR_WANTRC) != 0)
 
 #define MANAGER_MAGIC   ISC_MAGIC('N', 'S', 'C', 'm')
 #define VALID_MANAGER(m) ISC_MAGIC_VALID(m, MANAGER_MAGIC)
@@ -1238,6 +1236,16 @@ no_nsid:
                count++;
        }
 
+       if (WANTRC(client) && view != NULL && view->rad != NULL &&
+           !dns_name_equal(view->rad, dns_rootname))
+       {
+               INSIST(count < DNS_EDNSOPTIONS);
+               ednsopts[count].code = DNS_OPT_REPORT_CHANNEL;
+               ednsopts[count].length = view->rad->length;
+               ednsopts[count].value = view->rad->ndata;
+               count++;
+       }
+
        /* Padding must be added last */
        if ((view != NULL) && (view->padding > 0) && WANTPAD(client) &&
            (TCP_CLIENT(client) ||
index 2a27df8c2085ae4af4373b66f5fb21ce686ded03..e03bc11e6ddd4016fd94943818dff645ab33b6a7 100644 (file)
@@ -247,7 +247,7 @@ struct ns_client {
 #define NS_CLIENTATTR_WANTNSID  0x00020 /*%< include nameserver ID */
 #define NS_CLIENTATTR_BADCOOKIE \
        0x00040 /*%< Presented cookie is bad/out-of-date */
-/* Obsolete: NS_CLIENTATTR_FILTER_AAAA_RC 0x00080 */
+#define NS_CLIENTATTR_WANTRC      0x00080 /*%< include Report-Channel */
 #define NS_CLIENTATTR_WANTAD      0x00100 /*%< want AD in response if possible */
 #define NS_CLIENTATTR_WANTCOOKIE   0x00200 /*%< return a COOKIE */
 #define NS_CLIENTATTR_HAVECOOKIE   0x00400 /*%< has a valid COOKIE */
@@ -257,8 +257,8 @@ struct ns_client {
 #define NS_CLIENTATTR_HAVEECS     0x04000 /*%< received an ECS option */
 #define NS_CLIENTATTR_WANTPAD     0x08000 /*%< pad reply */
 #define NS_CLIENTATTR_USEKEEPALIVE 0x10000 /*%< use TCP keepalive */
-
-#define NS_CLIENTATTR_NOSETFC 0x20000 /*%< don't set servfail cache */
+#define NS_CLIENTATTR_NOSETFC     0x20000 /*%< don't set servfail cache */
+#define NS_CLIENTATTR_NEEDTCP     0x40000 /*%< send TC=1 */
 
 /*
  * Flag to use with the SERVFAIL cache to indicate
index 107c6ef23c47e1ff29c3c0da4a12036ebbb9d11c..4a11ca785734663821b0677ea8248858f5015e38 100644 (file)
        (((c)->query.attributes & NS_QUERYATTR_WANTRECURSION) != 0)
 /*% Is TCP? */
 #define TCP(c) (((c)->attributes & NS_CLIENTATTR_TCP) != 0)
+/*% This query needs to have been sent over TCP.  Return TC=1. */
+#define NEEDTCP(c) (((c)->attributes & NS_CLIENTATTR_NEEDTCP) != 0)
 
 /*% Want DNSSEC? */
 #define WANTDNSSEC(c) (((c)->attributes & NS_CLIENTATTR_WANTDNSSEC) != 0)
@@ -5363,6 +5365,17 @@ ns__query_start(query_ctx_t *qctx) {
                qctx->client->message->flags &= ~DNS_MESSAGEFLAG_AA;
                qctx->client->message->flags &= ~DNS_MESSAGEFLAG_AD;
                qctx->client->message->rcode = dns_rcode_badcookie;
+               qctx->client->attributes &= ~NS_CLIENTATTR_WANTRC;
+               return (ns_query_done(qctx));
+       }
+
+       /*
+        * Respond with TC=1 if we need TCP for this request.
+        */
+       if (!TCP(qctx->client) && NEEDTCP(qctx->client)) {
+               qctx->client->message->flags &= ~DNS_MESSAGEFLAG_AA;
+               qctx->client->message->flags &= ~DNS_MESSAGEFLAG_AD;
+               qctx->client->message->flags |= DNS_MESSAGEFLAG_TC;
                return (ns_query_done(qctx));
        }
 
@@ -6891,6 +6904,8 @@ query_checkrrl(query_ctx_t *qctx, isc_result_t result) {
                                                        ~DNS_MESSAGEFLAG_AD;
                                                qctx->client->message->rcode =
                                                        dns_rcode_badcookie;
+                                               qctx->client->attributes &=
+                                                       ~NS_CLIENTATTR_WANTRC;
                                        } else {
                                                qctx->client->message->flags |=
                                                        DNS_MESSAGEFLAG_TC;
@@ -11323,6 +11338,7 @@ ns_query_done(query_ctx_t *qctx) {
         */
        if (qctx->client->query.restarts == 0 && !qctx->authoritative) {
                qctx->client->message->flags &= ~DNS_MESSAGEFLAG_AA;
+               qctx->client->attributes &= ~NS_CLIENTATTR_WANTRC;
        }
 
        /*
@@ -11459,6 +11475,54 @@ cleanup:
        return (result);
 }
 
+static void
+log_reportchannel(ns_client_t *client) {
+       char classbuf[DNS_RDATACLASS_FORMATSIZE];
+       char namebuf[DNS_NAME_FORMATSIZE];
+
+       client->attributes |= NS_CLIENTATTR_WANTRC;
+
+       if (client->view->rad != NULL &&
+           dns_name_issubdomain(client->query.qname, client->view->rad))
+       {
+               /*
+                * Don't add Report-Channel to responses at or below the
+                * reporting agent domain to prevent infinite loops.
+                */
+               client->attributes &= ~NS_CLIENTATTR_WANTRC;
+       }
+
+       if (client->query.qtype != dns_rdatatype_txt ||
+           client->view->rad == NULL ||
+           !dns_name_israd(client->query.qname, client->view->rad))
+       {
+               return;
+       }
+
+       /*
+        * Check for TCP or a good server cookie.  If neither send
+        * back BADCOOKIE or TC=1.
+        */
+       if (!TCP(client) && !HAVECOOKIE(client)) {
+               if (WANTCOOKIE(client)) {
+                       client->attributes |= NS_CLIENTATTR_BADCOOKIE;
+               } else {
+                       client->attributes |= NS_CLIENTATTR_NEEDTCP;
+               }
+       }
+
+       if (!isc_log_wouldlog(ISC_LOG_INFO)) {
+               return;
+       }
+
+       dns_name_format(client->query.qname, namebuf, sizeof(namebuf));
+       dns_rdataclass_format(client->view->rdclass, classbuf,
+                             sizeof(classbuf));
+
+       isc_log_write(NS_LOGCATEGORY_DRA, NS_LOGMODULE_QUERY, ISC_LOG_INFO,
+                     "dns-reporting-agent '%s/%s'", namebuf, classbuf);
+}
+
 static void
 log_tat(ns_client_t *client) {
        char namebuf[DNS_NAME_FORMATSIZE];
@@ -11714,6 +11778,7 @@ ns_query_start(ns_client_t *client, isc_nmhandle_t *handle) {
        dns_rdatatypestats_increment(client->manager->sctx->rcvquerystats,
                                     qtype);
 
+       log_reportchannel(client);
        log_tat(client);
 
        if (dns_rdatatype_ismeta(qtype)) {
index f61c7ea4000b81f8b38176d0f9eb91d704f5ebdd..57be904cbc7fc023e3f1c6aa47cb729b5d0fd310 100644 (file)
@@ -1848,6 +1848,45 @@ ISC_RUN_TEST_IMPL(edns_client_subnet) {
                    dns_rdatatype_opt, sizeof(dns_rdata_opt_t));
 }
 
+ISC_RUN_TEST_IMPL(edns_rad) {
+       wire_ok_t wire_ok[] = {
+               /*
+                * Option code with no content.
+                */
+               WIRE_INVALID(0x00, 0x12, 0x00, 0x00),
+               /*
+                * Agent Domain = "."
+                */
+               WIRE_VALID(0x00, 0x12, 0x00, 0x01, 0x00),
+               /*
+                * Data after name.
+                */
+               WIRE_INVALID(0x00, 0x12, 0x00, 0x02, 0x00, 0x00),
+               /*
+                * Agent Domain = "example.com."
+                */
+               WIRE_VALID(0x00, 0x12, 0x00, 13, 7, 'e', 'x', 'a', 'm', 'p',
+                          'l', 'e', 3, 'c', 'o', 'm', 0x00),
+               /*
+                * No root label at end.
+                */
+               WIRE_INVALID(0x00, 0x12, 0x00, 12, 7, 'e', 'x', 'a', 'm', 'p',
+                            'l', 'e', 3, 'c', 'o', 'm'),
+               /*
+                * Truncated label.
+                */
+               WIRE_INVALID(0x00, 0x12, 0x00, 11, 7, 'e', 'x', 'a', 'm', 'p',
+                            'l', 'e', 3, 'c', 'o'),
+               /*
+                * Sentinel.
+                */
+               WIRE_SENTINEL()
+       };
+
+       check_rdata(NULL, wire_ok, NULL, true, dns_rdataclass_in,
+                   dns_rdatatype_opt, sizeof(dns_rdata_opt_t));
+}
+
 /*
  * http://ana-3.lcs.mit.edu/~jnc/nimrod/dns.txt
  *
@@ -3171,6 +3210,7 @@ ISC_TEST_ENTRY(zonemd)
 
 /* other tests */
 ISC_TEST_ENTRY(edns_client_subnet)
+ISC_TEST_ENTRY(edns_rad)
 ISC_TEST_ENTRY(atcname)
 ISC_TEST_ENTRY(atparent)
 ISC_TEST_ENTRY(iszonecutauth)
index e45dafe33fbf9ed79292fc90a3427b0c268999a4..a4ad7d7913647aec544ca0691ce41f01f4d889c2 100644 (file)
@@ -16,6 +16,7 @@ list1=$(
     | grep -E "^[[:space:]]+[^[:space:]]+_LOGCATEGORY_[^[:space:]]+([[:space:]]+=[[:space:]]+[-0-9]+)?," \
     | grep -Ev "ISC_LOGCATEGORY_(MAX|INVALID)" \
     | sed -e 's/.*LOGCATEGORY_\([A-Z_]*\).*/\1/' -e 's/^RRL$/rate-limit/' \
+      -e 's/DRA/dns-reporting-agent/' \
     | tr 'A-Z' 'a-z' \
     | tr _ - \
     | sed 's/^tat$/trust-anchor-telemetry/' \