]> git.ipfire.org Git - thirdparty/unbound.git/commitdiff
- Fix zonemd verification of key that is not in DNS but in the zone
authorW.C.A. Wijngaards <wouter@nlnetlabs.nl>
Fri, 13 Aug 2021 12:43:11 +0000 (14:43 +0200)
committerW.C.A. Wijngaards <wouter@nlnetlabs.nl>
Fri, 13 Aug 2021 12:43:11 +0000 (14:43 +0200)
  and needs a chain of trust.

doc/Changelog
services/authzone.c
services/authzone.h
testdata/auth_zonemd_xfr_chain_keyinxfr.rpl [new file with mode: 0644]

index 1cd0f3bada5dd0ce75f534b17e780e382e2464fb..c1548dd675b5e601445faa94213535c31328a5ed 100644 (file)
@@ -2,6 +2,8 @@
        - Support using system-wide crypto policies.
        - Fix for #431: Squelch permission denied errors for udp connect,
          and udp send, they are visible at higher verbosity settings.
+       - Fix zonemd verification of key that is not in DNS but in the zone
+         and needs a chain of trust.
 
 12 August 2021: George
        - Merge PR #514, from ziollek: Docker environment for run tests.
index e6e3a8cff9b42e2ea7d0328b72f87b444d696540..71b0331cae9996141a92508ff68f794968a1e4f9 100644 (file)
@@ -8065,15 +8065,77 @@ zonemd_get_dnskey_from_anchor(struct auth_zone* z, struct module_env* env,
        return NULL;
 }
 
