]> git.ipfire.org Git - thirdparty/unbound.git/commitdiff
RPZ: provide rpz-client-ip trigger and actions
authormb <mb@64k.by>
Wed, 4 Nov 2020 16:00:28 +0000 (17:00 +0100)
committermb <mb@64k.by>
Wed, 4 Nov 2020 16:00:28 +0000 (17:00 +0100)
services/localzone.c
services/rpz.c
services/rpz.h
testdata/rpz_clientip.rpl [new file with mode: 0644]

index b5d8472bc37a5a6c6b3fd2f2c867b377db8981cf..5e2104a7cf923365ca839e409cd375cb9c4b7e08 100644 (file)
@@ -1561,7 +1561,7 @@ local_zones_zone_answer(struct local_zone* z, struct module_env* env,
                        lz_type == local_zone_truncate)?
                        LDNS_RCODE_NOERROR:LDNS_RCODE_NXDOMAIN;
                rcode = lz_type == local_zone_truncate ? (rcode|BIT_TC) : rcode;
-               if(z->soa)
+               if(z != NULL && z->soa)
                        return local_encode(qinfo, env, edns, repinfo, buf, temp,
                                z->soa, 0, rcode);
                local_error_encode(qinfo, env, edns, repinfo, buf, temp, rcode,
@@ -1578,7 +1578,7 @@ local_zones_zone_answer(struct local_zone* z, struct module_env* env,
         * does not, then we should make this noerror/nodata */
        if(ld && ld->rrsets) {
                int rcode = LDNS_RCODE_NOERROR;
-               if(z->soa)
+               if(z != NULL && z->soa)
                        return local_encode(qinfo, env, edns, repinfo, buf, temp,
                                z->soa, 0, rcode);
                local_error_encode(qinfo, env, edns, repinfo, buf, temp, rcode,
index fb047a7f2dd65363aeb9d7846a138a1725b7df77..f8fa803de62327c3ca08bfcec8357f1f262a85f8 100644 (file)
@@ -51,6 +51,8 @@
 #include "util/locks.h"
 #include "util/regional.h"
 
+typedef struct resp_addr rpz_aclnode_type;
+
 /** string for RPZ action enum */
 const char*
 rpz_action_to_string(enum rpz_action a)
@@ -305,6 +307,7 @@ void rpz_delete(struct rpz* r)
                return;
        local_zones_delete(r->local_zones);
        respip_set_delete(r->respip_set);
+       respip_set_delete(r->client_set);
        regional_destroy(r->region);
        free(r->taglist);
        free(r->log_name);
@@ -317,12 +320,16 @@ rpz_clear(struct rpz* r)
        /* must hold write lock on auth_zone */
        local_zones_delete(r->local_zones);
        respip_set_delete(r->respip_set);
+       respip_set_delete(r->client_set);
        if(!(r->local_zones = local_zones_create())){
                return 0;
        }
        if(!(r->respip_set = respip_set_create())) {
                return 0;
        }
+       if(!(r->client_set = respip_set_create())) {
+               return 0;
+       }
        return 1;
 }
 
@@ -332,6 +339,10 @@ rpz_finish_config(struct rpz* r)
        lock_rw_wrlock(&r->respip_set->lock);
        addr_tree_init_parents(&r->respip_set->ip_tree);
        lock_rw_unlock(&r->respip_set->lock);
+
+       lock_rw_wrlock(&r->client_set->lock);
+       addr_tree_init_parents(&r->client_set->ip_tree);
+       lock_rw_unlock(&r->client_set->lock);
 }
 
 /** new rrset containing CNAME override, does not yet contain a dname */
@@ -398,6 +409,12 @@ rpz_create(struct config_auth* p)
        if(!(r->respip_set = respip_set_create())) {
                goto err;
        }
+
+       /* (ab)use respip for client acl */
+       if(!(r->client_set = respip_set_create())) {
+               goto err;
+       }
+
        r->taglistlen = p->rpz_taglistlen;
        r->taglist = memdup(p->rpz_taglist, r->taglistlen);
        if(p->rpz_action_override) {
@@ -440,6 +457,8 @@ err:
                        local_zones_delete(r->local_zones);
                if(r->respip_set)
                        respip_set_delete(r->respip_set);
+               if(r->client_set)
+                       respip_set_delete(r->client_set);
                if(r->taglist)
                        free(r->taglist);
                if(r->region)
@@ -541,52 +560,36 @@ rpz_insert_qname_trigger(struct rpz* r, uint8_t* dname, size_t dnamelen,
        return;
 }
 
-/** Insert RR into RPZ's respip_set */
 static int
-rpz_insert_response_ip_trigger(struct rpz* r, uint8_t* dname, size_t dnamelen,
-       enum rpz_action a, uint16_t rrtype, uint16_t rrclass, uint32_t ttl,
-       uint8_t* rdata, size_t rdata_len, uint8_t* rr, size_t rr_len)
+rpz_insert_ipaddr_based_trigger(struct respip_set* set, struct sockaddr_storage* addr,
+       socklen_t addrlen, int net, enum rpz_action a, uint16_t rrtype,
+       uint16_t rrclass, uint32_t ttl, uint8_t* rdata, size_t rdata_len,
+       uint8_t* rr, size_t rr_len)
 {
        struct resp_addr* node;
-       struct sockaddr_storage addr;
-       socklen_t addrlen;
-       int net, af;
        char* rrstr;
        enum respip_action respa = rpz_action_to_respip_action(a);
 
-       if(a == RPZ_INVALID_ACTION || respa == respip_invalid) {
-               verbose(VERB_ALGO, "RPZ: skipping unsupported action: %s",
-                       rpz_action_to_string(a));
-               return 0;
-       }
-
-       if(a == RPZ_TCP_ONLY_ACTION) {
-               verbose(VERB_ALGO, "RPZ: insert respip trigger: tcp-only");
-       }
-
-       if(!netblockdnametoaddr(dname, dnamelen, &addr, &addrlen, &net, &af))
-               return 0;
-
-       lock_rw_wrlock(&r->respip_set->lock);
+       lock_rw_wrlock(&set->lock);
        rrstr = sldns_wire2str_rr(rr, rr_len);
        if(!rrstr) {
                log_err("malloc error while inserting RPZ respip trigger");
-               lock_rw_unlock(&r->respip_set->lock);
+               lock_rw_unlock(&set->lock);
                return 0;
        }
-       if(!(node=respip_sockaddr_find_or_create(r->respip_set, &addr, addrlen,
+       if(!(node=respip_sockaddr_find_or_create(set, addr, addrlen,
                net, 1, rrstr))) {
-               lock_rw_unlock(&r->respip_set->lock);
+               lock_rw_unlock(&set->lock);
                free(rrstr);
                return 0;
        }
 
        lock_rw_wrlock(&node->lock);
-       lock_rw_unlock(&r->respip_set->lock);
+       lock_rw_unlock(&set->lock);
        node->action = respa;
 
        if(a == RPZ_LOCAL_DATA_ACTION) {
-               respip_enter_rr(r->respip_set->region, node, rrtype,
+               respip_enter_rr(set->region, node, rrtype,
                        rrclass, ttl, rdata, rdata_len, rrstr, "");
        }
        lock_rw_unlock(&node->lock);
@@ -594,6 +597,60 @@ rpz_insert_response_ip_trigger(struct rpz* r, uint8_t* dname, size_t dnamelen,
        return 1;
 }
 
+static int
+rpz_insert_client_ip_trigger(struct rpz* r, uint8_t* dname, size_t dnamelen,
+       enum rpz_action a, uint16_t rrtype, uint16_t rrclass, uint32_t ttl,
+       uint8_t* rdata, size_t rdata_len, uint8_t* rr, size_t rr_len)
+{
+       struct sockaddr_storage addr;
+       socklen_t addrlen;
+       int net, af;
+       enum respip_action respa = rpz_action_to_respip_action(a);
+
+       verbose(VERB_ALGO, "RPZ: insert client ip trigger: %s", rpz_action_to_string(a));
+       if(a == RPZ_INVALID_ACTION || respa == respip_invalid) {
+               verbose(VERB_ALGO, "RPZ: skipping unsupported action: %s",
+                       rpz_action_to_string(a));
+               return 0;
+       }
+
+       if(!netblockdnametoaddr(dname, dnamelen, &addr, &addrlen, &net, &af)) {
+               verbose(VERB_ALGO, "RPZ: unable to parse client ip");
+               return 0;
+       }
+
+       return rpz_insert_ipaddr_based_trigger(r->client_set, &addr, addrlen, net,
+                       a, rrtype, rrclass, ttl, rdata, rdata_len, rr, rr_len);
+}
+
+/** Insert RR into RPZ's respip_set */
+static int
+rpz_insert_response_ip_trigger(struct rpz* r, uint8_t* dname, size_t dnamelen,
+       enum rpz_action a, uint16_t rrtype, uint16_t rrclass, uint32_t ttl,
+       uint8_t* rdata, size_t rdata_len, uint8_t* rr, size_t rr_len)
+{
+       struct sockaddr_storage addr;
+       socklen_t addrlen;
+       int net, af;
+       enum respip_action respa = rpz_action_to_respip_action(a);
+
+       verbose(VERB_ALGO, "RPZ: insert response ip trigger: %s", rpz_action_to_string(a));
+
+       if(a == RPZ_INVALID_ACTION || respa == respip_invalid) {
+               verbose(VERB_ALGO, "RPZ: skipping unsupported action: %s",
+                       rpz_action_to_string(a));
+               return 0;
+       }
+
+       if(!netblockdnametoaddr(dname, dnamelen, &addr, &addrlen, &net, &af)) {
+               verbose(VERB_ALGO, "RPZ: unable to parse response ip");
+               return 0;
+       }
+
+       return rpz_insert_ipaddr_based_trigger(r->respip_set, &addr, addrlen, net,
+                       a, rrtype, rrclass, ttl, rdata, rdata_len, rr, rr_len);
+}
+
 int
 rpz_insert_rr(struct rpz* r, uint8_t* azname, size_t aznamelen, uint8_t* dname,
        size_t dnamelen, uint16_t rr_type, uint16_t rr_class, uint32_t rr_ttl,
@@ -643,14 +700,17 @@ rpz_insert_rr(struct rpz* r, uint8_t* azname, size_t aznamelen, uint8_t* dname,
                rpz_insert_qname_trigger(r, policydname, policydnamelen,
                        a, rr_type, rr_class, rr_ttl, rdatawl, rdatalen, rr,
                        rr_len);
-       }
-       else if(t == RPZ_RESPONSE_IP_TRIGGER) {
+       } else if(t == RPZ_RESPONSE_IP_TRIGGER) {
                rpz_insert_response_ip_trigger(r, policydname, policydnamelen,
                        a, rr_type, rr_class, rr_ttl, rdatawl, rdatalen, rr,
                        rr_len);
                free(policydname);
-       }
-       else {
+       } else if(t == RPZ_CLIENT_IP_TRIGGER) {
+               rpz_insert_client_ip_trigger(r, policydname, policydnamelen,
+                       a, rr_type, rr_class, rr_ttl, rdatawl, rdatalen, rr,
+                       rr_len);
+               free(policydname);
+       } else {
                free(policydname);
                verbose(VERB_ALGO, "RPZ: skipping unsupported trigger: %s",
                        rpz_trigger_to_string(t));
@@ -954,52 +1014,166 @@ log_rpz_apply(uint8_t* dname, enum rpz_action a, struct query_info* qinfo,
        log_nametypeclass(0, txt, qinfo->qname, qinfo->qtype, qinfo->qclass);
 }
 
-int
-rpz_apply_qname_trigger(struct auth_zones* az, struct module_env* env,
-       struct query_info* qinfo, struct edns_data* edns, sldns_buffer* buf,
-       struct regional* temp, struct comm_reply* repinfo,
-       uint8_t* taglist, size_t taglen, struct ub_server_stats* stats)
+static enum rpz_action
+rpz_apply_client_ip_trigger(struct rpz* r, struct comm_reply* repinfo)
+{
+       struct resp_addr* raddr = NULL;
+       enum rpz_action action = RPZ_INVALID_ACTION;
+       struct sockaddr_storage* addr = &repinfo->addr;
+       socklen_t addrlen = repinfo->addrlen;
+
+       lock_rw_rdlock(&r->client_set->lock);
+
+       raddr = (struct resp_addr*)addr_tree_lookup(&r->client_set->ip_tree,
+                       addr, addrlen);
+       if(raddr != NULL) {
+               action = respip_action_to_rpz_action(raddr->action);
+               lock_rw_unlock(&raddr->lock);
+       }
+
+       verbose(VERB_ALGO, "RPZ: apply client ip trigger: found=%d action=%s",
+               raddr != NULL, rpz_action_to_string(action));
+
+       lock_rw_unlock(&r->client_set->lock);
+
+       return action;
+}
+
+static inline
+enum rpz_action
+rpz_resolve_client_action_and_zone(struct auth_zones* az, struct query_info* qinfo,
+               struct comm_reply* repinfo, uint8_t* taglist, size_t taglen,
+               struct ub_server_stats* stats,
+               /* output parameters */
+               struct local_zone** z_out, struct auth_zone** a_out, struct rpz** r_out )
 {
+       struct auth_zone* a = NULL;
        struct rpz* r = NULL;
-       struct auth_zone* a;
-       int ret;
-       enum localzone_type lzt;
        struct local_zone* z = NULL;
-       struct local_data* ld = NULL;
+       enum rpz_action action = RPZ_PASSTHRU_ACTION;
+
        lock_rw_rdlock(&az->rpz_lock);
+
        for(a = az->rpz_first; a; a = a->rpz_az_next) {
                lock_rw_rdlock(&a->lock);
                r = a->rpz;
-               if(!r->taglist || taglist_intersect(r->taglist, 
-                       r->taglistlen, taglist, taglen)) {
-                       z = rpz_find_zone(r, qinfo->qname, qinfo->qname_len,
-                               qinfo->qclass, 0, 0, 0);
-                       if(z && r->action_override == RPZ_DISABLED_ACTION) {
-                               if(r->log)
-                                       log_rpz_apply(z->name,
-                                               r->action_override,
-                                               qinfo, repinfo, r->log_name);
-                               /* TODO only register stats when stats_extended?
-                                * */
-                               stats->rpz_action[r->action_override]++;
-                               lock_rw_unlock(&z->lock);
-                               z = NULL;
-                       }
-                       if(z)
-                               break;
+               if(r->taglist && !taglist_intersect(r->taglist,
+                                       r->taglistlen, taglist, taglen)) {
+                       lock_rw_unlock(&a->lock);
+                       continue;
+               }
+               z = rpz_find_zone(r, qinfo->qname, qinfo->qname_len,
+                       qinfo->qclass, 0, 0, 0);
+               action = rpz_apply_client_ip_trigger(r, repinfo);
+               if(z && r->action_override == RPZ_DISABLED_ACTION) {
+                       if(r->log)
+                               log_rpz_apply(z->name,
+                                       r->action_override,
+                                       qinfo, repinfo, r->log_name);
+                       /* TODO only register stats when stats_extended? */
+                       stats->rpz_action[r->action_override]++;
+                       lock_rw_unlock(&z->lock);
+                       z = NULL;
                }
-               lock_rw_unlock(&a->lock); /* not found in this auth_zone */
+               if(z) {
+                       break;
+               }
+               /* not found in this auth_zone */
+               lock_rw_unlock(&a->lock);
        }
+
        lock_rw_unlock(&az->rpz_lock);
 
-       if(!z)
-               return 0; /* not holding auth_zone.lock anymore */
+       *r_out = r;
+       *a_out = a;
+       *z_out = z;
 
-       log_assert(r);
-       if(r->action_override == RPZ_NO_OVERRIDE_ACTION)
-               lzt = z->type;
-       else
+       return action;
+}
+
+static inline int
+rpz_resolve_final_localzone_action(struct rpz* r, struct local_zone* z, enum rpz_action client_action)
+{
+       enum localzone_type lzt;
+       if(r->action_override == RPZ_NO_OVERRIDE_ACTION) {
+               switch (client_action) {
+               case RPZ_NODATA_ACTION:
+               case RPZ_NXDOMAIN_ACTION:
+               case RPZ_DROP_ACTION:
+               case RPZ_TCP_ONLY_ACTION:
+               case RPZ_PASSTHRU_ACTION:
+                       lzt = rpz_action_to_localzone_type(client_action);
+                       break;
+               case RPZ_LOCAL_DATA_ACTION:
+                       verbose(VERB_ALGO,
+                               "RPZ: client ip trigger with local-data unimplemented:"
+                               " defaulting to rpz-passthru");
+                       lzt = rpz_action_to_localzone_type(RPZ_PASSTHRU_ACTION);
+                       break;
+               case RPZ_INVALID_ACTION:
+                       lzt = z->type;
+                       break;
+               default:
+                       lzt = z->type;
+                       break;
+               }
+       } else {
                lzt = rpz_action_to_localzone_type(r->action_override);
+       }
+       return lzt;
+}
+
+static inline int
+rpz_is_udp_query(struct comm_reply* repinfo) {
+       return repinfo != NULL
+                       ? (repinfo->c != NULL
+                               ? repinfo->c->type == comm_udp
+                               : 0)
+                       : 0;
+}
+
+int
+rpz_apply_qname_trigger(struct auth_zones* az, struct module_env* env,
+       struct query_info* qinfo, struct edns_data* edns, sldns_buffer* buf,
+       struct regional* temp, struct comm_reply* repinfo,
+       uint8_t* taglist, size_t taglen, struct ub_server_stats* stats)
+{
+       struct rpz* r = NULL;
+       struct auth_zone* a = NULL;
+       struct local_zone* z = NULL;
+       struct local_data* ld = NULL;
+       int ret;
+       enum localzone_type lzt;
+       enum rpz_action client_action;
+
+       client_action = rpz_resolve_client_action_and_zone(
+               az, qinfo, repinfo, taglist, taglen, stats, &z, &a, &r);
+
+       verbose(VERB_ALGO, "RPZ: qname trigger: client action=%s",
+               rpz_action_to_string(client_action));
+
+       if(!z) {
+               verbose(VERB_ALGO, "RPZ: client action without zone");
+               if(client_action == RPZ_PASSTHRU_ACTION
+                       || client_action == RPZ_INVALID_ACTION
+                       || (client_action == RPZ_TCP_ONLY_ACTION
+                               && !rpz_is_udp_query(repinfo))) {
+                       return 0;
+               }
+               // XXX: log_rpz_apply not possbile because no zone
+               stats->rpz_action[client_action]++;
+               local_zones_zone_answer(NULL /*no zone*/, env, qinfo, edns,
+                                       repinfo, buf, temp, 0 /* no local data used */,
+                                       rpz_action_to_localzone_type(client_action));
+               return 1;
+       }
+
+       log_assert(r);
+
+       lzt = rpz_resolve_final_localzone_action(r, z, client_action);
+
+       verbose(VERB_ALGO, "RPZ: final client action=%s",
+               rpz_action_to_string(localzone_type_to_rpz_action(lzt)));
 
        if(r->action_override == RPZ_CNAME_OVERRIDE_ACTION) {
                qinfo->local_alias =
@@ -1040,7 +1214,7 @@ rpz_apply_qname_trigger(struct auth_zones* az, struct module_env* env,
                lock_rw_unlock(&a->lock);
                return !qinfo->local_alias;
        }
-verbose(VERB_ALGO, "xxxxxx repinfo=%p is_udp=%d", repinfo, repinfo->c->type == comm_udp);
+
        ret = local_zones_zone_answer(z, env, qinfo, edns, repinfo, buf, temp,
                0 /* no local data used */, lzt);
        if(r->log)
index 77a2db55ced429b8daaae02872fec8f5ab805cf5..1210f2039a4c06d72fb19273e065e00f0b2c8947 100644 (file)
@@ -92,6 +92,7 @@ enum rpz_action {
 struct rpz {
        struct local_zones* local_zones;
        struct respip_set* respip_set;
+       struct respip_set* client_set;
        uint8_t* taglist;
        size_t taglistlen;
        enum rpz_action action_override;
diff --git a/testdata/rpz_clientip.rpl b/testdata/rpz_clientip.rpl
new file mode 100644 (file)
index 0000000..bd55987
--- /dev/null
@@ -0,0 +1,180 @@
+; 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."
+       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.
+24.0.0.0.192.rpz-client-ip CNAME .
+24.0.1.0.192.rpz-client-ip CNAME *.
+24.0.2.0.192.rpz-client-ip CNAME rpz-drop.
+24.0.3.0.192.rpz-client-ip CNAME rpz-passthru.
+24.0.4.0.192.rpz-client-ip CNAME rpz-tcp-only.
+TEMPFILE_END
+
+stub-zone:
+       name: "a."
+       stub-addr: 10.20.30.40
+CONFIG_END
+
+SCENARIO_BEGIN Test RPZ client ip triggers
+
+RANGE_BEGIN 0 100
+       ADDRESS 10.20.30.40
+ENTRY_BEGIN
+MATCH opcode qtype qname
+ADJUST copy_id
+REPLY QR NOERROR
+SECTION QUESTION
+a. IN NS
+SECTION ANSWER
+a. IN NS ns.a.
+SECTION ADDITIONAL
+ns.a IN A 10.20.30.40
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode qtype qname
+ADJUST copy_id
+REPLY QR NOERROR
+SECTION QUESTION
+a.a.  IN  TXT
+SECTION ANSWER
+a.a.  IN  TXT "upstream txt rr a.a."
+ENTRY_END
+
+RANGE_END
+
+; unrelated client ip address -- passthru
+
+STEP 10 QUERY
+ENTRY_BEGIN
+REPLY RD
+SECTION QUESTION
+a.a.  IN TXT
+ENTRY_END
+
+STEP 11 CHECK_ANSWER
+ENTRY_BEGIN
+MATCH all
+REPLY QR RD RA NOERROR
+SECTION QUESTION
+a.a.  IN TXT
+SECTION ANSWER
+a.a.  IN TXT "upstream txt rr a.a."
+ENTRY_END
+
+; should be NXDOMAIN
+
+STEP 20 QUERY ADDRESS 192.0.0.1
+ENTRY_BEGIN
+REPLY RD
+SECTION QUESTION
+a.a.  IN TXT
+ENTRY_END
+
+STEP 21 CHECK_ANSWER
+ENTRY_BEGIN
+MATCH all
+REPLY QR AA RD RA NXDOMAIN
+SECTION QUESTION
+a.a.  IN TXT
+SECTION ANSWER
+ENTRY_END
+
+; should be NODATA
+
+STEP 30 QUERY ADDRESS 192.0.1.1
+ENTRY_BEGIN
+REPLY RD
+SECTION QUESTION
+a.a.  IN TXT
+ENTRY_END
+
+STEP 31 CHECK_ANSWER
+ENTRY_BEGIN
+MATCH all
+REPLY QR AA RD RA NOERROR
+SECTION QUESTION
+a.a.  IN TXT
+SECTION ANSWER
+ENTRY_END
+
+; should be PASSTHRU
+
+STEP 40 QUERY ADDRESS 192.0.3.1
+ENTRY_BEGIN
+REPLY RD
+SECTION QUESTION
+a.a.  IN TXT
+ENTRY_END
+
+STEP 41 CHECK_ANSWER
+ENTRY_BEGIN
+MATCH all
+REPLY QR RD RA NOERROR
+SECTION QUESTION
+a.a.  IN TXT
+SECTION ANSWER
+a.a.  IN TXT "upstream txt rr a.a."
+ENTRY_END
+
+; should be TRUNCATED
+
+STEP 50 QUERY ADDRESS 192.0.4.1
+ENTRY_BEGIN
+REPLY RD
+SECTION QUESTION
+a.a.  IN TXT
+ENTRY_END
+
+STEP 51 CHECK_ANSWER
+ENTRY_BEGIN
+MATCH all
+REPLY QR AA TC RD RA NOERROR
+SECTION QUESTION
+a.a.  IN TXT
+SECTION ANSWER
+ENTRY_END
+
+; should not be TRUNCATED via TCP
+
+STEP 52 QUERY ADDRESS 192.0.4.1
+ENTRY_BEGIN
+MATCH TCP
+REPLY RD
+SECTION QUESTION
+a.a.  IN TXT
+ENTRY_END
+
+STEP 53 CHECK_ANSWER
+ENTRY_BEGIN
+MATCH all TCP
+REPLY QR RD RA NOERROR
+SECTION QUESTION
+a.a.  IN TXT
+SECTION ANSWER
+a.a.  IN TXT "upstream txt rr a.a."
+ENTRY_END
+
+; should be DROPPED
+
+STEP 90 QUERY ADDRESS 192.0.2.1
+ENTRY_BEGIN
+REPLY RD
+SECTION QUESTION
+a.a.  IN TXT
+ENTRY_END
+
+SCENARIO_END