]> git.ipfire.org Git - thirdparty/unbound.git/commitdiff
- Fix rpz so that rpz CNAME can apply after rpz CNAME. And fix that
authorW.C.A. Wijngaards <wouter@nlnetlabs.nl>
Tue, 19 Mar 2024 08:32:53 +0000 (09:32 +0100)
committerW.C.A. Wijngaards <wouter@nlnetlabs.nl>
Tue, 19 Mar 2024 08:32:53 +0000 (09:32 +0100)
  clientip and nsip can give a CNAME.

doc/Changelog
iterator/iterator.c
services/rpz.c
testdata/rpz_cname_handle.rpl [new file with mode: 0644]

index 421d79a96aa6aeed6ebefc688dcb06d1714a2889..48106ac467852ff5fbf00de165116e2257db4f9f 100644 (file)
@@ -1,3 +1,7 @@
+19 March 2024: Wouter
+       - Fix rpz so that rpz CNAME can apply after rpz CNAME. And fix that
+         clientip and nsip can give a CNAME.
+
 18 March 2024: Wouter
        - Fix that rpz CNAME content is limited to the max number of cnames.
        - Fix rpz, it follows iterator CNAMEs for nsip and nsdname and sets
index b6d0b67d4de7ffba0d9334ff411c8f7de34d421b..6ec8af401c8e67a653f16106ac572502f069e420 100644 (file)
@@ -1389,7 +1389,59 @@ processInitRequest(struct module_qstate* qstate, struct iter_qstate* iq,
 
        /* This either results in a query restart (CNAME cache response), a
         * terminating response (ANSWER), or a cache miss (null). */
-       
+
+       /* Check RPZ for override */
+       if(qstate->env->auth_zones) {
+               /* apply rpz qname triggers, like after cname */
+               struct dns_msg* forged_response =
+                       rpz_callback_from_iterator_cname(qstate, iq);
+               if(forged_response) {
+                       uint8_t* sname = 0;
+                       size_t slen = 0;
+                       int count = 0;
+                       while(forged_response && reply_find_rrset_section_an(
+                               forged_response->rep, iq->qchase.qname,
+                               iq->qchase.qname_len, LDNS_RR_TYPE_CNAME,
+                               iq->qchase.qclass) &&
+                               iq->qchase.qtype != LDNS_RR_TYPE_CNAME &&
+                               count++ < ie->max_query_restarts) {
+                               /* another cname to follow */
+                               if(!handle_cname_response(qstate, iq, forged_response,
+                                       &sname, &slen)) {
+                                       errinf(qstate, "malloc failure, CNAME info");
+                                       return error_response(qstate, id, LDNS_RCODE_SERVFAIL);
+                               }
+                               iq->qchase.qname = sname;
+                               iq->qchase.qname_len = slen;
+                               forged_response =
+                                       rpz_callback_from_iterator_cname(qstate, iq);
+                       }
+                       if(forged_response != NULL) {
+                               qstate->ext_state[id] = module_finished;
+                               qstate->return_rcode = LDNS_RCODE_NOERROR;
+                               qstate->return_msg = forged_response;
+                               iq->response = forged_response;
+                               next_state(iq, FINISHED_STATE);
+                               if(!iter_prepend(iq, qstate->return_msg, qstate->region)) {
+                                       log_err("rpz: after cached cname, prepend rrsets: out of memory");
+                                       return error_response(qstate, id, LDNS_RCODE_SERVFAIL);
+                               }
+                               qstate->return_msg->qinfo = qstate->qinfo;
+                               return 0;
+                       }
+                       /* Follow the CNAME response */
+                       iq->dp = NULL;
+                       iq->refetch_glue = 0;
+                       iq->query_restart_count++;
+                       iq->sent_count = 0;
+                       iq->dp_target_count = 0;
+                       sock_list_insert(&qstate->reply_origin, NULL, 0, qstate->region);
+                       if(qstate->env->cfg->qname_minimisation)
+                               iq->minimisation_state = INIT_MINIMISE_STATE;
+                       return next_state(iq, INIT_REQUEST_STATE);
+               }
+       }
+
        if (iter_stub_fwd_no_cache(qstate, &iq->qchase, &dpname, &dpnamelen)) {
                /* Asked to not query cache. */
                verbose(VERB_ALGO, "no-cache set, going to the network");
@@ -1449,42 +1501,6 @@ processInitRequest(struct module_qstate* qstate, struct iter_qstate* iq,
                        }
                        iq->qchase.qname = sname;
                        iq->qchase.qname_len = slen;
-                       if(qstate->env->auth_zones) {
-                               /* apply rpz qname triggers after cname */
-                               struct dns_msg* forged_response =
-                                       rpz_callback_from_iterator_cname(qstate, iq);
-                               int count = 0;
-                               while(forged_response && reply_find_rrset_section_an(
-                                       forged_response->rep, iq->qchase.qname,
-                                       iq->qchase.qname_len, LDNS_RR_TYPE_CNAME,
-                                       iq->qchase.qclass) &&
-                                       iq->qchase.qtype != LDNS_RR_TYPE_CNAME &&
-                                       count++ < ie->max_query_restarts) {
-                                       /* another cname to follow */
-                                       if(!handle_cname_response(qstate, iq, forged_response,
-                                               &sname, &slen)) {
-                                               errinf(qstate, "malloc failure, CNAME info");
-                                               return error_response(qstate, id, LDNS_RCODE_SERVFAIL);
-                                       }
-                                       iq->qchase.qname = sname;
-                                       iq->qchase.qname_len = slen;
-                                       forged_response =
-                                               rpz_callback_from_iterator_cname(qstate, iq);
-                               }
-                               if(forged_response != NULL) {
-                                       qstate->ext_state[id] = module_finished;
-                                       qstate->return_rcode = LDNS_RCODE_NOERROR;
-                                       qstate->return_msg = forged_response;
-                                       iq->response = forged_response;
-                                       next_state(iq, FINISHED_STATE);
-                                       if(!iter_prepend(iq, qstate->return_msg, qstate->region)) {
-                                               log_err("rpz: after cached cname, prepend rrsets: out of memory");
-                                               return error_response(qstate, id, LDNS_RCODE_SERVFAIL);
-                                       }
-                                       qstate->return_msg->qinfo = qstate->qinfo;
-                                       return 0;
-                               }
-                       }
                        /* This *is* a query restart, even if it is a cheap 
                         * one. */
                        iq->dp = NULL;
@@ -1497,7 +1513,6 @@ processInitRequest(struct module_qstate* qstate, struct iter_qstate* iq,
                                iq->minimisation_state = INIT_MINIMISE_STATE;
                        return next_state(iq, INIT_REQUEST_STATE);
                }
-
                /* if from cache, NULL, else insert 'cache IP' len=0 */
                if(qstate->reply_origin)
                        sock_list_insert(&qstate->reply_origin, NULL, 0, qstate->region);
index 0637e326241fcd5e86dae7085b4af92b52747b3d..0bbb886f90bcc9d24cd0bb64a31b7da636048d71 100644 (file)
@@ -1244,16 +1244,20 @@ rpz_find_zone(struct local_zones* zones, uint8_t* qname, size_t qname_len, uint1
 /** Find entry for RR type in the list of rrsets for the clientip. */
 static struct local_rrset*
 rpz_find_synthesized_rrset(uint16_t qtype,
-       struct clientip_synthesized_rr* data)
+       struct clientip_synthesized_rr* data, int alias_ok)
 {
-       struct local_rrset* cursor = data->data;
+       struct local_rrset* cursor = data->data, *cname = NULL;
        while( cursor != NULL) {
                struct packed_rrset_key* packed_rrset = &cursor->rrset->rk;
                if(htons(qtype) == packed_rrset->type) {
                        return cursor;
                }
+               if(ntohs(packed_rrset->type) == LDNS_RR_TYPE_CNAME && alias_ok)
+                       cname = cursor;
                cursor = cursor->next;
        }
+       if(alias_ok)
+               return cname;
        return NULL;
 }
 
@@ -1439,7 +1443,7 @@ static int rpz_remove_clientip_rr(struct clientip_synthesized_rr* node,
        struct local_rrset* rrset;
        struct packed_rrset_data* d;
        size_t index;
-       rrset = rpz_find_synthesized_rrset(rr_type, node);
+       rrset = rpz_find_synthesized_rrset(rr_type, node, 0);
        if(rrset == NULL)
                return 0; /* type not found, ignore */
        d = (struct packed_rrset_data*)rrset->rrset->entry.data;
@@ -1842,7 +1846,7 @@ rpz_apply_clientip_localdata_action(struct clientip_synthesized_rr* raddr,
        }
 
        /* check query type / rr type */
-       rrset = rpz_find_synthesized_rrset(qinfo->qtype, raddr);
+       rrset = rpz_find_synthesized_rrset(qinfo->qtype, raddr, 1);
        if(rrset == NULL) {
                verbose(VERB_ALGO, "rpz: unable to find local-data for query");
                rrset_count = 0;
@@ -2056,7 +2060,7 @@ rpz_synthesize_nsip_localdata(struct rpz* r, struct module_qstate* ms,
 {
        struct local_rrset* rrset;
 
-       rrset = rpz_find_synthesized_rrset(qi->qtype, data);
+       rrset = rpz_find_synthesized_rrset(qi->qtype, data, 1);
        if(rrset == NULL) {
                verbose(VERB_ALGO, "rpz: nsip: no matching local data found");
                return NULL;
@@ -2128,12 +2132,12 @@ rpz_synthesize_qname_localdata_msg(struct rpz* r, struct module_qstate* ms,
        key.namelabs = dname_count_labels(qinfo->qname);
        ld = (struct local_data*)rbtree_search(&z->data, &key.node);
        if(ld == NULL) {
-               verbose(VERB_ALGO, "rpz: qname after cname: name not found");
+               verbose(VERB_ALGO, "rpz: qname: name not found");
                return NULL;
        }
        rrset = local_data_find_type(ld, qinfo->qtype, 1);
        if(rrset == NULL) {
-               verbose(VERB_ALGO, "rpz: qname after cname: type not found");
+               verbose(VERB_ALGO, "rpz: qname: type not found");
                return NULL;
        }
        return rpz_synthesize_localdata_from_rrset(r, ms, qinfo, rrset, az);
@@ -2539,10 +2543,10 @@ struct dns_msg* rpz_callback_from_iterator_cname(struct module_qstate* ms,
                dname_str(is->qchase.qname, nm);
                dname_str(z->name, zn);
                if(strcmp(zn, nm) != 0)
-                       verbose(VERB_ALGO, "rpz: qname trigger after cname %s on %s, with action=%s",
+                       verbose(VERB_ALGO, "rpz: qname trigger %s on %s, with action=%s",
                                zn, nm, rpz_action_to_string(localzone_type_to_rpz_action(lzt)));
                else
-                       verbose(VERB_ALGO, "rpz: qname trigger after cname %s, with action=%s",
+                       verbose(VERB_ALGO, "rpz: qname trigger %s, with action=%s",
                                nm, rpz_action_to_string(localzone_type_to_rpz_action(lzt)));
        }
        switch(localzone_type_to_rpz_action(lzt)) {
@@ -2571,7 +2575,7 @@ struct dns_msg* rpz_callback_from_iterator_cname(struct module_qstate* ms,
                ms->rpz_passthru = 1;
                break;
        default:
-               verbose(VERB_ALGO, "rpz: qname trigger after cname: bug: unhandled or invalid action: '%s'",
+               verbose(VERB_ALGO, "rpz: qname trigger: bug: unhandled or invalid action: '%s'",
                        rpz_action_to_string(localzone_type_to_rpz_action(lzt)));
                ret = NULL;
        }
diff --git a/testdata/rpz_cname_handle.rpl b/testdata/rpz_cname_handle.rpl
new file mode 100644 (file)
index 0000000..38dddf1
--- /dev/null
@@ -0,0 +1,779 @@
+; config options
+server:
+       module-config: "respip validator iterator"
+       target-fetch-policy: "0 0 0 0 0"
+       qname-minimisation: no
+       access-control: 192.0.0.0/8 allow
+
+rpz:
+       name: "rpz.example.com."
+       rpz-log: yes
+       rpz-log-name: "rpz.example.com"
+       zonefile:
+TEMPFILE_NAME rpz.example.com
+TEMPFILE_CONTENTS rpz.example.com
+$ORIGIN example.com.
+rpz    3600    IN      SOA     ns1.rpz.example.com. hostmaster.rpz.example.com. (
+               1379078166 28800 7200 604800 7200 )
+       3600    IN      NS      ns1.rpz.example.com.
+       3600    IN      NS      ns2.rpz.example.com.
+$ORIGIN rpz.example.com.
+www.gotham.a A 1.2.3.61
+www.gotham2.a CNAME g2.target.a.
+g2.target.a A 1.2.3.62
+www.gotham3.a CNAME g3.target.a.
+g3.target.a CNAME g3b.target.a.
+g3b.target.a A 1.2.3.63
+www.gotham4.a CNAME g4.target.a.
+g4.target.a CNAME g4b.target.a.
+g4b.target.a CNAME g4c.target.a.
+g4c.target.a A 1.2.3.64
+w2.gotham5.a A 1.2.3.65
+w2.gotham6.a CNAME g6.target.a.
+g6.target.a A 1.2.3.66
+w2.gotham7.a CNAME g7.target.a.
+g7.target.a CNAME g7b.target.a.
+g7b.target.a A 1.2.3.66
+; ns1.gotham8.a
+32.48.30.20.10.rpz-nsip A 1.2.3.68
+; ns1.gotham9.a
+32.49.30.20.10.rpz-nsip CNAME g9.target.a.
+g9.target.a A 1.2.3.69
+; ns1.gotham10.a
+32.50.30.20.10.rpz-nsip CNAME g10.target.a.
+g10.target.a CNAME g10b.target.a.
+g10b.target.a A 1.2.3.70
+www.gotham11.a CNAME g11.target.a.
+www.gotham12.a CNAME g12.target.a.
+g12.target.a CNAME g12b.target.a.
+www.gotham13.a CNAME g13.target.a.
+g13.target.a CNAME g13b.target.a.
+g13b.target.a CNAME g13c.target.a.
+w2.gotham14.a CNAME g14.target.a.
+w2.gotham15.a CNAME g15.target.a.
+g15.target.a CNAME g15b.target.a.
+; ns1.gotham16.a
+32.56.30.20.10.rpz-nsip CNAME g16.target.a.
+; ns1.gotham17.a
+32.57.30.20.10.rpz-nsip CNAME g17.target.a.
+g17.target.a CNAME g17b.target.a.
+TEMPFILE_END
+
+stub-zone:
+       name: "a."
+       stub-addr: 10.20.30.40
+CONFIG_END
+
+SCENARIO_BEGIN Test RPZ handling of CNAMEs.
+
+; a.
+RANGE_BEGIN 0 1000
+       ADDRESS 10.20.30.40
+ENTRY_BEGIN
+MATCH opcode subdomain
+ADJUST copy_id copy_query
+REPLY QR NOERROR
+SECTION QUESTION
+gotham5.a. IN NS
+SECTION AUTHORITY
+gotham5.a. NS ns1.gotham5.a.
+SECTION ADDITIONAL
+ns1.gotham5.a. A 10.20.30.45
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode subdomain
+ADJUST copy_id copy_query
+REPLY QR NOERROR
+SECTION QUESTION
+gotham6.a. IN NS
+SECTION AUTHORITY
+gotham6.a. NS ns1.gotham6.a.
+SECTION ADDITIONAL
+ns1.gotham6.a. A 10.20.30.46
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode subdomain
+ADJUST copy_id copy_query
+REPLY QR NOERROR
+SECTION QUESTION
+gotham7.a. IN NS
+SECTION AUTHORITY
+gotham7.a. NS ns1.gotham7.a.
+SECTION ADDITIONAL
+ns1.gotham7.a. A 10.20.30.47
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode subdomain
+ADJUST copy_id copy_query
+REPLY QR NOERROR
+SECTION QUESTION
+gotham8.a. IN NS
+SECTION AUTHORITY
+gotham8.a. NS ns1.gotham8.a.
+SECTION ADDITIONAL
+ns1.gotham8.a. A 10.20.30.48
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode subdomain
+ADJUST copy_id copy_query
+REPLY QR NOERROR
+SECTION QUESTION
+gotham9.a. IN NS
+SECTION AUTHORITY
+gotham9.a. NS ns1.gotham9.a.
+SECTION ADDITIONAL
+ns1.gotham9.a. A 10.20.30.49
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode subdomain
+ADJUST copy_id copy_query
+REPLY QR NOERROR
+SECTION QUESTION
+gotham10.a. IN NS
+SECTION AUTHORITY
+gotham10.a. NS ns1.gotham10.a.
+SECTION ADDITIONAL
+ns1.gotham10.a. A 10.20.30.50
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode subdomain
+ADJUST copy_id copy_query
+REPLY QR NOERROR
+SECTION QUESTION
+gotham14.a. IN NS
+SECTION AUTHORITY
+gotham14.a. NS ns1.gotham14.a.
+SECTION ADDITIONAL
+ns1.gotham14.a. A 10.20.30.54
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode subdomain
+ADJUST copy_id copy_query
+REPLY QR NOERROR
+SECTION QUESTION
+gotham15.a. IN NS
+SECTION AUTHORITY
+gotham15.a. NS ns1.gotham15.a.
+SECTION ADDITIONAL
+ns1.gotham15.a. A 10.20.30.55
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode subdomain
+ADJUST copy_id copy_query
+REPLY QR NOERROR
+SECTION QUESTION
+gotham16.a. IN NS
+SECTION AUTHORITY
+gotham16.a. NS ns1.gotham16.a.
+SECTION ADDITIONAL
+ns1.gotham16.a. A 10.20.30.56
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode subdomain
+ADJUST copy_id copy_query
+REPLY QR NOERROR
+SECTION QUESTION
+gotham17.a. IN NS
+SECTION AUTHORITY
+gotham17.a. NS ns1.gotham17.a.
+SECTION ADDITIONAL
+ns1.gotham17.a. A 10.20.30.57
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode qtype qname
+ADJUST copy_id
+REPLY QR NOERROR
+SECTION QUESTION
+target.a. IN A
+SECTION ANSWER
+target.a. IN A 1.2.3.6
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode qtype qname
+ADJUST copy_id
+REPLY QR AA NOERROR
+SECTION QUESTION
+g11.target.a. IN A
+SECTION ANSWER
+g11.target.a. IN A 1.2.3.11
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode qtype qname
+ADJUST copy_id
+REPLY QR AA NOERROR
+SECTION QUESTION
+g12b.target.a. IN A
+SECTION ANSWER
+g12b.target.a. A 1.2.3.12
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode qtype qname
+ADJUST copy_id
+REPLY QR AA NOERROR
+SECTION QUESTION
+g13c.target.a. IN A
+SECTION ANSWER
+g13c.target.a. A 1.2.3.13
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode qtype qname
+ADJUST copy_id
+REPLY QR AA NOERROR
+SECTION QUESTION
+g14.target.a. IN A
+SECTION ANSWER
+g14.target.a. A 1.2.3.14
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode qtype qname
+ADJUST copy_id
+REPLY QR AA NOERROR
+SECTION QUESTION
+g15b.target.a. IN A
+SECTION ANSWER
+g15b.target.a. A 1.2.3.15
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode qtype qname
+ADJUST copy_id
+REPLY QR AA NOERROR
+SECTION QUESTION
+g16.target.a. IN A
+SECTION ANSWER
+g16.target.a. A 1.2.3.16
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode qtype qname
+ADJUST copy_id
+REPLY QR AA NOERROR
+SECTION QUESTION
+g17b.target.a. IN A
+SECTION ANSWER
+g17b.target.a. A 1.2.3.17
+ENTRY_END
+RANGE_END
+
+; gotham5.a.
+RANGE_BEGIN 0 1000
+       ADDRESS 10.20.30.45
+ENTRY_BEGIN
+MATCH opcode qtype qname
+ADJUST copy_id
+REPLY QR AA NOERROR
+SECTION QUESTION
+www.gotham5.a. IN A
+SECTION ANSWER
+www.gotham5.a. CNAME w2.gotham5.a.
+ENTRY_END
+RANGE_END
+
+; gotham6.a.
+RANGE_BEGIN 0 1000
+       ADDRESS 10.20.30.46
+ENTRY_BEGIN
+MATCH opcode qtype qname
+ADJUST copy_id
+REPLY QR NOERROR
+SECTION QUESTION
+www.gotham6.a. IN A
+SECTION ANSWER
+www.gotham6.a. CNAME w2.gotham6.a.
+ENTRY_END
+RANGE_END
+
+; gotham7.a.
+RANGE_BEGIN 0 1000
+       ADDRESS 10.20.30.47
+ENTRY_BEGIN
+MATCH opcode qtype qname
+ADJUST copy_id
+REPLY QR AA NOERROR
+SECTION QUESTION
+www.gotham7.a. IN A
+SECTION ANSWER
+www.gotham7.a. CNAME w2.gotham7.a.
+ENTRY_END
+RANGE_END
+
+; gotham14.a.
+RANGE_BEGIN 0 1000
+       ADDRESS 10.20.30.54
+ENTRY_BEGIN
+MATCH opcode qtype qname
+ADJUST copy_id
+REPLY QR AA NOERROR
+SECTION QUESTION
+www.gotham14.a. IN A
+SECTION ANSWER
+www.gotham14.a. CNAME w2.gotham14.a.
+ENTRY_END
+RANGE_END
+
+; gotham15.a.
+RANGE_BEGIN 0 1000
+       ADDRESS 10.20.30.55
+ENTRY_BEGIN
+MATCH opcode qtype qname
+ADJUST copy_id
+REPLY QR AA NOERROR
+SECTION QUESTION
+www.gotham15.a. IN A
+SECTION ANSWER
+www.gotham15.a. CNAME w2.gotham15.a.
+ENTRY_END
+RANGE_END
+
+; Test with zero rpz CNAMEs, rpz answer.
+STEP 10 QUERY
+ENTRY_BEGIN
+REPLY RD
+SECTION QUESTION
+www.gotham.a.  IN      A
+ENTRY_END
+
+STEP 11 CHECK_ANSWER
+ENTRY_BEGIN
+MATCH all
+REPLY QR RD RA AA NOERROR
+SECTION QUESTION
+www.gotham.a.  IN      A
+SECTION ANSWER
+www.gotham.a. A 1.2.3.61
+ENTRY_END
+
+; Test with one rpz CNAME, rpz answer.
+STEP 20 QUERY
+ENTRY_BEGIN
+REPLY RD
+SECTION QUESTION
+www.gotham2.a. IN      A
+ENTRY_END
+
+STEP 21 CHECK_ANSWER
+ENTRY_BEGIN
+MATCH all
+REPLY QR RD RA AA NOERROR
+SECTION QUESTION
+www.gotham2.a. IN      A
+SECTION ANSWER
+www.gotham2.a. CNAME g2.target.a.
+g2.target.a. A 1.2.3.62
+ENTRY_END
+
+; Test with two rpz CNAMEs, rpz answer.
+STEP 30 QUERY
+ENTRY_BEGIN
+REPLY RD
+SECTION QUESTION
+www.gotham3.a. IN      A
+ENTRY_END
+
+STEP 31 CHECK_ANSWER
+ENTRY_BEGIN
+MATCH all
+REPLY QR RD RA AA NOERROR
+SECTION QUESTION
+www.gotham3.a. IN      A
+SECTION ANSWER
+www.gotham3.a. CNAME g3.target.a.
+g3.target.a. CNAME g3b.target.a.
+g3b.target.a. A 1.2.3.63
+ENTRY_END
+
+; Test with three rpz CNAMEs, rpz answer.
+STEP 40 QUERY
+ENTRY_BEGIN
+REPLY RD
+SECTION QUESTION
+www.gotham4.a. IN      A
+ENTRY_END
+
+STEP 41 CHECK_ANSWER
+ENTRY_BEGIN
+MATCH all
+REPLY QR RD RA AA NOERROR
+SECTION QUESTION
+www.gotham4.a. IN      A
+SECTION ANSWER
+www.gotham4.a. CNAME g4.target.a.
+g4.target.a. CNAME g4b.target.a.
+g4b.target.a. CNAME g4c.target.a.
+g4c.target.a. A 1.2.3.64
+ENTRY_END
+
+; Test with a CNAME from upstream, zero rpz CNAMEs, rpz answer.
+STEP 50 QUERY
+ENTRY_BEGIN
+REPLY RD
+SECTION QUESTION
+www.gotham5.a. IN      A
+ENTRY_END
+
+STEP 51 CHECK_ANSWER
+ENTRY_BEGIN
+MATCH all
+REPLY QR RD RA AA NOERROR
+SECTION QUESTION
+www.gotham5.a. IN      A
+SECTION ANSWER
+www.gotham5.a. CNAME w2.gotham5.a.
+w2.gotham5.a. A 1.2.3.65
+ENTRY_END
+
+; Test with a CNAME from upstream, one rpz CNAME, rpz answer.
+STEP 60 QUERY
+ENTRY_BEGIN
+REPLY RD
+SECTION QUESTION
+www.gotham6.a. IN      A
+ENTRY_END
+
+STEP 61 CHECK_ANSWER
+ENTRY_BEGIN
+MATCH all
+REPLY QR RD RA AA NOERROR
+SECTION QUESTION
+www.gotham6.a. IN      A
+SECTION ANSWER
+www.gotham6.a. CNAME w2.gotham6.a.
+w2.gotham6.a. CNAME g6.target.a.
+g6.target.a. A 1.2.3.66
+ENTRY_END
+
+; Test with a CNAME from upstream, two rpz CNAMEs, rpz answer.
+STEP 70 QUERY
+ENTRY_BEGIN
+REPLY RD
+SECTION QUESTION
+www.gotham7.a. IN      A
+ENTRY_END
+
+STEP 71 CHECK_ANSWER
+ENTRY_BEGIN
+MATCH all
+REPLY QR RD RA AA NOERROR
+SECTION QUESTION
+www.gotham7.a. IN      A
+SECTION ANSWER
+www.gotham7.a. CNAME w2.gotham7.a.
+w2.gotham7.a. CNAME g7.target.a.
+g7.target.a. CNAME g7b.target.a.
+g7b.target.a. A 1.2.3.66
+ENTRY_END
+
+; Test with a CNAME from cache, zero rpz CNAMEs, rpz answer.
+STEP 80 QUERY
+ENTRY_BEGIN
+REPLY RD
+SECTION QUESTION
+www.gotham5.a. IN      A
+ENTRY_END
+
+STEP 81 CHECK_ANSWER
+ENTRY_BEGIN
+MATCH all
+REPLY QR RD RA AA NOERROR
+SECTION QUESTION
+www.gotham5.a. IN      A
+SECTION ANSWER
+www.gotham5.a. CNAME w2.gotham5.a.
+w2.gotham5.a. A 1.2.3.65
+ENTRY_END
+
+; Test with a CNAME from cache, one rpz CNAME, rpz answer.
+STEP 90 QUERY
+ENTRY_BEGIN
+REPLY RD
+SECTION QUESTION
+www.gotham6.a. IN      A
+ENTRY_END
+
+STEP 91 CHECK_ANSWER
+ENTRY_BEGIN
+MATCH all
+REPLY QR RD RA AA NOERROR
+SECTION QUESTION
+www.gotham6.a. IN      A
+SECTION ANSWER
+www.gotham6.a. CNAME w2.gotham6.a.
+w2.gotham6.a. CNAME g6.target.a.
+g6.target.a. A 1.2.3.66
+ENTRY_END
+
+; Test with a CNAME from cache, two rpz CNAMEs, rpz answer.
+STEP 100 QUERY
+ENTRY_BEGIN
+REPLY RD
+SECTION QUESTION
+www.gotham7.a. IN      A
+ENTRY_END
+
+STEP 101 CHECK_ANSWER
+ENTRY_BEGIN
+MATCH all
+REPLY QR RD RA AA NOERROR
+SECTION QUESTION
+www.gotham7.a. IN      A
+SECTION ANSWER
+www.gotham7.a. CNAME w2.gotham7.a.
+w2.gotham7.a. CNAME g7.target.a.
+g7.target.a. CNAME g7b.target.a.
+g7b.target.a. A 1.2.3.66
+ENTRY_END
+
+; Test with lookup from nameserver, zero rpz CNAMEs, rpz nsip answer.
+STEP 110 QUERY
+ENTRY_BEGIN
+REPLY RD
+SECTION QUESTION
+www.gotham8.a. IN      A
+ENTRY_END
+
+STEP 111 CHECK_ANSWER
+ENTRY_BEGIN
+MATCH all
+REPLY QR RD RA AA NOERROR
+SECTION QUESTION
+www.gotham8.a. IN      A
+SECTION ANSWER
+www.gotham8.a. A 1.2.3.68
+ENTRY_END
+
+; Test with lookup from nameserver, one rpz CNAME, rpz nsip answer.
+STEP 120 QUERY
+ENTRY_BEGIN
+REPLY RD
+SECTION QUESTION
+www.gotham9.a. IN      A
+ENTRY_END
+
+STEP 121 CHECK_ANSWER
+ENTRY_BEGIN
+MATCH all
+REPLY QR RD RA AA NOERROR
+SECTION QUESTION
+www.gotham9.a. IN      A
+SECTION ANSWER
+www.gotham9.a. CNAME g9.target.a.
+g9.target.a. A 1.2.3.69
+ENTRY_END
+
+; Test with lookup from nameserver, two rpz CNAMEs, rpz nsip answer.
+STEP 130 QUERY
+ENTRY_BEGIN
+REPLY RD
+SECTION QUESTION
+www.gotham10.a.        IN      A
+ENTRY_END
+
+STEP 131 CHECK_ANSWER
+ENTRY_BEGIN
+MATCH all
+REPLY QR RD RA AA NOERROR
+SECTION QUESTION
+www.gotham10.a.        IN      A
+SECTION ANSWER
+www.gotham10.a. CNAME g10.target.a.
+g10.target.a. CNAME g10b.target.a.
+g10b.target.a. A 1.2.3.70
+ENTRY_END
+
+; Test with one rpz CNAME, upstream answer.
+STEP 140 QUERY
+ENTRY_BEGIN
+REPLY RD
+SECTION QUESTION
+www.gotham11.a.        IN      A
+ENTRY_END
+
+STEP 141 CHECK_ANSWER
+ENTRY_BEGIN
+MATCH all
+REPLY QR RD RA AA NOERROR
+SECTION QUESTION
+www.gotham11.a.        IN      A
+SECTION ANSWER
+www.gotham11.a. CNAME g11.target.a.
+g11.target.a. A 1.2.3.11
+ENTRY_END
+
+; Test with two rpz CNAMEs, upstream answer.
+STEP 150 QUERY
+ENTRY_BEGIN
+REPLY RD
+SECTION QUESTION
+www.gotham12.a.        IN      A
+ENTRY_END
+
+STEP 151 CHECK_ANSWER
+ENTRY_BEGIN
+MATCH all
+REPLY QR RD RA AA NOERROR
+SECTION QUESTION
+www.gotham12.a.        IN      A
+SECTION ANSWER
+www.gotham12.a. CNAME g12.target.a.
+g12.target.a. CNAME g12b.target.a.
+g12b.target.a. A 1.2.3.12
+ENTRY_END
+
+; Test with three rpz CNAMEs, upstream answer.
+STEP 160 QUERY
+ENTRY_BEGIN
+REPLY RD
+SECTION QUESTION
+www.gotham13.a.        IN      A
+ENTRY_END
+
+STEP 161 CHECK_ANSWER
+ENTRY_BEGIN
+MATCH all
+REPLY QR RD RA AA NOERROR
+SECTION QUESTION
+www.gotham13.a.        IN      A
+SECTION ANSWER
+www.gotham13.a. CNAME g13.target.a.
+g13.target.a. CNAME g13b.target.a.
+g13b.target.a. CNAME g13c.target.a.
+g13c.target.a. A 1.2.3.13
+ENTRY_END
+
+; Test with a CNAME from upstream, one rpz CNAME, upstream answer.
+STEP 170 QUERY
+ENTRY_BEGIN
+REPLY RD
+SECTION QUESTION
+www.gotham14.a.        IN      A
+ENTRY_END
+
+STEP 171 CHECK_ANSWER
+ENTRY_BEGIN
+MATCH all
+REPLY QR RD RA NOERROR
+SECTION QUESTION
+www.gotham14.a.        IN      A
+SECTION ANSWER
+www.gotham14.a. CNAME w2.gotham14.a.
+w2.gotham14.a. CNAME g14.target.a.
+g14.target.a. A 1.2.3.14
+ENTRY_END
+
+; Test with a CNAME from upstream, two rpz CNAMEs, upstream answer.
+STEP 180 QUERY
+ENTRY_BEGIN
+REPLY RD
+SECTION QUESTION
+www.gotham15.a.        IN      A
+ENTRY_END
+
+STEP 181 CHECK_ANSWER
+ENTRY_BEGIN
+MATCH all
+REPLY QR RD RA NOERROR
+SECTION QUESTION
+www.gotham15.a.        IN      A
+SECTION ANSWER
+www.gotham15.a. CNAME w2.gotham15.a.
+w2.gotham15.a. CNAME g15.target.a.
+g15.target.a. CNAME g15b.target.a.
+g15b.target.a. A 1.2.3.15
+ENTRY_END
+
+; Test with a CNAME from cache, one rpz CNAME, upstream answer.
+STEP 190 QUERY
+ENTRY_BEGIN
+REPLY RD
+SECTION QUESTION
+www.gotham14.a.        IN      A
+ENTRY_END
+
+STEP 191 CHECK_ANSWER
+ENTRY_BEGIN
+MATCH all
+REPLY QR RD RA NOERROR
+SECTION QUESTION
+www.gotham14.a.        IN      A
+SECTION ANSWER
+www.gotham14.a. CNAME w2.gotham14.a.
+w2.gotham14.a. CNAME g14.target.a.
+g14.target.a. A 1.2.3.14
+ENTRY_END
+
+; Test with a CNAME from cache, two rpz CNAMEs, upstream answer.
+STEP 200 QUERY
+ENTRY_BEGIN
+REPLY RD
+SECTION QUESTION
+www.gotham15.a.        IN      A
+ENTRY_END
+
+STEP 201 CHECK_ANSWER
+ENTRY_BEGIN
+MATCH all
+REPLY QR RD RA NOERROR
+SECTION QUESTION
+www.gotham15.a.        IN      A
+SECTION ANSWER
+www.gotham15.a. CNAME w2.gotham15.a.
+w2.gotham15.a. CNAME g15.target.a.
+g15.target.a. CNAME g15b.target.a.
+g15b.target.a. A 1.2.3.15
+ENTRY_END
+
+; Test with lookup from nameserver, one rpz nsip CNAME, upstream answer.
+STEP 210 QUERY
+ENTRY_BEGIN
+REPLY RD
+SECTION QUESTION
+www.gotham16.a.        IN      A
+ENTRY_END
+
+STEP 211 CHECK_ANSWER
+ENTRY_BEGIN
+MATCH all
+REPLY QR RD RA NOERROR
+SECTION QUESTION
+www.gotham16.a.        IN      A
+SECTION ANSWER
+www.gotham16.a. CNAME g16.target.a.
+g16.target.a. A 1.2.3.16
+ENTRY_END
+
+; Test with lookup from nameserver, two rpz nsip CNAMEs, upstream answer.
+STEP 220 QUERY
+ENTRY_BEGIN
+REPLY RD
+SECTION QUESTION
+www.gotham17.a.        IN      A
+ENTRY_END
+
+STEP 221 CHECK_ANSWER
+ENTRY_BEGIN
+MATCH all
+REPLY QR RD RA NOERROR
+SECTION QUESTION
+www.gotham17.a.        IN      A
+SECTION ANSWER
+www.gotham17.a. CNAME g17.target.a.
+g17.target.a. CNAME g17b.target.a.
+g17b.target.a. A 1.2.3.17
+ENTRY_END
+
+SCENARIO_END