]> git.ipfire.org Git - thirdparty/bind9.git/commitdiff
4747. [func] Synthesis of responses from DNSSEC-verified records.
authorMark Andrews <marka@isc.org>
Tue, 3 Oct 2017 00:16:37 +0000 (11:16 +1100)
committerMark Andrews <marka@isc.org>
Tue, 3 Oct 2017 00:16:37 +0000 (11:16 +1100)
                        Stage 3 - synthesize NODATA responses. [RT #40138]

CHANGES
bin/tests/system/synthfromdnssec/tests.sh
doc/arm/Bv9ARM-book.xml
doc/arm/notes.xml
lib/dns/rbtdb.c
lib/ns/query.c

diff --git a/CHANGES b/CHANGES
index 00f81672e2dba3003b0e279c40e7602d20c17f62..9d1d01222375e1f0ca6c0a2c1a3584dd6f1d864e 100644 (file)
--- a/CHANGES
+++ b/CHANGES
@@ -1,3 +1,6 @@
+4747.  [func]          Synthesis of responses from DNSSEC-verified records.
+                       Stage 3 - synthesize NODATA responses. [RT #40138]
+
 4746.  [cleanup]       Add configured prefixes to configure summary
                        output. [RT #46153]
 
index 4d9856378756af1dcd53cdfff32c75dcb00b2a17..a80d21bc3a8c9b93dddca80a69da3ad4084cf397 100644 (file)
@@ -92,8 +92,8 @@ grep "status: NOERROR," dig.out.ns2.test$n > /dev/null || ret=1
 grep "example.*3600.IN.SOA" dig.out.ns2.test$n > /dev/null && ret=1
 $PERL ../digcomp.pl $nodata dig.out.ns2.test$n || ret=1
 n=`expr $n + 1`
-if [ $ret != 0 ]; then echo "I:failed (ignored - to be supported in the future)"; fi
-status=`expr $status + $ret`
+if [ $ret != 0 ]; then echo "I:failed"; fi
+status=`expr $status + $ret`
 
 echo "I:check synthesized wildcard response ($n)"
 ret=0
index 516ec62ef57c642238352cfabd6c0c41b3ea98c8..0499e91b9ca2046065bacd5dbc77dc1884e4fe2d 100644 (file)
@@ -7229,6 +7229,12 @@ options {
                        DNSSEC validation must be enabled for this
                        option to be effective.
                      </para>
+                     <para>
+                       This initial implementation only covers synthesis
+                       of answers from NSEC records.  Synthesis from NSEC3
+                       is planned for the future.  This will also be
+                       controlled by <command>synth-from-dnssec</command>.
+                     </para>
                    </listitem>
                  </itemizedlist>
                </para>
index 5bad7a766d4605ce27d6247c85ce6a59868d6219..b9be4c7bd7fe696f06c99fcdbf5582692fe02bde 100644 (file)
       </listitem>
       <listitem>
        <para>
-         <command>named</command> can now synthesize NXDOMAIN responses
-         from cached DNSSEC-verified records returned in negative or
-         wildcard responses.  This will reduce query loads on
-         authoritative servers for signed domains: if existing cached
-         records can be used by the resolver to determine that a name does
-         not exist in the authorittive domain, then no query needs to
-         be sent.
+         <command>named</command> can now synthesize negative responses
+         (NXDOMAIN, NODATA, or wildcard answers) from cached DNSSEC-verified
+         records that were returned in negative or wildcard responses from
+         authoritative servers.
+       </para>
+       <para>
+         This will reduce query loads on authoritative servers for signed
+         domains: when existing cached records can be used by the resolver
+         to determine that a name does not exist in the authorittive domain,
+         no query needs to be sent. Reducing the number of iterative queries
+         should also improve resolver performance.
        </para>
        <para>
          This behavior is controlled by the new
          <command>synth-from-dnssec</command>.  It is enabled by
          default.
        </para>
+       <para>
+         Note: this currently only works for zones signed using NSEC.
+         Support for zones signed using NSEC3 (without opt-out) is
+         planned for the future.
+       </para>
        <para>
          Thanks to APNIC for sponsoring this work.
        </para>
index 7ecaa24e5649f0199fd8de4e71526fabb126ab31..74ae6185a3fd59a748c4c7b1dbdbb522de89528b 100644 (file)
@@ -5027,6 +5027,7 @@ cache_find(dns_db_t *db, const dns_name_t *name, dns_dbversion_t *version,
        rdatasetheader_t *found, *nsheader;
        rdatasetheader_t *foundsig, *nssig, *cnamesig;
        rdatasetheader_t *update, *updatesig;
+       rdatasetheader_t *nsecheader, *nsecsig;
        rbtdb_rdatatype_t sigtype, negtype;
 
        UNUSED(version);
@@ -5108,7 +5109,9 @@ cache_find(dns_db_t *db, const dns_name_t *name, dns_dbversion_t *version,
        sigtype = RBTDB_RDATATYPE_VALUE(dns_rdatatype_rrsig, type);
        negtype = RBTDB_RDATATYPE_VALUE(0, type);
        nsheader = NULL;
+       nsecheader = NULL;
        nssig = NULL;
+       nsecsig = NULL;
        cnamesig = NULL;
        empty_node = ISC_TRUE;
        header_prev = NULL;
@@ -5172,6 +5175,10 @@ cache_find(dns_db_t *db, const dns_name_t *name, dns_dbversion_t *version,
                                 * need its signature.
                                 */
                                nssig = header;
+                       } else if (header->type == dns_rdatatype_nsec) {
+                               nsecheader = header;
+                       } else if (header->type == RBTDB_RDATATYPE_SIGNSEC) {
+                               nsecsig = header;
                        } else if (cname_ok &&
                                   header->type == RBTDB_RDATATYPE_SIGCNAME) {
                                /*
@@ -5205,6 +5212,32 @@ cache_find(dns_db_t *db, const dns_name_t *name, dns_dbversion_t *version,
             ((options & DNS_DBFIND_GLUEOK) == 0)) ||
            (DNS_TRUST_PENDING(found->trust) &&
             ((options & DNS_DBFIND_PENDINGOK) == 0))) {
+
+               /*
+                * Return covering NODATA NSEC record.
+                */
+               if ((search.options & DNS_DBFIND_COVERINGNSEC) != 0 &&
+                   nsecheader != NULL)
+               {
+                       if (nodep != NULL) {
+                               new_reference(search.rbtdb, node);
+                               INSIST(!ISC_LINK_LINKED(node, deadlink));
+                               *nodep = node;
+                       }
+                       bind_rdataset(search.rbtdb, node, nsecheader,
+                                     search.now, rdataset);
+                       if (need_headerupdate(nsecheader, search.now))
+                               update = nsecheader;
+                       if (nsecsig != NULL) {
+                               bind_rdataset(search.rbtdb, node, nsecsig,
+                                             search.now, sigrdataset);
+                               if (need_headerupdate(nsecsig, search.now))
+                                       updatesig = nsecsig;
+                       }
+                       result = DNS_R_COVERINGNSEC;
+                       goto node_exit;
+               }
+
                /*
                 * If there is an NS rdataset at this node, then this is the
                 * deepest zone cut.
index eee578556e2e08251ade88741709f0d257048cfd..cf9b78e2188fa4246351a67aa59c5bc9cd8166a1 100644 (file)
@@ -8341,6 +8341,87 @@ log_noexistnodata(void *val, int level, const char *fmt, ...) {
        va_end(ap);
 }
 
+/*
+ * Synthesize a NODATA response from the SOA and covering NSEC in cache.
+ */
+static isc_result_t
+query_synthnodata(query_ctx_t *qctx, const dns_name_t *signer,
+                 dns_rdataset_t **soardatasetp,
+                 dns_rdataset_t **sigsoardatasetp)
+{
+       dns_name_t *name = NULL;
+       dns_ttl_t ttl;
+       isc_buffer_t *dbuf, b;
+       isc_result_t result;
+       dns_rdataset_t *clone = NULL, *sigclone = NULL;
+
+       /*
+        * Detemine the correct TTL to use for the SOA and RRSIG
+        */
+       ttl = ISC_MIN(qctx->rdataset->ttl, qctx->sigrdataset->ttl);
+       ttl = ISC_MIN(ttl, (*soardatasetp)->ttl);
+       ttl = ISC_MIN(ttl, (*sigsoardatasetp)->ttl);
+
+       (*soardatasetp)->ttl = (*sigsoardatasetp)->ttl = ttl;
+
+       /*
+        * We want the SOA record to be first, so save the
+        * NODATA proof's name now or else discard it.
+        */
+       if (WANTDNSSEC(qctx->client)) {
+               query_keepname(qctx->client, qctx->fname, qctx->dbuf);
+       } else {
+               query_releasename(qctx->client, &qctx->fname);
+       }
+
+       dbuf = query_getnamebuf(qctx->client);
+       if (dbuf == NULL) {
+               result = ISC_R_NOMEMORY;
+               goto cleanup;
+       }
+
+       name = query_newname(qctx->client, dbuf, &b);
+       if (name == NULL) {
+               result = ISC_R_NOMEMORY;
+               goto cleanup;
+       }
+
+       dns_name_clone(signer, name);
+
+       /*
+        * Add SOA record. Omit the RRSIG if DNSSEC was not requested.
+        */
+       if (!WANTDNSSEC(qctx->client)) {
+               sigsoardatasetp = NULL;
+       }
+       query_addrrset(qctx->client, &name, soardatasetp, sigsoardatasetp,
+                      dbuf, DNS_SECTION_AUTHORITY);
+
+       if (WANTDNSSEC(qctx->client)) {
+               /*
+                * Add NODATA proof.
+                */
+               query_addrrset(qctx->client, &qctx->fname,
+                              &qctx->rdataset, &qctx->sigrdataset,
+                              NULL, DNS_SECTION_AUTHORITY);
+       }
+
+       result = ISC_R_SUCCESS;
+       inc_stats(qctx->client, ns_statscounter_nodatasynth);
+
+cleanup:
+       if (name != NULL) {
+               query_releasename(qctx->client, &name);
+       }
+       if (clone != NULL) {
+               query_putrdataset(qctx->client, &clone);
+       }
+       if (sigclone != NULL) {
+               query_putrdataset(qctx->client, &sigclone);
+       }
+       return (result);
+}
+
 /*
  * Synthesize a wildcard answer using the contents of 'rdataset'.
  * qctx contains the NODATA proof.
@@ -8405,7 +8486,7 @@ query_synthwildcard(query_ctx_t *qctx, dns_rdataset_t *rdataset,
 
        if (WANTDNSSEC(qctx->client)) {
                /*
-                * Add NODATA proof.
+                * Add NOQNAME proof.
                 */
                query_addrrset(qctx->client, &qctx->fname,
                               &qctx->rdataset, &qctx->sigrdataset,
@@ -8429,7 +8510,7 @@ cleanup:
 }
 
 /*
- * Add a synthesised CNAME record from the wildard RRset (rdataset)
+ * Add a synthesized CNAME record from the wildard RRset (rdataset)
  * and NODATA proof by calling query_synthwildcard then setup to
  * follow the CNAME.
  */
@@ -8487,7 +8568,7 @@ query_synthcnamewildcard(query_ctx_t *qctx, dns_rdataset_t *rdataset,
 }
 
 /*
- * Synthesise a NXDOMAIN response from qctx (which contains the
+ * Synthesize a NXDOMAIN response from qctx (which contains the
  * NODATA proof), nowild + rdataset + sigrdataset (which contains
  * the NOWILDCARD proof) and signer + soardatasetp + sigsoardatasetp
  * which contain the SOA record + RRSIG for the negative answer.
@@ -8553,7 +8634,7 @@ query_synthnxdomain(query_ctx_t *qctx,
 
        if (WANTDNSSEC(qctx->client)) {
                /*
-                * Add NODATA proof.
+                * Add NOQNAME proof.
                 */
                query_addrrset(qctx->client, &qctx->fname,
                               &qctx->rdataset, &qctx->sigrdataset,
@@ -8607,22 +8688,51 @@ cleanup:
        return (result);
 }
 
+/*
+ * Check that all signer names in sigrdataset match the expected signer.
+ */
+static isc_result_t
+checksignames(dns_name_t *signer, dns_rdataset_t *sigrdataset) {
+       isc_result_t result;
+
+       for (result = dns_rdataset_first(sigrdataset);
+            result == ISC_R_SUCCESS;
+            result = dns_rdataset_next(sigrdataset)) {
+               dns_rdata_t rdata = DNS_RDATA_INIT;
+               dns_rdata_rrsig_t rrsig;
+
+               dns_rdataset_current(sigrdataset, &rdata);
+               result = dns_rdata_tostruct(&rdata, &rrsig, NULL);
+               RUNTIME_CHECK(result == ISC_R_SUCCESS);
+               if (dns_name_countlabels(signer) == 0) {
+                       dns_name_copy(&rrsig.signer, signer, NULL);
+               } else if (!dns_name_equal(signer, &rrsig.signer)) {
+                       return (ISC_R_FAILURE);
+               }
+       }
+
+       return (ISC_R_SUCCESS);
+}
+
 /*%
  * Handle covering NSEC responses.
  *
- * Verify the NSEC record is apropriate for the QNAME, if not
+ * Verify the NSEC record is apropriate for the QNAME; if not,
  * redo the initial query without DNS_DBFIND_COVERINGNSEC.
  *
- * Compute the wildcard record and check if the wildcard name
- * exists or not.  If we can't determine this redo the initial
- * query without DNS_DBFIND_COVERINGNSEC.
+ * If the covering NSEC proves that the name exists but not the type,
+ * synthesize a NODATA response.
+ *
+ * If the name doesn't exist, compute the wildcard record and check whether
+ * the wildcard name exists or not.  If we can't determine this, redo the
+ * initial query without DNS_DBFIND_COVERINGNSEC.
  *
- * If the wildcard name does not exist compute the SOA name and look
- * that up.  If the SOA record does not exist redo the initial query
- * without DNS_DBFIND_COVERINGNSEC.  If the SOA record exists constructed
- * NXDOMAIN response from the found records.
+ * If the wildcard name does not exist, compute the SOA name and look that
+ * up.  If the SOA record does not exist, redo the initial query without
+ * DNS_DBFIND_COVERINGNSEC.  If the SOA record exists, synthesize an
+ * NXDOMAIN response from the found records.
  *
- * If the wildcard name does exist perform a lookup for the requested
+ * If the wildcard name does exist, perform a lookup for the requested
  * type at the wildcard name.
  */
 static isc_result_t
@@ -8673,21 +8783,10 @@ query_coveringnsec(query_ctx_t *qctx) {
        /*
         * All signer names must be the same to accept.
         */
-       for (result = dns_rdataset_first(qctx->sigrdataset);
-            result == ISC_R_SUCCESS;
-            result = dns_rdataset_next(qctx->sigrdataset))
-       {
-               dns_rdata_t rdata = DNS_RDATA_INIT;
-               dns_rdata_rrsig_t rrsig;
-
-               dns_rdataset_current(qctx->sigrdataset, &rdata);
-               result = dns_rdata_tostruct(&rdata, &rrsig, NULL);
-               RUNTIME_CHECK(result == ISC_R_SUCCESS);
-               if (dns_name_countlabels(signer) == 0) {
-                       dns_name_copy(&rrsig.signer, signer, NULL);
-               } else if (!dns_name_equal(signer, &rrsig.signer)) {
-                       goto cleanup;
-               }
+       result = checksignames(signer, qctx->sigrdataset);
+       if (result != ISC_R_SUCCESS) {
+               result = ISC_R_SUCCESS;
+               goto cleanup;
        }
 
        /*
@@ -8698,7 +8797,56 @@ query_coveringnsec(query_ctx_t *qctx) {
                                        &exists, &data, wild,
                                        log_noexistnodata, qctx);
 
-       if (result != ISC_R_SUCCESS || exists) {
+       if (result != ISC_R_SUCCESS || (exists && data)) {
+               goto cleanup;
+       }
+
+       if (exists) {
+               if (qctx->type == dns_rdatatype_any) {  /* XXX not yet */
+                       goto cleanup;
+               }
+#ifdef ALLOW_FILTER_AAAA
+               if (qctx->client->filter_aaaa != dns_aaaa_ok &&
+                   (qctx->type == dns_rdatatype_a ||
+                    qctx->type == dns_rdatatype_aaaa)) /* XXX not yet */
+               {
+                       goto cleanup;
+               }
+#endif
+               if (!ISC_LIST_EMPTY(qctx->client->view->dns64) &&
+                   (qctx->type == dns_rdatatype_a ||
+                    qctx->type == dns_rdatatype_aaaa)) /* XXX not yet */
+               {
+                       goto cleanup;
+               }
+               if (!qctx->resuming && !STALE(qctx->rdataset) &&
+                   qctx->rdataset->ttl == 0 && RECURSIONOK(qctx->client))
+               {
+                       goto cleanup;
+               }
+
+               soardataset = query_newrdataset(qctx->client);
+               sigsoardataset = query_newrdataset(qctx->client);
+               if (soardataset == NULL || sigsoardataset == NULL) {
+                       goto cleanup;
+               }
+
+               /*
+                * Look for SOA record to construct NODATA response.
+                */
+               dns_db_attach(qctx->db, &db);
+               result = dns_db_findext(db, signer, qctx->version,
+                                       dns_rdatatype_soa, dboptions,
+                                       qctx->client->now, &node,
+                                       fname, &cm, &ci, soardataset,
+                                       sigsoardataset);
+
+               if (result != ISC_R_SUCCESS) {
+                       goto cleanup;
+               }
+               (void)query_synthnodata(qctx, signer,
+                                       &soardataset, &sigsoardataset);
+               done = ISC_TRUE;
                goto cleanup;
        }
 
@@ -8768,8 +8916,7 @@ query_coveringnsec(query_ctx_t *qctx) {
                done = ISC_TRUE;
                goto cleanup;
        case DNS_R_CNAME:               /* wild card cname */
-               (void)query_synthcnamewildcard(qctx, &rdataset,
-                                                 &sigrdataset);
+               (void)query_synthcnamewildcard(qctx, &rdataset, &sigrdataset);
                done = ISC_TRUE;
                goto cleanup;
        case DNS_R_NCACHENXRRSET:       /* wild card nodata */
@@ -8789,26 +8936,19 @@ query_coveringnsec(query_ctx_t *qctx) {
        }
 
        /*
-        * All signer names must be the same to accept.
+        * Must be signed to accept.
         */
        if (!dns_rdataset_isassociated(&sigrdataset)) {
                goto cleanup;
        }
 
-       for (result = dns_rdataset_first(&sigrdataset);
-            result == ISC_R_SUCCESS;
-            result = dns_rdataset_next(&sigrdataset)) {
-               dns_rdata_t rdata = DNS_RDATA_INIT;
-               dns_rdata_rrsig_t rrsig;
-
-               dns_rdataset_current(&sigrdataset, &rdata);
-               result = dns_rdata_tostruct(&rdata, &rrsig, NULL);
-               RUNTIME_CHECK(result == ISC_R_SUCCESS);
-               if (dns_name_countlabels(signer) == 0) {
-                       dns_name_copy(&rrsig.signer, signer, NULL);
-               } else if (!dns_name_equal(signer, &rrsig.signer)) {
-                       goto cleanup;
-               }
+       /*
+        * Check signer signer names again.
+        */
+       result = checksignames(signer, &sigrdataset);
+       if (result != ISC_R_SUCCESS) {
+               result = ISC_R_SUCCESS;
+               goto cleanup;
        }
 
        if (node != NULL) {