+/** verify the DNSKEY from the zone with looked up DS record */
+static struct ub_packed_rrset_key*
+auth_zone_verify_zonemd_key_with_ds(struct auth_zone* z,
+       struct module_env* env, struct module_stack* mods,
+       struct ub_packed_rrset_key* ds, int* is_insecure, char** why_bogus,
+       struct ub_packed_rrset_key* keystorage)
+{
+       struct auth_data* apex;
+       struct auth_rrset* dnskey_rrset;
+       enum sec_status sec;
+       struct val_env* ve;
+       int m;
+
+       /* fetch DNSKEY from zone data */
+       apex = az_find_name(z, z->name, z->namelen);
+       if(!apex) {
+               *why_bogus = "in verifywithDS, zone has no apex";
+               return NULL;
+       }
+       dnskey_rrset = az_domain_rrset(apex, LDNS_RR_TYPE_DNSKEY);
+       if(!dnskey_rrset || dnskey_rrset->data->count==0) {
+               *why_bogus = "in verifywithDS, zone has no DNSKEY";
+               return NULL;
+       }
+
+       m = modstack_find(mods, "validator");
+       if(m == -1) {
+               *why_bogus = "in verifywithDS, have no validator module";
+               return NULL;
+       }
+       ve = (struct val_env*)env->modinfo[m];
+
+       memset(keystorage, 0, sizeof(*keystorage));
+       keystorage->entry.key = keystorage;
+       keystorage->entry.data = dnskey_rrset->data;
+       keystorage->rk.dname = apex->name;
+       keystorage->rk.dname_len = apex->namelen;
+       keystorage->rk.type = htons(LDNS_RR_TYPE_DNSKEY);
+       keystorage->rk.rrset_class = htons(z->dclass);
+       auth_zone_log(z->name, VERB_QUERY, "zonemd: verify zone's DNSKEY with DS");
+       sec = val_verify_DNSKEY_with_DS(env, ve, keystorage, ds, NULL,
+               why_bogus, NULL);
+       regional_free_all(env->scratch);
+       if(sec == sec_status_secure) {
+               /* success */
+               return keystorage;
+       } else if(sec == sec_status_insecure) {
+               /* insecure */
+               *is_insecure = 1;
+       } else {
+               /* bogus */
+               *is_insecure = 0;
+               auth_zone_log(z->name, VERB_ALGO,
+                       "zonemd: verify DNSKEY RRset with DS failed: %s",
+                       *why_bogus);
+               if(*why_bogus == NULL)
+                       *why_bogus = "verify failed";
+       }
+       return NULL;
+}
+
 /** callback for ZONEMD lookup of DNSKEY */
 void auth_zonemd_dnskey_lookup_callback(void* arg, int rcode, sldns_buffer* buf,
        enum sec_status sec, char* why_bogus, int ATTR_UNUSED(was_ratelimited))
 {
        struct auth_zone* z = (struct auth_zone*)arg;
        struct module_env* env;
-       char* reason = NULL;
-       struct ub_packed_rrset_key* dnskey = NULL;
+       char* reason = NULL, *ds_bogus = NULL, *typestr="DNSKEY";
+       struct ub_packed_rrset_key* dnskey = NULL, *ds = NULL;
        int is_insecure = 0;
+       struct ub_packed_rrset_key keystorage;
 
        lock_rw_wrlock(&z->lock);
        env = z->zonemd_callback_env;
@@ -8084,16 +8146,21 @@ void auth_zonemd_dnskey_lookup_callback(void* arg, int rcode, sldns_buffer* buf,
                lock_rw_unlock(&z->lock);
                return; /* stop on quit */
        }
+       if(z->zonemd_callback_qtype == LDNS_RR_TYPE_DS)
+               typestr = "DS";
 
        /* process result */
        if(sec == sec_status_bogus) {
                reason = why_bogus;
-               if(!reason)
-                       reason = "lookup of DNSKEY was bogus";
+               if(!reason) {
+                       if(z->zonemd_callback_qtype == LDNS_RR_TYPE_DNSKEY)
+                               reason = "lookup of DNSKEY was bogus";
+                       else    reason = "lookup of DS was bogus";
+               }
                auth_zone_log(z->name, VERB_ALGO,
-                       "zonemd lookup of DNSKEY was bogus: %s", reason);
+                       "zonemd lookup of %s was bogus: %s", typestr, reason);
        } else if(rcode == LDNS_RCODE_NOERROR) {
-               uint16_t wanted_qtype = LDNS_RR_TYPE_DNSKEY;
+               uint16_t wanted_qtype = z->zonemd_callback_qtype;
                struct regional* temp = env->scratch;
                struct query_info rq;
                struct reply_info* rep;
@@ -8106,25 +8173,29 @@ void auth_zonemd_dnskey_lookup_callback(void* arg, int rcode, sldns_buffer* buf,
                        struct ub_packed_rrset_key* answer =
                                reply_find_answer_rrset(&rq, rep);
                        if(answer && sec == sec_status_secure) {
-                               dnskey = answer;
+                               if(z->zonemd_callback_qtype == LDNS_RR_TYPE_DNSKEY)
+                                       dnskey = answer;
+                               else    ds = answer;
                                auth_zone_log(z->name, VERB_ALGO,
-                                       "zonemd lookup of DNSKEY was secure");
+                                       "zonemd lookup of %s was secure", typestr);
                        } else if(sec == sec_status_secure && !answer) {
                                is_insecure = 1;
                                auth_zone_log(z->name, VERB_ALGO,
-                                       "zonemd lookup of DNSKEY has no content, but is secure, treat as insecure");
+                                       "zonemd lookup of %s has no content, but is secure, treat as insecure", typestr);
                        } else if(sec == sec_status_insecure) {
                                is_insecure = 1;
                                auth_zone_log(z->name, VERB_ALGO,
-                                       "zonemd lookup of DNSKEY was insecure");
+                                       "zonemd lookup of %s was insecure", typestr);
                        } else if(sec == sec_status_indeterminate) {
                                is_insecure = 1;
                                auth_zone_log(z->name, VERB_ALGO,
-                                       "zonemd lookup of DNSKEY was indeterminate, treat as insecure");
+                                       "zonemd lookup of %s was indeterminate, treat as insecure", typestr);
                        } else {
                                auth_zone_log(z->name, VERB_ALGO,
-                                       "zonemd lookup of DNSKEY has nodata");
-                               reason = "lookup of DNSKEY has nodata";
+                                       "zonemd lookup of %s has nodata", typestr);
+                               if(z->zonemd_callback_qtype == LDNS_RR_TYPE_DNSKEY)
+                                       reason = "lookup of DNSKEY has nodata";
+                               else    reason = "lookup of DS has nodata";
                        }
                } else if(rep && rq.qtype == wanted_qtype &&
                        query_dname_compare(z->name, rq.qname) == 0 &&
@@ -8137,34 +8208,46 @@ void auth_zonemd_dnskey_lookup_callback(void* arg, int rcode, sldns_buffer* buf,
                         * trust, as insecure. */
                        is_insecure = 1;
                        auth_zone_log(z->name, VERB_ALGO,
-                               "zonemd lookup of DNSKEY was secure NXDOMAIN, treat as insecure");
+                               "zonemd lookup of %s was secure NXDOMAIN, treat as insecure", typestr);
                } else if(rep && rq.qtype == wanted_qtype &&
                        query_dname_compare(z->name, rq.qname) == 0 &&
                        FLAGS_GET_RCODE(rep->flags) == LDNS_RCODE_NXDOMAIN &&
                        sec == sec_status_insecure) {
                        is_insecure = 1;
                        auth_zone_log(z->name, VERB_ALGO,
-                               "zonemd lookup of DNSKEY was insecure NXDOMAIN, treat as insecure");
+                               "zonemd lookup of %s was insecure NXDOMAIN, treat as insecure", typestr);
                } else if(rep && rq.qtype == wanted_qtype &&
                        query_dname_compare(z->name, rq.qname) == 0 &&
                        FLAGS_GET_RCODE(rep->flags) == LDNS_RCODE_NXDOMAIN &&
                        sec == sec_status_indeterminate) {
                        is_insecure = 1;
                        auth_zone_log(z->name, VERB_ALGO,
-                               "zonemd lookup of DNSKEY was indeterminate NXDOMAIN, treat as insecure");
+                               "zonemd lookup of %s was indeterminate NXDOMAIN, treat as insecure", typestr);
                } else {
                        auth_zone_log(z->name, VERB_ALGO,
-                               "zonemd lookup of DNSKEY has no answer");
-                       reason = "lookup of DNSKEY has no answer";
+                               "zonemd lookup of %s has no answer", typestr);
+                       if(z->zonemd_callback_qtype == LDNS_RR_TYPE_DNSKEY)
+                               reason = "lookup of DNSKEY has no answer";
+                       else    reason = "lookup of DS has no answer";
                }
        } else {
                auth_zone_log(z->name, VERB_ALGO,
-                       "zonemd lookup of DNSKEY failed");
-               reason = "lookup of DNSKEY failed";
+                       "zonemd lookup of %s failed", typestr);
+               if(z->zonemd_callback_qtype == LDNS_RR_TYPE_DNSKEY)
+                       reason = "lookup of DNSKEY failed";
+               else    reason = "lookup of DS failed";
+       }
+
+       if(!reason && !is_insecure && !dnskey && ds) {
+               dnskey = auth_zone_verify_zonemd_key_with_ds(z, env,
+                       &env->mesh->mods, ds, &is_insecure, &ds_bogus,
+                       &keystorage);
+               if(!dnskey && !is_insecure && !reason)
+                       reason = "DNSKEY verify with DS failed";
        }
 
        if(reason) {
-               auth_zone_zonemd_fail(z, env, reason, NULL, NULL);
+               auth_zone_zonemd_fail(z, env, reason, ds_bogus, NULL);
                lock_rw_unlock(&z->lock);
                return;
        }
