]> git.ipfire.org Git - thirdparty/bind9.git/commitdiff
Make the dns_validator validations asynchronous and limit it
authorOndřej Surý <ondrej@isc.org>
Mon, 11 Dec 2023 15:50:12 +0000 (16:50 +0100)
committerMichał Kępień <michal@isc.org>
Thu, 1 Feb 2024 20:45:06 +0000 (21:45 +0100)
Instead of running all the cryptographic validation in a tight loop,
spread it out into multiple event loop "ticks", but moving every single
validation into own isc_async_run() asynchronous event.  Move the
cryptographic operations - both verification and DNSKEY selection - to
the offloaded threads (isc_work_enqueue), this further limits the time
we spend doing expensive operations on the event loops that should be
fast.

Limit the impact of invalid or malicious RRSets that contain crafted
records causing the dns_validator to do many validations per single
fetch by adding a cap on the maximum number of validations and maximum
number of validation failures that can happen before the resolving
fails.

bin/named/server.c
doc/arm/reference.rst
doc/misc/options
lib/dns/dst_api.c
lib/dns/include/dns/resolver.h
lib/dns/include/dns/validator.h
lib/dns/include/dst/dst.h
lib/dns/resolver.c
lib/dns/validator.c
lib/isc/loop.c
lib/isccfg/namedconf.c