@@ -8183,14 +8266,21 @@ zonemd_lookup_dnskey(struct auth_zone* z, struct module_env* env)
        uint16_t qflags = BIT_RD;
        struct edns_data edns;
        sldns_buffer* buf = env->scratch_buffer;
+       int fetch_ds = 0;
 
+       if(!z->fallback_enabled) {
+               /* we cannot actually get the DNSKEY, because it is in the
+                * zone we have ourselves, and it is not served yet
+                * (possibly), so fetch type DS */
+               fetch_ds = 1;
+       }
        if(z->zonemd_callback_env) {
                /* another worker is already working on the callback
                 * for the DNSKEY lookup for ZONEMD verification.
                 * We do not also have to do ZONEMD verification, let that
                 * worker do it */
                auth_zone_log(z->name, VERB_ALGO,
-                       "zonemd needs lookup of DNSKEY and that already worked on by another worker");
+                       "zonemd needs lookup of %s and that already is worked on by another worker", (fetch_ds?"DS":"DNSKEY"));
                return 1;
        }
 
@@ -8199,14 +8289,17 @@ zonemd_lookup_dnskey(struct auth_zone* z, struct module_env* env)
        qinfo.qname_len = z->namelen;
        qinfo.qname = z->name;
        qinfo.qclass = z->dclass;
-       qinfo.qtype = LDNS_RR_TYPE_DNSKEY;
+       if(fetch_ds)
+               qinfo.qtype = LDNS_RR_TYPE_DS;
+       else    qinfo.qtype = LDNS_RR_TYPE_DNSKEY;
        qinfo.local_alias = NULL;
        if(verbosity >= VERB_ALGO) {
                char buf1[512];
                char buf2[LDNS_MAX_DOMAINLEN+1];
                dname_str(z->name, buf2);
-               snprintf(buf1, sizeof(buf1), "auth zone %s: lookup DNSKEY "
-                       "for zonemd verification", buf2);
+               snprintf(buf1, sizeof(buf1), "auth zone %s: lookup %s "
+                       "for zonemd verification", buf2,
+                       (fetch_ds?"DS":"DNSKEY"));
                log_query_info(VERB_ALGO, buf1, &qinfo);
        }
        edns.edns_present = 1;
@@ -8221,12 +8314,14 @@ zonemd_lookup_dnskey(struct auth_zone* z, struct module_env* env)
        /* store the worker-specific module env for the callback.
         * We can then reference this when the callback executes */
        z->zonemd_callback_env = env;
+       z->zonemd_callback_qtype = qinfo.qtype;
        /* the callback can be called straight away */
        lock_rw_unlock(&z->lock);
        if(!mesh_new_callback(env->mesh, &qinfo, qflags, &edns, buf, 0,
                &auth_zonemd_dnskey_lookup_callback, z)) {
                lock_rw_wrlock(&z->lock);
-               log_err("out of memory lookup up dnskey for zonemd");
+               log_err("out of memory lookup of %s for zonemd",
+                       (fetch_ds?"DS":"DNSKEY"));
                return 0;
        }
        lock_rw_wrlock(&z->lock);
@@ -8245,6 +8340,8 @@ void auth_zone_verify_zonemd(struct auth_zone* z, struct module_env* env,
         * If not present check if absence is allowed by DNSSEC */
        if(!z->zonemd_check)
                return;
+       if(z->data.count == 0)
+               return; /* no data */
 
        /* if zone is under a trustanchor */
        /* is it equal to trustanchor - get dnskey's verified */
index ffe234d59b531e5b24fb6a5de8b042b0cdc62918..1c6d5cf422eeba8dfe00e075774e3fd413b30be8 100644 (file)
@@ -143,6 +143,8 @@ struct auth_zone {
         * worker has already picked up the zonemd verification task and
         * this worker does not have to do it as well. */
        struct module_env* zonemd_callback_env;
+       /** for the zonemd callback, the type of data looked up */
+       uint16_t zonemd_callback_qtype;
        /** zone has been deleted */
        int zone_deleted;
        /** deletelist pointer, unused normally except during delete */
diff --git a/testdata/auth_zonemd_xfr_chain_keyinxfr.rpl b/testdata/auth_zonemd_xfr_chain_keyinxfr.rpl
new file mode 100644 (file)
index 0000000..2feec88
--- /dev/null
@@ -0,0 +1,315 @@
+; config options
+server:
+       target-fetch-policy: "0 0 0 0 0"
+       trust-anchor: "com. DS 1444 8 2 0d72034e3e18a9ef383c164b68302433bbde957616e10cf44575fea2abae469c"
+       trust-anchor-signaling: no
+       val-override-date: 20201020135527
+
+auth-zone:
+       name: "example.com."
+       ## zonefile (or none).
+       ## zonefile: "example.com.zone"
+       ## master by IP address or hostname
+       ## can list multiple masters, each on one line.
+       ## master:
+       master: 1.2.3.44
+       ## url for http fetch
+       ## url:
+       ## queries from downstream clients get authoritative answers.
+       ## for-downstream: yes
+
+       ## The for-downstream and fallback are disabled, the key cannot be
+       ## retrieved by DNS lookup, it is in the xfr itself.
+       ## only after the zone is loaded can it be looked up.
+       for-downstream: no
+       ## queries are used to fetch authoritative answers from this zone,
+       ## instead of unbound itself sending queries there.
+       ## for-upstream: yes
+       for-upstream: yes
+       ## on failures with for-upstream, fallback to sending queries to
+       ## the authority servers
+       ## fallback-enabled: no
+       fallback-enabled: no
+       zonemd-check: yes
+
+       ## this line generates zonefile: \n"/tmp/xxx.example.com"\n
+       zonefile:
+TEMPFILE_NAME example.com
+       ## this is the inline file /tmp/xxx.example.com
+       ## the tempfiles are deleted when the testrun is over.
+TEMPFILE_CONTENTS example.com
+TEMPFILE_END
+
+stub-zone:
+       name: "."
+       stub-addr: 193.0.14.129         # K.ROOT-SERVERS.NET.
+CONFIG_END
+
+SCENARIO_BEGIN Test authority zone with AXFR with ZONEMD with key in xfr
+
+; K.ROOT-SERVERS.NET.
+RANGE_BEGIN 0 100
+       ADDRESS 193.0.14.129 
+ENTRY_BEGIN
+MATCH opcode qtype qname
+ADJUST copy_id
+REPLY QR NOERROR
+SECTION QUESTION
+. IN NS
+SECTION ANSWER
+. IN NS        K.ROOT-SERVERS.NET.
+SECTION ADDITIONAL
+K.ROOT-SERVERS.NET.    IN      A       193.0.14.129
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode subdomain
+ADJUST copy_id copy_query
+REPLY QR NOERROR
+SECTION QUESTION
+com. IN NS
+SECTION AUTHORITY
+com.   IN NS   a.gtld-servers.net.
+SECTION ADDITIONAL
+a.gtld-servers.net.    IN      A       192.5.6.30
+ENTRY_END
+RANGE_END
+
+; a.gtld-servers.net.
+RANGE_BEGIN 0 100
+       ADDRESS 192.5.6.30
+ENTRY_BEGIN
+MATCH opcode qtype qname
+ADJUST copy_id
+REPLY QR NOERROR
+SECTION QUESTION
+com. IN NS
+SECTION ANSWER
+com.   IN NS   a.gtld-servers.net.
+SECTION ADDITIONAL
+a.gtld-servers.net.    IN      A       192.5.6.30
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode qname qtype
+ADJUST copy_id
+REPLY QR AA NOERROR
+SECTION QUESTION
+example.com. IN DS
+SECTION ANSWER
+example.com.   3600    IN      DS      55566 8 2 9c148338951ce1c3b5cd3da532f3d90dfcf92595148022f2c2fd98e5deee90af
+example.com.   3600    IN      RRSIG   DS 8 2 3600 20201116135527 20201019135527 1444 com. BpV1M171SSkbdlGawwweJwQ0W+aNaCrgkt2QTsxCvbo1acR5i3AKm4REOUzo4I36lRx26mYkF9Topkeu0aFmov7P2uUhCxk4faFK7k87k97FAqZaDGp/K9b3YCfiwJBc5pJSUW0ndU/Ve5zAh/wL493RMSC7LwJr5JjV0NxydFk=
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode subdomain
+ADJUST copy_id copy_query
+REPLY QR NOERROR
+SECTION QUESTION
+example.com. IN NS
+SECTION AUTHORITY
+example.com.   IN NS   ns.example.com.
+example.com.   3600    IN      DS      55566 8 2 9c148338951ce1c3b5cd3da532f3d90dfcf92595148022f2c2fd98e5deee90af
+example.com.   3600    IN      RRSIG   DS 8 2 3600 20201116135527 20201019135527 1444 com. BpV1M171SSkbdlGawwweJwQ0W+aNaCrgkt2QTsxCvbo1acR5i3AKm4REOUzo4I36lRx26mYkF9Topkeu0aFmov7P2uUhCxk4faFK7k87k97FAqZaDGp/K9b3YCfiwJBc5pJSUW0ndU/Ve5zAh/wL493RMSC7LwJr5JjV0NxydFk=
+SECTION ADDITIONAL
+ns.example.com. IN A 1.2.3.44
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode qtype qname
+ADJUST copy_id
+REPLY QR AA NOERROR
+SECTION QUESTION
+com. IN DNSKEY
+SECTION ANSWER
+com.   3600    IN      DNSKEY  257 3 8 AwEAAbd9WqjzE2Pynz21OG5doSf9hFzMr5dhzz2waZ3vTa+0o5r7AjTAqmA1yH/B3+aAMihUm5ucZSfVqo7+kOaRE8yFj9aivOmA1n1+JLevJq/oyvQyjxQN2Qb89LyaNUT5oKZIiL+uyyhNW3KDR3SSbQ/GBwQNDHVcZi+JDR3RC0r7 ;{id = 1444 (ksk), size = 1024b}
+com.   3600    IN      RRSIG   DNSKEY 8 1 3600 20201116135527 20201019135527 1444 com. BEOMfWvi6RgnHaHsst+Ed265hBuCkgMR7gDpu89J7ZrVL6DzMKnNVFdgjl/9xwLj/pkukc7qeLSHjAfLlN0E4THW7PVshscQnjvXCkktG2Ejx9fTyllAqeGDh9z9QDGlQZIGTMgb9413qZhNqe2Tda9PTJRpiZ8b4bdQp6V1kVo=
+SECTION ADDITIONAL
+ENTRY_END
+RANGE_END
+
+; ns.example.net.
+RANGE_BEGIN 0 100
+       ADDRESS 1.2.3.44
+ENTRY_BEGIN
+MATCH opcode qtype qname
+ADJUST copy_id
+REPLY QR NOERROR
+SECTION QUESTION
+example.net. IN NS
+SECTION ANSWER
+example.net.   IN NS   ns.example.net.
+SECTION ADDITIONAL
+ns.example.net.                IN      A       1.2.3.44
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode qtype qname
+ADJUST copy_id
+REPLY QR NOERROR
+SECTION QUESTION
+ns.example.net. IN A
+SECTION ANSWER
+ns.example.net. IN A   1.2.3.44
+SECTION AUTHORITY
+example.net.   IN NS   ns.example.net.
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode qtype qname
+ADJUST copy_id
+REPLY QR NOERROR
+SECTION QUESTION
+ns.example.net. IN AAAA
+SECTION AUTHORITY
+example.net.   IN NS   ns.example.net.
+SECTION ADDITIONAL
+www.example.net. IN A  1.2.3.44
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode qtype qname
+ADJUST copy_id
+REPLY QR NOERROR
+SECTION QUESTION
+example.com. IN NS
+SECTION ANSWER
+example.com.   IN NS   ns.example.net.
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode qtype qname
+ADJUST copy_id
+REPLY QR NOERROR
+SECTION QUESTION
+www.example.com. IN A
+SECTION ANSWER
+www.example.com. IN A  10.20.30.40
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode qtype qname
+ADJUST copy_id
+REPLY QR NOERROR
+SECTION QUESTION
+example.com. IN SOA
+SECTION ANSWER
+; serial, refresh, retry, expire, minimum
+example.com. IN SOA ns.example.com. hostmaster.example.com. 1 3600 900 86400 3600
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode qtype qname
+ADJUST copy_id
+REPLY QR AA NOERROR
+SECTION QUESTION
+example.com. IN AXFR
+SECTION ANSWER
+example.com.   3600    IN      SOA     ns.example.com. hostmaster.example.com. 200154054 28800 7200 604800 3600
+example.com.   3600    IN      RRSIG   SOA 8 2 3600 20201116135527 20201019135527 55566 example.com. gcFHT/Q4iDZ78CK6fyY2HZr8sRtgH2Rna9fEs06RW0gqMnfDntweoIaBamOZ7NlAP84aY2bZeanmEccmkHexByUpodCoKQ4NzVXctLr0TO4PVoFyfUfj62fjhM56SF8ioDxsoDQcPtYXcjNQjwfntWofMqHCMxrb9LzbgePzhOM=
+example.com.   3600    IN      NS      ns.example.com.
+example.com.   3600    IN      RRSIG   NS 8 2 3600 20201116135527 20201019135527 55566 example.com. X+V3XsbJbBi9OsHpjMkGCox8RLY/uXp/XX/O/flTrIre9fMDWm9ZGnewtuQFpLgGc6hUTi0eLsuRWRA5fZXEKUBhmoR2Ph01KgE1gvlL7v6zPWQwXVcBRUr3mOSbYdNNkHkXEjiDBGEhNkfqR216zNgw563eEGXOkLUFNIx5Zpg=
+example.com.   3600    IN      DNSKEY  256 3 8 AwEAAdug/L739i0mgN2nuK/bhxu3wFn5Ud9nK2+XUmZQlPUEZUC5YZvm1rfMmEWTGBn87fFxEu/kjFZHJ55JLzqsbbpVHLbmKCTT2gYR2FV2WDKROGKuYbVkJIXdKAjJ0ONuK507NinYvlWXIoxHn22KAWOd9wKgSTNHBlmGkX+ts3hh ;{id = 55566 (zsk), size = 1024b}
+example.com.   3600    IN      RRSIG   DNSKEY 8 2 3600 20201116135527 20201019135527 55566 example.com. fsdnVg38PKQTH2mDOwkXL6Jre7JP7Gf8WI3CvIbmeYQUJtAlpcSbZkS3wInm3kKMxOuT55BWzndQzpfmpo91OqJjG27W0k9301NMLUwFprA6b9HK+iPAT0JpYPDPzcm1bQdarLzLS+eD/GPwmyVSX7Gze+08VfE8m8sOW2r7UjA=
+example.com.   3600    IN      TYPE63  \# 70 0bee1bc6010258f7620f93204bbb31b44f795b3409cc4abd9ef5601decc15675bd7751213152984eddce0626e6062e744b03b3e47711202fbb79e4a2eb8bc5cf46741b5cae6f
+example.com.   3600    IN      RRSIG   TYPE63 8 2 3600 20201116135527 20201019135527 55566 example.com. orn8ZF/yqj9u4WrhiO6gtEcTaVsnZSWWZLfXhcIOiWSB8kKCxtZl5cG17dD3Du1NllUwMRqkp0KleLhIoUS9xeQ/0x05u+CYLrfQ62oAiD7q54ZQzpXJIH52aQzKV70ZnO03CZowhQBnetmIoKX6xLogKo8pt+BdQbo3oVHxV8Y=
+example.com.   3600    IN      NSEC    bar.example.com. NS SOA RRSIG NSEC DNSKEY TYPE63 
+example.com.   3600    IN      RRSIG   NSEC 8 2 3600 20201116135527 20201019135527 55566 example.com. ufLrlOQprAqjnH85Rt3T0Mxd3ZB0mBeeNIr84eFJ8Rk6WiWEPm0Y1R7GRufNI24Mj7iqLcL4nJM6KK6B7dJqjqu73jw1acuYNnbsoV2BNDRXRFP2FNWTpctVdi+955f3FzgsmEJXfGiSUG0YXAEcZmdCPCn5ii2jk8mk7r6KKYo=
+bar.example.com.       3600    IN      A       1.2.3.4
+bar.example.com.       3600    IN      RRSIG   A 8 3 3600 20201116135527 20201019135527 55566 example.com. NYhmRicF4C9+YxpWeQrepy4ALM1CM0USoDuGi3W5Xtp4/+YpCJfSIdR9vlJaJ2WayYuZrz9Ai2ci7oWwE1Fn3oywGwCKvGo9m0c3mC2eEtphE19wrop6pWu6um4RiFhmzYS1voraA3PAdYzze9U4NHzlk0+sb5vNZW9dSZS30Ds=
+bar.example.com.       3600    IN      NSEC    ding.example.com. A RRSIG NSEC 
+bar.example.com.       3600    IN      RRSIG   NSEC 8 3 3600 20201116135527 20201019135527 55566 example.com. VhsGuBx20DXQZNU8ITAMnasn6NVyEjN9xtB8msH5xJn80UCuaqvFBURzcPWN3aHnykEvGfdPF/9P3WvlON0cMikWkqSLy6Q9bpvgAq13HWYh+ZcDoqLtICaB7RkBQc+6aHAqZFyQbD8/m8Kxt5eVJtV6rEuf+yPX0+3aXHhsRg0=
+ding.example.com.      3600    IN      A       1.2.3.4
+ding.example.com.      3600    IN      RRSIG   A 8 3 3600 20201116135527 20201019135527 55566 example.com. OERsruISkpd1s68ute8Xm8YXisBCTkkiDMt34K+0dVqvySOJq63d3qN18BeUxZxLyHDB1eR3nZZKqEdkTqrv2r98skhWhjnOECpFbu5gKjtN/KPexbbJ+rxC0QqciuWOC7M6YE0cvI17/RB9KhVRy5rqY2X4Gt2wk2CNeD1dAko=
+ding.example.com.      3600    IN      NSEC    foo.example.com. A RRSIG NSEC 
+ding.example.com.      3600    IN      RRSIG   NSEC 8 3 3600 20201116135527 20201019135527 55566 example.com. nb1W2aaKrU5iAQiY8gMsoMOejID19JMTEwY2rRoe+KsvzMs0rE0ifEkqit4blXaU0tfy0foJ70uqdJFqBoGz1NcSwZ6GNk/iNfGvG3XpxZ/zqEe7kkIucqqei794G7z9psqV94yZ3WaT+IswPpWrSaWv1w41RtcWufPhe4fOAmU=
+foo.example.com.       3600    IN      A       1.2.3.4
+foo.example.com.       3600    IN      RRSIG   A 8 3 3600 20201116135527 20201019135527 55566 example.com. ZcUngb2pUejwnsshbJN/Dfr+Bzu8fcZXyqLArQ+10Bw1IPHyfx7yyUJ43V5tTYVHPSEsJzTnaWj+olVrNhVZxq5e0pgzSYPfGln2FEItEvMIOn33j8yKTpPW2MLyuFF5ZkXhosG20EUwRMvMmRHRz9mIZfwWoMbSGPukmLh8zMA=
+foo.example.com.       3600    IN      NSEC    ns.example.com. A RRSIG NSEC 
+foo.example.com.       3600    IN      RRSIG   NSEC 8 3 3600 20201116135527 20201019135527 55566 example.com. fUZEpkEULRWDntN5Z7Kr8M83Hjhf08ECMKRpo6IBoBc3ayenj+YMgWAvFXC825wjENPYYWNGag0d32U83zCZxqgv+8uXZd3B7QDpTbL41aWZdc++s5YWTkYjyOWwJ1XHOv4nL3qEnJBXVzo/E1gbSKhTFuG97i+7J1MFd9MsC5s=
+ns.example.com.        3600    IN      A       127.0.0.1
+ns.example.com.        3600    IN      RRSIG   A 8 3 3600 20201116135527 20201019135527 55566 example.com. SiuxuPtN/ITd+Z20j8UNUHJWbLHirE8zQOWMv5fAZ1rPKpAidrZgUL8J417GdrTwkueU2ywAJ7EzFJSwNTa7o/wUnq7svmOR6Ze6UQsKuZFZGEfqPNDRp4YuF86LU5jChuo+f/IRpydHrxVwGxDPCR9KarDM+ewfW+yI5bZeZcg=
+ns.example.com.        3600    IN      NSEC    www.example.com. A RRSIG NSEC 
+ns.example.com.        3600    IN      RRSIG   NSEC 8 3 3600 20201116135527 20201019135527 55566 example.com. 0upKNYjiow4NDJm3I1RbUddE9GGuFYEVKswww5BAc/6WHuukupncL30lskvcSKGpByDssP2Hi2CufyEtYeGWh6q1TxtOFRqFBX1p6Q5b3tBlCtvv4h31dQR9uqLvq+GkGS5MR+0LO5kWagIpZmnI8YY5plVdXEtNbp2Ar8zvz/A=
+www.example.com.       3600    IN      A       127.0.0.1
+www.example.com.       3600    IN      RRSIG   A 8 3 3600 20201116135527 20201019135527 55566 example.com. AaIeICaPjV50TDrpbyOn94+hs8EYIMTmN4pYqj7e8GIGimqQIk5jgpwSx6SOoOF+uOqkf9GKHkQTn5YVGaeXwEQleg7mPTmMYKAOk06Y7MFUO1Vwt1Vt7Wo+Cpa3x2a1CmEkfFOi4WqP43VJnUtjjKmXoKRz3VUmqByyJYUAGbQ=
+www.example.com.       3600    IN      NSEC    example.com. A RRSIG NSEC 
+www.example.com.       3600    IN      RRSIG   NSEC 8 3 3600 20201116135527 20201019135527 55566 example.com. meg/t6nIBqQZ0d5/dT7uu/3CuP4vE+HxqFQaj2fjUNceA/6C7QIQnqQ5Kyblg+XijDkQX0yvyFNHYdgF16UDgFT7tlNUCHk1SpF5BWzV4c4tBEhxASTz7UQo111O3Tyd6CldPzO/Se15Ud0/ZYltHEqWTfY5nJoXC/OJD9V2QOI=
+example.com.   3600    IN      SOA     ns.example.com. hostmaster.example.com. 200154054 28800 7200 604800 3600
+ENTRY_END
+RANGE_END
+
+STEP 1 QUERY
+ENTRY_BEGIN
+REPLY RD
+SECTION QUESTION
+www.example.com. IN A
+ENTRY_END
+
+; recursion happens here.
+STEP 20 CHECK_ANSWER
+ENTRY_BEGIN
+MATCH all
+REPLY QR RD RA SERVFAIL
+SECTION QUESTION
+www.example.com. IN A
+SECTION ANSWER
+ENTRY_END
+
+STEP 30 TIME_PASSES ELAPSE 10
+STEP 40 TRAFFIC
+
+STEP 50 QUERY
+ENTRY_BEGIN
+REPLY RD
+SECTION QUESTION
+www.example.com. IN A
+ENTRY_END
+
+; recursion happens here.
+STEP 60 CHECK_ANSWER
+ENTRY_BEGIN
+MATCH all
+REPLY QR RD RA NOERROR
+SECTION QUESTION
+www.example.com. IN A
+SECTION ANSWER
+www.example.com. IN A  127.0.0.1
+ENTRY_END
+
+; the zonefile was updated with new contents
+STEP 70 CHECK_TEMPFILE example.com
+FILE_BEGIN
+example.com.   3600    IN      SOA     ns.example.com. hostmaster.example.com. 200154054 28800 7200 604800 3600
+example.com.   3600    IN      RRSIG   SOA 8 2 3600 20201116135527 20201019135527 55566 example.com. gcFHT/Q4iDZ78CK6fyY2HZr8sRtgH2Rna9fEs06RW0gqMnfDntweoIaBamOZ7NlAP84aY2bZeanmEccmkHexByUpodCoKQ4NzVXctLr0TO4PVoFyfUfj62fjhM56SF8ioDxsoDQcPtYXcjNQjwfntWofMqHCMxrb9LzbgePzhOM=
+example.com.   3600    IN      NS      ns.example.com.
+example.com.   3600    IN      RRSIG   NS 8 2 3600 20201116135527 20201019135527 55566 example.com. X+V3XsbJbBi9OsHpjMkGCox8RLY/uXp/XX/O/flTrIre9fMDWm9ZGnewtuQFpLgGc6hUTi0eLsuRWRA5fZXEKUBhmoR2Ph01KgE1gvlL7v6zPWQwXVcBRUr3mOSbYdNNkHkXEjiDBGEhNkfqR216zNgw563eEGXOkLUFNIx5Zpg=
+example.com.   3600    IN      NSEC    bar.example.com. NS SOA RRSIG NSEC DNSKEY ZONEMD
+example.com.   3600    IN      RRSIG   NSEC 8 2 3600 20201116135527 20201019135527 55566 example.com. ufLrlOQprAqjnH85Rt3T0Mxd3ZB0mBeeNIr84eFJ8Rk6WiWEPm0Y1R7GRufNI24Mj7iqLcL4nJM6KK6B7dJqjqu73jw1acuYNnbsoV2BNDRXRFP2FNWTpctVdi+955f3FzgsmEJXfGiSUG0YXAEcZmdCPCn5ii2jk8mk7r6KKYo=
+example.com.   3600    IN      DNSKEY  256 3 8 AwEAAdug/L739i0mgN2nuK/bhxu3wFn5Ud9nK2+XUmZQlPUEZUC5YZvm1rfMmEWTGBn87fFxEu/kjFZHJ55JLzqsbbpVHLbmKCTT2gYR2FV2WDKROGKuYbVkJIXdKAjJ0ONuK507NinYvlWXIoxHn22KAWOd9wKgSTNHBlmGkX+ts3hh ;{id = 55566}
+example.com.   3600    IN      RRSIG   DNSKEY 8 2 3600 20201116135527 20201019135527 55566 example.com. fsdnVg38PKQTH2mDOwkXL6Jre7JP7Gf8WI3CvIbmeYQUJtAlpcSbZkS3wInm3kKMxOuT55BWzndQzpfmpo91OqJjG27W0k9301NMLUwFprA6b9HK+iPAT0JpYPDPzcm1bQdarLzLS+eD/GPwmyVSX7Gze+08VfE8m8sOW2r7UjA=
+example.com.   3600    IN      ZONEMD  200154054 1 2 58F7620F93204BBB31B44F795B3409CC4ABD9EF5601DECC15675BD7751213152984EDDCE0626E6062E744B03B3E47711202FBB79E4A2EB8BC5CF46741B5CAE6F
+example.com.   3600    IN      RRSIG   ZONEMD 8 2 3600 20201116135527 20201019135527 55566 example.com. orn8ZF/yqj9u4WrhiO6gtEcTaVsnZSWWZLfXhcIOiWSB8kKCxtZl5cG17dD3Du1NllUwMRqkp0KleLhIoUS9xeQ/0x05u+CYLrfQ62oAiD7q54ZQzpXJIH52aQzKV70ZnO03CZowhQBnetmIoKX6xLogKo8pt+BdQbo3oVHxV8Y=
+bar.example.com.       3600    IN      A       1.2.3.4
+bar.example.com.       3600    IN      RRSIG   A 8 3 3600 20201116135527 20201019135527 55566 example.com. NYhmRicF4C9+YxpWeQrepy4ALM1CM0USoDuGi3W5Xtp4/+YpCJfSIdR9vlJaJ2WayYuZrz9Ai2ci7oWwE1Fn3oywGwCKvGo9m0c3mC2eEtphE19wrop6pWu6um4RiFhmzYS1voraA3PAdYzze9U4NHzlk0+sb5vNZW9dSZS30Ds=
+bar.example.com.       3600    IN      NSEC    ding.example.com. A RRSIG NSEC
+bar.example.com.       3600    IN      RRSIG   NSEC 8 3 3600 20201116135527 20201019135527 55566 example.com. VhsGuBx20DXQZNU8ITAMnasn6NVyEjN9xtB8msH5xJn80UCuaqvFBURzcPWN3aHnykEvGfdPF/9P3WvlON0cMikWkqSLy6Q9bpvgAq13HWYh+ZcDoqLtICaB7RkBQc+6aHAqZFyQbD8/m8Kxt5eVJtV6rEuf+yPX0+3aXHhsRg0=
+ding.example.com.      3600    IN      A       1.2.3.4
+ding.example.com.      3600    IN      RRSIG   A 8 3 3600 20201116135527 20201019135527 55566 example.com. OERsruISkpd1s68ute8Xm8YXisBCTkkiDMt34K+0dVqvySOJq63d3qN18BeUxZxLyHDB1eR3nZZKqEdkTqrv2r98skhWhjnOECpFbu5gKjtN/KPexbbJ+rxC0QqciuWOC7M6YE0cvI17/RB9KhVRy5rqY2X4Gt2wk2CNeD1dAko=
+ding.example.com.      3600    IN      NSEC    foo.example.com. A RRSIG NSEC
+ding.example.com.      3600    IN      RRSIG   NSEC 8 3 3600 20201116135527 20201019135527 55566 example.com. nb1W2aaKrU5iAQiY8gMsoMOejID19JMTEwY2rRoe+KsvzMs0rE0ifEkqit4blXaU0tfy0foJ70uqdJFqBoGz1NcSwZ6GNk/iNfGvG3XpxZ/zqEe7kkIucqqei794G7z9psqV94yZ3WaT+IswPpWrSaWv1w41RtcWufPhe4fOAmU=
+foo.example.com.       3600    IN      A       1.2.3.4
+foo.example.com.       3600    IN      RRSIG   A 8 3 3600 20201116135527 20201019135527 55566 example.com. ZcUngb2pUejwnsshbJN/Dfr+Bzu8fcZXyqLArQ+10Bw1IPHyfx7yyUJ43V5tTYVHPSEsJzTnaWj+olVrNhVZxq5e0pgzSYPfGln2FEItEvMIOn33j8yKTpPW2MLyuFF5ZkXhosG20EUwRMvMmRHRz9mIZfwWoMbSGPukmLh8zMA=
+foo.example.com.       3600    IN      NSEC    ns.example.com. A RRSIG NSEC
+foo.example.com.       3600    IN      RRSIG   NSEC 8 3 3600 20201116135527 20201019135527 55566 example.com. fUZEpkEULRWDntN5Z7Kr8M83Hjhf08ECMKRpo6IBoBc3ayenj+YMgWAvFXC825wjENPYYWNGag0d32U83zCZxqgv+8uXZd3B7QDpTbL41aWZdc++s5YWTkYjyOWwJ1XHOv4nL3qEnJBXVzo/E1gbSKhTFuG97i+7J1MFd9MsC5s=
+ns.example.com.        3600    IN      A       127.0.0.1
+ns.example.com.        3600    IN      RRSIG   A 8 3 3600 20201116135527 20201019135527 55566 example.com. SiuxuPtN/ITd+Z20j8UNUHJWbLHirE8zQOWMv5fAZ1rPKpAidrZgUL8J417GdrTwkueU2ywAJ7EzFJSwNTa7o/wUnq7svmOR6Ze6UQsKuZFZGEfqPNDRp4YuF86LU5jChuo+f/IRpydHrxVwGxDPCR9KarDM+ewfW+yI5bZeZcg=
+ns.example.com.        3600    IN      NSEC    www.example.com. A RRSIG NSEC
+ns.example.com.        3600    IN      RRSIG   NSEC 8 3 3600 20201116135527 20201019135527 55566 example.com. 0upKNYjiow4NDJm3I1RbUddE9GGuFYEVKswww5BAc/6WHuukupncL30lskvcSKGpByDssP2Hi2CufyEtYeGWh6q1TxtOFRqFBX1p6Q5b3tBlCtvv4h31dQR9uqLvq+GkGS5MR+0LO5kWagIpZmnI8YY5plVdXEtNbp2Ar8zvz/A=
+www.example.com.       3600    IN      A       127.0.0.1
+www.example.com.       3600    IN      RRSIG   A 8 3 3600 20201116135527 20201019135527 55566 example.com. AaIeICaPjV50TDrpbyOn94+hs8EYIMTmN4pYqj7e8GIGimqQIk5jgpwSx6SOoOF+uOqkf9GKHkQTn5YVGaeXwEQleg7mPTmMYKAOk06Y7MFUO1Vwt1Vt7Wo+Cpa3x2a1CmEkfFOi4WqP43VJnUtjjKmXoKRz3VUmqByyJYUAGbQ=
+www.example.com.       3600    IN      NSEC    example.com. A RRSIG NSEC
+www.example.com.       3600    IN      RRSIG   NSEC 8 3 3600 20201116135527 20201019135527 55566 example.com. meg/t6nIBqQZ0d5/dT7uu/3CuP4vE+HxqFQaj2fjUNceA/6C7QIQnqQ5Kyblg+XijDkQX0yvyFNHYdgF16UDgFT7tlNUCHk1SpF5BWzV4c4tBEhxASTz7UQo111O3Tyd6CldPzO/Se15Ud0/ZYltHEqWTfY5nJoXC/OJD9V2QOI=
+FILE_END
+
+SCENARIO_END