index 80c5b5f1bb8a99c019b7d442d488f0f8dab774cb..67cafe633f70927dbd2487b755c53c475bbd956b 100644 (file)
@@ -5455,6 +5455,21 @@ configure_view(dns_view_t *view, dns_viewlist_t *viewlist, cfg_obj_t *config,
        INSIST(result == ISC_R_SUCCESS);
        dns_resolver_setmaxqueries(view->resolver, cfg_obj_asuint32(obj));
 
+       obj = NULL;
+       result = named_config_get(maps, "max-validations-per-fetch", &obj);
+       if (result == ISC_R_SUCCESS) {
+               dns_resolver_setmaxvalidations(view->resolver,
+                                              cfg_obj_asuint32(obj));
+       }
+
+       obj = NULL;
+       result = named_config_get(maps, "max-validation-failures-per-fetch",
+                                 &obj);
+       if (result == ISC_R_SUCCESS) {
+               dns_resolver_setmaxvalidationfails(view->resolver,
+                                                  cfg_obj_asuint32(obj));
+       }
+
        obj = NULL;
        result = named_config_get(maps, "fetches-per-zone", &obj);
        INSIST(result == ISC_R_SUCCESS);
index 8468a785eaf5c84bff30b90cffb660fe7825a1c9..6fb5937fa22ba8b7fb414ecb805b9523806c0495 100644 (file)
@@ -3691,6 +3691,21 @@ system.
    set to zero, :any:`max-clients-per-query` no longer applies and there is no
    upper bound, other than that imposed by :any:`recursive-clients`.
 
+.. namedconf:statement:: max-validations-per-fetch
+   :tags: server
+   :short: Set the maximum number of DNSSEC validations that can happen in single fetch
+
+   This is an **experimental** setting to set the maximum number of DNSSEC
+   validations that can happen in a single resolver fetch.  The default is 16.
+
+.. namedconf:statement:: max-validation-failures-per-fetch
+   :tags: server
+   :short: Set the maximum number of DNSSEC validation failures that can happen in single fetch
+
+   This is an **experimental** setting to set the maximum number of DNSSEC
+   validation failures that can happen in a single resolver fetch.  The default
+   is 1.
+
 .. namedconf:statement:: fetches-per-zone
    :tags: server, query
    :short: Sets the maximum number of simultaneous iterative queries allowed to any one domain before the server blocks new queries for data in or beneath that zone.
index ac5dd667946dcb17bc719d0984c8c1cf3caa0317..5fe4f32685b5ae5226f5abb804c526bf34e31677 100644 (file)
@@ -188,6 +188,8 @@ options {
        max-transfer-time-in <integer>;
        max-transfer-time-out <integer>;
        max-udp-size <integer>;
+       max-validation-failures-per-fetch <integer>; // experimental
+       max-validations-per-fetch <integer>; // experimental
        max-zone-ttl ( unlimited | <duration> ); // deprecated
        memstatistics <boolean>;
        memstatistics-file <quoted_string>;
@@ -469,6 +471,8 @@ view <string> [ <class> ] {
        max-transfer-time-in <integer>;
        max-transfer-time-out <integer>;
        max-udp-size <integer>;
+       max-validation-failures-per-fetch <integer>; // experimental
+       max-validations-per-fetch <integer>; // experimental
        max-zone-ttl ( unlimited | <duration> ); // deprecated
        message-compression <boolean>;
        min-cache-ttl <duration>;
index af53947ac6515b11fb5442137c56606e5b6206ff..ce41b99e5d85c1ae213114d4c1ccd58ddb0a8a93 100644 (file)
@@ -164,7 +164,8 @@ computeid(dst_key_t *key);
 static isc_result_t
 frombuffer(const dns_name_t *name, unsigned int alg, unsigned int flags,
           unsigned int protocol, dns_rdataclass_t rdclass,
-          isc_buffer_t *source, isc_mem_t *mctx, dst_key_t **keyp);
+          isc_buffer_t *source, isc_mem_t *mctx, bool no_rdata,
+          dst_key_t **keyp);
 
 static isc_result_t
 algorithm_status(unsigned int alg);
@@ -750,6 +751,13 @@ dst_key_todns(const dst_key_t *key, isc_buffer_t *target) {
 isc_result_t
 dst_key_fromdns(const dns_name_t *name, dns_rdataclass_t rdclass,
                isc_buffer_t *source, isc_mem_t *mctx, dst_key_t **keyp) {
+       return (dst_key_fromdns_ex(name, rdclass, source, mctx, false, keyp));
+}
+
+isc_result_t
+dst_key_fromdns_ex(const dns_name_t *name, dns_rdataclass_t rdclass,
+                  isc_buffer_t *source, isc_mem_t *mctx, bool no_rdata,
+                  dst_key_t **keyp) {
        uint8_t alg, proto;
        uint32_t flags, extflags;
        dst_key_t *key = NULL;
@@ -780,7 +788,7 @@ dst_key_fromdns(const dns_name_t *name, dns_rdataclass_t rdclass,
        }
 
        result = frombuffer(name, alg, flags, proto, rdclass, source, mctx,
-                           &key);
+                           no_rdata, &key);
        if (result != ISC_R_SUCCESS) {
                return (result);
        }
@@ -801,7 +809,7 @@ dst_key_frombuffer(const dns_name_t *name, unsigned int alg, unsigned int flags,
        REQUIRE(dst_initialized);
 
        result = frombuffer(name, alg, flags, protocol, rdclass, source, mctx,
-                           &key);
+                           false, &key);
        if (result != ISC_R_SUCCESS) {
                return (result);
        }
@@ -2302,7 +2310,8 @@ computeid(dst_key_t *key) {
 static isc_result_t
 frombuffer(const dns_name_t *name, unsigned int alg, unsigned int flags,
           unsigned int protocol, dns_rdataclass_t rdclass,
-          isc_buffer_t *source, isc_mem_t *mctx, dst_key_t **keyp) {
+          isc_buffer_t *source, isc_mem_t *mctx, bool no_rdata,
+          dst_key_t **keyp) {
        dst_key_t *key;
        isc_result_t ret;
 
@@ -2324,10 +2333,12 @@ frombuffer(const dns_name_t *name, unsigned int alg, unsigned int flags,
                        return (DST_R_UNSUPPORTEDALG);
                }
 
-               ret = key->func->fromdns(key, source);
-               if (ret != ISC_R_SUCCESS) {
-                       dst_key_free(&key);
-                       return (ret);
+               if (!no_rdata) {
+                       ret = key->func->fromdns(key, source);
+                       if (ret != ISC_R_SUCCESS) {
+                               dst_key_free(&key);
+                               return (ret);
+                       }
                }
        }
 
index e9258827e474df0762a7958d46770588b569391b..7f0dde65a55e8e492c783eab365c66c11971b1ff 100644 (file)
@@ -578,6 +578,14 @@ dns_resolver_printbadcache(dns_resolver_t *resolver, FILE *fp);
  * \li resolver to be valid.
  */
 
+void
+dns_resolver_setmaxvalidations(dns_resolver_t *resolver, uint32_t max);
+void
+dns_resolver_setmaxvalidationfails(dns_resolver_t *resolver, uint32_t max);
+/*%
+ * Set maximum numbers of validations and maximum validation failures per fetch.
+ */
+
 void
 dns_resolver_setmaxdepth(dns_resolver_t *resolver, unsigned int maxdepth);
 unsigned int
index d42cdca637659c7624cc7ea5f0e344cc0ccca243..0b0222c5c6d1b289a9bcf5ca2505ad8488c77945 100644 (file)
@@ -53,6 +53,7 @@
 #include <isc/refcount.h>
 
 #include <dns/fixedname.h>
+#include <dns/rdata.h>
 #include <dns/rdataset.h>
 #include <dns/rdatastruct.h> /* for dns_rdata_rrsig_t */
 #include <dns/types.h>
@@ -144,6 +145,13 @@ struct dns_validator {
        unsigned int  authcount;
        unsigned int  authfail;
        isc_stdtime_t start;
+
+       bool        digest_sha1;
+       bool        supported_algorithm;
+       dns_rdata_t rdata;
+       bool        resume;
+       uint32_t   *nvalidations;
+       uint32_t   *nfails;
 };
 
 /*%
@@ -161,6 +169,7 @@ dns_validator_create(dns_view_t *view, dns_name_t *name, dns_rdatatype_t type,
                     dns_rdataset_t *rdataset, dns_rdataset_t *sigrdataset,
                     dns_message_t *message, unsigned int options,
                     isc_loop_t *loop, isc_job_cb cb, void *arg,
+                    uint32_t *nvalidations, uint32_t *nfails,
                     dns_validator_t **validatorp);
 /*%<
  * Start a DNSSEC validation.
index c0912f3e67ad6c80674177d03704885b2f83c936..6c7a4e50db4104a113903c6a188f4e3e249514bb 100644 (file)
@@ -482,6 +482,10 @@ dst_key_tofile(const dst_key_t *key, int type, const char *directory);
  */
 
 isc_result_t
+dst_key_fromdns_ex(const dns_name_t *name, dns_rdataclass_t rdclass,
+                  isc_buffer_t *source, isc_mem_t *mctx, bool no_rdata,
+                  dst_key_t **keyp);
+isc_result_t
 dst_key_fromdns(const dns_name_t *name, dns_rdataclass_t rdclass,
                isc_buffer_t *source, isc_mem_t *mctx, dst_key_t **keyp);
 /*%<
index f0f48d990df45c45912de8246dfe7123bb568c0d..442dfabce0a561cd5fb66319bc83fdbb29173624 100644 (file)
  */
 #define MINIMUM_QUERY_TIMEOUT (MAX_SINGLE_QUERY_TIMEOUT + 1000U)
 
+/*
+ * The default maximum number of validations and validation failures per-fetch
+ */
+#ifndef DEFAULT_MAX_VALIDATIONS
+#define DEFAULT_MAX_VALIDATIONS 16
+#endif
+#ifndef DEFAULT_MAX_VALIDATION_FAILURES
+#define DEFAULT_MAX_VALIDATION_FAILURES 1
+#endif
+
 /* The default time in seconds for the whole query to live. */
 #ifndef DEFAULT_QUERY_TIMEOUT
 #define DEFAULT_QUERY_TIMEOUT MINIMUM_QUERY_TIMEOUT
@@ -457,6 +467,9 @@ struct fetchctx {
        dns_adbaddrinfo_t *addrinfo;
        unsigned int depth;
        char clientstr[ISC_SOCKADDR_FORMATSIZE];
+
+       uint32_t nvalidations;
+       uint32_t nfails;
 };
 
 #define FCTX_MAGIC      ISC_MAGIC('F', '!', '!', '!')
@@ -567,6 +580,9 @@ struct dns_resolver {
        atomic_bool exiting;
        atomic_bool priming;
 
+       atomic_uint_fast32_t maxvalidations;
+       atomic_uint_fast32_t maxvalidationfails;
+
        /* Locked by lock. */
        unsigned int spillat; /* clients-per-query */
 
@@ -961,7 +977,8 @@ valcreate(fetchctx_t *fctx, dns_message_t *message, dns_adbaddrinfo_t *addrinfo,
 
        result = dns_validator_create(
                fctx->res->view, name, type, rdataset, sigrdataset, message,
-               valoptions, fctx->loop, validated, valarg, &validator);
+               valoptions, fctx->loop, validated, valarg, &fctx->nvalidations,
+               &fctx->nfails, &validator);
        RUNTIME_CHECK(result == ISC_R_SUCCESS);
        inc_stats(fctx->res, dns_resstatscounter_val);
        if ((valoptions & DNS_VALIDATOR_DEFER) == 0) {
@@ -4518,6 +4535,8 @@ fctx_create(dns_resolver_t *res, isc_loop_t *loop, const dns_name_t *name,
                .fwdpolicy = dns_fwdpolicy_none,
                .result = ISC_R_FAILURE,
                .loop = loop,
+               .nvalidations = atomic_load_relaxed(&res->maxvalidations),
+               .nfails = atomic_load_relaxed(&res->maxvalidationfails),
        };
 
        isc_mem_attach(mctx, &fctx->mctx);
@@ -9960,6 +9979,8 @@ dns_resolver_create(dns_view_t *view, isc_loopmgr_t *loopmgr, isc_nm_t *nm,
                .maxqueries = DEFAULT_MAX_QUERIES,
                .alternates = ISC_LIST_INITIALIZER,
                .nloops = isc_loopmgr_nloops(loopmgr),
+               .maxvalidations = DEFAULT_MAX_VALIDATIONS,
+               .maxvalidationfails = DEFAULT_MAX_VALIDATION_FAILURES,
        };
 
        RTRACE("create");
@@ -10925,6 +10946,18 @@ dns_resolver_settimeout(dns_resolver_t *resolver, unsigned int timeout) {
        resolver->query_timeout = timeout;
 }
 
+void
+dns_resolver_setmaxvalidations(dns_resolver_t *resolver, uint32_t max) {
+       REQUIRE(VALID_RESOLVER(resolver));
+       atomic_store(&resolver->maxvalidations, max);
+}
+
+void
+dns_resolver_setmaxvalidationfails(dns_resolver_t *resolver, uint32_t max) {
+       REQUIRE(VALID_RESOLVER(resolver));
+       atomic_store(&resolver->maxvalidationfails, max);
+}
+
 void
 dns_resolver_setmaxdepth(dns_resolver_t *resolver, unsigned int maxdepth) {
        REQUIRE(VALID_RESOLVER(resolver));
index b679392c49ef838f739549e5abd20e55f7ad1e9f..9afd2ea11fca3aad861f4accba300ae29e32d3ae 100644 (file)
@@ -24,6 +24,7 @@
 #include <isc/string.h>
 #include <isc/tid.h>
 #include <isc/util.h>
+#include <isc/work.h>
 
 #include <dns/client.h>
 #include <dns/db.h>
@@ -55,7 +56,7 @@
  *     validator_start -> proveunsecure
  *
  * \li When called with no rdataset or sigrdataset:
- *     validator_start -> validate_nx-> proveunsecure
+ *     validator_start -> validate_nx -> proveunsecure
  *
  * validator_start:   determine what type of validation to do.
  * validate_answer:   attempt to perform a positive validation.
 #define VALIDATOR_MAGIC           ISC_MAGIC('V', 'a', 'l', '?')
 #define VALID_VALIDATOR(v) ISC_MAGIC_VALID(v, VALIDATOR_MAGIC)
 
-#define VALATTR_CANCELED 0x0002 /*%< Canceled. */
-#define VALATTR_TRIEDVERIFY                                    \
-       0x0004                    /*%< We have found a key and \
-                                  * have attempted a verify. */
-#define VALATTR_COMPLETE   0x0008 /*%< Completion event sent. */
-#define VALATTR_INSECURITY 0x0010 /*%< Attempting proveunsecure. */
-
-/*!
- * NSEC proofs to be looked for.
- */
-#define VALATTR_NEEDNOQNAME    0x00000100
-#define VALATTR_NEEDNOWILDCARD 0x00000200
-#define VALATTR_NEEDNODATA     0x00000400
+enum valattr {
+       VALATTR_CANCELED = 1 << 1,           /*%< Canceled. */
+       VALATTR_TRIEDVERIFY = 1 << 2,        /*%< We have found a key and have
+                                               attempted a verify. */
+       VALATTR_COMPLETE = 1 << 3,           /*%< Completion event sent. */
+       VALATTR_INSECURITY = 1 << 4,         /*%< Attempting proveunsecure. */
+       VALATTR_MAXVALIDATIONS = 1 << 5,     /*%< Max validations quota */
+       VALATTR_MAXVALIDATIONFAILS = 1 << 6, /*%< Max validation fails quota */
+
+       /*!
+        * NSEC proofs to be looked for.
+        */
+       VALATTR_NEEDNOQNAME = 1 << 8,
+       VALATTR_NEEDNOWILDCARD = 1 << 9,
+       VALATTR_NEEDNODATA = 1 << 10,
 
-/*!
- * NSEC proofs that have been found.
- */
-#define VALATTR_FOUNDNOQNAME   0x00001000
-#define VALATTR_FOUNDNOWILDCARD 0x00002000
-#define VALATTR_FOUNDNODATA    0x00004000
-#define VALATTR_FOUNDCLOSEST   0x00008000
-#define VALATTR_FOUNDOPTOUT    0x00010000
-#define VALATTR_FOUNDUNKNOWN   0x00020000
+       /*!
+        * NSEC proofs that have been found.
+        */
+       VALATTR_FOUNDNOQNAME = 1 << 12,
+       VALATTR_FOUNDNOWILDCARD = 1 << 13,
+       VALATTR_FOUNDNODATA = 1 << 14,
+       VALATTR_FOUNDCLOSEST = 1 << 15,
+       VALATTR_FOUNDOPTOUT = 1 << 16,
+       VALATTR_FOUNDUNKNOWN = 1 << 17,
+};
 
 #define NEEDNODATA(val)             ((val->attributes & VALATTR_NEEDNODATA) != 0)
 #define NEEDNOQNAME(val)     ((val->attributes & VALATTR_NEEDNOQNAME) != 0)
 #define NEGATIVE(r) (((r)->attributes & DNS_RDATASETATTR_NEGATIVE) != 0)
 #define NXDOMAIN(r) (((r)->attributes & DNS_RDATASETATTR_NXDOMAIN) != 0)
 
+#define MAXVALIDATIONS(r) (((r)->attributes & VALATTR_MAXVALIDATIONS) != 0)
+#define MAXVALIDATIONFAILS(r) \
+       (((r)->attributes & VALATTR_MAXVALIDATIONFAILS) != 0)
+
 static void
 destroy_validator(dns_validator_t *val);
 
 static isc_result_t
 select_signing_key(dns_validator_t *val, dns_rdataset_t *rdataset);
 
+static void
+resume_answer(void *arg);
+static void
+validate_async_done(dns_validator_t *val, isc_result_t result);
 static isc_result_t
-validate_answer(dns_validator_t *val, bool resume);
+validate_async_run(dns_validator_t *val, isc_job_cb cb);
 
-static isc_result_t
-validate_dnskey(dns_validator_t *val);
+static void
+validate_dnskey(void *arg);
+static void
+validate_dnskey_dsset_done(dns_validator_t *val, isc_result_t result);
 
 static isc_result_t
 validate_nx(dns_validator_t *val, bool resume);
@@ -204,18 +218,9 @@ marksecure(dns_validator_t *val) {
        val->secure = true;
 }
 
-static void
-validator_done_cb(void *arg) {
-       dns_validator_t *val = arg;
-       val->cb(val);
-       dns_validator_detach(&val);
-}
-
 /*
  * Validator 'val' is finished; send the completion event to the loop
  * that called dns_validator_create(), with result `result`.
- *
- * Caller must be holding the validator lock.
  */
 static void
 validator_done(dns_validator_t *val, isc_result_t result) {
@@ -226,8 +231,7 @@ validator_done(dns_validator_t *val, isc_result_t result) {
        val->attributes |= VALATTR_COMPLETE;
        val->result = result;
 
-       dns_validator_ref(val);
-       isc_async_run(val->loop, validator_done_cb, val);
+       isc_async_run(val->loop, val->cb, val);
 }
 
 /*%
@@ -349,6 +353,24 @@ trynsec3:
        return (found);
 }
 
+static void
+resume_answer_with_key(void *arg) {
+       dns_validator_t *val = arg;
+       dns_rdataset_t *rdataset = &val->frdataset;
+
+       isc_result_t result = select_signing_key(val, rdataset);
+       if (result == ISC_R_SUCCESS) {
+               val->keyset = &val->frdataset;
+       }
+}
+
+static void
+resume_answer_with_key_done(void *arg) {
+       dns_validator_t *val = arg;
+
+       resume_answer(val);
+}
+
 /*%
  * We have been asked to look for a key.
  * If found, resume the validation process.
@@ -361,8 +383,6 @@ fetch_callback_dnskey(void *arg) {
        dns_rdataset_t *rdataset = &val->frdataset;
        isc_result_t eresult = resp->result;
        isc_result_t result;
-       isc_result_t saved_result;
-       dns_fetch_t *fetch = NULL;
 
        INSIST(resp->type == FETCHDONE);
 
@@ -376,14 +396,18 @@ fetch_callback_dnskey(void *arg) {
        if (dns_rdataset_isassociated(&val->fsigrdataset)) {
                dns_rdataset_disassociate(&val->fsigrdataset);
        }
-       isc_mem_putanddetach(&resp->mctx, resp, sizeof(*resp));
 
        validator_log(val, ISC_LOG_DEBUG(3), "in fetch_callback_dnskey");
-       fetch = val->fetch;
-       val->fetch = NULL;
+       dns_resolver_destroyfetch(&val->fetch);
+
        if (CANCELED(val)) {
-               validator_done(val, ISC_R_CANCELED);
-       } else if (eresult == ISC_R_SUCCESS || eresult == DNS_R_NCACHENXRRSET) {
+               result = ISC_R_CANCELED;
+               goto cleanup;
+       }
+
+       switch (eresult) {
+       case ISC_R_SUCCESS:
+       case DNS_R_NCACHENXRRSET:
                /*
                 * We have an answer to our DNSKEY query.  Either the DNSKEY
                 * RRset or a NODATA response.
@@ -398,41 +422,23 @@ fetch_callback_dnskey(void *arg) {
                if (eresult == ISC_R_SUCCESS &&
                    rdataset->trust >= dns_trust_secure)
                {
-                       result = select_signing_key(val, rdataset);
-                       if (result == ISC_R_SUCCESS) {
-                               val->keyset = &val->frdataset;
-                       }
-               }
-               result = validate_answer(val, true);
-               if (result == DNS_R_NOVALIDSIG &&
-                   (val->attributes & VALATTR_TRIEDVERIFY) == 0)
-               {
-                       saved_result = result;
-                       validator_log(val, ISC_LOG_DEBUG(3),
-                                     "falling back to insecurity proof");
-                       result = proveunsecure(val, false, false);
-                       if (result == DNS_R_NOTINSECURE) {
-                               result = saved_result;
-                       }
-               }
-               if (result != DNS_R_WAIT) {
-                       validator_done(val, result);
+                       isc_work_enqueue(val->loop, resume_answer_with_key,
+                                        resume_answer, val);
+                       result = DNS_R_WAIT;
+               } else {
+                       result = validate_async_run(val, resume_answer);
                }
-       } else {
+               break;
+       default:
                validator_log(val, ISC_LOG_DEBUG(3),
                              "fetch_callback_dnskey: got %s",
                              isc_result_totext(eresult));
-               if (eresult == ISC_R_CANCELED) {
-                       validator_done(val, eresult);
-               } else {
-                       validator_done(val, DNS_R_BROKENCHAIN);
-               }
-       }
-
-       if (fetch != NULL) {
-               dns_resolver_destroyfetch(&fetch);
+               result = DNS_R_BROKENCHAIN;
        }
 
+cleanup:
+       isc_mem_putanddetach(&resp->mctx, resp, sizeof(*resp));
+       validate_async_done(val, result);
        dns_validator_detach(&val);
 }
 
@@ -447,7 +453,6 @@ fetch_callback_ds(void *arg) {
        dns_rdataset_t *rdataset = &val->frdataset;
        isc_result_t eresult = resp->result;
        isc_result_t result;
-       dns_fetch_t *fetch = NULL;
        bool trustchain;
 
        INSIST(resp->type == FETCHDONE);
@@ -470,28 +475,16 @@ fetch_callback_ds(void *arg) {
        }
 
        validator_log(val, ISC_LOG_DEBUG(3), "in fetch_callback_ds");
-       fetch = val->fetch;
-       val->fetch = NULL;
+       dns_resolver_destroyfetch(&val->fetch);
 
        if (CANCELED(val)) {
-               validator_done(val, ISC_R_CANCELED);
-               goto done;
+               result = ISC_R_CANCELED;
+               goto cleanup;
        }
 
-       switch (eresult) {
-       case DNS_R_NXDOMAIN:
-       case DNS_R_NCACHENXDOMAIN:
-               /*
-                * These results only make sense if we're attempting
-                * an insecurity proof, not when walking a chain of trust.
-                */
-               if (trustchain) {
-                       goto unexpected;
-               }
-
-               FALLTHROUGH;
-       case ISC_R_SUCCESS:
-               if (trustchain) {
+       if (trustchain) {
+               switch (eresult) {
+               case ISC_R_SUCCESS:
                        /*
                         * We looked for a DS record as part of
                         * following a key chain upwards; resume following
@@ -501,29 +494,12 @@ fetch_callback_ds(void *arg) {
                                      "dsset with trust %s",
                                      dns_trust_totext(rdataset->trust));
                        val->dsset = &val->frdataset;
-                       result = validate_dnskey(val);
-                       if (result != DNS_R_WAIT) {
-                               validator_done(val, result);
-                       }
-               } else {
-                       /*
-                        * There is a DS which may or may not be a zone cut.
-                        * In either case we are still in a secure zone,
-                        * so keep looking for the break in the chain
-                        * of trust.
-                        */
-                       result = proveunsecure(val, (eresult == ISC_R_SUCCESS),
-                                              true);
-                       if (result != DNS_R_WAIT) {
-                               validator_done(val, result);
-                       }
-               }
-               break;
-       case DNS_R_CNAME:
-       case DNS_R_NXRRSET:
-       case DNS_R_NCACHENXRRSET:
-       case DNS_R_SERVFAIL: /* RFC 1034 parent? */
-               if (trustchain) {
+                       result = validate_async_run(val, validate_dnskey);
+                       break;
+               case DNS_R_CNAME:
+               case DNS_R_NXRRSET:
+               case DNS_R_NCACHENXRRSET:
+               case DNS_R_SERVFAIL: /* RFC 1034 parent? */
                        /*
                         * Failed to find a DS while following the
                         * chain of trust; now we need to prove insecurity.
@@ -532,54 +508,69 @@ fetch_callback_ds(void *arg) {
                                      "falling back to insecurity proof (%s)",
                                      isc_result_totext(eresult));
                        result = proveunsecure(val, false, false);
-                       if (result != DNS_R_WAIT) {
-                               validator_done(val, result);
-                       }
-               } else if (eresult == DNS_R_SERVFAIL) {
-                       goto unexpected;
-               } else if (eresult != DNS_R_CNAME &&
-                          isdelegation(resp->foundname, &val->frdataset,
-                                       eresult))
-               {
+                       break;
+               default:
+                       validator_log(val, ISC_LOG_DEBUG(3),
+                                     "fetch_callback_ds: got %s",
+                                     isc_result_totext(eresult));
+                       result = DNS_R_BROKENCHAIN;
+                       break;
+               }
+       } else {
+               switch (eresult) {
+               case DNS_R_NXDOMAIN:
+               case DNS_R_NCACHENXDOMAIN:
                        /*
-                        * Failed to find a DS while trying to prove
-                        * insecurity. If this is a zone cut, that
-                        * means we're insecure.
+                        * These results only make sense if we're attempting
+                        * an insecurity proof, not when walking a chain of
+                        * trust.
                         */
-                       result = markanswer(val, "fetch_callback_ds",
-                                           "no DS and this is a delegation");
-                       validator_done(val, result);
-               } else {
+
+                       result = proveunsecure(val, false, true);
+                       break;
+               case ISC_R_SUCCESS:
+                       /*
+                        * There is a DS which may or may not be a zone cut.
+                        * In either case we are still in a secure zone,
+                        * so keep looking for the break in the chain
+                        * of trust.
+                        */
+                       result = proveunsecure(val, true, true);
+                       break;
+               case DNS_R_NXRRSET:
+               case DNS_R_NCACHENXRRSET:
+                       if (isdelegation(resp->foundname, &val->frdataset,
+                                        eresult))
+                       {
+                               /*
+                                * Failed to find a DS while trying to prove
+                                * insecurity. If this is a zone cut, that
+                                * means we're insecure.
+                                */
+                               result = markanswer(
+                                       val, "fetch_callback_ds",
+                                       "no DS and this is a delegation");
+                               break;
+                       }
+                       FALLTHROUGH;
+               case DNS_R_CNAME:
                        /*
                         * Not a zone cut, so we have to keep looking for
                         * the break point in the chain of trust.
                         */
                        result = proveunsecure(val, false, true);
-                       if (result != DNS_R_WAIT) {
-                               validator_done(val, result);
-                       }
-               }
-               break;
-
-       default:
-       unexpected:
-               validator_log(val, ISC_LOG_DEBUG(3),
-                             "fetch_callback_ds: got %s",
-                             isc_result_totext(eresult));
-               if (eresult == ISC_R_CANCELED) {
-                       validator_done(val, eresult);
-               } else {
-                       validator_done(val, DNS_R_BROKENCHAIN);
+                       break;
+               default:
+                       validator_log(val, ISC_LOG_DEBUG(3),
+                                     "fetch_callback_ds: got %s",
+                                     isc_result_totext(eresult));
+                       result = DNS_R_BROKENCHAIN;
                }
        }
-done:
 
+cleanup:
        isc_mem_putanddetach(&resp->mctx, resp, sizeof(*resp));
-
-       if (fetch != NULL) {
-               dns_resolver_destroyfetch(&fetch);
-       }
-
+       validate_async_done(val, result);
        dns_validator_detach(&val);
 }
 
@@ -592,52 +583,43 @@ static void
 validator_callback_dnskey(void *arg) {
        dns_validator_t *subvalidator = (dns_validator_t *)arg;
        dns_validator_t *val = subvalidator->parent;
-       isc_result_t result;
-       isc_result_t eresult = subvalidator->result;
-       isc_result_t saved_result;
+       isc_result_t result = subvalidator->result;
 
        val->subvalidator = NULL;
-       subvalidator->parent = NULL;
 
-       validator_log(val, ISC_LOG_DEBUG(3), "in validator_callback_dnskey");
        if (CANCELED(val)) {
-               validator_done(val, ISC_R_CANCELED);
-       } else if (eresult == ISC_R_SUCCESS) {
+               result = ISC_R_CANCELED;
+               goto cleanup;
+       }
+
+       validator_log(val, ISC_LOG_DEBUG(3), "in validator_callback_dnskey");
+       if (result == ISC_R_SUCCESS) {
                validator_log(val, ISC_LOG_DEBUG(3), "keyset with trust %s",
                              dns_trust_totext(val->frdataset.trust));
                /*
                 * Only extract the dst key if the keyset is secure.
                 */
                if (val->frdataset.trust >= dns_trust_secure) {
-                       (void)select_signing_key(val, &val->frdataset);
-               }
-               result = validate_answer(val, true);
-               if (result == DNS_R_NOVALIDSIG &&
-                   (val->attributes & VALATTR_TRIEDVERIFY) == 0)
-               {
-                       saved_result = result;
-                       validator_log(val, ISC_LOG_DEBUG(3),
-                                     "falling back to insecurity proof");
-                       result = proveunsecure(val, false, false);
-                       if (result == DNS_R_NOTINSECURE) {
-                               result = saved_result;
-                       }
-               }
-               if (result != DNS_R_WAIT) {
-                       validator_done(val, result);
+                       isc_work_enqueue(val->loop, resume_answer_with_key,
+                                        resume_answer_with_key_done, val);
+                       result = DNS_R_WAIT;
+               } else {
+                       result = validate_async_run(val, resume_answer);
                }
        } else {
-               if (eresult != DNS_R_BROKENCHAIN) {
+               if (result != DNS_R_BROKENCHAIN) {
                        expire_rdatasets(val);
                }
                validator_log(val, ISC_LOG_DEBUG(3),
                              "validator_callback_dnskey: got %s",
-                             isc_result_totext(eresult));
-               validator_done(val, DNS_R_BROKENCHAIN);
+                             isc_result_totext(result));
+               result = DNS_R_BROKENCHAIN;
        }
 
+cleanup:
+       dns_validator_detach(&subvalidator->parent);
        dns_validator_destroy(&subvalidator);
-       dns_validator_detach(&val);
+       validate_async_done(val, result);
 }
 
 /*%
@@ -653,12 +635,14 @@ validator_callback_ds(void *arg) {
        isc_result_t eresult = subvalidator->result;
 
        val->subvalidator = NULL;
-       subvalidator->parent = NULL;
 
-       validator_log(val, ISC_LOG_DEBUG(3), "in validator_callback_ds");
        if (CANCELED(val)) {
-               validator_done(val, ISC_R_CANCELED);
-       } else if (eresult == ISC_R_SUCCESS) {
+               result = ISC_R_CANCELED;
+               goto cleanup;
+       }
+
+       validator_log(val, ISC_LOG_DEBUG(3), "in validator_callback_ds");
+       if (eresult == ISC_R_SUCCESS) {
                bool have_dsset;
                dns_name_t *name;
                validator_log(val, ISC_LOG_DEBUG(3), "%s with trust %s",
@@ -678,10 +662,7 @@ validator_callback_ds(void *arg) {
                } else if ((val->attributes & VALATTR_INSECURITY) != 0) {
                        result = proveunsecure(val, have_dsset, true);
                } else {
-                       result = validate_dnskey(val);
-               }
-               if (result != DNS_R_WAIT) {
-                       validator_done(val, result);
+                       result = validate_async_run(val, validate_dnskey);
                }
        } else {
                if (eresult != DNS_R_BROKENCHAIN) {
@@ -690,11 +671,13 @@ validator_callback_ds(void *arg) {
                validator_log(val, ISC_LOG_DEBUG(3),
                              "validator_callback_ds: got %s",
                              isc_result_totext(eresult));
-               validator_done(val, DNS_R_BROKENCHAIN);
+               result = DNS_R_BROKENCHAIN;
        }
 
+cleanup:
+       dns_validator_detach(&subvalidator->parent);
        dns_validator_destroy(&subvalidator);
-       dns_validator_detach(&val);
+       validate_async_done(val, result);
 }
 
 /*%
@@ -713,16 +696,16 @@ validator_callback_cname(void *arg) {
 
        val->subvalidator = NULL;
 
-       validator_log(val, ISC_LOG_DEBUG(3), "in validator_callback_cname");
        if (CANCELED(val)) {
-               validator_done(val, ISC_R_CANCELED);
-       } else if (eresult == ISC_R_SUCCESS) {
+               result = ISC_R_CANCELED;
+               goto cleanup;
+       }
+
+       validator_log(val, ISC_LOG_DEBUG(3), "in validator_callback_cname");
+       if (eresult == ISC_R_SUCCESS) {
                validator_log(val, ISC_LOG_DEBUG(3), "cname with trust %s",
                              dns_trust_totext(val->frdataset.trust));
                result = proveunsecure(val, false, true);
-               if (result != DNS_R_WAIT) {
-                       validator_done(val, result);
-               }
        } else {
                if (eresult != DNS_R_BROKENCHAIN) {
                        expire_rdatasets(val);
@@ -730,11 +713,13 @@ validator_callback_cname(void *arg) {
                validator_log(val, ISC_LOG_DEBUG(3),
                              "validator_callback_cname: got %s",
                              isc_result_totext(eresult));
-               validator_done(val, DNS_R_BROKENCHAIN);
+               result = DNS_R_BROKENCHAIN;
        }
 
+cleanup:
+       dns_validator_detach(&subvalidator->parent);
        dns_validator_destroy(&subvalidator);
-       dns_validator_detach(&val);
+       validate_async_done(val, result);
 }
 
 /*%
@@ -749,30 +734,19 @@ validator_callback_nsec(void *arg) {
        dns_validator_t *subvalidator = (dns_validator_t *)arg;
        dns_validator_t *val = subvalidator->parent;
        dns_rdataset_t *rdataset = subvalidator->rdataset;
-       isc_result_t result = subvalidator->result;
+       isc_result_t result;
+       isc_result_t eresult = subvalidator->result;
        bool exists, data;
 
        val->subvalidator = NULL;
 
-       validator_log(val, ISC_LOG_DEBUG(3), "in validator_callback_nsec");
        if (CANCELED(val)) {
-               validator_done(val, ISC_R_CANCELED);
-       } else if (result != ISC_R_SUCCESS) {
-               validator_log(val, ISC_LOG_DEBUG(3),
-                             "validator_callback_nsec: got %s",
-                             isc_result_totext(result));
-               if (result == DNS_R_BROKENCHAIN) {
-                       val->authfail++;
-               }
-               if (result == ISC_R_CANCELED) {
-                       validator_done(val, result);
-               } else {
-                       result = validate_nx(val, true);
-                       if (result != DNS_R_WAIT) {
-                               validator_done(val, result);
-                       }
-               }
-       } else {
+               result = ISC_R_CANCELED;
+               goto cleanup;
+       }
+
+       validator_log(val, ISC_LOG_DEBUG(3), "in validator_callback_nsec");
+       if (eresult == ISC_R_SUCCESS) {
                dns_name_t **proofs = val->proofs;
                dns_name_t *wild = dns_fixedname_name(&val->wild);
 
@@ -824,13 +798,27 @@ validator_callback_nsec(void *arg) {
                }
 
                result = validate_nx(val, true);
-               if (result != DNS_R_WAIT) {
-                       validator_done(val, result);
+       } else {
+               validator_log(val, ISC_LOG_DEBUG(3),
+                             "validator_callback_nsec: got %s",
+                             isc_result_totext(eresult));
+               switch (eresult) {
+               case ISC_R_CANCELED:
+               case ISC_R_SHUTTINGDOWN:
+                       result = eresult;
+                       break;
+               case DNS_R_BROKENCHAIN:
+                       val->authfail++;
+                       FALLTHROUGH;
+               default:
+                       result = validate_nx(val, true);
                }
        }
 
+cleanup:
+       dns_validator_detach(&subvalidator->parent);
        dns_validator_destroy(&subvalidator);
-       dns_validator_detach(&val);
+       validate_async_done(val, result);
 }
 
 /*%
@@ -949,7 +937,6 @@ create_fetch(dns_validator_t *val, dns_name_t *name, dns_rdatatype_t type,
        validator_logcreate(val, name, type, caller, "fetch");
 
        dns_validator_ref(val);
-
        result = dns_resolver_createfetch(
                val->view->resolver, name, type, NULL, NULL, NULL, NULL, 0,
                fopts, 0, NULL, val->loop, callback, val, &val->frdataset,
@@ -987,9 +974,9 @@ create_validator(dns_validator_t *val, dns_name_t *name, dns_rdatatype_t type,
                  (DNS_VALIDATOR_NOCDFLAG | DNS_VALIDATOR_NONTA));
 
        validator_logcreate(val, name, type, caller, "validator");
-       result = dns_validator_create(val->view, name, type, rdataset, sig,
-                                     NULL, vopts, val->loop, cb, val,
-                                     &val->subvalidator);
+       result = dns_validator_create(
+               val->view, name, type, rdataset, sig, NULL, vopts, val->loop,
+               cb, val, val->nvalidations, val->nfails, &val->subvalidator);
        if (result == ISC_R_SUCCESS) {
                dns_validator_attach(val, &val->subvalidator->parent);
                val->subvalidator->depth = val->depth + 1;
@@ -1016,59 +1003,59 @@ select_signing_key(dns_validator_t *val, dns_rdataset_t *rdataset) {
        isc_buffer_t b;
        dns_rdata_t rdata = DNS_RDATA_INIT;
        dst_key_t *oldkey = val->key;
-       bool foundold;
+       bool no_rdata = false;
 
        if (oldkey == NULL) {
-               foundold = true;
+               result = dns_rdataset_first(rdataset);
        } else {
-               foundold = false;
+               dst_key_free(&oldkey);
                val->key = NULL;
+               result = dns_rdataset_next(rdataset);
        }
-
-       result = dns_rdataset_first(rdataset);
        if (result != ISC_R_SUCCESS) {
-               goto failure;
+               goto done;
        }
+
        do {
                dns_rdataset_current(rdataset, &rdata);
 
                isc_buffer_init(&b, rdata.data, rdata.length);
                isc_buffer_add(&b, rdata.length);
                INSIST(val->key == NULL);
-               result = dst_key_fromdns(&siginfo->signer, rdata.rdclass, &b,
-                                        val->view->mctx, &val->key);
+               result = dst_key_fromdns_ex(&siginfo->signer, rdata.rdclass, &b,
+                                           val->view->mctx, no_rdata,
+                                           &val->key);
                if (result == ISC_R_SUCCESS) {
                        if (siginfo->algorithm ==
                                    (dns_secalg_t)dst_key_alg(val->key) &&
                            siginfo->keyid ==
                                    (dns_keytag_t)dst_key_id(val->key) &&
+                           (dst_key_flags(val->key) & DNS_KEYFLAG_REVOKE) ==
+                                   0 &&
                            dst_key_iszonekey(val->key))
                        {
-                               if (foundold) {
-                                       /*
-                                        * This is the key we're looking for.
-                                        */
-                                       return (ISC_R_SUCCESS);
-                               } else if (dst_key_compare(oldkey, val->key)) {
-                                       foundold = true;
-                                       dst_key_free(&oldkey);
+                               if (no_rdata) {
+                                       /* Retry with full key */
+                                       dns_rdata_reset(&rdata);
+                                       dst_key_free(&val->key);
+                                       no_rdata = false;
+                                       continue;
                                }
+                               /* This is the key we're looking for. */
+                               goto done;
                        }
                        dst_key_free(&val->key);
                }
                dns_rdata_reset(&rdata);
                result = dns_rdataset_next(rdataset);
+               no_rdata = true;
        } while (result == ISC_R_SUCCESS);
 
+done:
        if (result == ISC_R_NOMORE) {
                result = ISC_R_NOTFOUND;
        }
 
-failure:
-       if (oldkey != NULL) {
-               dst_key_free(&oldkey);
-       }
-
        return (result);
 }
 
@@ -1181,15 +1168,22 @@ seek_dnskey(dns_validator_t *val) {
                        validator_log(val, ISC_LOG_DEBUG(3),
                                      "keyset with trust %s",
                                      dns_trust_totext(val->frdataset.trust));
-                       result = select_signing_key(val, val->keyset);
-                       if (result != ISC_R_SUCCESS) {
-                               /*
-                                * Either the key we're looking for is not
-                                * in the rrset, or something bad happened.
-                                * Give up.
-                                */
-                               result = DNS_R_CONTINUE;
+
+                       /*
+                        * Cleanup before passing control to the offload thread
+                        */
+                       if (dns_rdataset_isassociated(&val->frdataset) &&
+                           val->keyset != &val->frdataset)
+                       {
+                               dns_rdataset_disassociate(&val->frdataset);
+                       }
+                       if (dns_rdataset_isassociated(&val->fsigrdataset)) {
+                               dns_rdataset_disassociate(&val->fsigrdataset);
                        }
+
+                       isc_work_enqueue(val->loop, resume_answer_with_key,
+                                        resume_answer_with_key_done, val);
+                       return (DNS_R_WAIT);
                }
                break;
 
@@ -1246,20 +1240,47 @@ compute_keytag(dns_rdata_t *rdata) {
        return (dst_region_computeid(&r));
 }
 
+static bool
+over_max_validations(dns_validator_t *val) {
+       if (val->nvalidations == NULL) {
+               return (false);
+       }
+       if (*val->nvalidations > 0) {
+               (*val->nvalidations)--;
+               return (false);
+       }
+
+       val->attributes |= VALATTR_MAXVALIDATIONS;
+       return (true);
+}
+
+static bool
+over_max_fails(dns_validator_t *val) {
+       if (val->nfails == NULL) {
+               return (false);
+       }
+       if (*val->nfails > 0) {
+               (*val->nfails)--;
+               return (false);
+       }
+
+       val->attributes |= VALATTR_MAXVALIDATIONFAILS;
+       return (true);
+}
+
 /*%
  * Is the DNSKEY rrset in val->rdataset self-signed?
  */
-static bool
+static isc_result_t
 selfsigned_dnskey(dns_validator_t *val) {
        dns_rdataset_t *rdataset = val->rdataset;
        dns_rdataset_t *sigrdataset = val->sigrdataset;
        dns_name_t *name = val->name;
        isc_result_t result;
        isc_mem_t *mctx = val->view->mctx;
-       bool answer = false;
 
        if (rdataset->type != dns_rdatatype_dnskey) {
-               return (false);
+               return (DNS_R_NOKEYMATCH);
        }
 
        for (result = dns_rdataset_first(rdataset); result == ISC_R_SUCCESS;
@@ -1301,8 +1322,7 @@ selfsigned_dnskey(dns_validator_t *val) {
                         * This will be verified later.
                         */
                        if ((key.flags & DNS_KEYFLAG_REVOKE) == 0) {
-                               answer = true;
-                               continue;
+                               return (ISC_R_SUCCESS);
                        }
 
                        result = dns_dnssec_keyfromrdata(name, &keyrdata, mctx,
@@ -1318,6 +1338,10 @@ selfsigned_dnskey(dns_validator_t *val) {
                        if (DNS_TRUST_PENDING(rdataset->trust) &&
                            dns_view_istrusted(val->view, name, &key))
                        {
+                               if (over_max_validations(val)) {
+                                       dst_key_free(&dstkey);
+                                       return (ISC_R_QUOTA);
+                               }
                                result = dns_dnssec_verify(
                                        name, rdataset, dstkey, true,
                                        val->view->maxbits, mctx, &sigrdata,
@@ -1329,6 +1353,9 @@ selfsigned_dnskey(dns_validator_t *val) {
                                         * good.
                                         */
                                        dns_view_untrust(val->view, name, &key);
+                               } else if (over_max_fails(val)) {
+                                       dst_key_free(&dstkey);
+                                       return (ISC_R_QUOTA);
                                }
                        } else if (rdataset->trust >= dns_trust_secure) {
                                /*
@@ -1342,7 +1369,7 @@ selfsigned_dnskey(dns_validator_t *val) {
                }
        }
 
-       return (answer);
+       return (DNS_R_NOKEYMATCH);
 }
 
 /*%
@@ -1365,6 +1392,9 @@ verify(dns_validator_t *val, dst_key_t *key, dns_rdata_t *rdata,
        val->attributes |= VALATTR_TRIEDVERIFY;
        wild = dns_fixedname_initname(&fixed);
 again:
+       if (over_max_validations(val)) {
+               return (ISC_R_QUOTA);
+       }
        result = dns_dnssec_verify(val->name, val->rdataset, key, ignore,
                                   val->view->maxbits, val->view->mctx, rdata,
                                   wild);
@@ -1408,6 +1438,10 @@ again:
                }
                result = ISC_R_SUCCESS;
        }
+
+       if (result != ISC_R_SUCCESS && over_max_fails(val)) {
+               result = ISC_R_QUOTA;
+       }
        return (result);
 }
 
@@ -1420,132 +1454,323 @@ again:
  *                     for an event.
  * \li Other return codes are possible and all indicate failure.
  */
-static isc_result_t
-validate_answer(dns_validator_t *val, bool resume) {
-       isc_result_t result, vresult = DNS_R_NOVALIDSIG;
-       dns_rdata_t rdata = DNS_RDATA_INIT;
+
+static void
+validate_answer_iter_next(void *arg);
+static void
+validate_answer_process(void *arg);
+static void
+validate_answer_iter_done(dns_validator_t *val, isc_result_t result);
+
+static void
+validate_answer_iter_start(dns_validator_t *val) {
+       isc_result_t result = ISC_R_SUCCESS;
 
        /*
         * Caller must be holding the validator lock.
         */
 
-       if (resume) {
-               /*
-                * We already have a sigrdataset.
-                */
+       if (CANCELED(val)) {
+               result = ISC_R_CANCELED;
+               goto cleanup;
+       }
+
+       if (val->resume) {
+               /* We already have a sigrdataset. */
                result = ISC_R_SUCCESS;
                validator_log(val, ISC_LOG_DEBUG(3), "resuming validate");
        } else {
                result = dns_rdataset_first(val->sigrdataset);
        }
 
-       for (; result == ISC_R_SUCCESS;
-            result = dns_rdataset_next(val->sigrdataset))
-       {
-               dns_rdata_reset(&rdata);
-               dns_rdataset_current(val->sigrdataset, &rdata);
-               if (val->siginfo == NULL) {
-                       val->siginfo = isc_mem_get(val->view->mctx,
-                                                  sizeof(*val->siginfo));
-               }
-               result = dns_rdata_tostruct(&rdata, val->siginfo, NULL);
-               if (result != ISC_R_SUCCESS) {
-                       return (result);
-               }
+cleanup:
+       if (result != ISC_R_SUCCESS) {
+               validate_answer_iter_done(val, result);
+               return;
+       }
 
-               /*
-                * At this point we could check that the signature algorithm
-                * was known and "sufficiently good".
-                */
-               if (!dns_resolver_algorithm_supported(val->view->resolver,
-                                                     val->name,
-                                                     val->siginfo->algorithm))
-               {
-                       resume = false;
-                       continue;
-               }
+       result = validate_async_run(val, validate_answer_process);
+       INSIST(result == DNS_R_WAIT);
+}
 
-               if (!resume) {
-                       result = seek_dnskey(val);
-                       if (result == DNS_R_CONTINUE) {
-                               continue; /* Try the next SIG RR. */
-                       }
-                       if (result != ISC_R_SUCCESS) {
-                               return (result);
-                       }
-               }
+static void
+validate_answer_iter_next(void *arg) {
+       dns_validator_t *val = arg;
+       isc_result_t result;
 
-               /*
-                * There isn't a secure DNSKEY for this signature so move
-                * onto the next RRSIG.
-                */
-               if (val->key == NULL) {
-                       resume = false;
-                       continue;
+       if (CANCELED(val)) {
+               result = ISC_R_CANCELED;
+               goto cleanup;
+       }
+
+       val->resume = false;
+       result = dns_rdataset_next(val->sigrdataset);
+
+cleanup:
+       if (result != ISC_R_SUCCESS) {
+               validate_answer_iter_done(val, result);
+               return;
+       }
+
+       (void)validate_async_run(val, validate_answer_process);
+}
+
+static void
+validate_answer_finish(void *arg);
+
+static void
+validate_answer_signing_key(void *arg) {
+       dns_validator_t *val = arg;
+       isc_result_t result = ISC_R_NOTFOUND;
+
+       if (CANCELED(val)) {
+               val->result = ISC_R_CANCELED;
+       } else {
+               val->result = verify(val, val->key, &val->rdata,
+                                    val->siginfo->keyid);
+       }
+
+       switch (val->result) {
+       case ISC_R_CANCELED:     /* Validation was canceled */
+       case ISC_R_SHUTTINGDOWN: /* Server shutting down */
+       case ISC_R_QUOTA:        /* Validation fails quota reached */
+       case ISC_R_SUCCESS: /* We found our valid signature, we are done! */
+               if (val->key != NULL) {
+                       dst_key_free(&val->key);
+                       val->key = NULL;
                }
 
-               do {
-                       isc_result_t tresult;
-                       vresult = verify(val, val->key, &rdata,
-                                        val->siginfo->keyid);
-                       if (vresult == ISC_R_SUCCESS) {
-                               break;
-                       }
+               break;
+       default:
+               /* Select next signing key */
+               result = select_signing_key(val, val->keyset);
+               break;
+       }
 
-                       tresult = select_signing_key(val, val->keyset);
-                       if (tresult != ISC_R_SUCCESS) {
-                               break;
-                       }
-               } while (1);
-               if (vresult != ISC_R_SUCCESS) {
-                       validator_log(val, ISC_LOG_DEBUG(3),
-                                     "failed to verify rdataset");
-               } else {
-                       dns_rdataset_trimttl(val->rdataset, val->sigrdataset,
-                                            val->siginfo, val->start,
-                                            val->view->acceptexpired);
+       if (result == ISC_R_SUCCESS) {
+               INSIST(val->key != NULL);
+       } else {
+               INSIST(val->key == NULL);
+       }
+}
+
+static void
+validate_answer_signing_key_done(void *arg) {
+       dns_validator_t *val = arg;
+
+       if (CANCELED(val)) {
+               val->result = ISC_R_CANCELED;
+       } else if (val->key != NULL) {
+               /* Process with next key if we selected one */
+               isc_work_enqueue(val->loop, validate_answer_signing_key,
+                                validate_answer_signing_key_done, val);
+               return;
+       }
+
+       validate_answer_finish(val);
+}
+
+static void
+validate_answer_process(void *arg) {
+       dns_validator_t *val = arg;
+       isc_result_t result;
+
+       if (CANCELED(val)) {
+               result = ISC_R_CANCELED;
+               goto cleanup;
+       }
+
+       dns_rdata_reset(&val->rdata);
+
+       dns_rdataset_current(val->sigrdataset, &val->rdata);
+       if (val->siginfo == NULL) {
+               val->siginfo = isc_mem_get(val->view->mctx,
+                                          sizeof(*val->siginfo));
+       }
+       result = dns_rdata_tostruct(&val->rdata, val->siginfo, NULL);
+       if (result != ISC_R_SUCCESS) {
+               goto cleanup;
+       }
+
+       /*
+        * At this point we could check that the signature algorithm
+        * was known and "sufficiently good".
+        */
+       if (!dns_resolver_algorithm_supported(val->view->resolver, val->name,
+                                             val->siginfo->algorithm))
+       {
+               goto next_key;
+       }
+
+       if (!val->resume) {
+               result = seek_dnskey(val);
+               switch (result) {
+               case ISC_R_SUCCESS:
+                       break;
+               case DNS_R_CONTINUE:
+                       goto next_key;
+               case DNS_R_WAIT:
+                       goto cleanup;
+               default:
+                       goto cleanup;
                }
+       }
+
+       /*
+        * There isn't a secure DNSKEY for this signature so move
+        * onto the next RRSIG.
+        */
+       if (val->key == NULL) {
+               val->resume = false;
+               goto next_key;
+       }
+
+       isc_work_enqueue(val->loop, validate_answer_signing_key,
+                        validate_answer_signing_key_done, val);
+       return;
+
+next_key:
+       result = validate_async_run(val, validate_answer_iter_next);
+       goto cleanup;
+
+cleanup:
+       validate_async_done(val, result);
+}
+
+static void
+validate_answer_finish(void *arg) {
+       dns_validator_t *val = arg;
+       isc_result_t result = ISC_R_UNSET;
+
+       if (val->result != ISC_R_SUCCESS) {
+               validator_log(val, ISC_LOG_DEBUG(3),
+                             "failed to verify rdataset: %s",
+                             isc_result_totext(val->result));
+       } else {
+               dns_rdataset_trimttl(val->rdataset, val->sigrdataset,
+                                    val->siginfo, val->start,
+                                    val->view->acceptexpired);
+       }
 
-               if (val->key != NULL) {
-                       dst_key_free(&val->key);
-               }
-               if (val->keyset != NULL) {
-                       dns_rdataset_disassociate(val->keyset);
-                       val->keyset = NULL;
-               }
+       if (val->key != NULL) {
+               dst_key_free(&val->key);
                val->key = NULL;
-               if (NEEDNOQNAME(val)) {
-                       if (val->message == NULL) {
-                               validator_log(val, ISC_LOG_DEBUG(3),
-                                             "no message available "
-                                             "for noqname proof");
-                               return (DNS_R_NOVALIDSIG);
-                       }
+       }
+       if (val->keyset != NULL) {
+               dns_rdataset_disassociate(val->keyset);
+               val->keyset = NULL;
+       }
+
+       switch (val->result) {
+       case ISC_R_CANCELED:
+               /* The validation was canceled */
+               validator_log(val, ISC_LOG_DEBUG(3), "validation was canceled");
+               validate_async_done(val, val->result);
+               return;
+       case ISC_R_SHUTTINGDOWN:
+               validator_log(val, ISC_LOG_DEBUG(3), "server is shutting down");
+               validate_async_done(val, val->result);
+               return;
+       case ISC_R_QUOTA:
+               if (MAXVALIDATIONS(val)) {
                        validator_log(val, ISC_LOG_DEBUG(3),
-                                     "looking for noqname proof");
-                       return (validate_nx(val, false));
-               } else if (vresult == ISC_R_SUCCESS) {
-                       marksecure(val);
+                                     "maximum number of validations exceeded");
+               } else if (MAXVALIDATIONFAILS(val)) {
                        validator_log(val, ISC_LOG_DEBUG(3),
-                                     "marking as secure, "
-                                     "noqname proof not needed");
-                       return (ISC_R_SUCCESS);
+                                     "maximum number of validation failures "
+                                     "exceeded");
                } else {
+                       validator_log(
+                               val, ISC_LOG_DEBUG(3),
+                               "unknown error: validation quota exceeded");
+               }
+               validate_async_done(val, val->result);
+               return;
+       default:
+               break;
+       }
+
+       if (NEEDNOQNAME(val)) {
+               if (val->message == NULL) {
                        validator_log(val, ISC_LOG_DEBUG(3),
-                                     "verify failure: %s",
-                                     isc_result_totext(result));
-                       resume = false;
+                                     "no message available for noqname proof");
+                       validate_async_done(val, DNS_R_NOVALIDSIG);
+                       return;
                }
+
+               validator_log(val, ISC_LOG_DEBUG(3),
+                             "looking for noqname proof");
+               result = validate_nx(val, false);
+               validate_async_done(val, result);
+               return;
+       }
+
+       if (val->result == ISC_R_SUCCESS) {
+               marksecure(val);
+               validator_log(val, ISC_LOG_DEBUG(3),
+                             "marking as secure, noqname proof not needed");
+               validate_async_done(val, val->result);
+               return;
        }
+
+       validator_log(val, ISC_LOG_DEBUG(3), "verify failure: %s",
+                     isc_result_totext(val->result));
+       (void)validate_async_run(val, validate_answer_iter_next);
+}
+
+static void
+validate_answer_iter_done(dns_validator_t *val, isc_result_t result) {
        if (result != ISC_R_NOMORE) {
                validator_log(val, ISC_LOG_DEBUG(3),
                              "failed to iterate signatures: %s",
                              isc_result_totext(result));
-               return (result);
+               validate_async_done(val, result);
+               return;
        }
 
        validator_log(val, ISC_LOG_INFO, "no valid signature found");
-       return (vresult);
+       validate_async_done(val, val->result);
+}
+
+static void
+resume_answer(void *arg) {
+       dns_validator_t *val = arg;
+       val->resume = true;
+       validate_answer_iter_start(val);
+}
+
+static void
+validate_answer(void *arg) {
+       dns_validator_t *val = arg;
+       val->resume = false;
+       validate_answer_iter_start(val);
+}
+
+static isc_result_t
+validate_async_run(dns_validator_t *val, isc_job_cb cb) {
+       isc_async_run(val->loop, cb, val);
+       return (DNS_R_WAIT);
+}
+
+static void
+validate_async_done(dns_validator_t *val, isc_result_t result) {
+       if (result == DNS_R_NOVALIDSIG &&
+           (val->attributes & VALATTR_TRIEDVERIFY) == 0)
+       {
+               isc_result_t saved_result = result;
+               validator_log(val, ISC_LOG_DEBUG(3),
+                             "falling back to insecurity proof");
+               result = proveunsecure(val, false, false);
+               if (result == DNS_R_NOTINSECURE) {
+                       result = saved_result;
+               }
+       }
+
+       if (result != DNS_R_WAIT) {
+               /* We are still continuing */
+               validator_done(val, result);
+               dns_validator_detach(&val);
+       }
 }
 
 /*%
@@ -1582,7 +1807,7 @@ check_signer(dns_validator_t *val, dns_rdata_t *keyrdata, uint16_t keyid,
                        }
                }
                result = verify(val, dstkey, &rdata, sig.keyid);
-               if (result == ISC_R_SUCCESS) {
+               if (result == ISC_R_SUCCESS || result == ISC_R_QUOTA) {
                        break;
                }
        }
@@ -1683,27 +1908,163 @@ get_dsset(dns_validator_t *val, dns_name_t *tname, isc_result_t *resp) {
        return (DNS_R_CONTINUE);
 }
 
-/*%
- * Attempts positive response validation of an RRset containing zone keys
- * (i.e. a DNSKEY rrset).
- *
- * Caller must be holding the validator lock.
- *
- * Returns:
- * \li ISC_R_SUCCESS   Validation completed successfully
- * \li DNS_R_WAIT      Validation has started but is waiting
- *                     for an event.
- * \li Other return codes are possible and all indicate failure.
- */
+static void
+validate_dnskey_dsset_done(dns_validator_t *val, isc_result_t result) {
+       if (result == ISC_R_SUCCESS) {
+               marksecure(val);
+               validator_log(val, ISC_LOG_DEBUG(3), "marking as secure (DS)");
+       } else if (result == ISC_R_NOMORE && !val->supported_algorithm) {
+               validator_log(val, ISC_LOG_DEBUG(3),
+                             "no supported algorithm/digest (DS)");
+               result = markanswer(val, "validate_dnskey (3)",
+                                   "no supported algorithm/digest (DS)");
+       } else {
+               validator_log(val, ISC_LOG_INFO,
+                             "no valid signature found (DS)");
+               result = DNS_R_NOVALIDSIG;
+       }
+
+       if (val->dsset == &val->fdsset) {
+               val->dsset = NULL;
+               dns_rdataset_disassociate(&val->fdsset);
+       }
+
+       validate_async_done(val, result);
+}
+
 static isc_result_t
-validate_dnskey(dns_validator_t *val) {
-       isc_result_t result;
+validate_dnskey_dsset(dns_validator_t *val) {
        dns_rdata_t dsrdata = DNS_RDATA_INIT;
        dns_rdata_t keyrdata = DNS_RDATA_INIT;
+       isc_result_t result;
+       dns_rdata_ds_t ds;
+
+       dns_rdata_reset(&dsrdata);
+       dns_rdataset_current(val->dsset, &dsrdata);
+       result = dns_rdata_tostruct(&dsrdata, &ds, NULL);
+       RUNTIME_CHECK(result == ISC_R_SUCCESS);
+
+       if (ds.digest_type == DNS_DSDIGEST_SHA1 && val->digest_sha1 == false) {
+               return (DNS_R_BADALG);
+       }
+
+       if (!dns_resolver_ds_digest_supported(val->view->resolver, val->name,
+                                             ds.digest_type))
+       {
+               return (DNS_R_BADALG);
+       }
+
+       if (!dns_resolver_algorithm_supported(val->view->resolver, val->name,
+                                             ds.algorithm))
+       {
+               return (DNS_R_BADALG);
+       }
+
+       val->supported_algorithm = true;
+
+       /*
+        * Find the DNSKEY matching the DS...
+        */
+       result = dns_dnssec_matchdskey(val->name, &dsrdata, val->rdataset,
+                                      &keyrdata);
+       if (result != ISC_R_SUCCESS) {
+               validator_log(val, ISC_LOG_DEBUG(3), "no DNSKEY matching DS");
+               return (DNS_R_NOKEYMATCH);
+       }
+
+       /*
+        * ... and check that it signed the DNSKEY RRset.
+        */
+       result = check_signer(val, &keyrdata, ds.key_tag, ds.algorithm);
+       if (result != ISC_R_SUCCESS) {
+               validator_log(val, ISC_LOG_DEBUG(3),
+                             "no RRSIG matching DS key");
+
+               return (result);
+       }
+
+       return (ISC_R_SUCCESS);
+}
+
+static void
+validate_dnskey_dsset_next(void *arg) {
+       dns_validator_t *val = arg;
+
+       if (CANCELED(val)) {
+               val->result = ISC_R_CANCELED;
+       } else {
+               val->result = dns_rdataset_next(val->dsset);
+       }
+
+       if (val->result == ISC_R_SUCCESS) {
+               /* continue async run */
+               val->result = validate_dnskey_dsset(val);
+       }
+}
+
+static void
+validate_dnskey_dsset_next_done(void *arg) {
+       dns_validator_t *val = arg;
+       isc_result_t result = val->result;
+
+       if (CANCELED(val)) {
+               result = ISC_R_CANCELED;
+       }
+
+       switch (result) {
+       case ISC_R_CANCELED:
+       case ISC_R_SHUTTINGDOWN:
+               /* Abort, abort, abort! */
+               break;
+       case ISC_R_SUCCESS:
+       case ISC_R_NOMORE:
+               /* We are done */
+               break;
+       default:
+               /* Continue validation until we have success or no more data */
+               isc_work_enqueue(val->loop, validate_dnskey_dsset_next,
+                                validate_dnskey_dsset_next_done, val);
+               return;
+       }
+
+       validate_dnskey_dsset_done(val, result);
+       return;
+}
+
+static void
+validate_dnskey_dsset_first(dns_validator_t *val) {
+       isc_result_t result;
+
+       if (CANCELED(val)) {
+               result = ISC_R_CANCELED;
+       } else {
+               result = dns_rdataset_first(val->dsset);
+       }
+
+       if (result == ISC_R_SUCCESS) {
+               /* continue async run */
+               result = validate_dnskey_dsset(val);
+               if (result != ISC_R_SUCCESS) {
+                       isc_work_enqueue(val->loop, validate_dnskey_dsset_next,
+                                        validate_dnskey_dsset_next_done, val);
+                       return;
+               }
+       }
+
+       validate_dnskey_dsset_done(val, result);
+}
+
+static void
+validate_dnskey(void *arg) {
+       dns_validator_t *val = arg;
+       isc_result_t result = ISC_R_SUCCESS;
        dns_keynode_t *keynode = NULL;
        dns_rdata_ds_t ds;
-       bool supported_algorithm;
-       char digest_types[256];
+
+       if (CANCELED(val)) {
+               result = ISC_R_CANCELED;
+               goto cleanup;
+       }
 
        /*
         * If we don't already have a DS RRset, check to see if there's
@@ -1767,7 +2128,7 @@ validate_dnskey(dns_validator_t *val) {
         * verification.
         */
 
-       supported_algorithm = false;
+       val->supported_algorithm = false;
 
        /*
         * If DNS_DSDIGEST_SHA256 or DNS_DSDIGEST_SHA384 is present we
@@ -1775,7 +2136,8 @@ validate_dnskey(dns_validator_t *val) {
         * practice means that we need to ignore DNS_DSDIGEST_SHA1 if a
         * DNS_DSDIGEST_SHA256 or DNS_DSDIGEST_SHA384 is present.
         */
-       memset(digest_types, 1, sizeof(digest_types));
+       val->digest_sha1 = true;
+       dns_rdata_t dsrdata = DNS_RDATA_INIT;
        for (result = dns_rdataset_first(val->dsset); result == ISC_R_SUCCESS;
             result = dns_rdataset_next(val->dsset))
        {
@@ -1801,80 +2163,20 @@ validate_dnskey(dns_validator_t *val) {
                    (ds.digest_type == DNS_DSDIGEST_SHA384 &&
                     ds.length == ISC_SHA384_DIGESTLENGTH))
                {
-                       digest_types[DNS_DSDIGEST_SHA1] = 0;
-                       break;
-               }
-       }
-
-       for (result = dns_rdataset_first(val->dsset); result == ISC_R_SUCCESS;
-            result = dns_rdataset_next(val->dsset))
-       {
-               dns_rdata_reset(&dsrdata);
-               dns_rdataset_current(val->dsset, &dsrdata);
-               result = dns_rdata_tostruct(&dsrdata, &ds, NULL);
-               RUNTIME_CHECK(result == ISC_R_SUCCESS);
-
-               if (digest_types[ds.digest_type] == 0) {
-                       continue;
-               }
-
-               if (!dns_resolver_ds_digest_supported(
-                           val->view->resolver, val->name, ds.digest_type))
-               {
-                       continue;
-               }
-
-               if (!dns_resolver_algorithm_supported(val->view->resolver,
-                                                     val->name, ds.algorithm))
-               {
-                       continue;
-               }
-
-               supported_algorithm = true;
-
-               /*
-                * Find the DNSKEY matching the DS...
-                */
-               result = dns_dnssec_matchdskey(val->name, &dsrdata,
-                                              val->rdataset, &keyrdata);
-               if (result != ISC_R_SUCCESS) {
-                       validator_log(val, ISC_LOG_DEBUG(3),
-                                     "no DNSKEY matching DS");
-                       continue;
-               }
-
-               /*
-                * ... and check that it signed the DNSKEY RRset.
-                */
-               result = check_signer(val, &keyrdata, ds.key_tag, ds.algorithm);
-               if (result == ISC_R_SUCCESS) {
+                       val->digest_sha1 = false;
                        break;
                }
-               validator_log(val, ISC_LOG_DEBUG(3),
-                             "no RRSIG matching DS key");
        }
 
-       if (result == ISC_R_SUCCESS) {
-               marksecure(val);
-               validator_log(val, ISC_LOG_DEBUG(3), "marking as secure (DS)");
-       } else if (result == ISC_R_NOMORE && !supported_algorithm) {
-               validator_log(val, ISC_LOG_DEBUG(3),
-                             "no supported algorithm/digest (DS)");
-               result = markanswer(val, "validate_dnskey (3)",
-                                   "no supported algorithm/digest (DS)");
-       } else {
-               validator_log(val, ISC_LOG_INFO,
-                             "no valid signature found (DS)");
-               result = DNS_R_NOVALIDSIG;
-       }
+       validate_dnskey_dsset_first(val);
+       return;
 
 cleanup:
        if (val->dsset == &val->fdsset) {
                val->dsset = NULL;
                dns_rdataset_disassociate(&val->fdsset);
        }
-
-       return (result);
+       validate_async_done(val, result);
 }
 
 /*%
@@ -2521,7 +2823,6 @@ validate_nx(dns_validator_t *val, bool resume) {
                return (DNS_R_BROKENCHAIN);
        }
 
-       validator_log(val, ISC_LOG_DEBUG(3), "nonexistence proof(s) not found");
        return (proveunsecure(val, false, false));
 }
 
@@ -2597,10 +2898,10 @@ seek_ds(dns_validator_t *val, isc_result_t *resp) {
                 */
                if (val->frdataset.trust >= dns_trust_secure) {
                        if (!check_ds_algs(val, tname, &val->frdataset)) {
-                               validator_log(val, ISC_LOG_DEBUG(3),
-                                             "no supported algorithm/"
-                                             "digest (%s/DS)",
-                                             namebuf);
+                               validator_log(
+                                       val, ISC_LOG_DEBUG(3),
+                                       "no supported algorithm/digest (%s/DS)",
+                                       namebuf);
                                *resp = markanswer(val, "proveunsecure (5)",
                                                   "no supported "
                                                   "algorithm/digest (DS)");
@@ -2912,14 +3213,13 @@ validator_start(void *arg) {
        isc_result_t result = ISC_R_FAILURE;
 
        if (CANCELED(val)) {
-               return;
+               result = ISC_R_CANCELED;
+               goto cleanup;
        }
 
        validator_log(val, ISC_LOG_DEBUG(3), "starting");
 
        if (val->rdataset != NULL && val->sigrdataset != NULL) {
-               isc_result_t saved_result;
-
                /*
                 * This looks like a simple validation.  We say "looks like"
                 * because it might end up requiring an insecurity proof.
@@ -2929,21 +3229,19 @@ validator_start(void *arg) {
 
                INSIST(dns_rdataset_isassociated(val->rdataset));
                INSIST(dns_rdataset_isassociated(val->sigrdataset));
-               if (selfsigned_dnskey(val)) {
-                       result = validate_dnskey(val);
-               } else {
-                       result = validate_answer(val, false);
-               }
-               if (result == DNS_R_NOVALIDSIG &&
-                   (val->attributes & VALATTR_TRIEDVERIFY) == 0)
-               {
-                       saved_result = result;
-                       validator_log(val, ISC_LOG_DEBUG(3),
-                                     "falling back to insecurity proof");
-                       result = proveunsecure(val, false, false);
-                       if (result == DNS_R_NOTINSECURE) {
-                               result = saved_result;
-                       }
+
+               result = selfsigned_dnskey(val);
+               switch (result) {
+               case ISC_R_QUOTA:
+                       goto cleanup;
+               case ISC_R_SUCCESS:
+                       result = validate_async_run(val, validate_dnskey);
+                       break;
+               case DNS_R_NOKEYMATCH:
+                       result = validate_async_run(val, validate_answer);
+                       break;
+               default:
+                       UNREACHABLE();
                }
        } else if (val->rdataset != NULL && val->rdataset->type != 0) {
                /*
@@ -2996,11 +3294,8 @@ validator_start(void *arg) {
                UNREACHABLE();
        }
 
-       if (result != DNS_R_WAIT) {
-               validator_done(val, result);
-       }
-
-       dns_validator_detach(&val);
+cleanup:
+       validate_async_done(val, result);
 }
 
 isc_result_t
@@ -3008,6 +3303,7 @@ dns_validator_create(dns_view_t *view, dns_name_t *name, dns_rdatatype_t type,
                     dns_rdataset_t *rdataset, dns_rdataset_t *sigrdataset,
                     dns_message_t *message, unsigned int options,
                     isc_loop_t *loop, isc_job_cb cb, void *arg,
+                    uint32_t *nvalidations, uint32_t *nfails,
                     dns_validator_t **validatorp) {
        isc_result_t result = ISC_R_FAILURE;
        dns_validator_t *val = NULL;
@@ -3024,18 +3320,23 @@ dns_validator_create(dns_view_t *view, dns_name_t *name, dns_rdatatype_t type,
        }
 
        val = isc_mem_get(view->mctx, sizeof(*val));
-       *val = (dns_validator_t){ .tid = isc_tid(),
-                                 .result = ISC_R_FAILURE,
-                                 .rdataset = rdataset,
-                                 .sigrdataset = sigrdataset,
-                                 .name = name,
-                                 .type = type,
-                                 .options = options,
-                                 .keytable = kt,
-                                 .link = ISC_LINK_INITIALIZER,
-                                 .loop = loop,
-                                 .cb = cb,
-                                 .arg = arg };
+       *val = (dns_validator_t){
+               .tid = isc_tid(),
+               .result = DNS_R_NOVALIDSIG,
+               .rdataset = rdataset,
+               .sigrdataset = sigrdataset,
+               .name = name,
+               .type = type,
+               .options = options,
+               .keytable = kt,
+               .link = ISC_LINK_INITIALIZER,
+               .loop = loop,
+               .cb = cb,
+               .arg = arg,
+               .rdata = DNS_RDATA_INIT,
+               .nvalidations = nvalidations,
+               .nfails = nfails,
+       };
 
        isc_refcount_init(&val->references, 1);
        dns_view_attach(view, &val->view);
@@ -3054,7 +3355,7 @@ dns_validator_create(dns_view_t *view, dns_name_t *name, dns_rdatatype_t type,
 
        if ((options & DNS_VALIDATOR_DEFER) == 0) {
                dns_validator_ref(val);
-               isc_async_run(val->loop, validator_start, val);
+               (void)validate_async_run(val, validator_start);
        }
 
        *validatorp = val;
@@ -3063,15 +3364,15 @@ dns_validator_create(dns_view_t *view, dns_name_t *name, dns_rdatatype_t type,
 }
 
 void
-dns_validator_send(dns_validator_t *validator) {
-       REQUIRE(VALID_VALIDATOR(validator));
-       REQUIRE(validator->tid == isc_tid());
+dns_validator_send(dns_validator_t *val) {
+       REQUIRE(VALID_VALIDATOR(val));
+       REQUIRE(val->tid == isc_tid());
 
-       INSIST((validator->options & DNS_VALIDATOR_DEFER) != 0);
-       validator->options &= ~DNS_VALIDATOR_DEFER;
+       INSIST((val->options & DNS_VALIDATOR_DEFER) != 0);
+       val->options &= ~DNS_VALIDATOR_DEFER;
 
-       dns_validator_ref(validator);
-       isc_async_run(validator->loop, validator_start, validator);
+       dns_validator_ref(val);
+       (void)validate_async_run(val, validator_start);
 }
 
 void
@@ -3092,7 +3393,6 @@ dns_validator_cancel(dns_validator_t *validator) {
                        validator->options &= ~DNS_VALIDATOR_DEFER;
                        validator_done(validator, ISC_R_CANCELED);
                }
-
                validator->attributes |= VALATTR_CANCELED;
        }
 }
@@ -3111,9 +3411,6 @@ destroy_validator(dns_validator_t *val) {
        if (val->keytable != NULL) {
                dns_keytable_detach(&val->keytable);
        }
-       if (val->subvalidator != NULL) {
-               dns_validator_destroy(&val->subvalidator);
-       }
        disassociate_rdatasets(val);
        mctx = val->view->mctx;
        if (val->siginfo != NULL) {
index e31563b63d520bf792dfb46810c8fdd5aba104a4..829cf168a46174f72aca6ebf71c828bff3c6b928 100644 (file)
@@ -138,6 +138,8 @@ static void
 shutdown_trigger_close_cb(uv_handle_t *handle) {
        isc_loop_t *loop = uv_handle_get_data(handle);
 
+       loop->shuttingdown = true;
+
        isc_loop_detach(&loop);
 }
 
index 10aa92ee229cf8e7ae81db306f4286022a33e617..3ffa2c95c4f52f5f6304d23002a6309d95bc7580 100644 (file)
@@ -2103,6 +2103,10 @@ static cfg_clausedef_t view_clauses[] = {
        { "max-recursion-queries", &cfg_type_uint32, 0 },
        { "max-stale-ttl", &cfg_type_duration, 0 },
        { "max-udp-size", &cfg_type_uint32, 0 },
+       { "max-validations-per-fetch", &cfg_type_uint32,
+         CFG_CLAUSEFLAG_EXPERIMENTAL },
+       { "max-validation-failures-per-fetch", &cfg_type_uint32,
+         CFG_CLAUSEFLAG_EXPERIMENTAL },
        { "message-compression", &cfg_type_boolean, 0 },
        { "min-cache-ttl", &cfg_type_duration, 0 },
        { "min-ncache-ttl", &cfg_type_duration, 0 },